From 65928846fa390de65397ec6ec27f561099850f3c Mon Sep 17 00:00:00 2001 From: heswell Date: Tue, 26 Nov 2024 21:29:23 +0000 Subject: [PATCH] fix feature confoguration, basket trading --- .../src/layout-view-actions/ViewContext.ts | 4 +- .../FeatureAndLayoutProvider.tsx | 1 + vuu-ui/packages/vuu-table-types/index.d.ts | 5 + .../src/table-search/TableSearch.tsx | 2 - .../packages/vuu-utils/src/feature-utils.ts | 112 +++++----- .../feature-basket-trading/index.ts | 3 - .../feature-basket-trading/package.json | 2 +- .../src/VuuBasketTradingFeature.tsx | 38 ++-- .../src/basket-table-edit/BasketTableEdit.tsx | 14 +- .../ColHeaderAddSymbol.tsx | 18 +- .../src/useBasketTrading.tsx | 137 ++++++------ .../src/useBasketTradingDatasources.ts | 204 +++++++++++------- .../feature-filter-table/package.json | 2 +- .../feature-instrument-tiles/package.json | 2 +- .../src/examples/Apps/SampleApp.examples.tsx | 24 +-- .../BasketTradingFeature.examples.tsx | 107 +++------ 16 files changed, 326 insertions(+), 349 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/layout-view-actions/ViewContext.ts b/vuu-ui/packages/vuu-layout/src/layout-view-actions/ViewContext.ts index 28d95d3d9..a8e985608 100644 --- a/vuu-ui/packages/vuu-layout/src/layout-view-actions/ViewContext.ts +++ b/vuu-ui/packages/vuu-layout/src/layout-view-actions/ViewContext.ts @@ -5,7 +5,7 @@ export type QueryReponse = { [key: string]: unknown }; export type ViewDispatch = ( action: Action, - evt?: SyntheticEvent + evt?: SyntheticEvent, ) => Promise; /** @@ -20,7 +20,7 @@ export interface ViewContextAPI { */ dispatch?: ViewDispatch | null; id?: string; - load?: (key?: string) => unknown; + load?: (key?: string) => T; loadSession?: (key?: string) => unknown; onConfigChange?: (config: unknown) => void; path?: string; diff --git a/vuu-ui/packages/vuu-shell/src/feature-and-layout-provider/FeatureAndLayoutProvider.tsx b/vuu-ui/packages/vuu-shell/src/feature-and-layout-provider/FeatureAndLayoutProvider.tsx index 94e48778b..694f83742 100644 --- a/vuu-ui/packages/vuu-shell/src/feature-and-layout-provider/FeatureAndLayoutProvider.tsx +++ b/vuu-ui/packages/vuu-shell/src/feature-and-layout-provider/FeatureAndLayoutProvider.tsx @@ -59,6 +59,7 @@ export const FeatureAndLayoutProvider = ({ systemLayouts, }: FeatureAndLayoutProviderProps): ReactElement => { const tableSchemas = useVuuTables(); + const { dynamicFeatures, tableFeatures } = useMemo<{ dynamicFeatures: DynamicFeatureProps[]; tableFeatures: DynamicFeatureProps[]; diff --git a/vuu-ui/packages/vuu-table-types/index.d.ts b/vuu-ui/packages/vuu-table-types/index.d.ts index 27b69f266..269b57d5e 100644 --- a/vuu-ui/packages/vuu-table-types/index.d.ts +++ b/vuu-ui/packages/vuu-table-types/index.d.ts @@ -249,6 +249,11 @@ export interface ColumnDescriptor extends DataValueDescriptor { aggregate?: VuuAggType; align?: ColumnAlignment; className?: string; + /** + * Allows custom content to be rendered into the column header. This will be an identifier. + * The identifier will be used to retrieve content from the component registry. The componnet + * yielded will be rendered into the column header. + */ colHeaderContentRenderer?: string; colHeaderLabelRenderer?: string; flex?: number; diff --git a/vuu-ui/packages/vuu-ui-controls/src/table-search/TableSearch.tsx b/vuu-ui/packages/vuu-ui-controls/src/table-search/TableSearch.tsx index 927b24f8f..fd7f41c35 100644 --- a/vuu-ui/packages/vuu-ui-controls/src/table-search/TableSearch.tsx +++ b/vuu-ui/packages/vuu-ui-controls/src/table-search/TableSearch.tsx @@ -105,5 +105,3 @@ export const TableSearch = ({ registerComponent("search-cell", SearchCell, "cell-renderer", { serverDataType: "private", }); - -registerComponent?.("TableSearch", TableSearch, "view"); diff --git a/vuu-ui/packages/vuu-utils/src/feature-utils.ts b/vuu-ui/packages/vuu-utils/src/feature-utils.ts index b380df86c..b4ccf9f55 100644 --- a/vuu-ui/packages/vuu-utils/src/feature-utils.ts +++ b/vuu-ui/packages/vuu-utils/src/feature-utils.ts @@ -48,8 +48,7 @@ export interface DynamicFeatureDescriptor { css?: string; leftNavLocation: "vuu-features" | "vuu-tables"; featureProps?: { - schema?: "*" | VuuTable; - schemas?: VuuTable[]; + vuuTables?: "*" | VuuTable[]; }; viewProps?: ViewConfig; } @@ -95,15 +94,20 @@ export interface VuuConfig { ssl: boolean; } +/** + * We currently categorize 'features' simply by the leftNavLocation + * @param feature + * @returns + */ export const isCustomFeature = (feature: DynamicFeatureDescriptor) => feature.leftNavLocation === "vuu-features"; -export const isWildcardSchema = (schema?: "*" | VuuTable): schema is "*" => - schema === "*"; -export const isVuuTable = (vuuTable?: "*" | VuuTable): vuuTable is VuuTable => - typeof vuuTable === "object" && - typeof vuuTable.module === "string" && - typeof vuuTable.table === "string"; +export const isWildcardSchema = ( + vuuTables?: "*" | VuuTable[], +): vuuTables is "*" => vuuTables === "*"; +export const isVuuTables = ( + vuuTables?: "*" | VuuTable[], +): vuuTables is VuuTable[] => Array.isArray(vuuTables); export interface FeaturePropsWithFilterTableFeature extends Omit { @@ -182,7 +186,14 @@ export const assertComponentsRegistered = (componentList: Component[]) => { assertComponentRegistered(componentName, component); } }; - +/** + * Process the DynamicFeature descriptors. Identify + * the vuu tables required and inject the appropriate TableSchemas + * + * @param dynamicFeatures + * @param tableSchemas + * @returns + */ export const getCustomAndTableFeatures = ( dynamicFeatures: DynamicFeatureDescriptor[], tableSchemas: TableSchema[], @@ -190,6 +201,7 @@ export const getCustomAndTableFeatures = ( dynamicFeatures: DynamicFeatureProps[]; tableFeatures: DynamicFeatureProps[]; } => { + // Split features into simple tables and 'custom' features const [customFeatureConfig, tableFeaturesConfig] = partition( dynamicFeatures, isCustomFeature, @@ -203,24 +215,27 @@ export const getCustomAndTableFeatures = ( viewProps, ...feature } of tableFeaturesConfig) { - const { schema: vuuTable } = featureProps; - if (isWildcardSchema(vuuTable) && tableSchemas) { - for (const tableSchema of tableSchemas) { - tableFeatures.push({ - ...feature, - ComponentProps: { - tableSchema, - }, - title: `${tableSchema.table.module} ${wordify( - tableSchema.table.table, - )}`, - ViewProps: { - ...viewProps, - allowRename: true, - }, - }); + const { vuuTables } = featureProps; + // Currently FilterTable is the only 'tableFeature' and it uses the wildcard + if (isWildcardSchema(vuuTables)) { + if (tableSchemas) { + for (const tableSchema of tableSchemas) { + tableFeatures.push({ + ...feature, + ComponentProps: { + tableSchema, + }, + title: `${tableSchema.table.module} ${wordify( + tableSchema.table.table, + )}`, + ViewProps: { + ...viewProps, + allowRename: true, + }, + }); + } } - } else if (isVuuTable(vuuTable) && tableSchemas) { + } /*else if (isVuuTables(vuuTables) && tableSchemas) { const tableSchema = tableSchemas.find((tableSchema) => isSameTable(vuuTable, tableSchema.table), ); @@ -233,7 +248,7 @@ export const getCustomAndTableFeatures = ( ViewProps: viewProps, }); } - } + }*/ } for (const { @@ -241,32 +256,23 @@ export const getCustomAndTableFeatures = ( viewProps, ...feature } of customFeatureConfig) { - const { schema: vuuTable, schemas } = featureProps; - if (isVuuTable(vuuTable) && tableSchemas) { - const tableSchema = tableSchemas.find((tableSchema) => - isSameTable(vuuTable, tableSchema.table), - ); - customFeatures.push({ - ...feature, - ComponentProps: { - tableSchema, - }, - ViewProps: viewProps, - }); - } else if (Array.isArray(schemas) && tableSchemas) { - customFeatures.push({ - ...feature, - ComponentProps: schemas.reduce>( - (map, vuuTable) => { - map[`${vuuTable.table}Schema`] = tableSchemas.find((tableSchema) => - isSameTable(vuuTable, tableSchema.table), - ) as TableSchema; - return map; - }, - {}, - ), - ViewProps: viewProps, - }); + const { vuuTables } = featureProps; + if (isVuuTables(vuuTables)) { + if (tableSchemas) { + customFeatures.push({ + ...feature, + ComponentProps: vuuTables.reduce>( + (map, vuuTable) => { + map[`${vuuTable.table}Schema`] = tableSchemas.find( + (tableSchema) => isSameTable(vuuTable, tableSchema.table), + ) as TableSchema; + return map; + }, + {}, + ), + ViewProps: viewProps, + }); + } } else { customFeatures.push(feature); } diff --git a/vuu-ui/sample-apps/feature-basket-trading/index.ts b/vuu-ui/sample-apps/feature-basket-trading/index.ts index dd893f903..2de895906 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/index.ts +++ b/vuu-ui/sample-apps/feature-basket-trading/index.ts @@ -1,10 +1,7 @@ import VuuBasketTradingFeature from "./src/VuuBasketTradingFeature"; export default VuuBasketTradingFeature; - -export type { BasketTradingFeatureProps } from "./src/VuuBasketTradingFeature"; export type { basketDataSourceKey } from "./src/useBasketTradingDatasources"; export { BasketSelector } from "./src/basket-selector"; export { BasketToolbar } from "./src/basket-toolbar"; export { NewBasketPanel } from "./src/new-basket-panel"; - export { Basket } from "./src/useBasketTrading"; diff --git a/vuu-ui/sample-apps/feature-basket-trading/package.json b/vuu-ui/sample-apps/feature-basket-trading/package.json index 47f7de018..0007eeb88 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/package.json +++ b/vuu-ui/sample-apps/feature-basket-trading/package.json @@ -47,7 +47,7 @@ "header": false }, "featureProps": { - "schemas": [ + "vuuTables": [ { "module": "BASKET", "table": "basket" diff --git a/vuu-ui/sample-apps/feature-basket-trading/src/VuuBasketTradingFeature.tsx b/vuu-ui/sample-apps/feature-basket-trading/src/VuuBasketTradingFeature.tsx index 3e60d8efc..1ce50da0d 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/src/VuuBasketTradingFeature.tsx +++ b/vuu-ui/sample-apps/feature-basket-trading/src/VuuBasketTradingFeature.tsx @@ -1,33 +1,22 @@ -import { TableSchema } from "@finos/vuu-data-types"; import { FlexboxLayout, Stack } from "@finos/vuu-layout"; import { BasketTableEdit } from "./basket-table-edit"; import { BasketTableLive } from "./basket-table-live"; import { BasketToolbar } from "./basket-toolbar"; - -import "./VuuBasketTradingFeature.css"; import { EmptyBasketsPanel } from "./empty-baskets-panel"; import { useBasketTrading } from "./useBasketTrading"; +import "./VuuBasketTradingFeature.css"; + const classBase = "VuuBasketTradingFeature"; export type BasketStatus = "design" | "on-market"; const basketStatus: [BasketStatus, BasketStatus] = ["design", "on-market"]; -export interface BasketTradingFeatureProps { - basketSchema: TableSchema; - basketConstituentSchema: TableSchema; - basketTradingSchema: TableSchema; - basketTradingConstituentJoinSchema: TableSchema; -} - -const VuuBasketTradingFeature = (props: BasketTradingFeatureProps) => { - const { - basketSchema, - basketConstituentSchema, - basketTradingSchema, - basketTradingConstituentJoinSchema, - } = props; - +const VuuBasketTradingFeature = () => { + const basketTradingProps = useBasketTrading(); + if (basketTradingProps === undefined) { + return null; + } const { basket, basketCount, @@ -44,14 +33,13 @@ const VuuBasketTradingFeature = (props: BasketTradingFeatureProps) => { onDropInstrument, onSendToMarket, onTakeOffMarket, - } = useBasketTrading({ - basketSchema, - basketConstituentSchema, - basketTradingSchema, - basketTradingConstituentJoinSchema, - }); + } = basketTradingProps; - if (basketCount === -1) { + if ( + basketCount === -1 || + dataSourceBasketTradingConstituentJoin === undefined || + basketSelectorProps === undefined + ) { // TODO loading return null; } else if (basketCount === 0) { diff --git a/vuu-ui/sample-apps/feature-basket-trading/src/basket-table-edit/BasketTableEdit.tsx b/vuu-ui/sample-apps/feature-basket-trading/src/basket-table-edit/BasketTableEdit.tsx index 1c424cb57..5d03b08b7 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/src/basket-table-edit/BasketTableEdit.tsx +++ b/vuu-ui/sample-apps/feature-basket-trading/src/basket-table-edit/BasketTableEdit.tsx @@ -1,17 +1,21 @@ -import { Table, TableProps } from "@finos/vuu-table"; import { ContextMenuConfiguration, ContextMenuProvider, } from "@finos/vuu-popups"; +import { Table, TableProps } from "@finos/vuu-table"; +import { registerComponent } from "@finos/vuu-utils"; import { ColHeaderAddSymbol } from "../cell-renderers"; import "./BasketTableEdit.css"; -const classBase = "vuuBasketTableEdit"; +registerComponent( + "col-header-add-symbol", + ColHeaderAddSymbol, + "column-header-content-renderer", + {}, +); -if (typeof ColHeaderAddSymbol !== "function") { - console.warn("BasketTableEdit not all custom cell renderers are available"); -} +const classBase = "vuuBasketTableEdit"; export interface BasketTableEditProps extends TableProps { contextMenuConfig: ContextMenuConfiguration; diff --git a/vuu-ui/sample-apps/feature-basket-trading/src/cell-renderers/col-header-add-symbol/ColHeaderAddSymbol.tsx b/vuu-ui/sample-apps/feature-basket-trading/src/cell-renderers/col-header-add-symbol/ColHeaderAddSymbol.tsx index 965cca284..9b24757da 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/src/cell-renderers/col-header-add-symbol/ColHeaderAddSymbol.tsx +++ b/vuu-ui/sample-apps/feature-basket-trading/src/cell-renderers/col-header-add-symbol/ColHeaderAddSymbol.tsx @@ -3,14 +3,17 @@ import { useLayoutProviderDispatch, useViewContext, } from "@finos/vuu-layout"; -import { VuuShellLocation, registerComponent } from "@finos/vuu-utils"; +import { registerComponent, VuuShellLocation } from "@finos/vuu-utils"; import { Button } from "@salt-ds/core"; import type { DataSource } from "@finos/vuu-data-types"; import type { TableSearchProps } from "@finos/vuu-ui-controls/src"; import { MouseEventHandler, useCallback, useMemo } from "react"; +import { TableSearch } from "@finos/vuu-ui-controls"; import "./ColHeaderAddSymbol.css"; +registerComponent("TableSearch", TableSearch, "view"); + const classBase = "vuuColHeaderAddSymbol"; export const ColHeaderAddSymbol = () => { @@ -37,13 +40,17 @@ export const ColHeaderAddSymbol = () => { props: { expanded: true, content: { - type: "InstrumentSearch", + type: "TableSearch", props: { TableProps: { allowDragDrop: "drag-copy", + config: { + columns: [{ name: "description" }], + }, dataSource, id: "basket-instruments", }, + searchColumns: ["description"], } as TableSearchProps, }, title: "Add Ticker", @@ -59,10 +66,3 @@ export const ColHeaderAddSymbol = () => { ); }; - -registerComponent( - "col-header-add-symbol", - ColHeaderAddSymbol, - "column-header-content-renderer", - {}, -); diff --git a/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTrading.tsx b/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTrading.tsx index e9619c776..a0d1eec53 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTrading.tsx +++ b/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTrading.tsx @@ -5,18 +5,20 @@ import { ViewportRpcResponse, } from "@finos/vuu-data-types"; import { useViewContext } from "@finos/vuu-layout"; -import { ContextMenuConfiguration, useNotifications } from "@finos/vuu-popups"; -import { buildColumnMap, ColumnMap, metadataKeys } from "@finos/vuu-utils"; +import { + type ContextMenuConfiguration, + useNotifications, +} from "@finos/vuu-popups"; +import { VuuRpcViewportRequest } from "@finos/vuu-protocol-types"; import { TableConfig, TableConfigChangeHandler } from "@finos/vuu-table-types"; +import { type ColumnMap, metadataKeys } from "@finos/vuu-utils"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { BasketSelectorProps } from "./basket-selector"; -import { BasketChangeHandler } from "./basket-toolbar"; -import { BasketCreatedHandler, NewBasketPanel } from "./new-basket-panel"; -import { useBasketTradingDataSources } from "./useBasketTradingDatasources"; -import { BasketTradingFeatureProps } from "./VuuBasketTradingFeature"; +import type { BasketSelectorProps } from "./basket-selector"; import defaultEditColumns from "./basket-table-edit/basketConstituentEditColumns"; import defaultLiveColumns from "./basket-table-live/basketConstituentLiveColumns"; -import { VuuRpcViewportRequest } from "@finos/vuu-protocol-types"; +import type { BasketChangeHandler } from "./basket-toolbar"; +import { type BasketCreatedHandler, NewBasketPanel } from "./new-basket-panel"; +import { useBasketTradingDataSources } from "./useBasketTradingDatasources"; const { KEY } = metadataKeys; @@ -48,27 +50,16 @@ export class Basket { } } -export type BasketTradingHookProps = Pick< - BasketTradingFeatureProps, - | "basketSchema" - | "basketConstituentSchema" - | "basketTradingSchema" - | "basketTradingConstituentJoinSchema" ->; - type BasketState = { basketInstanceId?: string; dialog?: JSX.Element; }; -const NO_STATE = { basketId: undefined } as any; +const NO_STATE = { basketInstanceId: undefined } as { + basketInstanceId: undefined; +}; -export const useBasketTrading = ({ - basketSchema, - basketConstituentSchema, - basketTradingSchema, - basketTradingConstituentJoinSchema, -}: BasketTradingHookProps) => { +export const useBasketTrading = () => { const { load, save } = useViewContext(); const notify = useNotifications(); @@ -92,29 +83,22 @@ export const useBasketTrading = ({ ); }, [load]); - const basketConstituentMap = useMemo( - () => buildColumnMap(basketConstituentSchema.columns), - [basketConstituentSchema], - ); - - const basketInstanceId = useMemo(() => { - const { basketInstanceId } = load?.("basket-state") ?? NO_STATE; + const basketInstanceId = useMemo(() => { + const { basketInstanceId } = + load<{ basketInstanceId: string }>?.("basket-state") ?? NO_STATE; return basketInstanceId; }, [load]); const { + basketConstituentMap, + basketSchema, + basketTradingMap, dataSourceBasketTradingControl, dataSourceBasketTradingSearch, dataSourceBasketTradingConstituentJoin, onSendToMarket, onTakeOffMarket, - } = useBasketTradingDataSources({ - basketInstanceId, - basketSchema, - basketConstituentSchema, - basketTradingSchema, - basketTradingConstituentJoinSchema, - }); + } = useBasketTradingDataSources({ basketInstanceId }); const [basket, setBasket] = useState(); @@ -125,27 +109,22 @@ export const useBasketTrading = ({ dialog: undefined, }); - const columnMapBasketTrading = useMemo( - () => buildColumnMap(dataSourceBasketTradingControl.columns), - [dataSourceBasketTradingControl.columns], - ); - const handleMessageFromBasketTradingControl = useCallback( (message) => { if (message.type === "viewport-update") { if (message.size) { setBasketCount(message.size); } - if (message.rows && message.rows.length > 0) { - setBasket(new Basket(message.rows[0], columnMapBasketTrading)); + if (message.rows && message.rows.length > 0 && basketTradingMap) { + setBasket(new Basket(message.rows[0], basketTradingMap)); } } }, - [columnMapBasketTrading], + [basketTradingMap], ); useMemo(() => { - dataSourceBasketTradingControl.subscribe( + dataSourceBasketTradingControl?.subscribe( { range: { from: 0, to: 1 }, }, @@ -167,10 +146,15 @@ export const useBasketTrading = ({ const handleSelectBasket = useCallback( (basketInstanceId: string) => { - save?.({ basketInstanceId }, "basket-state"); - const filter = { filter: `instanceId = "${basketInstanceId}"` }; - dataSourceBasketTradingConstituentJoin.filter = filter; - dataSourceBasketTradingControl.filter = filter; + if ( + dataSourceBasketTradingConstituentJoin && + dataSourceBasketTradingControl + ) { + save?.({ basketInstanceId }, "basket-state"); + const filter = { filter: `instanceId = "${basketInstanceId}"` }; + dataSourceBasketTradingConstituentJoin.filter = filter; + dataSourceBasketTradingControl.filter = filter; + } }, [ dataSourceBasketTradingConstituentJoin, @@ -191,25 +175,32 @@ export const useBasketTrading = ({ ); const handleAddBasket = useCallback(() => { - setBasketState((state) => ({ - ...state, - dialog: ( - - ), - })); + if (basketSchema) { + setBasketState((state) => ({ + ...state, + dialog: ( + + ), + })); + } }, [basketSchema, handleBasketCreated, handleCloseNewBasketPanel]); - const basketSelectorProps = useMemo>( - () => ({ - basketInstanceId, - dataSourceBasketTradingSearch, - onClickAddBasket: handleAddBasket, - onSelectBasket: handleSelectBasket, - }), + const basketSelectorProps = useMemo< + Omit | undefined + >( + () => + dataSourceBasketTradingSearch + ? { + basketInstanceId, + dataSourceBasketTradingSearch, + onClickAddBasket: handleAddBasket, + onSelectBasket: handleSelectBasket, + } + : undefined, [ basketInstanceId, dataSourceBasketTradingSearch, @@ -220,7 +211,7 @@ export const useBasketTrading = ({ const handleCommitBasketChange = useCallback( (columnName, value) => { - if (basket) { + if (basket && dataSourceBasketTradingControl) { const key = basket.dataSourceRow[KEY]; return dataSourceBasketTradingControl.applyEdit(key, columnName, value); } @@ -250,10 +241,10 @@ export const useBasketTrading = ({ const handleDropInstrument = useCallback( (dragDropState) => { const constituentRow = dragDropState.payload; - if (constituentRow) { + if (constituentRow && basketConstituentMap) { const ric = constituentRow[basketConstituentMap.ric]; dataSourceBasketTradingConstituentJoin - .rpcCall?.({ + ?.rpcCall?.({ type: "VIEW_PORT_RPC_CALL", rpcName: "addConstituent", namedParams: {}, @@ -276,7 +267,7 @@ export const useBasketTrading = ({ }); } }, - [basketConstituentMap.ric, dataSourceBasketTradingConstituentJoin, notify], + [basketConstituentMap, dataSourceBasketTradingConstituentJoin, notify], ); const handleConfigChangeEdit = useCallback( @@ -294,9 +285,9 @@ export const useBasketTrading = ({ ); useEffect(() => { - dataSourceBasketTradingControl.resume?.(); + dataSourceBasketTradingControl?.resume?.(); return () => { - dataSourceBasketTradingControl.suspend?.(); + dataSourceBasketTradingControl?.suspend?.(); }; }, [dataSourceBasketTradingControl]); diff --git a/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTradingDatasources.ts b/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTradingDatasources.ts index 9678516e0..1db7c1a76 100644 --- a/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTradingDatasources.ts +++ b/vuu-ui/sample-apps/feature-basket-trading/src/useBasketTradingDatasources.ts @@ -5,11 +5,10 @@ import { TableSchema, ViewportRpcResponse, } from "@finos/vuu-data-types"; -import { useCallback, useMemo } from "react"; -import { BasketTradingFeatureProps } from "./VuuBasketTradingFeature"; +import { useCallback, useMemo, useState } from "react"; import { useNotifications } from "@finos/vuu-popups"; import { VuuRpcViewportRequest } from "@finos/vuu-protocol-types"; -import { useDataSource } from "@finos/vuu-utils"; +import { buildColumnMap, ColumnMap, useDataSource } from "@finos/vuu-utils"; export type basketDataSourceKey = | "data-source-basket" @@ -18,96 +17,147 @@ export type basketDataSourceKey = | "data-source-basket-trading-constituent-join" | "data-source-basket-constituent"; -const NO_CONFIG = {}; +type BasketTableState = { + basketSchema: TableSchema; + basketConstituentMap: ColumnMap; + basketConstituentSchema: TableSchema; + basketTradingMap: ColumnMap; + basketTradingSchema: TableSchema; + basketTradingConstituentJoinSchema: TableSchema; + dataSourceBasketConstituents: DataSource; + dataSourceBasketTradingControl: DataSource; + dataSourceBasketTradingSearch: DataSource; + dataSourceBasketTradingConstituentJoin: DataSource; +}; + +const module = "BASKET"; export const useBasketTradingDataSources = ({ - basketConstituentSchema, basketInstanceId, - basketTradingSchema, - basketTradingConstituentJoinSchema, -}: BasketTradingFeatureProps & { - basketInstanceId: string; +}: { + basketInstanceId?: string; }) => { + const [basketState, setBasketState] = useState(); const notify = useNotifications(); const { id, loadSession, saveSession, title } = useViewContext(); - const { VuuDataSource } = useDataSource(); + const { getServerAPI, VuuDataSource } = useDataSource(); - const [ - dataSourceBasketTradingControl, - dataSourceBasketTradingSearch, - dataSourceBasketTradingConstituentJoin, - ] = useMemo(() => { - const basketFilter: DataSourceConfig = basketInstanceId + useMemo(async () => { + const serverAPI = await getServerAPI(); + const [ + basketSchema, + basketConstituentSchema, + basketTradingSchema, + basketTradingConstituentJoinSchema, + ] = await Promise.all([ + serverAPI.getTableSchema({ module, table: "basket" }), + serverAPI.getTableSchema({ module, table: "basketConstituent" }), + serverAPI.getTableSchema({ module, table: "basketTrading" }), + serverAPI.getTableSchema({ + module, + table: "basketTradingConstituentJoin", + }), + ]); + + const filterSpec: DataSourceConfig["filterSpec"] = basketInstanceId ? { - filterSpec: { - filter: `instanceId = "${basketInstanceId}"`, - }, + filter: `instanceId = "${basketInstanceId}"`, } - : NO_CONFIG; + : undefined; + + const basketTradingControlKey = `data-source-basket-trading-control`; + let dataSourceBasketTradingControl = loadSession?.( + basketTradingControlKey, + ) as DataSource; + if (!dataSourceBasketTradingControl) { + dataSourceBasketTradingControl = new VuuDataSource({ + bufferSize: 0, + filterSpec, + viewport: `${id}-${basketTradingControlKey}`, + table: basketTradingSchema.table, + columns: basketTradingSchema.columns.map((col) => col.name), + title, + }); + saveSession?.(dataSourceBasketTradingControl, basketTradingControlKey); + } - const constituentSort: DataSourceConfig = { - sort: { sortDefs: [{ column: "description", sortType: "D" }] }, - }; + const basketTradingSearchKey = `data-source-basket-trading-search`; + let dataSourceBasketTradingSearch = loadSession?.( + basketTradingControlKey, + ) as DataSource; + if (!dataSourceBasketTradingSearch) { + dataSourceBasketTradingSearch = new VuuDataSource({ + bufferSize: 100, + viewport: `${id}-${basketTradingSearchKey}`, + table: basketTradingSchema.table, + columns: basketTradingSchema.columns.map((col) => col.name), + title, + }); + saveSession?.(dataSourceBasketTradingSearch, basketTradingSearchKey); + } - const dataSourceConfig: [ - basketDataSourceKey, - TableSchema, - number, - DataSourceConfig?, - ][] = [ - [ - "data-source-basket-trading-control", - basketTradingSchema, - 0, - basketFilter, - ], - ["data-source-basket-trading-search", basketTradingSchema, 100], - [ - "data-source-basket-trading-constituent-join", - basketTradingConstituentJoinSchema, - 100, - basketFilter, - ], - [ - "data-source-basket-constituent", - basketConstituentSchema, - 100, - constituentSort, - ], - ]; + const basketTradingConstituentJoinKey = `data-source-basket-trading-constituent-join`; + let dataSourceBasketTradingConstituentJoin = loadSession?.( + basketTradingConstituentJoinKey, + ) as DataSource; + if (!dataSourceBasketTradingConstituentJoin) { + dataSourceBasketTradingConstituentJoin = new VuuDataSource({ + bufferSize: 100, + filterSpec, + viewport: `${id}-${basketTradingConstituentJoinKey}`, + table: basketTradingConstituentJoinSchema.table, + columns: basketTradingConstituentJoinSchema.columns.map( + (col) => col.name, + ), + title, + }); + saveSession?.( + dataSourceBasketTradingConstituentJoin, + basketTradingConstituentJoinKey, + ); + } - const dataSources: DataSource[] = []; - for (const [key, schema, bufferSize, config] of dataSourceConfig) { - let dataSource = loadSession?.(key) as DataSource; - if (dataSource === undefined) { - dataSource = new VuuDataSource({ - ...config, - bufferSize, - viewport: `${id}-${key}`, - table: schema.table, - columns: schema.columns.map((col) => col.name), - title, - }); - saveSession?.(dataSource, key); - } - dataSources.push(dataSource); + const basketConstituentsKey = `data-source-basket-constituent`; + let dataSourceBasketConstituents = loadSession?.( + basketConstituentsKey, + ) as DataSource; + if (!dataSourceBasketConstituents) { + dataSourceBasketConstituents = new VuuDataSource({ + bufferSize: 100, + sort: { sortDefs: [{ column: "description", sortType: "A" }] }, + viewport: `${id}-${basketConstituentsKey}`, + table: basketConstituentSchema.table, + columns: basketConstituentSchema.columns.map((col) => col.name), + title, + }); + saveSession?.(dataSourceBasketConstituents, basketConstituentsKey); } - return dataSources; + + setBasketState({ + basketSchema, + basketConstituentMap: buildColumnMap(basketConstituentSchema.columns), + basketConstituentSchema, + basketTradingMap: buildColumnMap(dataSourceBasketTradingControl.columns), + basketTradingSchema, + basketTradingConstituentJoinSchema, + dataSourceBasketConstituents, + dataSourceBasketTradingControl, + dataSourceBasketTradingSearch, + dataSourceBasketTradingConstituentJoin, + }); }, [ - basketInstanceId, - basketTradingSchema, - basketTradingConstituentJoinSchema, - basketConstituentSchema, - loadSession, VuuDataSource, + basketInstanceId, + getServerAPI, id, - title, + loadSession, saveSession, + title, ]); const handleSendToMarket = useCallback( (basketInstanceId: string) => { - dataSourceBasketTradingControl + basketState?.dataSourceBasketTradingControl .rpcCall?.({ namedParams: {}, params: [basketInstanceId], @@ -125,12 +175,12 @@ export const useBasketTradingDataSources = ({ } }); }, - [dataSourceBasketTradingControl, notify], + [basketState, notify], ); const handleTakeOffMarket = useCallback( (basketInstanceId: string) => { - dataSourceBasketTradingControl + basketState?.dataSourceBasketTradingControl .rpcCall?.({ namedParams: {}, params: [basketInstanceId], @@ -148,16 +198,14 @@ export const useBasketTradingDataSources = ({ } }); }, - [dataSourceBasketTradingControl, notify], + [basketState, notify], ); // Note: we do not need to return the BasketConstituent dataSource, we just stash it // in session state from where it will be used by the AddInstrument button in Col // Header return { - dataSourceBasketTradingControl, - dataSourceBasketTradingSearch, - dataSourceBasketTradingConstituentJoin, + ...basketState, onSendToMarket: handleSendToMarket, onTakeOffMarket: handleTakeOffMarket, }; diff --git a/vuu-ui/sample-apps/feature-filter-table/package.json b/vuu-ui/sample-apps/feature-filter-table/package.json index 128692469..a855f4edc 100644 --- a/vuu-ui/sample-apps/feature-filter-table/package.json +++ b/vuu-ui/sample-apps/feature-filter-table/package.json @@ -43,7 +43,7 @@ }, "vuu": { "featureProps": { - "schema": "*" + "vuuTables": "*" } } } diff --git a/vuu-ui/sample-apps/feature-instrument-tiles/package.json b/vuu-ui/sample-apps/feature-instrument-tiles/package.json index 662ca5694..9c62a516b 100644 --- a/vuu-ui/sample-apps/feature-instrument-tiles/package.json +++ b/vuu-ui/sample-apps/feature-instrument-tiles/package.json @@ -42,7 +42,7 @@ }, "vuu": { "featureProps": { - "schemas": [ + "vuuTables": [ { "module": "SIMUL", "table": "instrumentPrices" diff --git a/vuu-ui/showcase/src/examples/Apps/SampleApp.examples.tsx b/vuu-ui/showcase/src/examples/Apps/SampleApp.examples.tsx index 34192cb13..7f27bc6e0 100644 --- a/vuu-ui/showcase/src/examples/Apps/SampleApp.examples.tsx +++ b/vuu-ui/showcase/src/examples/Apps/SampleApp.examples.tsx @@ -60,7 +60,7 @@ const dynamicFeatures: DynamicFeatureDescriptor[] = [ name: "filter-table", ...featurePaths.FilterTableFeature, featureProps: { - schema: "*", + vuuTables: "*", }, leftNavLocation: "vuu-tables", }, @@ -69,7 +69,7 @@ const dynamicFeatures: DynamicFeatureDescriptor[] = [ name: "instrument-tiles", ...featurePaths.InstrumentTiles, featureProps: { - schemas: [ + vuuTables: [ { module: "SIMUL", table: "instrumentPrices", @@ -85,26 +85,6 @@ const dynamicFeatures: DynamicFeatureDescriptor[] = [ viewProps: { header: false, }, - featureProps: { - schemas: [ - { - module: "BASKET", - table: "basket", - }, - { - module: "BASKET", - table: "basketTrading", - }, - { - module: "BASKET", - table: "basketTradingConstituentJoin", - }, - { - module: "BASKET", - table: "basketConstituent", - }, - ], - }, leftNavLocation: "vuu-features", }, ]; diff --git a/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx b/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx index 807443f7a..4051c2731 100644 --- a/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx +++ b/vuu-ui/showcase/src/examples/VuuFeatures/BasketTradingFeature.examples.tsx @@ -1,13 +1,12 @@ -import { getAllSchemas } from "@finos/vuu-data-test"; +import { getAllSchemas, LocalDataSourceProvider } from "@finos/vuu-data-test"; import { LayoutProvider, View } from "@finos/vuu-layout"; -import { Feature, ShellContextProvider, useWorkspace } from "@finos/vuu-shell"; +import { Feature, ShellContextProvider } from "@finos/vuu-shell"; import { DynamicFeatureProps, LookupTableProvider, registerComponent, } from "@finos/vuu-utils"; -import { useCallback, useEffect } from "react"; -import { BasketTradingFeatureProps } from "sample-apps/feature-basket-trading"; +import { useCallback } from "react"; import BasketTradingFeature from "../../features/BasketTrading.feature"; import { VuuBlotterHeader } from "./VuuBlotterHeader"; @@ -16,27 +15,6 @@ registerComponent("BasketTradingFeature", BasketTradingFeature, "view"); let displaySequence = 1; export const DefaultBasketTradingFeature = () => { - const schemas = getAllSchemas(); - //----------------------------------------------------------------------------------- - // Note the following functionality is provided by the Shell in a full application. - // Likewise the Shell provides the LayoutProvider wrapper. Again, in a full Vuu - // application, the Palette wraps each feature in a View. - //----------------------------------------------------------------------------------- - const { workspaceJSON, saveApplicationLayout } = useWorkspace(); - - useEffect(() => { - console.log(`%clayout changed`, "color: blue; font-weight: bold;"); - }, [workspaceJSON]); - - const handleLayoutChange = useCallback( - (layout) => { - console.log("layout change"); - saveApplicationLayout(layout); - }, - [saveApplicationLayout], - ); - // ---------------------------------------------------------------------------------- - const getLookupValues = useCallback((table) => { if (table.table === "algoType") { return [ @@ -58,31 +36,23 @@ export const DefaultBasketTradingFeature = () => { }, []); return ( - - - - - - - + + + + + + + + + ); }; DefaultBasketTradingFeature.displaySequence = displaySequence++; @@ -101,31 +71,20 @@ const featurePropsForEnv: Record = { export const BasketTradingFeatureAsFeature = () => { const { url, css } = featurePropsForEnv[env]; - const schemas = getAllSchemas(); return ( - - - + + + + + ); }; BasketTradingFeatureAsFeature.displayName = "BasketTrading";