Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions vuu-ui/packages/vuu-data-editing/src/EditSession.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
DeleteSelectedRowsResult,
EditApi,
EditSessionMode,
SessionType,
UndoRowChangeResult,
} from "@vuu-ui/vuu-data-types";
import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
Expand Down Expand Up @@ -490,7 +491,7 @@ export class EditSession extends EventEmitter<EditSessionEvents> {
return result;
}

begin(copyOption: CopyOption = "All"): Promise<DataSource> {
begin(copyOption: CopyOption = "All", sessionType: SessionType = "edit"): Promise<DataSource> {
return this.#enqueue(async () => {
if (
this.#lifecycle.status === "active" ||
Expand Down Expand Up @@ -519,7 +520,7 @@ export class EditSession extends EventEmitter<EditSessionEvents> {
? await sourceDataSource?.beginEditSession?.(
toEditSessionMode(copyOption),
)
: await sourceDataSource?.createSessionDataSource?.(copyOption);
: await sourceDataSource?.createSessionDataSource?.(copyOption, sessionType);
if (!sessionDataSource) {
throw new Error(
`[EditSession] datasource does not support ${this.#editSessionApi}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,9 @@ describe("EditSession lifecycle", () => {
expect(editSession.lifecycle.status).toBe("active");
});

it("forwards copy options and defaults to All", async () => {
it("forwards copy options and defaults to All, defaults sessionType to edit", async () => {
await editSession.begin();
expect(createSession).toHaveBeenCalledWith("All");
expect(createSession).toHaveBeenCalledWith("All", "edit");

await editSession.end();
createSession = vi.fn(
Expand All @@ -214,7 +214,12 @@ describe("EditSession lifecycle", () => {
const selectedDataSource = new MockDataSource(endEdit, createSession);
editSession = new EditSession(selectedDataSource);
await editSession.begin("Selected");
expect(createSession).toHaveBeenCalledWith("Selected");
expect(createSession).toHaveBeenCalledWith("Selected", "edit");
});

it("forwards sessionType to createSessionDataSource", async () => {
await editSession.begin("Empty", "import");
expect(createSession).toHaveBeenCalledWith("Empty", "import");
});

it("keeps a failed end session active and allows retry", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,16 @@ export class ArrayDataSource
filterSpec: { filterStruct },
} = combineFilters(this._config);
if (filterStruct) {
// When a dataMap exists use actual raw-data positions so the predicate
// remains correct after processNewColumns rebuilds #columnMap.
if (this.dataMap) {
const { count: offset } = metadataKeys;
const actualColumnMap: ColumnMap = {};
for (const [col, idx] of Object.entries(this.dataMap)) {
actualColumnMap[col] = offset + (idx as number);
}
return filterPredicate(actualColumnMap, filterStruct);
}
return filterPredicate(this.#columnMap, filterStruct);
} else {
throw Error("filter must include filterStruct");
Expand Down
4 changes: 3 additions & 1 deletion vuu-ui/packages/vuu-data-remote/src/VuuDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
CopyOption,
DataSource,
DataSourceBase,
SessionType,
DataSourceCallbackMessage,
DataSourceConstructorProps,
DataSourceStatus,
Expand Down Expand Up @@ -686,11 +687,12 @@ export class VuuDataSource extends BaseDataSource implements DataSourceBase {

async createSessionDataSource(
copyOption: CopyOption,
sessionType: SessionType = "edit",
): Promise<VuuDataSource | undefined> {
const rpcResponse = await this?.rpcRequest?.({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption },
params: { copyOption, sessionType },
});
if (isRpcSuccess(rpcResponse)) {
const { table: sessionTable } = rpcResponse.data as { table: VuuTable };
Expand Down
19 changes: 16 additions & 3 deletions vuu-ui/packages/vuu-data-test/src/TickingArrayDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
DeleteRowMode,
CopyOption,
EditSessionMode,
SessionType,
} from "@vuu-ui/vuu-data-types";
import type {
LinkDescriptorWithLabel,
Expand Down Expand Up @@ -169,17 +170,22 @@ export class TickingArrayDataSource extends ArrayDataSource {

async createSessionDataSource(
copyOption: CopyOption,
sessionType: SessionType = "edit",
): Promise<DataSourceBase<DataSourceRowWithBigint> | undefined> {
const rpcResponse = await this?.rpcRequest?.({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption },
params: { copyOption, sessionType },
});
if (isRpcSuccess(rpcResponse)) {
const { table: sessionTable } = rpcResponse.data as { table: VuuTable };
const columns = this.config.columns.includes("vuuAction")
const baseColumns = this.config.columns.includes("vuuAction")
? this.config.columns
: this.config.columns.concat("vuuAction");
const columns =
sessionType === "import" && !baseColumns.includes("vuuRowNum")
? baseColumns.concat("vuuRowNum")
: baseColumns;
const sessionDataSource = this.#vuuModule?.createDataSource(
sessionTable.table,
sessionTable.table,
Expand Down Expand Up @@ -237,10 +243,17 @@ export class TickingArrayDataSource extends ArrayDataSource {
addRow = async (
rowData: Record<string, VuuRowDataItemType> = {},
): Promise<true | string> => {
const keyValue = rowData[this.tableSchema.key];
const key =
keyValue !== undefined
? String(keyValue)
: "vuuRowNum" in rowData
? String(rowData.vuuRowNum)
: undefined;
const response = await this.rpcRequest?.({
type: "RPC_REQUEST",
rpcName: "addRow",
params: { key: rowData[this.tableSchema.key] as string, data: rowData },
params: { key, data: rowData },
});
if (isRpcSuccess(response)) {
return true;
Expand Down
17 changes: 13 additions & 4 deletions vuu-ui/packages/vuu-data-test/src/core/module/VuuModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
DeleteRowMode,
EditSessionMode,
CopyOption,
SessionType,
TableSchema,
} from "@vuu-ui/vuu-data-types";
import type {
Expand Down Expand Up @@ -669,7 +670,7 @@ export abstract class VuuModule<T extends string = string>
private createSessionTableService: ServiceHandler = async (rpcRequest) => {
if (isCreateSessionTableRpcRequest(rpcRequest)) {
const { viewPortId } = rpcRequest.context;
const { copyOption } = rpcRequest.params;
const { copyOption, sessionType } = rpcRequest.params;
const subscription = this.getSubscriptionByViewport(viewPortId);
const { dataSource } = subscription;
const { table: vuuTable } = dataSource;
Expand All @@ -683,6 +684,7 @@ export abstract class VuuModule<T extends string = string>
sessionTableName,
copyOption,
dataSource as TickingArrayDataSource,
sessionType,
);
this.#sessionTableMap[sessionTableName] = sessionTable;
this.#sessionSourceTableMap[sessionTableName] = vuuTable.table as T;
Expand Down Expand Up @@ -770,9 +772,9 @@ export abstract class VuuModule<T extends string = string>
errorMessage: `addRow: no active session table for viewport ${viewPortId}`,
};
}
const { data } = rpcRequest.params;
const { data, key } = rpcRequest.params;
const keyColumn = sessionTable.schema.key;
const rowKey = data[keyColumn] ?? uuid();
const rowKey = data[keyColumn] ?? key ?? uuid();
const rowData = { ...data, [keyColumn]: rowKey };

const columnMap = sessionTable.map;
Expand Down Expand Up @@ -919,6 +921,7 @@ export abstract class VuuModule<T extends string = string>
sessionTableName: string,
editSessionMode: EditSessionMode | CopyOption,
dataSource: TickingArrayDataSource,
sessionType?: SessionType,
) {
if (editSessionMode === "All" || editSessionMode.endsWith("all-rows")) {
return this.createSessionTableWithAllRows(sourceTable, sessionTableName);
Expand All @@ -935,7 +938,7 @@ export abstract class VuuModule<T extends string = string>
editSessionMode === "Empty" ||
editSessionMode === "empty-session-table"
) {
return this.createEmptySessionTable(sourceTable, sessionTableName);
return this.createEmptySessionTable(sourceTable, sessionTableName, sessionType);
} else {
throw Error(
`[VuuModule] createSessionTable, invalid editSessionMode ${editSessionMode}`,
Expand All @@ -958,13 +961,19 @@ export abstract class VuuModule<T extends string = string>
protected createEmptySessionTable(
{ schema }: Table,
sessionTableName: string,
sessionType?: SessionType,
) {
// Override schema.table.table so isSessionTable() returns true for this session.
// sessionTableSchema adds the vuuMsg column, consistent with all other session tables.
const sessionSchema = sessionTableSchema({
...schema,
table: { ...schema.table, table: sessionTableName },
});
if (sessionType === "import") {
(sessionSchema.columns as { name: string; serverDataType: string }[]).push(
{ name: "vuuRowNum", serverDataType: "int" },
);
}
return new Table(
sessionSchema,
[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,41 +307,67 @@ describe("createSessionDataSource", () => {
data: { table: { module: "TEST", table: "session-xyz" } },
};

it("dispatches createSessionTable RPC with copyOption 'All'", async () => {
it("dispatches createSessionTable RPC with copyOption 'All' and default sessionType 'edit'", async () => {
const ds = createDataSource();
vi.mocked(ds.rpcRequest).mockResolvedValue(sessionSuccess);
await ds.createSessionDataSource?.("All");
expect(ds.rpcRequest).toHaveBeenCalledWith(
expect.objectContaining({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption: "All" },
params: { copyOption: "All", sessionType: "edit" },
}),
);
});

it("dispatches createSessionTable RPC with copyOption 'Selected'", async () => {
it("dispatches createSessionTable RPC with copyOption 'Selected' and default sessionType 'edit'", async () => {
const ds = createDataSource();
vi.mocked(ds.rpcRequest).mockResolvedValue(sessionSuccess);
await ds.createSessionDataSource?.("Selected");
expect(ds.rpcRequest).toHaveBeenCalledWith(
expect.objectContaining({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption: "Selected" },
params: { copyOption: "Selected", sessionType: "edit" },
}),
);
});

it("dispatches createSessionTable RPC with copyOption 'Empty'", async () => {
it("dispatches createSessionTable RPC with copyOption 'Empty' and default sessionType 'edit'", async () => {
const ds = createDataSource();
vi.mocked(ds.rpcRequest).mockResolvedValue(sessionSuccess);
await ds.createSessionDataSource?.("Empty");
expect(ds.rpcRequest).toHaveBeenCalledWith(
expect.objectContaining({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption: "Empty" },
params: { copyOption: "Empty", sessionType: "edit" },
}),
);
});

it("passes sessionType 'import' to the RPC request", async () => {
const ds = createDataSource();
vi.mocked(ds.rpcRequest).mockResolvedValue(sessionSuccess);
await ds.createSessionDataSource?.("Empty", "import");
expect(ds.rpcRequest).toHaveBeenCalledWith(
expect.objectContaining({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption: "Empty", sessionType: "import" },
}),
);
});

it("passes sessionType 'export' to the RPC request", async () => {
const ds = createDataSource();
vi.mocked(ds.rpcRequest).mockResolvedValue(sessionSuccess);
await ds.createSessionDataSource?.("All", "export");
expect(ds.rpcRequest).toHaveBeenCalledWith(
expect.objectContaining({
type: "RPC_REQUEST",
rpcName: "createSessionTable",
params: { copyOption: "All", sessionType: "export" },
}),
);
});
Expand Down
4 changes: 4 additions & 0 deletions vuu-ui/packages/vuu-data-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,9 @@ export declare type DataSourceSuspenseProps = {
/** Controls which source rows are copied into a newly created session table. */
export declare type CopyOption = "All" | "Empty" | "Selected";

/** Controls the intended purpose of a created session table. */
export declare type SessionType = "edit" | "import" | "export";

/**
* Wire-level values used by the legacy beginEditSession menu/server service.
* Client edit lifecycles use CopyOption with createSessionDataSource instead.
Expand Down Expand Up @@ -618,6 +621,7 @@ export interface EditApi<
*/
createSessionDataSource?: (
copyOption: CopyOption,
sessionType?: SessionType,
) => Promise<DataSource<T> | undefined>;
/**
* Legacy session creation API. Prefer createSessionDataSource for new servers.
Expand Down
27 changes: 25 additions & 2 deletions vuu-ui/packages/vuu-filter-parser/src/filter-evaluation-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export function filterPredicate(
return testDataRowInclude(columnMapOrFilter);
case "=":
return testDataRowEQ(columnMapOrFilter);
case "!=":
return testDataRowNE(columnMapOrFilter);
case ">":
return testDataRowGT(columnMapOrFilter);
case ">=":
Expand All @@ -110,7 +112,8 @@ export function filterPredicate(
case "or":
return testDataRowOR(columnMapOrFilter as MultiClauseFilter<"or">);
default:
console.log(`unrecognized filter type ${columnMapOrFilter.op}`);
const unreachable: never = columnMapOrFilter;
console.log('unrecognized filter type', unreachable);
return () => true;
}
} else if (filter) {
Expand All @@ -120,6 +123,8 @@ export function filterPredicate(
return testInclude(columnMapOrFilter, filter);
case "=":
return testEQ(columnMapOrFilter, filter);
case "!=":
return testNE(columnMapOrFilter, filter);
case ">":
return testGT(columnMapOrFilter, filter);
case ">=":
Expand All @@ -139,7 +144,8 @@ export function filterPredicate(
case "or":
return testOR(columnMapOrFilter, filter as MultiClauseFilter<"or">);
default:
console.log(`unrecognized filter type ${filter.op}`);
const unreachable: never = filter;
console.log('unrecognized filter type', unreachable);
return () => true;
}
} else {
Expand Down Expand Up @@ -173,12 +179,29 @@ const testEQ = (
}
};

const testNE = (
columnMap: ColumnMap,
filter: SingleValueFilterClause,
): FilterPredicate => {
if (isScaledDecimalFilterClause(filter)) {
return (row) => row[columnMap[filter.column]] !== filter.value.asLong;
} else {
return (row) => row[columnMap[filter.column]] !== filter.value;
}
};

const testDataRowEQ = (
filter: SingleValueFilterClause,
): DataRowFilterPredicate => {
return (row) => row[filter.column] === filter.value;
};

const testDataRowNE = (
filter: SingleValueFilterClause,
): DataRowFilterPredicate => {
return (row) => row[filter.column] !== filter.value;
};

const testGT = (
columnMap: ColumnMap,
filter: SingleValueFilterClause,
Expand Down
1 change: 1 addition & 0 deletions vuu-ui/packages/vuu-protocol-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ export declare type DeleteSelectedRowsRpcServiceRequest = {
*/
export declare type CreateSessionTableParams = {
copyOption: "All" | "Empty" | "Selected";
sessionType?: "edit" | "import" | "export";
};
export declare type CreateSessionTableRpcServiceRequest = {
context: ViewportRpcContext;
Expand Down
Loading