diff --git a/packages/datalib/src/PerspectiveCache.ts b/packages/datalib/src/PerspectiveCache.ts index 00ef68cd9..5446d87a8 100644 --- a/packages/datalib/src/PerspectiveCache.ts +++ b/packages/datalib/src/PerspectiveCache.ts @@ -5,6 +5,7 @@ import _difference from 'lodash/difference'; import debug from 'debug'; import stableStringify from 'json-stable-stringify'; import { PerspectiveDataPattern } from './PerspectiveDataPattern'; +import { perspectiveValueMatcher } from './perspectiveTools'; const dbg = debug('dbgate:PerspectiveCache'); @@ -17,7 +18,9 @@ export class PerspectiveBindingGroup { bindingValues: any[]; matchRow(row) { - return this.table.bindingColumns.every((column, index) => row[column] == this.bindingValues[index]); + return this.table.bindingColumns.every((column, index) => + perspectiveValueMatcher(row[column], this.bindingValues[index]) + ); } } @@ -69,7 +72,11 @@ export class PerspectiveCacheTable { } storeGroupSize(props: PerspectiveDataLoadProps, bindingValues: any[], count: number) { - const originalBindingValue = props.bindingValues.find(v => _zip(v, bindingValues).every(([x, y]) => x == y)); + const originalBindingValue = props.bindingValues.find(v => + _zip(v, bindingValues).every(([x, y]) => perspectiveValueMatcher(x, y)) + ); + // console.log('storeGroupSize NEW', bindingValues); + // console.log('storeGroupSize ORIGINAL', originalBindingValue); if (originalBindingValue) { const key = stableStringify(originalBindingValue); // console.log('SET SIZE', originalBindingValue, bindingValues, key, count); diff --git a/packages/datalib/src/PerspectiveDataPattern.ts b/packages/datalib/src/PerspectiveDataPattern.ts index 876ffc00a..f151f6a27 100644 --- a/packages/datalib/src/PerspectiveDataPattern.ts +++ b/packages/datalib/src/PerspectiveDataPattern.ts @@ -51,7 +51,7 @@ function addObjectToColumns(columns: PerspectiveDataPatternColumn[], row) { if (!column.types.includes(type)) { column.types.push(type); } - if (_isPlainObject(value)) { + if (_isPlainObject(value) && type != 'oid') { addObjectToColumns(column.columns, value); } if (_isArray(value)) { diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index 130f12d69..d1dd11dd1 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -47,6 +47,7 @@ export class PerspectiveDataProvider { async loadDataNested(props: PerspectiveDataLoadProps): Promise<{ rows: any[]; incomplete: boolean }> { const tableCache = this.cache.getTableCache(props); + // console.log('loadDataNested', props); const uncached = tableCache.getUncachedBindingGroups(props); if (uncached.length > 0) { @@ -54,7 +55,7 @@ export class PerspectiveDataProvider { ...props, bindingValues: uncached, }); - // console.log('COUNTS', counts); + // console.log('loadDataNested COUNTS', counts); for (const resetItem of uncached) { tableCache.storeGroupSize(props, resetItem, 0); } @@ -196,6 +197,14 @@ export class PerspectiveDataProvider { }, }); + if (!nextRows) { + // return tableCache.getRowsResult(props); + return { + rows: [], + incomplete: false, + }; + } + if (nextRows.errorMessage) { throw new Error(nextRows.errorMessage); } diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 7774d73d3..f90025d05 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -22,6 +22,7 @@ import { PerspectiveReferenceConfig, } from './PerspectiveConfig'; import _isEqual from 'lodash/isEqual'; +import _isArray from 'lodash/isArray'; import _cloneDeep from 'lodash/cloneDeep'; import _compact from 'lodash/compact'; import _uniq from 'lodash/uniq'; @@ -38,6 +39,11 @@ import { Condition, Expression, Select } from 'dbgate-sqltree'; // import { getPerspectiveDefaultColumns } from './getPerspectiveDefaultColumns'; import uuidv1 from 'uuid/v1'; import { PerspectiveDataPatternColumn } from './PerspectiveDataPattern'; +import { + getPerspectiveMostNestedChildColumnName, + getPerspectiveParentColumnName, + perspectiveValueMatcher, +} from './perspectiveTools'; export interface PerspectiveDataLoadPropsWithNode { props: PerspectiveDataLoadProps; @@ -137,6 +143,16 @@ export abstract class PerspectiveTreeNode { get generatesDataGridColumn() { return this.isCheckedColumn; } + get validParentDesignerId() { + if (this.designerId) return this.designerId; + return this.parentNode?.validParentDesignerId; + } + get preloadedLevelData() { + return false; + } + get findByDesignerIdWithoutDesignerId() { + return false; + } matchChildRow(parentRow: any, childRow: any): boolean { return true; } @@ -210,7 +226,7 @@ export abstract class PerspectiveTreeNode { get hasUncheckedNodeInPath() { if (!this.parentNode) return false; - if (!this.isCheckedNode) return true; + if (this.designerId && !this.isCheckedNode) return true; return this.parentNode.hasUncheckedNodeInPath; } @@ -404,7 +420,7 @@ export abstract class PerspectiveTreeNode { // } findNodeByDesignerId(designerId: string): PerspectiveTreeNode { - if (!this.designerId) { + if (!this.designerId && !this.findByDesignerIdWithoutDesignerId) { return null; } if (!designerId) { @@ -758,15 +774,22 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { ) { super(dbs, config, setConfig, parentNode, dataProvider, databaseConfig, designerId); this.parentNodeConfig = this.tableNodeOrParent?.nodeConfig; + // console.log('PATTERN COLUMN', column); } get isChildColumn() { return this.parentNode instanceof PerspectivePatternColumnNode; } + get findByDesignerIdWithoutDesignerId() { + return this.isExpandable; + } + // matchChildRow(parentRow: any, childRow: any): boolean { - // if (!this.foreignKey) return false; - // return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName]; + // console.log('MATCH PATTENR ROW', parentRow, childRow); + // return false; + // // if (!this.foreignKey) return false; + // // return parentRow[this.foreignKey.columns[0].columnName] == childRow[this.foreignKey.columns[0].refColumnName]; // } // getChildMatchColumns() { @@ -808,12 +831,19 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { // } getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { + // console.log('GETTING PATTERN', parentRows); return null; } - get generatesHiearchicGridColumn() { + get generatesHiearchicGridColumn(): boolean { + // return true; // console.log('generatesHiearchicGridColumn', this.parentTableNode?.nodeConfig?.checkedColumns, this.codeName + '::'); - return !!this.tableNodeOrParent?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::')); + if (this.tableNodeOrParent?.nodeConfig?.checkedColumns?.find(x => x.startsWith(this.codeName + '::'))) { + return true; + } + // return false; + + return this.hasCheckedJoinChild(); } // get generatesHiearchicGridColumn() { @@ -859,7 +889,11 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { return 'mongo'; } - generateChildNodes(): PerspectiveTreeNode[] { + get preloadedLevelData() { + return true; + } + + generatePatternChildNodes(): PerspectivePatternColumnNode[] { return this.column.columns.map( column => new PerspectivePatternColumnNode( @@ -875,7 +909,92 @@ export class PerspectivePatternColumnNode extends PerspectiveTreeNode { null ) ); - return []; + } + + hasCheckedJoinChild() { + for (const node of this.childNodes) { + if (node instanceof PerspectivePatternColumnNode) { + if (node.hasCheckedJoinChild()) return true; + } + if (node.isCheckedNode) return true; + } + return false; + } + + generateChildNodes(): PerspectiveTreeNode[] { + const patternChildren = this.generatePatternChildNodes(); + + const customs = []; + // console.log('GETTING CHILDREN', this.config.nodes, this.config.references); + for (const node of this.config.nodes) { + for (const ref of this.config.references) { + const validDesignerId = this.validParentDesignerId; + if ( + (ref.sourceId == validDesignerId && ref.targetId == node.designerId) || + (ref.targetId == validDesignerId && ref.sourceId == node.designerId) + ) { + // console.log('TESTING REF', ref, this.codeName); + if (ref.columns.length != 1) continue; + // console.log('CP1'); + if ( + ref.sourceId == validDesignerId && + this.codeName == getPerspectiveParentColumnName(ref.columns[0].source) + ) { + if (ref.columns[0].target.includes('::')) continue; + } else if ( + ref.targetId == validDesignerId && + this.codeName == getPerspectiveParentColumnName(ref.columns[0].target) + ) { + if (ref.columns[0].source.includes('::')) continue; + } else { + continue; + } + // console.log('CP2'); + + const newConfig = { ...this.databaseConfig }; + if (node.conid) newConfig.conid = node.conid; + if (node.database) newConfig.database = node.database; + const db = this.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, + referenceDesignerId: ref.designerId, + baseDesignerId: validDesignerId, + joinName: node.alias, + refTableName: node.pureName, + refSchemaName: node.schemaName, + conid: node.conid, + database: node.database, + columns: + ref.sourceId == validDesignerId + ? ref.columns.map(col => ({ baseColumnName: col.source, refColumnName: col.target })) + : ref.columns.map(col => ({ baseColumnName: col.target, refColumnName: col.source })), + }; + + if (table || view || collection) { + customs.push( + new PerspectiveCustomJoinTreeNode( + join, + table || view || collection, + this.dbs, + this.config, + this.setConfig, + this.dataProvider, + newConfig, + this, + node.designerId + ) + ); + } + } + } + } + + return [...patternChildren, ...customs]; + // return []; // if (!this.foreignKey) return []; // const tbl = this?.db?.tables?.find( // x => x.pureName == this.foreignKey?.refTableName && x.schemaName == this.foreignKey?.refSchemaName @@ -1153,8 +1272,14 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { } matchChildRow(parentRow: any, childRow: any): boolean { + // console.log('MATCH ROW', parentRow, childRow); for (const column of this.customJoin.columns) { - if (parentRow[column.baseColumnName] != childRow[column.refColumnName]) { + if ( + !perspectiveValueMatcher( + parentRow[getPerspectiveMostNestedChildColumnName(column.baseColumnName)], + childRow[column.refColumnName] + ) + ) { return false; } } @@ -1171,17 +1296,68 @@ export class PerspectiveCustomJoinTreeNode extends PerspectiveTableNode { getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { // console.log('CUSTOM JOIN', this.customJoin); + // console.log('PARENT ROWS', parentRows); + // console.log('this.getDataLoadColumns()', this.getDataLoadColumns()); const isMongo = isCollectionInfo(this.table); + // const bindingValues = []; + + // for (const row of parentRows) { + // const rowBindingValueArrays = []; + // for (const col of this.customJoin.columns) { + // const path = col.baseColumnName.split('::'); + // const values = []; + + // function processSubpath(parent, subpath) { + // if (subpath.length == 0) { + // values.push(parent); + // return; + // } + // if (parent == null) { + // return; + // } + + // const obj = parent[subpath[0]]; + // if (_isArray(obj)) { + // for (const elem of obj) { + // processSubpath(elem, subpath.slice(1)); + // } + // } else { + // processSubpath(obj, subpath.slice(1)); + // } + // } + + // processSubpath(row, path); + + // rowBindingValueArrays.push(values); + // } + + // const valueCount = Math.max(...rowBindingValueArrays.map(x => x.length)); + + // for (let i = 0; i < valueCount; i += 1) { + // const value = Array(this.customJoin.columns.length); + // for (let col = 0; col < this.customJoin.columns.length; col++) { + // value[col] = rowBindingValueArrays[col][i % rowBindingValueArrays[col].length]; + // } + // bindingValues.push(value); + // } + // } + const bindingValues = parentRows.map(row => + this.customJoin.columns.map(x => row[getPerspectiveMostNestedChildColumnName(x.baseColumnName)]) + ); + + // console.log('bindingValues', bindingValues); + // console.log( + // 'bindingValues UNIQ', + // _uniqBy(bindingValues, x => JSON.stringify(x)) + // ); + return { schemaName: this.table.schemaName, pureName: this.table.pureName, bindingColumns: this.getParentMatchColumns(), - bindingValues: _uniqBy( - parentRows.map(row => this.customJoin.columns.map(x => row[x.baseColumnName])), - stableStringify - ), + bindingValues: _uniqBy(bindingValues, x => JSON.stringify(x)), dataColumns: this.getDataLoadColumns(), allColumns: isMongo, databaseConfig: this.databaseConfig, @@ -1412,6 +1588,9 @@ export function getTableChildPerspectiveNodes( (ref.sourceId == parentNode.designerId && ref.targetId == node.designerId) || (ref.targetId == parentNode.designerId && ref.sourceId == node.designerId) ) { + if (ref.columns.find(x => x.source.includes('::') || x.target.includes('::'))) { + continue; + } const newConfig = { ...databaseConfig }; if (node.conid) newConfig.conid = node.conid; if (node.database) newConfig.database = node.database; diff --git a/packages/datalib/src/index.ts b/packages/datalib/src/index.ts index 2c782139f..4225d648d 100644 --- a/packages/datalib/src/index.ts +++ b/packages/datalib/src/index.ts @@ -21,3 +21,4 @@ export * from './PerspectiveConfig'; export * from './processPerspectiveDefaultColunns'; export * from './PerspectiveDataPattern'; export * from './PerspectiveDataLoader'; +export * from './perspectiveTools'; diff --git a/packages/datalib/src/perspectiveTools.ts b/packages/datalib/src/perspectiveTools.ts new file mode 100644 index 000000000..0ce3f2d83 --- /dev/null +++ b/packages/datalib/src/perspectiveTools.ts @@ -0,0 +1,22 @@ +export function getPerspectiveParentColumnName(columnName: string) { + const path = columnName.split('::'); + if (path.length >= 2) return path.slice(0, -1).join('::'); + return null; +} + +export function getPerspectiveMostNestedChildColumnName(columnName: string) { + const path = columnName.split('::'); + return path[path.length - 1]; +} + +// export function perspectiveValueMatcher(value1, value2): boolean { +// if (value1?.$oid && value2?.$oid) return value1.$oid == value2.$oid; +// if (Array.isArray(value1)) return !!value1.find(x => perspectiveValueMatcher(x, value2)); +// if (Array.isArray(value2)) return !!value2.find(x => perspectiveValueMatcher(value1, x)); +// return value1 == value2; +// } + +export function perspectiveValueMatcher(value1, value2): boolean { + if (value1?.$oid && value2?.$oid) return value1.$oid == value2.$oid; + return value1 == value2; +} diff --git a/packages/web/src/designer/ColumnLine.svelte b/packages/web/src/designer/ColumnLine.svelte index 4a3284e8a..7128ff89c 100644 --- a/packages/web/src/designer/ColumnLine.svelte +++ b/packages/web/src/designer/ColumnLine.svelte @@ -6,6 +6,7 @@ import ColumnLabel from '../elements/ColumnLabel.svelte'; import CheckboxField from '../forms/CheckboxField.svelte'; + import { plusExpandIcon } from '../icons/expandIcons'; import FontIcon from '../icons/FontIcon.svelte'; import contextMenu from '../utility/contextMenu'; import SortOrderIcon from './SortOrderIcon.svelte'; @@ -21,6 +22,11 @@ export let onAddReferenceByColumn; export let onSelectColumn; export let settings; + export let nestingSupported = null; + export let isExpandable = false; + export let isExpanded = false; + export let expandLevel = 0; + export let toggleExpanded = null; $: designerColumn = (designer.columns || []).find( x => x.designerId == designerId && x.columnName == column.columnName @@ -115,16 +121,27 @@ })} use:contextMenu={settings?.canSelectColumns ? createMenu : '__no_menu'} > + {#if nestingSupported} + + { + toggleExpanded(!isExpanded); + }} + /> + + {/if} + {#if settings?.allowColumnOperations} x.designerId == designerId && x.columnName == column.columnName && x.isOutput )} on:change={e => { if (settings?.setColumnChecked) { - settings?.setColumnChecked(designerId, column.columnName, e.target.checked); + settings?.setColumnChecked(designerId, column, e.target.checked); } else { if (e.target.checked) { onChangeColumn( @@ -147,7 +164,13 @@ }} /> {/if} - + {#if designerColumn?.filter} {/if} diff --git a/packages/web/src/designer/DesignerTable.svelte b/packages/web/src/designer/DesignerTable.svelte index ca3162057..81363a6b1 100644 --- a/packages/web/src/designer/DesignerTable.svelte +++ b/packages/web/src/designer/DesignerTable.svelte @@ -1,6 +1,6 @@
tick().then(onMoveReferences)} class:scroll={settings?.allowScrollColumns}> - {#each columns || [] as column} + {#each flatColumns || [] as column (column.columnName)} settings?.isColumnExpandable(x))} + isExpandable={settings?.isColumnExpandable && settings?.isColumnExpandable(column)} + isExpanded={settings?.isColumnExpanded && settings?.isColumnExpanded(column)} + expandLevel={settings?.columnExpandLevel ? settings?.columnExpandLevel(column) : 0} + toggleExpanded={value => settings?.toggleExpandedColumn(column, value)} {column} {table} {designer} diff --git a/packages/web/src/designer/DomTableRef.ts b/packages/web/src/designer/DomTableRef.ts index 85f6c7590..a592e34cd 100644 --- a/packages/web/src/designer/DomTableRef.ts +++ b/packages/web/src/designer/DomTableRef.ts @@ -6,13 +6,15 @@ export default class DomTableRef { table: DesignerTableInfo; designerId: string; domRefs: { [column: string]: Element }; + settings: any; - constructor(table: DesignerTableInfo, domRefs, domWrapper: Element) { + constructor(table: DesignerTableInfo, domRefs, domWrapper: Element, settings) { this.domTable = domRefs['']; this.domWrapper = domWrapper; this.table = table; this.designerId = table.designerId; this.domRefs = domRefs; + this.settings = settings; } getRect() { @@ -31,6 +33,10 @@ export default class DomTableRef { getColumnY(columnName: string) { let col = this.domRefs[columnName]; + while (col == null && this.settings?.getParentColumnName && this.settings?.getParentColumnName(columnName)) { + columnName = this.settings?.getParentColumnName(columnName); + col = this.domRefs[columnName]; + } if (!col) return null; const rect = col.getBoundingClientRect(); const wrap = this.domWrapper.getBoundingClientRect(); diff --git a/packages/web/src/perspectives/PerspectiveDesigner.svelte b/packages/web/src/perspectives/PerspectiveDesigner.svelte index 4731813f2..3b3a49ed5 100644 --- a/packages/web/src/perspectives/PerspectiveDesigner.svelte +++ b/packages/web/src/perspectives/PerspectiveDesigner.svelte @@ -1,6 +1,10 @@