Skip to content

Commit

Permalink
Merge pull request #1541 from finos/showcase-search
Browse files Browse the repository at this point in the history
add filter to TreeDataSource, search to showcase
  • Loading branch information
heswell authored Nov 20, 2024
2 parents de5288a + f7cde22 commit f13f2b7
Show file tree
Hide file tree
Showing 25 changed files with 4,887 additions and 62 deletions.
3 changes: 3 additions & 0 deletions .semgrepignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ vuu/src/main/resources/www/ws-example.html
example/order/src/main/scala/org/finos/vuu/provider/simulation/SimulatedBigInstrumentsProvider.scala
vuu/src/main/scala/org/finos/vuu/provider/simulation/SimulatedBigInstrumentsProvider.scala
vuu-ui/packages/vuu-data-local/src/array-data-source/group-utils.ts
vuu-ui/packages/vuu-data-local/src/tree-data-source/TreeDataSource.ts
vuu-ui/packages/vuu-data-test/src/makeSuggestions.ts
vuu-ui/packages/vuu-datagrid-extras/src/column-expression-input/column-language-parser/walkExpressionTree.ts
vuu-ui/packages/vuu-layout/src/layout-persistence/RemoteLayoutPersistenceManager.ts
Expand All @@ -18,6 +19,8 @@ vuu-ui/showcase/scripts/build-file-list.mjs
vuu-ui/showcase/src/index.tsx
vuu-ui/showcase/src/examples/Layout/Menu.examples.tsx
vuu-ui/tools/websocket-test.html
vuu-ui/tools/vuu-showcase/src/shared-utils.ts
vuu-ui/tools/vuu-showcase/src/showcase-main/showcase-utils.ts
vuu/src/test/scala/org/finos/vuu/viewport/CreateViewPortScenarioTest.scala
vuu/src/test/scala/org/finos/vuu/net/ws/WebSocketServerClientTest.scala
vuu/src/test/scala/org/finos/vuu/net/rpc/RpcModuleTest.scala
Expand Down
111 changes: 97 additions & 14 deletions vuu-ui/packages/vuu-data-local/src/tree-data-source/TreeDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,26 @@ import type {
MenuRpcResponse,
VuuUIMessageInRPCEditReject,
VuuUIMessageInRPCEditResponse,
DataSourceFilter,
} from "@finos/vuu-data-types";
import {
BaseDataSource,
getParentRow,
isSelected,
isSingleValueFilter,
KeySet,
lastPathSegment,
metadataKeys,
missingAncestor,
NULL_RANGE,
rangesAreSame,
TreeSourceNode,
treeToDataSourceRows,
uuid,
} from "@finos/vuu-utils";
import { IconProvider } from "./IconProvider";
import { parseFilter } from "@finos/vuu-filter-parser";
import { FilterClause } from "@finos/vuu-filter-types";

const NULL_SCHEMA = { columns: [], key: "", table: { module: "", table: "" } };

Expand All @@ -55,14 +62,14 @@ export class TreeDataSource extends BaseDataSource {
private expandedRows = new Set<string>();
private visibleRows: DataSourceRow[] = [];
private visibleRowIndex: VisibleRowIndex = {};
private selectedRows: Selection = [];

#aggregations: VuuAggregation[] = [];
#data: DataSourceRow[];
#iconProvider: IconProvider;
#selectedRowsCount = 0;
#size = 0;
#status: DataSourceStatus = "initialising";
#filterSet: number[] | undefined;

public rowCount: number | undefined;

Expand Down Expand Up @@ -190,6 +197,56 @@ export class TreeDataSource extends BaseDataSource {
});
}

get filter() {
return this._config.filterSpec;
}

set filter(filter: DataSourceFilter) {
// Note not using the setter
this._config = {
...this._config,
filterSpec: filter,
};

if (filter.filter) {
this.applyFilter(filter);
} else {
this.#filterSet = undefined;
}

[this.visibleRows, this.visibleRowIndex] = getVisibleRows(
this.#data,
this.expandedRows,
this.#filterSet,
);

const { from, to } = this.range;
this.clientCallback?.({
clientViewportId: this.viewport,
mode: "batch",
rows: this.visibleRows
.slice(from, to)
.map((row) => toClientRow(row, this.keys)),
size: this.visibleRows.length,
type: "viewport-update",
});
}

