Skip to content

Commit a0eb00a

Browse files
authored
add suppport for grouping data outside ag grid (#792)
1 parent ebb1db3 commit a0eb00a

14 files changed

Lines changed: 182 additions & 57 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ColumnDescriptor } from "@finos/vuu-datagrid-types";
2+
import { VuuGroupBy } from "@finos/vuu-protocol-types";
3+
import { FilterDataProvider } from "./FilterDataProvider";
4+
5+
export interface AgGridColDef {
6+
[key: string]: unknown;
7+
field?: string;
8+
enableRowGroup?: boolean;
9+
rowGroup?: boolean;
10+
}
11+
12+
export const createColumnDefs = (
13+
setFilterDataProvider: FilterDataProvider,
14+
columns: ColumnDescriptor[],
15+
groupBy: VuuGroupBy = []
16+
): AgGridColDef[] => {
17+
return columns.map((column) => ({
18+
// cellRenderer: cellRenderers[column.name],
19+
// enableRowGroup: groupableColumns.has(column.name),
20+
enableRowGroup: true,
21+
field: column.name,
22+
hide: column.hidden || groupBy.includes(column.name),
23+
rowGroup: groupBy.includes(column.name),
24+
25+
// ...getFilterAttributes(column, setFilterDataProvider),
26+
sortable: true,
27+
width: 100,
28+
}));
29+
};

vuu-ui/packages/vuu-data-ag-grid/src/ViewportRowModelDataSource.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ const reverseColumnMap = (columnMap: ColumnMap): Map<number, string> =>
3131
)
3232
);
3333

