Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Presentation: Enforce result paging for hierarchy compare #678

Merged
merged 9 commits into from
Feb 5, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion common/api/presentation-backend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { NodeKey } from '@bentley/presentation-common';
import { NodePathElement } from '@bentley/presentation-common';
import { Paged } from '@bentley/presentation-common';
import { PagedResponse } from '@bentley/presentation-common';
import { PartialHierarchyModification } from '@bentley/presentation-common';
import { PresentationDataCompareOptions } from '@bentley/presentation-common';
import { PresentationUnitSystem } from '@bentley/presentation-common';
import { RegisteredRuleset } from '@bentley/presentation-common';
Expand Down Expand Up @@ -155,7 +156,7 @@ export class PresentationManager {
activeLocale: string | undefined;
activeUnitSystem: PresentationUnitSystem | undefined;
// @deprecated (undocumented)
compareHierarchies(requestContext: ClientRequestContext, requestOptions: PresentationDataCompareOptions<IModelDb, NodeKey>): Promise<HierarchyCompareInfo>;
compareHierarchies(requestContext: ClientRequestContext, requestOptions: PresentationDataCompareOptions<IModelDb, NodeKey>): Promise<PartialHierarchyModification[]>;
// @beta
compareHierarchies(requestOptions: WithClientRequestContext<PresentationDataCompareOptions<IModelDb, NodeKey>>): Promise<HierarchyCompareInfo>;
// @deprecated
Expand Down
22 changes: 13 additions & 9 deletions common/api/presentation-common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ export interface HierarchyCompareInfo {
// (undocumented)
changes: PartialHierarchyModification[];
// (undocumented)
nextStep?: {
continuationToken?: {
prevHierarchyNode: string;
currHierarchyNode: string;
};
Expand All @@ -834,7 +834,7 @@ export interface HierarchyCompareInfoJSON {
// (undocumented)
changes: PartialHierarchyModificationJSON[];
// (undocumented)
nextStep?: {
continuationToken?: {
prevHierarchyNode: string;
currHierarchyNode: string;
};
Expand Down Expand Up @@ -1551,6 +1551,11 @@ export const PRESENTATION_COMMON_ROOT: string;

// @alpha
export interface PresentationDataCompareOptions<TIModel, TNodeKey> extends RequestOptionsWithRuleset<TIModel> {
// (undocumented)
continuationToken?: {
prevHierarchyNode: string;
currHierarchyNode: string;
};
// (undocumented)
expandedNodeKeys?: TNodeKey[];
// (undocumented)
Expand All @@ -1560,11 +1565,6 @@ export interface PresentationDataCompareOptions<TIModel, TNodeKey> extends Reque
};
// (undocumented)
resultSetSize?: number;
// (undocumented)
startPosition?: {
prevHierarchyNode: string;
currHierarchyNode: string;
};
}

// @alpha
Expand All @@ -1583,8 +1583,10 @@ export enum PresentationRpcEvents {

// @public
export class PresentationRpcInterface extends RpcInterface {
// @alpha @deprecated (undocumented)
compareHierarchies(_token: IModelRpcProps, _options: PresentationDataCompareRpcOptions): PresentationRpcResponse<PartialHierarchyModificationJSON[]>;
// @alpha (undocumented)
compareHierarchies(_token: IModelRpcProps, _options: PresentationDataCompareRpcOptions): PresentationRpcResponse<HierarchyCompareInfoJSON>;
compareHierarchiesPaged(_token: IModelRpcProps, _options: PresentationDataCompareRpcOptions): PresentationRpcResponse<HierarchyCompareInfoJSON>;
// (undocumented)
computeSelection(_token: IModelRpcProps, _options: SelectionScopeRpcRequestOptions, _ids: Id64String[], _scopeId: string): PresentationRpcResponse<KeySetJSON>;
// @deprecated (undocumented)
Expand Down Expand Up @@ -2104,7 +2106,9 @@ export class RpcRequestsHandler implements IDisposable {
constructor(props?: RpcRequestsHandlerProps);
readonly clientId: string;
// (undocumented)
compareHierarchies(options: PresentationDataCompareOptions<IModelRpcProps, NodeKeyJSON>): Promise<HierarchyCompareInfoJSON>;
compareHierarchies(options: PresentationDataCompareOptions<IModelRpcProps, NodeKeyJSON>): Promise<PartialHierarchyModificationJSON[]>;
// (undocumented)
compareHierarchiesPaged(options: PresentationDataCompareOptions<IModelRpcProps, NodeKeyJSON>): Promise<HierarchyCompareInfoJSON>;
// (undocumented)
computeSelection(options: SelectionScopeRequestOptions<IModelRpcProps>, ids: Id64String[], scopeId: string): Promise<KeySetJSON>;
// (undocumented)
Expand Down
71 changes: 71 additions & 0 deletions full-stack-tests/presentation/src/frontend/Update.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,77 @@ describe("Update", () => {

});

describe("paging", () => {

it("collects results from multiple pages", async () => {
const ruleset = await Presentation.presentation.rulesets().add({
id: faker.random.uuid(),
rules: [{
ruleType: RuleTypes.RootNodes,
specifications: [{
specType: ChildNodeSpecificationTypes.CustomNode,
type: "T_ROOT-1",
label: "root-1",
}],
}],
});
expect(ruleset).to.not.be.undefined;

const { result, unmount } = renderHook(
(props: PresentationTreeNodeLoaderProps) => usePresentationTreeNodeLoader(props),
{ initialProps: { imodel, ruleset, pagingSize: 100, enableHierarchyAutoUpdate: true } },
);
await loadHierarchy(result.current);
unmount();

const modifiedRuleset = await Presentation.presentation.rulesets().modify(ruleset, {
rules: [
{
ruleType: RuleTypes.RootNodes,
specifications: [{
specType: ChildNodeSpecificationTypes.CustomNode,
type: "T_ROOT-0",
label: "root-0",
}],
},
...ruleset.rules,
{
ruleType: RuleTypes.RootNodes,
specifications: [{
specType: ChildNodeSpecificationTypes.CustomNode,
type: "T_ROOT-2",
label: "root-2",
}],
},
],
});
expect(modifiedRuleset).to.not.be.undefined;

const rpcSpy = sinon.spy(Presentation.presentation.rpcRequestsHandler, "compareHierarchiesPaged");
const changes = await Presentation.presentation.compareHierarchies({
imodel,
prev: {
rulesetOrId: ruleset,
rulesetVariables: [],
},
rulesetOrId: modifiedRuleset,
rulesetVariables: [],
resultSetSize: 1,
});
expect(changes).to.containSubset([{
type: "Insert",
node: { key: { type: "T_ROOT-0" } },
position: 0,
}, {
type: "Insert",
node: { key: { type: "T_ROOT-2" } },
position: 2,
}]);
expect(rpcSpy).to.be.calledTwice;
});

});

});

});
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { BriefcaseDb, EventSink, IModelDb, IModelHost, IModelJsNative } from "@b
import {
Content, ContentDescriptorRequestOptions, ContentFlags, ContentRequestOptions, DefaultContentDisplayTypes, Descriptor, DescriptorOverrides,
DisplayLabelRequestOptions, DisplayLabelsRequestOptions, DisplayValueGroup, DistinctValuesRequestOptions, ExtendedContentRequestOptions,
ExtendedHierarchyRequestOptions, getLocalesDirectory, HierarchyCompareInfo, HierarchyRequestOptions, InstanceKey, KeySet, LabelDefinition, LabelRequestOptions, Node,
NodeKey, NodePathElement, Paged, PagedResponse, PresentationDataCompareOptions, PresentationError, PresentationStatus,
PresentationUnitSystem, RequestPriority, Ruleset, SelectionInfo, SelectionScope, SelectionScopeRequestOptions,
ExtendedHierarchyRequestOptions, getLocalesDirectory, HierarchyCompareInfo, HierarchyRequestOptions, InstanceKey, KeySet, LabelDefinition,
LabelRequestOptions, Node, NodeKey, NodePathElement, Paged, PagedResponse, PartialHierarchyModification, PresentationDataCompareOptions,
PresentationError, PresentationStatus, PresentationUnitSystem, RequestPriority, Ruleset, SelectionInfo, SelectionScope,
SelectionScopeRequestOptions,
} from "@bentley/presentation-common";
import { PresentationBackendLoggerCategory } from "./BackendLoggerCategory";
import { PRESENTATION_BACKEND_ASSETS_ROOT, PRESENTATION_COMMON_ASSETS_ROOT } from "./Constants";
Expand Down Expand Up @@ -848,16 +849,16 @@ export class PresentationManager {
}

/** @deprecated Use an overload with one argument */
public async compareHierarchies(requestContext: ClientRequestContext, requestOptions: PresentationDataCompareOptions<IModelDb, NodeKey>): Promise<HierarchyCompareInfo>;
public async compareHierarchies(requestContext: ClientRequestContext, requestOptions: PresentationDataCompareOptions<IModelDb, NodeKey>): Promise<PartialHierarchyModification[]>;
/**
* Compares two hierarchies specified in the request options
* TODO: Return results in pages
* @beta
*/
public async compareHierarchies(requestOptions: WithClientRequestContext<PresentationDataCompareOptions<IModelDb, NodeKey>>): Promise<HierarchyCompareInfo>;
public async compareHierarchies(requestContextOrOptions: ClientRequestContext | WithClientRequestContext<PresentationDataCompareOptions<IModelDb, NodeKey>>, deprecatedRequestOptions?: PresentationDataCompareOptions<IModelDb, NodeKey>): Promise<HierarchyCompareInfo> {
public async compareHierarchies(requestContextOrOptions: ClientRequestContext | WithClientRequestContext<PresentationDataCompareOptions<IModelDb, NodeKey>>, deprecatedRequestOptions?: PresentationDataCompareOptions<IModelDb, NodeKey>): Promise<HierarchyCompareInfo | PartialHierarchyModification[]> {
if (requestContextOrOptions instanceof ClientRequestContext) {
return this.compareHierarchies({ ...deprecatedRequestOptions!, requestContext: requestContextOrOptions });
return (await this.compareHierarchies({ ...deprecatedRequestOptions!, requestContext: requestContextOrOptions })).changes;
}

if (!requestContextOrOptions.prev.rulesetOrId && !requestContextOrOptions.prev.rulesetVariables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import { IModelRpcProps } from "@bentley/imodeljs-common";
import {
ContentDescriptorRpcRequestOptions, ContentJSON, ContentRpcRequestOptions, Descriptor, DescriptorJSON, DescriptorOverrides, DiagnosticsOptions,
DiagnosticsScopeLogs, DisplayLabelRpcRequestOptions, DisplayLabelsRpcRequestOptions, DisplayValueGroup, DisplayValueGroupJSON,
DistinctValuesRpcRequestOptions, ExtendedContentRpcRequestOptions, ExtendedHierarchyRpcRequestOptions, HierarchyCompareInfo, HierarchyCompareInfoJSON,
HierarchyRpcRequestOptions, InstanceKey, InstanceKeyJSON, isContentDescriptorRequestOptions, isDisplayLabelRequestOptions, isExtendedContentRequestOptions,
isExtendedHierarchyRequestOptions, ItemJSON, KeySet, KeySetJSON, LabelDefinition, LabelDefinitionJSON, LabelRpcRequestOptions, Node, NodeJSON,
NodeKey, NodeKeyJSON, NodePathElement, NodePathElementJSON, Paged, PagedResponse, PageOptions, PresentationDataCompareRpcOptions, PresentationError,
PresentationRpcInterface, PresentationRpcResponse, PresentationStatus, Ruleset, SelectionInfo, SelectionScope, SelectionScopeRpcRequestOptions,
DistinctValuesRpcRequestOptions, ExtendedContentRpcRequestOptions, ExtendedHierarchyRpcRequestOptions, HierarchyCompareInfo,
HierarchyCompareInfoJSON, HierarchyRpcRequestOptions, InstanceKey, InstanceKeyJSON, isContentDescriptorRequestOptions, isDisplayLabelRequestOptions,
isExtendedContentRequestOptions, isExtendedHierarchyRequestOptions, ItemJSON, KeySet, KeySetJSON, LabelDefinition, LabelDefinitionJSON,
LabelRpcRequestOptions, Node, NodeJSON, NodeKey, NodeKeyJSON, NodePathElement, NodePathElementJSON, Paged, PagedResponse, PageOptions,
PartialHierarchyModification, PartialHierarchyModificationJSON, PresentationDataCompareRpcOptions, PresentationError, PresentationRpcInterface,
PresentationRpcResponse, PresentationStatus, Ruleset, SelectionInfo, SelectionScope, SelectionScopeRpcRequestOptions,
} from "@bentley/presentation-common";
import { PresentationBackendLoggerCategory } from "./BackendLoggerCategory";
import { Presentation } from "./Presentation";
Expand Down Expand Up @@ -356,7 +357,18 @@ export class PresentationRpcImpl extends PresentationRpcInterface {
});
}

public async compareHierarchies(token: IModelRpcProps, requestOptions: PresentationDataCompareRpcOptions): PresentationRpcResponse<HierarchyCompareInfoJSON> {
public async compareHierarchies(token: IModelRpcProps, requestOptions: PresentationDataCompareRpcOptions): PresentationRpcResponse<PartialHierarchyModificationJSON[]> {
return this.makeRequest(token, "compareHierarchies", requestOptions, async (options) => {
options = {
...options,
...(options.expandedNodeKeys ? { expandedNodeKeys: options.expandedNodeKeys.map(NodeKey.fromJSON) } : undefined),
};
const result = await this.getManager(requestOptions.clientId).compareHierarchies(options);
return result.changes.map(PartialHierarchyModification.toJSON);
});
}

public async compareHierarchiesPaged(token: IModelRpcProps, requestOptions: PresentationDataCompareRpcOptions): PresentationRpcResponse<HierarchyCompareInfoJSON> {
return this.makeRequest(token, "compareHierarchies", requestOptions, async (options) => {
options = {
...options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ describe("PresentationManager", () => {
expandedNodeKeys: [nodeKey],
};
const result = await manager.compareHierarchies(ClientRequestContext.current, options);
verifyWithExpectedResult(result, HierarchyCompareInfo.fromJSON(addonResponse), expectedParams);
verifyWithExpectedResult(result, HierarchyCompareInfo.fromJSON(addonResponse).changes, expectedParams);
});

it("requests addon to compare hierarchies based on ruleset and variables' changes", async () => {
Expand Down
78 changes: 70 additions & 8 deletions presentation/backend/src/test/PresentationRpcImpl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
ContentDescriptorRequestOptions, ContentDescriptorRpcRequestOptions, ContentRequestOptions, ContentRpcRequestOptions, Descriptor, DescriptorJSON,
DescriptorOverrides, DiagnosticsScopeLogs, DisplayLabelRequestOptions, DisplayLabelRpcRequestOptions, DisplayLabelsRequestOptions,
DisplayLabelsRpcRequestOptions, DistinctValuesRequestOptions, ExtendedContentRequestOptions, ExtendedContentRpcRequestOptions,
ExtendedHierarchyRequestOptions, ExtendedHierarchyRpcRequestOptions, FieldDescriptor, FieldDescriptorType, HierarchyCompareInfo, HierarchyRequestOptions,
HierarchyRpcRequestOptions, InstanceKey, Item, KeySet, KeySetJSON, Node, NodeKey, NodePathElement, Paged, PageOptions,
PresentationDataCompareOptions, PresentationDataCompareRpcOptions, PresentationError, PresentationRpcRequestOptions,
PresentationStatus, SelectionScopeRequestOptions, VariableValueTypes,
ExtendedHierarchyRequestOptions, ExtendedHierarchyRpcRequestOptions, FieldDescriptor, FieldDescriptorType, HierarchyCompareInfo,
HierarchyRequestOptions, HierarchyRpcRequestOptions, InstanceKey, Item, KeySet, KeySetJSON, Node, NodeKey, NodePathElement, Paged, PageOptions,
PresentationDataCompareOptions, PresentationDataCompareRpcOptions, PresentationError, PresentationRpcRequestOptions, PresentationStatus,
SelectionScopeRequestOptions, VariableValueTypes,
} from "@bentley/presentation-common";
import * as moq from "@bentley/presentation-common/lib/test/_helpers/Mocks";
import { ResolvablePromise } from "@bentley/presentation-common/lib/test/_helpers/Promises";
Expand Down Expand Up @@ -1631,7 +1631,69 @@ describe("PresentationRpcImpl", () => {

});

describe("compareHierarchies", () => {
describe("[deprecated] compareHierarchies", () => {

it("calls manager for comparison based on ruleset changes", async () => {
const result: HierarchyCompareInfo = {
changes: [{
type: "Delete",
node: createRandomECInstancesNode(),
}],
};
const rpcOptions: PresentationDataCompareRpcOptions = {
...defaultRpcParams,
prev: {
rulesetOrId: "1",
},
rulesetOrId: "2",
};
const managerOptions: WithClientRequestContext<PresentationDataCompareOptions<IModelDb, NodeKey>> = {
requestContext: ClientRequestContext.current,
imodel: testData.imodelMock.object,
prev: rpcOptions.prev,
rulesetOrId: rpcOptions.rulesetOrId,
};
presentationManagerMock.setup((x) => x.compareHierarchies(managerOptions))
.returns(async () => result)
.verifiable();
const actualResult = await impl.compareHierarchies(testData.imodelToken, rpcOptions);
presentationManagerMock.verifyAll();
expect(actualResult.result).to.deep.eq(HierarchyCompareInfo.toJSON(result).changes);
});

it("calls manager for comparison based on ruleset variables' changes", async () => {
const result: HierarchyCompareInfo = {
changes: [{
type: "Delete",
node: createRandomECInstancesNode(),
}],
};
const rpcOptions: PresentationDataCompareRpcOptions = {
...defaultRpcParams,
prev: {
rulesetVariables: [{ id: "test", type: VariableValueTypes.Int, value: 123 }],
},
rulesetOrId: "2",
expandedNodeKeys: [createRandomECInstancesNodeKeyJSON()],
};
const managerOptions: WithClientRequestContext<PresentationDataCompareOptions<IModelDb, NodeKey>> = {
requestContext: ClientRequestContext.current,
imodel: testData.imodelMock.object,
prev: rpcOptions.prev,
rulesetOrId: rpcOptions.rulesetOrId,
expandedNodeKeys: rpcOptions.expandedNodeKeys!.map(NodeKey.fromJSON),
};
presentationManagerMock.setup((x) => x.compareHierarchies(managerOptions))
.returns(async () => result)
.verifiable();
const actualResult = await impl.compareHierarchies(testData.imodelToken, rpcOptions);
presentationManagerMock.verifyAll();
expect(actualResult.result).to.deep.eq(HierarchyCompareInfo.toJSON(result).changes);
});

});

describe("compareHierarchiesPaged", () => {

it("calls manager for comparison based on ruleset changes", async () => {
const result: HierarchyCompareInfo = {
Expand All @@ -1658,7 +1720,7 @@ describe("PresentationRpcImpl", () => {
presentationManagerMock.setup((x) => x.compareHierarchies(managerOptions))
.returns(async () => result)
.verifiable();
const actualResult = await impl.compareHierarchies(testData.imodelToken, rpcOptions);
const actualResult = await impl.compareHierarchiesPaged(testData.imodelToken, rpcOptions);
presentationManagerMock.verifyAll();
expect(actualResult.result).to.deep.eq(HierarchyCompareInfo.toJSON(result));
});
Expand Down Expand Up @@ -1690,7 +1752,7 @@ describe("PresentationRpcImpl", () => {
presentationManagerMock.setup((x) => x.compareHierarchies(managerOptions))
.returns(async () => result)
.verifiable();
const actualResult = await impl.compareHierarchies(testData.imodelToken, rpcOptions);
const actualResult = await impl.compareHierarchiesPaged(testData.imodelToken, rpcOptions);
presentationManagerMock.verifyAll();
expect(actualResult.result).to.deep.eq(HierarchyCompareInfo.toJSON(result));
});
Expand Down Expand Up @@ -1721,7 +1783,7 @@ describe("PresentationRpcImpl", () => {
presentationManagerMock.setup((x) => x.compareHierarchies(managerOptions))
.returns(async () => result)
.verifiable();
const actualResult = await impl.compareHierarchies(testData.imodelToken, rpcOptions);
const actualResult = await impl.compareHierarchiesPaged(testData.imodelToken, rpcOptions);
presentationManagerMock.verifyAll();
expect(actualResult.result).to.deep.eq(HierarchyCompareInfo.toJSON(result));
});
Expand Down
Loading