From b4426e9a7a6f593149d313a3a9fdc46504dfc103 Mon Sep 17 00:00:00 2001 From: heswell Date: Thu, 15 Aug 2024 16:11:10 +0100 Subject: [PATCH 1/3] make sure keys are set when range is set after subscribe --- .../src/array-data-source/array-data-source.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts b/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts index d2792622a..626f28ca1 100644 --- a/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts +++ b/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts @@ -117,6 +117,7 @@ export class ArrayDataSource #columnMap: ColumnMap; #config: WithFullConfig = vanillaConfig; #data: DataSourceRow[]; + #keys = new KeySet(NULL_RANGE); #links: LinkDescriptorWithLabel[] | undefined; #range: VuuRange = NULL_RANGE; #selectedRowsCount = 0; @@ -130,7 +131,6 @@ export class ArrayDataSource public tableSchema: TableSchema; public viewport: string; - private keys = new KeySet(this.#range); protected processedData: DataSourceRow[] | undefined = undefined; constructor({ @@ -206,7 +206,7 @@ export class ArrayDataSource aggregations || columns || filterSpec || groupBy || sort; if (hasConfigProps) { if (range) { - this.#range = range; + this.setRange(range); } config = { ...config, @@ -563,7 +563,7 @@ export class ArrayDataSource private setRange(range: VuuRange, forceFullRefresh = false) { if (range.from !== this.#range.from || range.to !== this.#range.to) { this.#range = range; - const keysResequenced = this.keys.reset(range); + const keysResequenced = this.#keys.reset(range); this.sendRowsToClient(forceFullRefresh || keysResequenced); } else if (forceFullRefresh) { this.sendRowsToClient(forceFullRefresh); @@ -576,7 +576,7 @@ export class ArrayDataSource clientViewportId: this.viewport, mode: "update", rows: [ - toClientRow(row, this.keys, this.selectedRows, this.dataIndices), + toClientRow(row, this.#keys, this.selectedRows, this.dataIndices), ], type: "viewport-update", }); @@ -590,7 +590,7 @@ export class ArrayDataSource const rowsWithinViewport = data .slice(rowRange.from, rowRange.to) .map((row) => - toClientRow(row, this.keys, this.selectedRows, this.dataIndices) + toClientRow(row, this.#keys, this.selectedRows, this.dataIndices) ); this.clientCallback?.({ From e7523272a98b2aa06efa6b4a8ae816aea6556cf5 Mon Sep 17 00:00:00 2001 From: heswell Date: Thu, 15 Aug 2024 22:35:07 +0100 Subject: [PATCH 2/3] add example of VuuModule subclass --- .../array-data-source/array-data-source.ts | 4 +- .../vuu-data-remote/src/vuu-data-source.ts | 4 +- .../src/TickingArrayDataSource.ts | 3 +- .../packages/vuu-data-test/src/VuuModule.ts | 16 ++++- .../vuu-data-test/src/simul/SimulModule.ts | 61 +++++++++++++++++++ .../vuu-data-test/src/simul/simul-module.ts | 5 +- vuu-ui/packages/vuu-data-types/index.d.ts | 1 + vuu-ui/packages/vuu-protocol-types/index.d.ts | 4 +- .../packages/vuu-utils/src/selection-utils.ts | 11 ++++ .../examples/Table/TableLayout.examples.tsx | 56 ++++++++++++++--- 10 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 vuu-ui/packages/vuu-data-test/src/simul/SimulModule.ts diff --git a/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts b/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts index 626f28ca1..ca992f5a8 100644 --- a/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts +++ b/vuu-ui/packages/vuu-data-local/src/array-data-source/array-data-source.ts @@ -50,6 +50,7 @@ import { vanillaConfig, withConfigDefaults, DataSourceConfigChanges, + selectionCount, } from "@finos/vuu-utils"; import { aggregateData } from "./aggregate-utils"; import { buildDataToClientMap, toClientRow } from "./array-data-utils"; @@ -278,10 +279,11 @@ export class ArrayDataSource } select(selected: Selection) { - this.#selectedRowsCount = selected.length; + this.#selectedRowsCount = selectionCount(selected); debug?.(`select ${JSON.stringify(selected)}`); this.selectedRows = selected; this.setRange(resetRange(this.#range), true); + this.emit("row-selection", selected, this.#selectedRowsCount); } openTreeNode(key: string) { diff --git a/vuu-ui/packages/vuu-data-remote/src/vuu-data-source.ts b/vuu-ui/packages/vuu-data-remote/src/vuu-data-source.ts index 3b6b8426a..05bea1c51 100644 --- a/vuu-ui/packages/vuu-data-remote/src/vuu-data-source.ts +++ b/vuu-ui/packages/vuu-data-remote/src/vuu-data-source.ts @@ -45,6 +45,7 @@ import { vanillaConfig, withConfigDefaults, DataSourceConfigChanges, + selectionCount, } from "@finos/vuu-utils"; import { getServerAPI, ServerAPI } from "./connection-manager"; import { isDataSourceConfigMessage } from "./data-source"; @@ -314,13 +315,14 @@ export class VuuDataSource select(selected: Selection) { //TODO this isn't always going to be correct - need to count // selection block items - this.#selectedRowsCount = selected.length; + this.#selectedRowsCount = selectionCount(selected); if (this.viewport) { this.server?.send({ viewport: this.viewport, type: "select", selected, }); + this.emit("row-selection", selected, this.#selectedRowsCount); } } diff --git a/vuu-ui/packages/vuu-data-test/src/TickingArrayDataSource.ts b/vuu-ui/packages/vuu-data-test/src/TickingArrayDataSource.ts index 519234593..b4e870d1f 100644 --- a/vuu-ui/packages/vuu-data-test/src/TickingArrayDataSource.ts +++ b/vuu-ui/packages/vuu-data-test/src/TickingArrayDataSource.ts @@ -235,10 +235,9 @@ export class TickingArrayDataSource extends ArrayDataSource { return rpcService.service(rpcRequest) as Promise; } } - const selectedRows = this.getSelectedRows(); return rpcService.service({ ...rpcRequest, - selectedRows, + selectedRowIds: this.getSelectedRowIds(), }) as Promise; } else { console.log(`no implementation for PRC service ${rpcRequest.rpcName}`); diff --git a/vuu-ui/packages/vuu-data-test/src/VuuModule.ts b/vuu-ui/packages/vuu-data-test/src/VuuModule.ts index d66f5659f..65d295a6e 100644 --- a/vuu-ui/packages/vuu-data-test/src/VuuModule.ts +++ b/vuu-ui/packages/vuu-data-test/src/VuuModule.ts @@ -190,6 +190,14 @@ export class VuuModule implements IVuuModule { return () => this.suggestionFetcher; } + protected get sessionTableMap() { + return this.#sessionTableMap; + } + + protected get tables() { + return this.#tables; + } + private getSessionTable() { if (Object.keys(this.#sessionTableMap).length === 1) { const [sessionTable] = Object.values(this.#sessionTableMap); @@ -273,7 +281,11 @@ export class VuuModule implements IVuuModule { const newRow = sessionTable.data[i]; const { column, value } = rpcRequest.namedParams; const keyIndex = sessionTable.map[sessionTable.schema.key]; - sessionTable.update(String(newRow[keyIndex]), column as string, value); + sessionTable.update( + String(newRow[keyIndex]), + column as string, + value as VuuRowDataItemType + ); } return { action: { @@ -310,7 +322,7 @@ export class VuuModule implements IVuuModule { } }; - private createSessionTableFromSelectedRows( + protected createSessionTableFromSelectedRows( { data, map, schema }: Table, selectedRowIds: string[] ) { diff --git a/vuu-ui/packages/vuu-data-test/src/simul/SimulModule.ts b/vuu-ui/packages/vuu-data-test/src/simul/SimulModule.ts new file mode 100644 index 000000000..8c991ee43 --- /dev/null +++ b/vuu-ui/packages/vuu-data-test/src/simul/SimulModule.ts @@ -0,0 +1,61 @@ +import { uuid } from "@finos/vuu-utils"; +import { VuuTable } from "@finos/vuu-protocol-types"; +import { + RpcService, + RpcServiceRequest, + VuuModule, + VuuModuleConstructorProps, + withParams, +} from "../VuuModule"; +import { SimulTableName } from "./simul-schemas"; + +/** + * This is an example of how we might extend the built-in VuuModule to + * implement a module-specific service in such a way that it can invoke + * methods on the VuuModule. + */ +export class SimulModule extends VuuModule { + constructor(props: VuuModuleConstructorProps) { + super(props); + } + + getServices(tableName: SimulTableName) { + return this.#services.concat(super.getServices(tableName)); + } + + private openEditSession = async (rpcRequest: RpcServiceRequest) => { + if (withParams(rpcRequest)) { + const { namedParams, selectedRowIds } = rpcRequest; + if (selectedRowIds && namedParams.table) { + const table = namedParams.table as VuuTable; + const dataTable = this.tables[table.table as SimulTableName]; + + const sessionTable = this.createSessionTableFromSelectedRows( + dataTable, + selectedRowIds + ); + + const sessionTableName = `${table.table}-${uuid()}`; + this.sessionTableMap[sessionTableName] = sessionTable; + + return { + table: { + module: "SIMUL", + table: sessionTableName, + }, + tableSchema: dataTable.schema, + type: "VIEW_PORT_RPC_REPONSE", + requestId: "request_id", + rpcName: "openEditSession", + }; + } + } + }; + + #services: RpcService[] = [ + { + rpcName: "openEditSession", + service: this.openEditSession, + }, + ]; +} diff --git a/vuu-ui/packages/vuu-data-test/src/simul/simul-module.ts b/vuu-ui/packages/vuu-data-test/src/simul/simul-module.ts index 07cace530..037fddfce 100644 --- a/vuu-ui/packages/vuu-data-test/src/simul/simul-module.ts +++ b/vuu-ui/packages/vuu-data-test/src/simul/simul-module.ts @@ -10,8 +10,9 @@ import { instrumentsExtendedTable } from "./reference-data/instruments-extended" import { ordersTable } from "./reference-data/orders"; import { pricesTable } from "./reference-data/prices"; import { schemas, type SimulTableName } from "./simul-schemas"; -import { RpcService, RpcServiceRequest, VuuModule } from "../VuuModule"; +import { RpcService, RpcServiceRequest } from "../VuuModule"; import { MenuRpcResponse } from "packages/vuu-data-types"; +import { SimulModule } from "./SimulModule"; const undefinedTables = { childOrders: undefined, @@ -122,7 +123,7 @@ const services: Record = { ], }; -export const simulModule = new VuuModule({ +export const simulModule = new SimulModule({ menus, name: "SIMUL", schemas, diff --git a/vuu-ui/packages/vuu-data-types/index.d.ts b/vuu-ui/packages/vuu-data-types/index.d.ts index 310612828..febe4d4cd 100644 --- a/vuu-ui/packages/vuu-data-types/index.d.ts +++ b/vuu-ui/packages/vuu-data-types/index.d.ts @@ -340,6 +340,7 @@ export type DataSourceEvents = { optimize: (optimize: OptimizeStrategy) => void; range: (range: VuuRange) => void; resize: (size: number) => void; + "row-selection": (selection: Selection, selectedRowCount: number) => void; subscribed: (subscription: DataSourceSubscribedMessage) => void; unsubscribed: DataSourceEventHandler; disabled: DataSourceEventHandler; diff --git a/vuu-ui/packages/vuu-protocol-types/index.d.ts b/vuu-ui/packages/vuu-protocol-types/index.d.ts index 4f3609404..401c26487 100644 --- a/vuu-ui/packages/vuu-protocol-types/index.d.ts +++ b/vuu-ui/packages/vuu-protocol-types/index.d.ts @@ -411,7 +411,7 @@ export interface ServerToClientRPC { export interface ClientToServerViewportRpcCall { type: "VIEW_PORT_RPC_CALL"; rpcName: string; - namedParams: { [key: string]: VuuRowDataItemType }; + namedParams: { [key: string]: VuuRowDataItemType | VuuTable }; params: string[]; vpId: string; } @@ -424,7 +424,7 @@ export interface ServerToClientViewportRpcResponse { }; type: "VIEW_PORT_RPC_REPONSE"; method: string; - namedParams: { [key: string]: VuuRowDataItemType }; + namedParams: { [key: string]: VuuRowDataItemType | VuuTable }; params: string[]; vpId: string; } diff --git a/vuu-ui/packages/vuu-utils/src/selection-utils.ts b/vuu-ui/packages/vuu-utils/src/selection-utils.ts index 5c724cefb..5dd97d3fc 100644 --- a/vuu-ui/packages/vuu-utils/src/selection-utils.ts +++ b/vuu-ui/packages/vuu-utils/src/selection-utils.ts @@ -330,3 +330,14 @@ export type SelectionDiff = { added: SelectionItem[]; removed: SelectionItem[]; }; + +export const selectionCount = (selected: Selection) => { + let count = selected.length; + for (const selectionItem of selected){ + if (Array.isArray(selectionItem)){ + const [from, to] = selectionItem; + count += (to - (from + 1)); + } + } + return count; +} diff --git a/vuu-ui/showcase/src/examples/Table/TableLayout.examples.tsx b/vuu-ui/showcase/src/examples/Table/TableLayout.examples.tsx index 5fdffa730..72a707172 100644 --- a/vuu-ui/showcase/src/examples/Table/TableLayout.examples.tsx +++ b/vuu-ui/showcase/src/examples/Table/TableLayout.examples.tsx @@ -14,17 +14,29 @@ import { columnGenerator, rowGenerator } from "./SimpleTableDataGenerator"; let displaySequence = 1; -export const DataTable = (props: Partial) => { +type DataTableProps = Partial< + Omit & { config?: Partial } +>; + +export const DataTable = ({ + dataSource: dataSourceProp, + navigationStyle = "cell", + width = 600, + ...props +}: DataTableProps) => { const tableConfig = useMemo(() => { return { + ...props.config, columns: getSchema("instruments").columns, rowSeparators: true, zebraStripes: true, }; - }, []); + }, [props.config]); + const dataSource = useMemo(() => { - return vuuModule("SIMUL").createDataSource("instruments"); - }, []); + return dataSourceProp ?? vuuModule("SIMUL").createDataSource("instruments"); + }, [dataSourceProp]); + return ( <> ) => { dataSource={dataSource} height={500} renderBufferSize={20} - width={600} + navigationStyle={navigationStyle} + width={width} /> ); @@ -55,19 +68,38 @@ const InlineDrawer = ({ const list = useRef(null); const [open, setOpen] = useState(false); + const dataSource = useMemo(() => { + const ds = vuuModule("SIMUL").createDataSource("instruments"); + return ds; + }, []); + const handleSelectionChange: SelectionChangeHandler = useCallback( (selection: Selection) => { if (selection.length > 0) { setOpen(true); + dataSource + .rpcCall({ + rpcName: "openEditSession", + type: "VIEW_PORT_RPC_CALL", + namedParams: { + table: dataSource.table, + }, + params: [], + }) + .then((response) => { + console.log(`response`, { + response, + }); + }); } else { setOpen(false); } }, - [] + [dataSource] ); return ( - + - + ); }; export const RightInlineDrawerPeek = () => ( - + ); RightInlineDrawerPeek.displaySequence = displaySequence++; From 4dbdd17dd62552124bd3c47ce42ac95958878490 Mon Sep 17 00:00:00 2001 From: heswell Date: Thu, 15 Aug 2024 22:50:52 +0100 Subject: [PATCH 3/3] fix cypress test --- .../vuu-shell/src/__tests__/ShellLayout.cy.tsx | 2 +- .../src/examples/Shell/ShellLayout.examples.tsx | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/vuu-ui/packages/vuu-shell/src/__tests__/ShellLayout.cy.tsx b/vuu-ui/packages/vuu-shell/src/__tests__/ShellLayout.cy.tsx index 51c5bdbf3..90468247b 100644 --- a/vuu-ui/packages/vuu-shell/src/__tests__/ShellLayout.cy.tsx +++ b/vuu-ui/packages/vuu-shell/src/__tests__/ShellLayout.cy.tsx @@ -63,7 +63,7 @@ describe("ShellLayout", () => { cy.mount(); cy.findByRole("img", { name: "Create Tab" }).realClick(); cy.findAllByRole("tab").should("have.length", 4); - cy.findByTestId("custom-placeholder2").should("be.visible"); + cy.findByTestId("custom-placeholder").should("be.visible"); }); }); }); diff --git a/vuu-ui/showcase/src/examples/Shell/ShellLayout.examples.tsx b/vuu-ui/showcase/src/examples/Shell/ShellLayout.examples.tsx index 946f2a609..8fc3d3e8e 100644 --- a/vuu-ui/showcase/src/examples/Shell/ShellLayout.examples.tsx +++ b/vuu-ui/showcase/src/examples/Shell/ShellLayout.examples.tsx @@ -210,7 +210,7 @@ export const SimpleShellMultiLayouts = () => { const persistNothing = useMemo(() => new StaticPersistenceManager({}), []); const workspaceProps = useMemo(() => { - const placeHolder = { + const layoutPlaceholderJSON = { type: "Placeholder", props: { "data-testid": "custom-placeholder", @@ -219,11 +219,10 @@ export const SimpleShellMultiLayouts = () => { }, }, }; - const layouts = [ + const layoutJSON = [ { type: "Placeholder", props: { - "data-testid": "custom-placeholder1", style: { background: "yellow", }, @@ -241,7 +240,6 @@ export const SimpleShellMultiLayouts = () => { { type: "Placeholder", props: { - "data-testid": "custom-placeholder3", style: { background: "green", }, @@ -249,8 +247,8 @@ export const SimpleShellMultiLayouts = () => { }, ]; return { - layoutJSON: layouts, - layoutPlaceholderJSON: placeHolder, + layoutJSON, + layoutPlaceholderJSON, activeLayoutIndex: 1, }; }, []);