34+
/**
35+
* This is a custom ViewportRowModelDataSource that complies with the interface
36+
* expected by AgGrid. Internally, it interacts with a remote Vuu server to
37+
* fetch data. It wraps a Vuu RemoteDataSource.
38+
*/
3439
export class ViewportRowModelDataSource {
3540
private columnMap: ColumnMap;
3641
private reverseColumnMap: Map<number, string>;
@@ -42,6 +47,7 @@ export class ViewportRowModelDataSource {
4247
private onFeatureEnabled?: (message: VuuFeatureMessage) => void
4348
) {
4449
this.dataSource.subscribe({}, this.handleMessageFromDataSource);
50+
// this.dataSource.on("config", this.handleConfigChange);
4551
this.columnMap = buildColumnMap(dataSource.columns);
4652
this.reverseColumnMap = reverseColumnMap(this.columnMap);
4753
}

vuu-ui/packages/vuu-data-ag-grid/src/useViewportRowModel.ts

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {
2-
DataSource,
2+
DataSourceConfig,
33
isViewportMenusAction,
44
isVisualLinksAction,
55
MenuActionConfig,
66
MenuRpcResponse,
7+
RemoteDataSource,
78
SuggestionFetcher,
89
useTypeaheadSuggestions,
910
useVuuMenuActions,
@@ -12,8 +13,8 @@ import {
1213
VuuUIMessageInRPCEditReject,
1314
VuuUIMessageInRPCEditSuccess,
1415
} from "@finos/vuu-data";
15-
import { VuuMenu, VuuTable } from "@finos/vuu-protocol-types";
16-
import { useCallback, useMemo, useRef } from "react";
16+
import { VuuGroupBy, VuuMenu, VuuTable } from "@finos/vuu-protocol-types";
17+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1718
import { bySortIndex, isSortedColumn, toSortDef } from "./AgGridDataUtils";
1819

1920
import {
@@ -23,9 +24,11 @@ import {
2324
import { FilterDataProvider } from "./FilterDataProvider";
2425
import { GroupCellRenderer } from "./GroupCellRenderer";
2526
import { ViewportRowModelDataSource } from "./ViewportRowModelDataSource";
26-
import { buildColumnMap } from "@finos/vuu-utils";
27+
import { buildColumnMap, itemsOrOrderChanged } from "@finos/vuu-utils";
2728
import { vuuMenuToAgGridMenu } from "./agGridMenuUtils";
2829
import { AgData } from "./AgDataWindow";
30+
import { ColumnDescriptor } from "@finos/vuu-datagrid-types";
31+
import { createColumnDefs } from "./AgGridColumnUtils";
2932

3033
type Column = {
3134
getId: () => string;
@@ -38,11 +41,23 @@ interface FilterChangedEvent {
3841
getFilterModel: () => AgGridFilter;
3942
};
4043
}
44+
45+
export interface AgGridDataRow {
46+
[key: string]: unknown;
47+
expanded?: boolean;
48+
groupKey: string;
49+
groupKeys?: string;
50+
groupRow: boolean;
51+
}
52+
53+
const isAgGridGroupDataRow = (data: unknown): data is AgGridDataRow =>
54+
typeof data === "object" &&
55+
data !== null &&
56+
typeof (data as AgGridDataRow)["groupKey"] === "string";
57+
4158
type RowGroupOpenedEvent = {
42-
data: {
43-
expanded: boolean;
44-
groupKey: string;
45-
};
59+
data?: AgGridDataRow;
60+
expanded: boolean;
4661
node: { expanded?: boolean };
4762
};
4863
type ColumnState = { colId: string; sortIndex: number }[];
@@ -55,7 +70,8 @@ type SortChangedEvent = {
5570
const NullSuggestionFetcher: SuggestionFetcher = async () => [];
5671

5772
export interface ViewportRowModelHookProps {
58-
dataSource: DataSource;
73+
columns?: ColumnDescriptor[];
74+
dataSource: RemoteDataSource;
5975
onFeatureEnabled?: (message: VuuFeatureMessage) => void;
6076
onRpcResponse?: (
6177
response:
@@ -65,7 +81,16 @@ export interface ViewportRowModelHookProps {
6581
) => void;
6682
}
6783

84+
type GroupByConfigChange = {
85+
groupBy: VuuGroupBy;
86+
};
87+
88+
const hasGroupByChange = (
89+
message?: Partial<DataSourceConfig>
90+
): message is GroupByConfigChange => Array.isArray(message?.groupBy);
91+
6892
export const useViewportRowModel = ({
93+
columns,
6994
dataSource,
7095
onRpcResponse,
7196
onFeatureEnabled,
@@ -74,8 +99,12 @@ export const useViewportRowModel = ({
7499
const getTypeaheadSuggestionsRef = useRef<SuggestionFetcher>(
75100
NullSuggestionFetcher
76101
);
102+
const groupByRef = useRef<VuuGroupBy>([]);
103+
const [groupBy, setGroupBy] = useState<VuuGroupBy>(groupByRef.current);
77104
getTypeaheadSuggestionsRef.current = useTypeaheadSuggestions();
78105

106+
const { table } = dataSource;
107+
79108
// It is important that these values are not assigned in advance. They
80109
// are accessed at the point of construction of ContextMenu
81110
const menuActionConfig: MenuActionConfig = useMemo(
@@ -140,23 +169,57 @@ export const useViewportRowModel = ({
140169
return new FilterDataProvider(table, getTypeaheadSuggestionsRef);
141170
}, []);
142171

172+
const columnDefs = useMemo(() => {
173+
return Array.isArray(columns)
174+
? createColumnDefs(createFilterDataProvider(table), columns, groupBy)
175+
: undefined;
176+
}, [columns, createFilterDataProvider, groupBy, table]);
177+
178+
useEffect(() => {
179+
// We listen to dsataSource config changes to detect changes applied
180+
// directly to the dataSource, i.e. not applied via AgGrid and detected via
181+
// AgGrid event callbacks. For the former, AgGrid has no knowledge
182+
// that a change has occurred, so will not render the subsequent row refresh
183+
// correctly. In the case of GroupBy, for example. we need to recompute the
184+
// column defs to apply grouping. This will cause AgGrid to re-render columns
185+
// taking grouping into account and will enable subsequent refresh of grouped
186+
// data to be handled correctly.
187+
// Where a config change DOES originate from AgGrid (e.g user has applied
188+
// grouping from the Ag Grid context menu), we store latest value in a ref,
189+
// so that we can ignore the config change event(s) that will be fired by
190+
// the dataSource for this config change.
191+
dataSource.on("config", (config) => {
192+
if (
193+
hasGroupByChange(config) &&
194+
itemsOrOrderChanged(groupByRef.current, config.groupBy)
195+
) {
196+
setGroupBy(config.groupBy);
197+
}
198+
});
199+
}, [dataSource]);
200+
201+
// Fired when user has applied grouping from AG Grid, either via the
202+
// column menu or by dragging a column onto the group bar.
143203
const handleColumnRowGroupChanged = useCallback(
144204
(evt: unknown) => {
145205
const columnRowGroupChangedEvent = evt as ColumnRowGroupChangedEvent;
146206
const { columns } = columnRowGroupChangedEvent;
147207
if (columns !== null) {
148-
const colIds = columns.map((c) => c.getId());
149-
viewportDatasource.setRowGroups(colIds);
208+
const vuuGroupBy: VuuGroupBy = columns.map((c) => c.getId());
209+
groupByRef.current = vuuGroupBy;
210+
viewportDatasource.setRowGroups(vuuGroupBy);
150211
}
151212
},
152213
[viewportDatasource]
153214
);
154215

155216
const handleRowGroupOpened = useCallback(
156217
(evt: RowGroupOpenedEvent) => {
157-
const { groupKey } = evt.data;
158-
const { expanded = false } = evt.node;
159-
viewportDatasource.setExpanded(groupKey, !expanded);
218+
if (isAgGridGroupDataRow(evt.data)) {
219+
const { groupKey } = evt.data;
220+
const { expanded = false } = evt.node;
221+
viewportDatasource.setExpanded(groupKey, !expanded);
222+
}
160223
},
161224
[viewportDatasource]
162225
);
@@ -238,6 +301,7 @@ export const useViewportRowModel = ({
238301

239302
return {
240303
autoGroupColumnDef,
304+
columnDefs,
241305
createFilterDataProvider,
242306
viewportDatasource,
243307
defaultColDef: {

vuu-ui/packages/vuu-data/src/array-data-source/array-data-source.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
} from "../vuuUIMessageTypes";
4242

4343
import { collapseGroup, expandGroup, GroupMap, groupRows } from "./group-utils";
44+
import { TableSchema } from "../message-utils";
4445

4546
export interface ArrayDataSourceConstructorProps
4647
extends Omit<DataSourceConstructorProps, "bufferSize" | "table"> {
@@ -69,20 +70,18 @@ const toDataSourceRow = (
6970
...data,
7071
];
7172

72-
const buildTableMeta = (columns: ColumnDescriptor[]): VuuTableMeta => {
73-
const meta = {
74-
columns: [],
75-
dataTypes: [],
73+
const buildTableSchema = (columns: ColumnDescriptor[]): TableSchema => {
74+
const schema: TableSchema = {
75+
columns: columns.map(({ name, serverDataType = "string" }) => ({
76+
name,
77+
serverDataType,
78+
})),
7679
// how do we identify the key field ?
7780
key: columns[0].name,
78-
} as VuuTableMeta;
81+
table: { module: "", table: "Array" },
82+
};
7983

80-
columns.forEach((column) => {
81-
meta.columns.push(column.name);
82-
meta.dataTypes.push(column.serverDataType ?? "string");
83-
});
84-
85-
return meta;
84+
return schema;
8685
};
8786

8887
const toClientRow = (row: DataSourceRow, keys: KeySet) => {
@@ -104,7 +103,7 @@ export class ArrayDataSource
104103
private groupMap: undefined | GroupMap;
105104
private suspended = false;
106105
private clientCallback: SubscribeCallback | undefined;
107-
private tableMeta: VuuTableMeta;
106+
private tableSchema: TableSchema;
108107
private lastRangeServed: VuuRange = { from: 0, to: 0 };
109108
private rangeChangeRowset: "delta" | "full";
110109
private openTreeNodes: string[] = [];
@@ -154,7 +153,7 @@ export class ArrayDataSource
154153
this.#columns = columnDescriptors.map((column) => column.name);
155154
this.#columnMap = buildColumnMap(this.#columns);
156155
this.rangeChangeRowset = rangeChangeRowset;
157-
this.tableMeta = buildTableMeta(columnDescriptors);
156+
this.tableSchema = buildTableSchema(columnDescriptors);
158157

159158
this.#data = data.map<DataSourceRow>(toDataSourceRow);
160159
this.viewport = viewport || uuid();
@@ -205,7 +204,7 @@ export class ArrayDataSource
205204
type: "subscribed",
206205
clientViewportId: this.viewport,
207206
range: this.#range,
208-
tableMeta: this.tableMeta,
207+
tableSchema: this.tableSchema,
209208
});
210209

211210
this.clientCallback({

vuu-ui/packages/vuu-data/src/connection-manager.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ import {
3131

3232
// Note: inlined-worker is a generated file, it must be built
3333
import { workerSourceCode } from "./inlined-worker";
34-
import { VuuTableMetaWithTable } from "./hooks";
3534
import { ConnectionQualityMetrics } from "./vuuUIMessageTypes";
3635
import { WebSocketProtocol } from "./websocket-connection";
36+
import { TableSchema } from "./message-utils";
3737

3838
const workerBlob = new Blob([getLoggingConfigForWorker() + workerSourceCode], {
3939
type: "text/javascript",
@@ -205,7 +205,7 @@ const asyncRequest = <T = unknown>(
205205

206206
export interface ServerAPI {
207207
destroy: (viewportId?: string) => void;
208-
getTableMeta: (table: VuuTable) => Promise<VuuTableMetaWithTable>;
208+
getTableSchema: (table: VuuTable) => Promise<TableSchema>;
209209
getTableList: () => Promise<VuuTableList>;
210210
rpcCall: <T = unknown>(
211211
msg: VuuRpcRequest | ClientToServerMenuRPC
@@ -249,8 +249,8 @@ const connectedServerAPI: ServerAPI = {
249249
getTableList: async () =>
250250
asyncRequest<VuuTableList>({ type: Message.GET_TABLE_LIST }),
251251

252-
getTableMeta: async (table) =>
253-
asyncRequest<VuuTableMetaWithTable>({
252+
getTableSchema: async (table) =>
253+
asyncRequest<TableSchema>({
254254
type: Message.GET_TABLE_META,
255255
table,
256256
}),

vuu-ui/packages/vuu-data/src/hooks/useVuuTables.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { VuuTableMetaWithTable } from "@finos/vuu-protocol-types";
21
import { useCallback, useEffect, useState } from "react";
32
import { getServerAPI } from "../connection-manager";
4-
import { createSchemaFromTableMetadata, TableSchema } from "../message-utils";
3+
import { TableSchema } from "../message-utils";
54

65
export const useVuuTables = () => {
76
const [tables, setTables] = useState<Map<string, TableSchema> | undefined>();
87

9-
const buildTables = useCallback((schemas: VuuTableMetaWithTable[]) => {
8+
const buildTables = useCallback((schemas: TableSchema[]) => {
109
const vuuTables = new Map<string, TableSchema>();
1110
schemas.forEach((schema) => {
12-
vuuTables.set(schema.table.table, createSchemaFromTableMetadata(schema));
11+
vuuTables.set(schema.table.table, schema);
1312
});
1413
return vuuTables;
1514
}, []);
@@ -20,7 +19,9 @@ export const useVuuTables = () => {
2019
const { tables } = await server.getTableList();
2120
const tableSchemas = buildTables(
2221
await Promise.all(
23-
tables.map((tableDescriptor) => server.getTableMeta(tableDescriptor))
22+
tables.map((tableDescriptor) =>
23+
server.getTableSchema(tableDescriptor)
24+
)
2425
)
2526
);
2627
setTables(tableSchemas);

vuu-ui/packages/vuu-data/src/json-data-source.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
VuuUIMessageInRPCEditSuccess,
3333
} from "./vuuUIMessageTypes";
3434

35+
const NULL_SCHEMA = { columns: [], key: "", table: { module: "", table: "" } };
36+
3537
export interface JsonDataSourceConstructorProps
3638
extends Omit<DataSourceConstructorProps, "bufferSize" | "table"> {
3739
data: JsonData;
@@ -165,7 +167,7 @@ export class JsonDataSource
165167
groupBy: this.#groupBy,
166168
range: this.#range,
167169
sort: this.#sort,
168-
tableMeta: { columns: [], dataTypes: [] },
170+
tableSchema: NULL_SCHEMA,
169171
});
170172

171173
this.clientCallback({

vuu-ui/packages/vuu-data/src/remote-data-source.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,6 @@ export class RemoteDataSource
519519
rows: [],
520520
});
521521
}
522-
console.log("emit config changed");
523522
this.emit("config", this.#config);
524523
this.setConfigPending({ groupBy });
525524
}

vuu-ui/packages/vuu-data/src/server-proxy/server-proxy.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -903,9 +903,7 @@ export class ServerProxy {
903903
} else {
904904
this.postMessageToClient({
905905
type: Message.TABLE_META_RESP,
906-
table: body.table,
907-
columns: body.columns,
908-
dataTypes: body.dataTypes,
906+
tableSchema,
909907
requestId,
910908
} as VuuUIMessageInTableMeta);
911909
}

0 commit comments

Comments
 (0)