diff --git a/packages/datalib/src/PerspectiveCache.ts b/packages/datalib/src/PerspectiveCache.ts index 567dd3f53..83b658f3f 100644 --- a/packages/datalib/src/PerspectiveCache.ts +++ b/packages/datalib/src/PerspectiveCache.ts @@ -88,7 +88,7 @@ export class PerspectiveCache { getTableCache(props: PerspectiveDataLoadProps) { const tableKey = stableStringify( - _pick(props, ['schemaName', 'pureName', 'bindingColumns', 'databaseConfig', 'orderBy']) + _pick(props, ['schemaName', 'pureName', 'bindingColumns', 'databaseConfig', 'orderBy', 'condition']) ); let res = this.tables[tableKey]; diff --git a/packages/datalib/src/PerspectiveConfig.ts b/packages/datalib/src/PerspectiveConfig.ts index 263dc0a95..e17c63c3c 100644 --- a/packages/datalib/src/PerspectiveConfig.ts +++ b/packages/datalib/src/PerspectiveConfig.ts @@ -1,15 +1,23 @@ -export interface PerspectiveConfig { +export interface PerspectiveConfigColumns { expandedColumns: string[]; checkedColumns: string[]; uncheckedColumns: string[]; } +export interface PerspectiveConfig extends PerspectiveConfigColumns { + filters: { [uniqueName: string]: string }; +} + export function createPerspectiveConfig(): PerspectiveConfig { return { expandedColumns: [], checkedColumns: [], uncheckedColumns: [], + filters: {}, }; } -export type ChangePerspectiveConfigFunc = (changeFunc: (config: PerspectiveConfig) => PerspectiveConfig) => void; +export type ChangePerspectiveConfigFunc = ( + changeFunc: (config: PerspectiveConfig) => PerspectiveConfig, + reload?: boolean +) => void; diff --git a/packages/datalib/src/PerspectiveDataLoader.ts b/packages/datalib/src/PerspectiveDataLoader.ts index d50a76fdf..577eb3d9e 100644 --- a/packages/datalib/src/PerspectiveDataLoader.ts +++ b/packages/datalib/src/PerspectiveDataLoader.ts @@ -1,4 +1,4 @@ -import { Expression, Select } from 'dbgate-sqltree'; +import { Condition, Expression, Select } from 'dbgate-sqltree'; import { PerspectiveDataLoadProps } from './PerspectiveDataProvider'; import debug from 'debug'; @@ -7,6 +7,37 @@ const dbg = debug('dbgate:PerspectiveDataLoader'); export class PerspectiveDataLoader { constructor(public apiCall) {} + buildCondition(props: PerspectiveDataLoadProps): Condition { + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + + const conditions = []; + + if (condition) { + conditions.push(condition); + } + + if (bindingColumns?.length == 1) { + conditions.push({ + conditionType: 'in', + expr: { + exprType: 'column', + columnName: bindingColumns[0], + source: { + name: { schemaName, pureName }, + }, + }, + values: bindingValues.map(x => x[0]), + }); + } + + return conditions.length > 0 + ? { + conditionType: 'and', + conditions, + } + : null; + } + async loadGrouping(props: PerspectiveDataLoadProps) { const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props; @@ -40,20 +71,8 @@ export class PerspectiveDataLoader { }, ...bindingColumnExpressions, ], + where: this.buildCondition(props), }; - if (bindingColumns?.length == 1) { - select.where = { - conditionType: 'in', - expr: { - exprType: 'column', - columnName: bindingColumns[0], - source: { - name: { schemaName, pureName }, - }, - }, - values: bindingValues.map(x => x[0]), - }; - } select.groupBy = bindingColumnExpressions; @@ -75,7 +94,8 @@ export class PerspectiveDataLoader { } async loadData(props: PerspectiveDataLoadProps) { - const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy } = props; + const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props; + const select: Select = { commandType: 'select', from: { @@ -98,20 +118,8 @@ export class PerspectiveDataLoader { }, })), range: props.range, + where: this.buildCondition(props), }; - if (bindingColumns?.length == 1) { - select.where = { - conditionType: 'in', - expr: { - exprType: 'column', - columnName: bindingColumns[0], - source: { - name: { schemaName, pureName }, - }, - }, - values: bindingValues.map(x => x[0]), - }; - } if (dbg?.enabled) { dbg( diff --git a/packages/datalib/src/PerspectiveDataProvider.ts b/packages/datalib/src/PerspectiveDataProvider.ts index c70e4f942..2dedaa371 100644 --- a/packages/datalib/src/PerspectiveDataProvider.ts +++ b/packages/datalib/src/PerspectiveDataProvider.ts @@ -1,3 +1,4 @@ +import { Condition } from 'dbgate-sqltree'; import { RangeDefinition } from 'dbgate-types'; import { format } from 'path'; import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache'; @@ -20,6 +21,7 @@ export interface PerspectiveDataLoadProps { bindingValues?: any[][]; range?: RangeDefinition; topCount?: number; + condition?: Condition; } export class PerspectiveDataProvider { diff --git a/packages/datalib/src/PerspectiveTreeNode.ts b/packages/datalib/src/PerspectiveTreeNode.ts index 3fb79b077..27ee36d4d 100644 --- a/packages/datalib/src/PerspectiveTreeNode.ts +++ b/packages/datalib/src/PerspectiveTreeNode.ts @@ -1,18 +1,22 @@ import { ColumnInfo, DatabaseInfo, ForeignKeyInfo, RangeDefinition, TableInfo } from 'dbgate-types'; import { clearConfigCache } from 'prettier'; -import { ChangePerspectiveConfigFunc, PerspectiveConfig } from './PerspectiveConfig'; +import { ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveConfigColumns } from './PerspectiveConfig'; import _isEqual from 'lodash/isEqual'; import _cloneDeep from 'lodash/cloneDeep'; import _compact from 'lodash/compact'; import _uniq from 'lodash/uniq'; import _flatten from 'lodash/flatten'; import _uniqBy from 'lodash/uniqBy'; +import _cloneDeepWith from 'lodash/cloneDeepWith'; import { PerspectiveDatabaseConfig, PerspectiveDataLoadProps, PerspectiveDataProvider, } from './PerspectiveDataProvider'; import stableStringify from 'json-stable-stringify'; +import { getFilterType, parseFilter } from 'dbgate-filterparser'; +import { FilterType } from 'dbgate-filterparser/lib/types'; +import { Condition, Expression } from 'dbgate-sqltree'; export interface PerspectiveDataLoadPropsWithNode { props: PerspectiveDataLoadProps; @@ -84,6 +88,9 @@ export abstract class PerspectiveTreeNode { get columnTitle() { return this.title; } + get filterType(): FilterType { + return 'string'; + } getChildMatchColumns() { return []; @@ -93,6 +100,10 @@ export abstract class PerspectiveTreeNode { return []; } + parseFilterCondition() { + return null; + } + get childDataColumn() { if (!this.isExpandable && this.isChecked) { return this.codeName; @@ -108,7 +119,7 @@ export abstract class PerspectiveTreeNode { this.includeInColumnSet('checkedColumns', this.uniqueName, value == null ? !this.isChecked : value); } - includeInColumnSet(field: keyof PerspectiveConfig, uniqueName: string, isIncluded: boolean) { + includeInColumnSet(field: keyof PerspectiveConfigColumns, uniqueName: string, isIncluded: boolean) { if (isIncluded) { this.setConfig(cfg => ({ ...cfg, @@ -122,6 +133,23 @@ export abstract class PerspectiveTreeNode { } } + setFilter(value) { + this.setConfig( + cfg => ({ + ...cfg, + filters: { + ...cfg.filters, + [this.uniqueName]: value, + }, + }), + true + ); + } + + getFilter() { + return this.config.filters[this.uniqueName]; + } + getDataLoadColumns() { return _compact( _uniq([ @@ -131,6 +159,20 @@ export abstract class PerspectiveTreeNode { ]) ); } + + getChildrenCondition(): Condition { + const conditions = _compact(this.childNodes.map(x => x.parseFilterCondition())); + if (conditions.length == 0) { + return null; + } + if (conditions.length == 1) { + return conditions[0]; + } + return { + conditionType: 'and', + conditions, + }; + } } export class PerspectiveTableColumnNode extends PerspectiveTreeNode { @@ -205,6 +247,10 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { return !!this.foreignKey; } + get filterType(): FilterType { + return getFilterType(this.column.dataType); + } + get childNodes(): PerspectiveTreeNode[] { if (!this.foreignKey) return []; const tbl = this?.db?.tables?.find( @@ -220,6 +266,23 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode { this ); } + + parseFilterCondition() { + 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, + }; + } + }); + + return condition; + } } export class PerspectiveTableNode extends PerspectiveTreeNode { @@ -235,13 +298,14 @@ export class PerspectiveTableNode extends PerspectiveTreeNode { super(config, setConfig, parentNode, dataProvider, databaseConfig); } - getNodeLoadProps(parentRows: any[]) { + getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps { return { schemaName: this.table.schemaName, pureName: this.table.pureName, dataColumns: this.getDataLoadColumns(), databaseConfig: this.databaseConfig, orderBy: this.table.primaryKey?.columns.map(x => x.columnName) || [this.table.columns[0].columnName], + condition: this.getChildrenCondition(), }; } diff --git a/packages/web/src/datagrid/DataFilterControl.svelte b/packages/web/src/datagrid/DataFilterControl.svelte index 1fbef6cbc..6a8dad17b 100644 --- a/packages/web/src/datagrid/DataFilterControl.svelte +++ b/packages/web/src/datagrid/DataFilterControl.svelte @@ -320,6 +320,7 @@ input { flex: 1; min-width: 10px; + width: 1px; } input.isError { diff --git a/packages/web/src/perspectives/PerspectiveTable.svelte b/packages/web/src/perspectives/PerspectiveTable.svelte index f115d2218..9ca5dc848 100644 --- a/packages/web/src/perspectives/PerspectiveTable.svelte +++ b/packages/web/src/perspectives/PerspectiveTable.svelte @@ -15,6 +15,7 @@ import debug from 'debug'; import contextMenu from '../utility/contextMenu'; import DataFilterControl from '../datagrid/DataFilterControl.svelte'; +import { countVisibleRealColumns } from '../datagrid/gridutil'; const dbg = debug('dbgate:PerspectivaTable'); @@ -115,15 +116,10 @@ } -
+
{#if display} - + {#each _.range(display.columnLevelCount) as columnLevel} {#each display.columns as column} @@ -136,13 +132,18 @@ {/each} {/each} - + {#each display.rows as row} @@ -218,6 +219,10 @@ border-right: 1px solid var(--theme-border); } + th.filter { + padding: 0; + } + thead tr:first-child th { border-top: 1px solid var(--theme-border); } diff --git a/packages/web/src/tabs/PerspectiveTab.svelte b/packages/web/src/tabs/PerspectiveTab.svelte index af2d68272..758d468e2 100644 --- a/packages/web/src/tabs/PerspectiveTab.svelte +++ b/packages/web/src/tabs/PerspectiveTab.svelte @@ -50,7 +50,15 @@ {schemaName} {pureName} config={$config} - setConfig={config.update} + setConfig={(value, reload) => { + if (reload) { + cache.clear(); + } + config.update(value); + if (reload) { + loadedCounts.set({}); + } + }} {cache} {loadedCounts} />