perspective filters

This commit is contained in:
Jan Prochazka
2022-07-25 21:42:01 +02:00
parent f504283002
commit 3bdb5c0152
8 changed files with 142 additions and 46 deletions

View File

@@ -88,7 +88,7 @@ export class PerspectiveCache {
getTableCache(props: PerspectiveDataLoadProps) { getTableCache(props: PerspectiveDataLoadProps) {
const tableKey = stableStringify( const tableKey = stableStringify(
_pick(props, ['schemaName', 'pureName', 'bindingColumns', 'databaseConfig', 'orderBy']) _pick(props, ['schemaName', 'pureName', 'bindingColumns', 'databaseConfig', 'orderBy', 'condition'])
); );
let res = this.tables[tableKey]; let res = this.tables[tableKey];

View File

@@ -1,15 +1,23 @@
export interface PerspectiveConfig { export interface PerspectiveConfigColumns {
expandedColumns: string[]; expandedColumns: string[];
checkedColumns: string[]; checkedColumns: string[];
uncheckedColumns: string[]; uncheckedColumns: string[];
} }
export interface PerspectiveConfig extends PerspectiveConfigColumns {
filters: { [uniqueName: string]: string };
}
export function createPerspectiveConfig(): PerspectiveConfig { export function createPerspectiveConfig(): PerspectiveConfig {
return { return {
expandedColumns: [], expandedColumns: [],
checkedColumns: [], checkedColumns: [],
uncheckedColumns: [], uncheckedColumns: [],
filters: {},
}; };
} }
export type ChangePerspectiveConfigFunc = (changeFunc: (config: PerspectiveConfig) => PerspectiveConfig) => void; export type ChangePerspectiveConfigFunc = (
changeFunc: (config: PerspectiveConfig) => PerspectiveConfig,
reload?: boolean
) => void;

View File

@@ -1,4 +1,4 @@
import { Expression, Select } from 'dbgate-sqltree'; import { Condition, Expression, Select } from 'dbgate-sqltree';
import { PerspectiveDataLoadProps } from './PerspectiveDataProvider'; import { PerspectiveDataLoadProps } from './PerspectiveDataProvider';
import debug from 'debug'; import debug from 'debug';
@@ -7,6 +7,37 @@ const dbg = debug('dbgate:PerspectiveDataLoader');
export class PerspectiveDataLoader { export class PerspectiveDataLoader {
constructor(public apiCall) {} 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) { async loadGrouping(props: PerspectiveDataLoadProps) {
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props; const { schemaName, pureName, bindingColumns, bindingValues, dataColumns } = props;
@@ -40,20 +71,8 @@ export class PerspectiveDataLoader {
}, },
...bindingColumnExpressions, ...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; select.groupBy = bindingColumnExpressions;
@@ -75,7 +94,8 @@ export class PerspectiveDataLoader {
} }
async loadData(props: PerspectiveDataLoadProps) { async loadData(props: PerspectiveDataLoadProps) {
const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy } = props; const { schemaName, pureName, bindingColumns, bindingValues, dataColumns, orderBy, condition } = props;
const select: Select = { const select: Select = {
commandType: 'select', commandType: 'select',
from: { from: {
@@ -98,20 +118,8 @@ export class PerspectiveDataLoader {
}, },
})), })),
range: props.range, 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) { if (dbg?.enabled) {
dbg( dbg(

View File

@@ -1,3 +1,4 @@
import { Condition } from 'dbgate-sqltree';
import { RangeDefinition } from 'dbgate-types'; import { RangeDefinition } from 'dbgate-types';
import { format } from 'path'; import { format } from 'path';
import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache'; import { PerspectiveBindingGroup, PerspectiveCache } from './PerspectiveCache';
@@ -20,6 +21,7 @@ export interface PerspectiveDataLoadProps {
bindingValues?: any[][]; bindingValues?: any[][];
range?: RangeDefinition; range?: RangeDefinition;
topCount?: number; topCount?: number;
condition?: Condition;
} }
export class PerspectiveDataProvider { export class PerspectiveDataProvider {

View File

@@ -1,18 +1,22 @@
import { ColumnInfo, DatabaseInfo, ForeignKeyInfo, RangeDefinition, TableInfo } from 'dbgate-types'; import { ColumnInfo, DatabaseInfo, ForeignKeyInfo, RangeDefinition, TableInfo } from 'dbgate-types';
import { clearConfigCache } from 'prettier'; import { clearConfigCache } from 'prettier';
import { ChangePerspectiveConfigFunc, PerspectiveConfig } from './PerspectiveConfig'; import { ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveConfigColumns } from './PerspectiveConfig';
import _isEqual from 'lodash/isEqual'; import _isEqual from 'lodash/isEqual';
import _cloneDeep from 'lodash/cloneDeep'; import _cloneDeep from 'lodash/cloneDeep';
import _compact from 'lodash/compact'; import _compact from 'lodash/compact';
import _uniq from 'lodash/uniq'; import _uniq from 'lodash/uniq';
import _flatten from 'lodash/flatten'; import _flatten from 'lodash/flatten';
import _uniqBy from 'lodash/uniqBy'; import _uniqBy from 'lodash/uniqBy';
import _cloneDeepWith from 'lodash/cloneDeepWith';
import { import {
PerspectiveDatabaseConfig, PerspectiveDatabaseConfig,
PerspectiveDataLoadProps, PerspectiveDataLoadProps,
PerspectiveDataProvider, PerspectiveDataProvider,
} from './PerspectiveDataProvider'; } from './PerspectiveDataProvider';
import stableStringify from 'json-stable-stringify'; 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 { export interface PerspectiveDataLoadPropsWithNode {
props: PerspectiveDataLoadProps; props: PerspectiveDataLoadProps;
@@ -84,6 +88,9 @@ export abstract class PerspectiveTreeNode {
get columnTitle() { get columnTitle() {
return this.title; return this.title;
} }
get filterType(): FilterType {
return 'string';
}
getChildMatchColumns() { getChildMatchColumns() {
return []; return [];
@@ -93,6 +100,10 @@ export abstract class PerspectiveTreeNode {
return []; return [];
} }
parseFilterCondition() {
return null;
}
get childDataColumn() { get childDataColumn() {
if (!this.isExpandable && this.isChecked) { if (!this.isExpandable && this.isChecked) {
return this.codeName; return this.codeName;
@@ -108,7 +119,7 @@ export abstract class PerspectiveTreeNode {
this.includeInColumnSet('checkedColumns', this.uniqueName, value == null ? !this.isChecked : value); 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) { if (isIncluded) {
this.setConfig(cfg => ({ this.setConfig(cfg => ({
...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() { getDataLoadColumns() {
return _compact( return _compact(
_uniq([ _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 { export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
@@ -205,6 +247,10 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
return !!this.foreignKey; return !!this.foreignKey;
} }
get filterType(): FilterType {
return getFilterType(this.column.dataType);
}
get childNodes(): PerspectiveTreeNode[] { get childNodes(): PerspectiveTreeNode[] {
if (!this.foreignKey) return []; if (!this.foreignKey) return [];
const tbl = this?.db?.tables?.find( const tbl = this?.db?.tables?.find(
@@ -220,6 +266,23 @@ export class PerspectiveTableColumnNode extends PerspectiveTreeNode {
this 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 { export class PerspectiveTableNode extends PerspectiveTreeNode {
@@ -235,13 +298,14 @@ export class PerspectiveTableNode extends PerspectiveTreeNode {
super(config, setConfig, parentNode, dataProvider, databaseConfig); super(config, setConfig, parentNode, dataProvider, databaseConfig);
} }
getNodeLoadProps(parentRows: any[]) { getNodeLoadProps(parentRows: any[]): PerspectiveDataLoadProps {
return { return {
schemaName: this.table.schemaName, schemaName: this.table.schemaName,
pureName: this.table.pureName, pureName: this.table.pureName,
dataColumns: this.getDataLoadColumns(), dataColumns: this.getDataLoadColumns(),
databaseConfig: this.databaseConfig, databaseConfig: this.databaseConfig,
orderBy: this.table.primaryKey?.columns.map(x => x.columnName) || [this.table.columns[0].columnName], orderBy: this.table.primaryKey?.columns.map(x => x.columnName) || [this.table.columns[0].columnName],
condition: this.getChildrenCondition(),
}; };
} }

View File

@@ -320,6 +320,7 @@
input { input {
flex: 1; flex: 1;
min-width: 10px; min-width: 10px;
width: 1px;
} }
input.isError { input.isError {

View File

@@ -15,6 +15,7 @@
import debug from 'debug'; import debug from 'debug';
import contextMenu from '../utility/contextMenu'; import contextMenu from '../utility/contextMenu';
import DataFilterControl from '../datagrid/DataFilterControl.svelte'; import DataFilterControl from '../datagrid/DataFilterControl.svelte';
import { countVisibleRealColumns } from '../datagrid/gridutil';
const dbg = debug('dbgate:PerspectivaTable'); const dbg = debug('dbgate:PerspectivaTable');
@@ -115,15 +116,10 @@
} }
</script> </script>
<div <div class="wrapper" bind:this={domWrapper} use:resizeObserver={true} use:contextMenu={buildMenu}>
class="wrapper"
bind:this={domWrapper}
use:resizeObserver={true}
use:contextMenu={buildMenu}
>
{#if display} {#if display}
<table> <table>
<thead > <thead>
{#each _.range(display.columnLevelCount) as columnLevel} {#each _.range(display.columnLevelCount) as columnLevel}
<tr> <tr>
{#each display.columns as column} {#each display.columns as column}
@@ -136,13 +132,18 @@
{/each} {/each}
</tr> </tr>
{/each} {/each}
<!-- <tr> <tr>
{#each display.columns as column} {#each display.columns as column}
<th> <th class="filter">
<DataFilterControl filter="" setFilter={null} columnName={column.dataNode.codeName} filterType="string" /> <DataFilterControl
filter={column.dataNode.getFilter()}
setFilter={value => column.dataNode.setFilter(value)}
columnName={column.dataNode.uniqueName}
filterType={column.dataNode.filterType}
/>
</th> </th>
{/each} {/each}
</tr> --> </tr>
</thead> </thead>
<tbody> <tbody>
{#each display.rows as row} {#each display.rows as row}
@@ -218,6 +219,10 @@
border-right: 1px solid var(--theme-border); border-right: 1px solid var(--theme-border);
} }
th.filter {
padding: 0;
}
thead tr:first-child th { thead tr:first-child th {
border-top: 1px solid var(--theme-border); border-top: 1px solid var(--theme-border);
} }

View File

@@ -50,7 +50,15 @@
{schemaName} {schemaName}
{pureName} {pureName}
config={$config} config={$config}
setConfig={config.update} setConfig={(value, reload) => {
if (reload) {
cache.clear();
}
config.update(value);
if (reload) {
loadedCounts.set({});
}
}}
{cache} {cache}
{loadedCounts} {loadedCounts}
/> />