private applyFilter({ filter: filterQuery, filterStruct }: DataSourceFilter) {
const filter = filterStruct ?? (parseFilter(filterQuery) as FilterClause);
if (isSingleValueFilter(filter)) {
const filterSet = [];
const regex = new RegExp(`${filter.value}`, "i");
for (const row of this.#data) {
const { [KEY]: key, [IDX]: idx } = row;
if (regex.test(lastPathSegment(key, "|"))) {
filterSet.push(idx);
}
}
this.#filterSet = filterSet;
}
}

/**
* used to apply an initial selection. These may not necessarily be
* visible. If revealOnSelect is in force, expand nodes as necessary
Expand All @@ -202,9 +259,7 @@ export class TreeDataSource extends BaseDataSource {
row[SELECTED] = 1;

if (revealSelected && row[DEPTH] !== 1) {
console.log(`we've got a deep one here`);
const keys = key.slice(6).split("|").slice(0, -1);
console.log(JSON.stringify(keys));

let path = "$root";
do {
Expand Down Expand Up @@ -408,27 +463,55 @@ export class TreeDataSource extends BaseDataSource {
function getVisibleRows(
rows: DataSourceRow[],
expandedKeys: Set<string>,
filterset?: number[],
): [visibleRows: DataSourceRow[], index: VisibleRowIndex] {
const visibleRows: DataSourceRow[] = [];
const visibleRowIndex: VisibleRowIndex = {};

for (let i = 0, index = 0; i < rows.length; i++) {
const row = rows[i];
const data = filterset ?? rows;

for (let i = 0, index = 0; i < data.length; i++) {
const idx = filterset ? filterset[i] : i;
const row = rows[idx];
const {
[COUNT]: count,
[DEPTH]: depth,
[KEY]: key,
[IS_LEAF]: isLeaf,
} = row;
const isExpanded = expandedKeys.has(key);
visibleRows.push(cloneRow(row, index, isExpanded));
visibleRowIndex[index] = i;
index += 1;
const skipNonVisibleRows = !isLeaf && !isExpanded && count > 0;
if (skipNonVisibleRows) {
do {
i += 1;
} while (i < rows.length - 1 && rows[i + 1][DEPTH] > depth);
if (filterset) {
// assume expanded for now

// if we have skipped a higher level group (that didn't directly match
// our filter criteria, add it.
const previousRow = visibleRows.at(-1);
if (missingAncestor(row, previousRow)) {
let currentRow: DataSourceRow | undefined = row;
const missingRows = [];
while (currentRow) {
currentRow = getParentRow(rows, currentRow);
if (currentRow) {
// we will get the missing rows in reverse order
missingRows.unshift(currentRow);
}
}
missingRows.forEach((row) => {
visibleRows.push(cloneRow(row, index++, true));
});
}

visibleRows.push(cloneRow(row, index++, true));
visibleRowIndex[index] = i;
} else {
const isExpanded = expandedKeys.has(key);
visibleRows.push(cloneRow(row, index, isExpanded));
visibleRowIndex[index++] = i;
const skipNonVisibleRows = !isLeaf && !isExpanded && count > 0;
if (skipNonVisibleRows) {
do {
i += 1;
} while (i < rows.length - 1 && rows[i + 1][DEPTH] > depth);
}
}
}
return [visibleRows, visibleRowIndex];
Expand Down
35 changes: 19 additions & 16 deletions vuu-ui/packages/vuu-datatable/src/tree-table/TreeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TableProps } from "@finos/vuu-table";
import { Table } from "@finos/vuu-table";
import { TreeDataSource } from "@finos/vuu-data-local";
import { useEffect, useMemo, useRef } from "react";
import { useMemo, useRef } from "react";
import { TableConfig } from "@finos/vuu-table-types";
import {
isRowSelected,
Expand All @@ -12,15 +12,18 @@ import {

const { DEPTH, IS_LEAF, KEY, IDX } = metadataKeys;

export interface TreeTableProps
extends Omit<TableProps, "config" | "dataSource"> {
interface Props extends Omit<TableProps, "config" | "dataSource"> {
config?: Pick<
TableConfig,
"columnSeparators" | "rowSeparators" | "zebraStripes"
>;
source: TreeSourceNode[];
dataSource?: TreeDataSource;
source?: TreeSourceNode[];
}

export type TreeTableProps = Props &
({ dataSource: TreeDataSource } | { source: TreeSourceNode[] });

const rowToTreeNodeObject: RowToObjectMapper = (row, columnMap) => {
const { [IS_LEAF]: isLeaf, [KEY]: key, [IDX]: index, [DEPTH]: depth } = row;
const firstColIdx = columnMap.nodeData;
Expand All @@ -40,16 +43,22 @@ const rowToTreeNodeObject: RowToObjectMapper = (row, columnMap) => {

export const TreeTable = ({
config,
source: sourceProp,
dataSource,
source,
...tableProps
}: TreeTableProps) => {
const sourceRef = useRef(sourceProp);
const dataSourceRef = useRef<TreeDataSource>();
useMemo(() => {
dataSourceRef.current = new TreeDataSource({
data: sourceRef.current,
});
}, []);
if (dataSource) {
dataSourceRef.current = dataSource;
} else if (source) {
dataSourceRef.current = new TreeDataSource({
data: source,
});
} else {
throw Error(`TreeTable either source or dataSource must be provided`);
}
}, [dataSource, source]);

const tableConfig = useMemo<TableConfig>(() => {
return {
Expand All @@ -60,12 +69,6 @@ export const TreeTable = ({
};
}, [config]);

useEffect(() => {
if (dataSourceRef.current) {
dataSourceRef.current.data = sourceProp;
}
}, [sourceProp]);

if (dataSourceRef.current === undefined) {
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion vuu-ui/packages/vuu-utils/src/column-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ export const getGroupValue = (
} else if (depth === 0) {
return "$root";
} else {
// offset 1 for now to allow for $root
// offset allows for $root
const { name, valueFormatter } = columns[depth - 1];
const value = valueFormatter(row[columnMap[name]]);
return value;
Expand Down
44 changes: 43 additions & 1 deletion vuu-ui/packages/vuu-utils/src/tree-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { DataSourceRow } from "@finos/vuu-data-types";
import { metadataKeys } from "./column-utils";
import { IconProvider } from "@finos/vuu-data-local/src/tree-data-source/IconProvider";

const { COUNT } = metadataKeys;
const { COUNT, DEPTH, IDX, KEY } = metadataKeys;

type Index = { value: number };

Expand Down Expand Up @@ -88,3 +88,45 @@ const addChildValues = (

return [leafCount, rowCount];
};

export const lastPathSegment = (path: string, separator = "/") => {
const root = path.endsWith(separator) ? path.slice(0, -1) : path;
return root.slice(root.lastIndexOf(separator) + 1);
};

export const dropLastPathSegment = (path: string, separator = "/") => {
return path.slice(0, path.lastIndexOf(separator));
};

export const getParentRow = (rows: DataSourceRow[], row: DataSourceRow) => {
const { [IDX]: idx, [DEPTH]: depth } = row;
for (let i = idx - 1; i >= 0; i--) {
const nextRow = rows[i];
if (nextRow[DEPTH] === depth - 1) {
return nextRow;
}
}
};

const rowsAreSiblings = (key1: string, key2: string) =>
dropLastPathSegment(key1, "|") === dropLastPathSegment(key2, "|");

export const missingAncestor = (
row: DataSourceRow,
previousRow?: DataSourceRow,
) => {
if (previousRow) {
const prevKey = previousRow[KEY];
const key = row[KEY];

if (key.startsWith(prevKey)) {
return false;
} else if (!rowsAreSiblings(prevKey, key)) {
return true;
}
} else if (row[DEPTH] > 1) {
return true;
}

return false;
};
6 changes: 1 addition & 5 deletions vuu-ui/showcase/scripts/treeSourceFromFileSystem.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import fs from "fs";
import path from "path";
import { type TreeSourceNode } from "@finos/vuu-utils";
import { dropLastPathSegment, type TreeSourceNode } from "@finos/vuu-utils";

const lastPathSegment = (path: string, separator = "/") => {
const root = path.endsWith(separator) ? path.slice(0, -1) : path;
return root.slice(root.lastIndexOf(separator) + 1);
};
// TODO use version from vuu-utils
const dropLastPathSegment = (path: string, separator = "/") => {
return path.slice(0, path.lastIndexOf(separator));
};

const exportPattern = /export const ([A-Z][a-zA-Z]+) = /g;

Expand Down
Loading

0 comments on commit f13f2b7

Please sign in to comment.