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: Add functions that return async iterators for paged responses #6472

Merged
merged 89 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
73ed3f0
Initial implementation
Yato333 Feb 23, 2024
20385ec
Implement some edge cases for empty responses
Yato333 Feb 23, 2024
6c05b4d
Implement edge case for undefined page size
Yato333 Feb 23, 2024
a1c89ef
Reenable old tests
Yato333 Feb 23, 2024
2b240f5
refactor getter test
Yato333 Feb 23, 2024
1d0c68e
Make new page generator work with the PresentationManager (WIP)
Yato333 Feb 23, 2024
a99cfac
Implement gathering of page sizes smaller than requested
Yato333 Feb 26, 2024
c2c578b
Fix all remaining issues
Yato333 Feb 26, 2024
cd5746d
Add docs and rename
Yato333 Feb 26, 2024
5803167
Fix tests
Yato333 Feb 26, 2024
f199824
Add public utilities
Yato333 Feb 26, 2024
955e5ff
Revert "Add public utilities"
Yato333 Feb 26, 2024
76207b6
getDistinctValuesStream()
Yato333 Feb 26, 2024
78b8c36
Refactor to avoid confusion with pages and batches
Yato333 Feb 26, 2024
5a03cee
Refactor
Yato333 Feb 27, 2024
432c0dc
Fix coverage
Yato333 Feb 27, 2024
57c55a3
Expose the parallelism argument for GetNodes
Yato333 Feb 27, 2024
5d5226e
Expose for getContent
Yato333 Feb 27, 2024
f1c639b
expose the rest
Yato333 Feb 27, 2024
69e48da
Merge branch 'master' into presentation/stream-result-sets
Yato333 Feb 27, 2024
3377481
Merge branch 'master' into presentation/page-streaming
Yato333 Feb 27, 2024
977996c
Fix
Yato333 Feb 27, 2024
ff0ca20
Add public
Yato333 Feb 27, 2024
1fbb3ad
Merge branch 'master' into presentation/page-streaming
Yato333 Feb 27, 2024
5af5dae
extract-api
Yato333 Feb 27, 2024
2e5e0c5
Rush change
Yato333 Feb 27, 2024
fc5764a
Apply suggestions from code review
Yato333 Feb 28, 2024
92925b3
Refactor "should run requests concurrently" test
Yato333 Feb 28, 2024
d340a83
Add test for a valid order
Yato333 Feb 28, 2024
08769f7
Fix ordering issue
Yato333 Feb 28, 2024
3ddeaca
Add return type for getLocalizedNode
Yato333 Feb 28, 2024
748f918
Add header
Yato333 Feb 28, 2024
224c356
Change type architecture
Yato333 Feb 28, 2024
8530032
4.5.0-dev.20
imodeljs-admin Feb 28, 2024
099139f
extract-api
Yato333 Feb 28, 2024
4efb8be
Use suggested refactoring of StreamedResponseGenerator
Yato333 Feb 28, 2024
0ec3d27
Implement getDisplayLabelDefinitionIterator
Yato333 Feb 28, 2024
fcbcfa6
getContentIterator
Yato333 Feb 28, 2024
cbf99ca
extract-api
Yato333 Feb 28, 2024
0834bce
Update changelog
Yato333 Feb 28, 2024
3810a91
Merge branch 'master' into presentation/page-streaming
Yato333 Feb 28, 2024
589a483
Merge branch 'master' into presentation/page-streaming
Yato333 Feb 29, 2024
3bc660f
Use AsyncIterableIterator
Yato333 Feb 29, 2024
6fec104
Add copyright
Yato333 Feb 29, 2024
ef24442
Fix typo
Yato333 Feb 29, 2024
39cde90
Use shorter expression in getContentInstanceKeys
Yato333 Feb 29, 2024
6479e95
Remove createItemsResponse from StreamedResponseGenerator
Yato333 Feb 29, 2024
042594d
Simplify promise resolution order test
Yato333 Feb 29, 2024
0a61d5a
Deprecate array functions
Yato333 Feb 29, 2024
5161fc8
Break early when taking batches from the accumulator
Yato333 Feb 29, 2024
f934f12
Revert "Simplify promise resolution order test"
Yato333 Feb 29, 2024
8d05c84
Refactor order test
Yato333 Feb 29, 2024
f1effaf
Merge branch 'master' into presentation/page-streaming
Yato333 Feb 29, 2024
e69b9bf
extract-api
Yato333 Feb 29, 2024
9d53d1c
add rush change
Yato333 Feb 29, 2024
ecf1a25
Fix resolvable promise class
Yato333 Feb 29, 2024
8dcc61e
Add next version info
Yato333 Feb 29, 2024
466f248
Fix resolvable promise test
Yato333 Feb 29, 2024
33caca5
Add deprecation ignore
Yato333 Feb 29, 2024
e2ce7a6
Update hilite set provider to use the iterator version
Yato333 Feb 29, 2024
d072ed5
extract-api
Yato333 Feb 29, 2024
6e7c90c
Revert "Add deprecation ignore"
Yato333 Feb 29, 2024
15cb79a
Add collect utility
Yato333 Feb 29, 2024
686434b
ignore warnings in PresentationManager tests
Yato333 Feb 29, 2024
967ba96
Fix usage of getNodes
Yato333 Feb 29, 2024
97f2145
Fix usage of deprecated getContent
Yato333 Feb 29, 2024
40b57d6
getPagedDistinctValues
Yato333 Feb 29, 2024
4a7e072
Fix the rest
Yato333 Feb 29, 2024
17612e2
Apply suggestions from code review
Yato333 Mar 1, 2024
d7424b7
Fix
Yato333 Mar 1, 2024
44d3b9f
fix nextVersion.md
Yato333 Mar 1, 2024
892ca41
fix hierarchies tests
Yato333 Mar 1, 2024
fe04c00
Remove release tag from test utility
Yato333 Mar 1, 2024
8348bce
Fix descriptor not being localized
Yato333 Mar 1, 2024
97e1067
Fix content values being in wrong order
Yato333 Mar 1, 2024
e9d117c
Optimize collection of items where possible in performance tests
Yato333 Mar 1, 2024
5d05620
remove public method which returns an observable
Yato333 Mar 1, 2024
2888f52
Fix tests
Yato333 Mar 1, 2024
7edf209
Move getContentObservable to another place
Yato333 Mar 1, 2024
0ebf054
Merge branch 'master' into presentation/page-streaming
Yato333 Mar 1, 2024
758beac
Fix some tests
Yato333 Mar 1, 2024
261ff6d
use collect instead of iterator next
Yato333 Mar 4, 2024
57a2359
Remove unused imports
Yato333 Mar 4, 2024
b2376f6
Refactor PresentationManager internal
Yato333 Mar 4, 2024
56a217a
Merge branch 'master' into presentation/page-streaming
Yato333 Mar 4, 2024
46c281e
Refactor to use iterators
Yato333 Mar 4, 2024
ded8028
Empty commit
Yato333 Mar 4, 2024
b74e717
Fix rpc tests
Yato333 Mar 4, 2024
68156c8
rush change
Yato333 Mar 4, 2024
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
2 changes: 2 additions & 0 deletions common/api/presentation-common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,8 @@ export class LocalizationHelper {
// (undocumented)
getLocalizedLabelDefinitions(labelDefinitions: LabelDefinition[]): LabelDefinition[];
// (undocumented)
getLocalizedNode(node: Node_2): Node_2;
// (undocumented)
getLocalizedNodePathElement(npe: NodePathElement): NodePathElement;
// (undocumented)
getLocalizedNodes(nodes: Node_2[]): Node_2[];
Expand Down
60 changes: 47 additions & 13 deletions common/api/presentation-frontend.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { IDisposable } from '@itwin/core-bentley';
import { IModelConnection } from '@itwin/core-frontend';
import { InstanceKey } from '@itwin/presentation-common';
import { InternetConnectivityStatus } from '@itwin/core-common';
import { Item } from '@itwin/presentation-common';
import { Key } from '@itwin/presentation-common';
import { Keys } from '@itwin/presentation-common';
import { KeySet } from '@itwin/presentation-common';
Expand All @@ -44,7 +45,6 @@ import { NodeKey } from '@itwin/presentation-common';
import { NodePathElement } from '@itwin/presentation-common';
import { Paged } from '@itwin/presentation-common';
import { PagedResponse } from '@itwin/presentation-common';
import { PageOptions } from '@itwin/presentation-common';
import { RegisteredRuleset } from '@itwin/presentation-common';
import { RpcRequestsHandler } from '@itwin/presentation-common';
import { Ruleset } from '@itwin/presentation-common';
Expand Down Expand Up @@ -78,9 +78,6 @@ export class BrowserLocalFavoritePropertiesStorage implements IFavoritePropertie
savePropertiesOrder(orderInfos: FavoritePropertiesOrderInfo[], iTwinId: string | undefined, imodelId: string): Promise<void>;
}

// @internal (undocumented)
export const buildPagedArrayResponse: <TItem>(requestedPage: PageOptions | undefined, getter: (page: Required<PageOptions>, requestIndex: number) => Promise<PagedResponse<TItem>>) => Promise<PagedResponse<TItem>>;

// @beta
export function consoleDiagnosticsHandler(diagnostics: ClientDiagnostics): void;

Expand Down Expand Up @@ -165,9 +162,18 @@ export enum FavoritePropertiesScope {
ITwin = 1
}

// @public
export type GetContentRequestOptions = ContentRequestOptions<IModelConnection, Descriptor | DescriptorOverrides, KeySet, RulesetVariable> & ClientDiagnosticsAttribute;

// @public
export type GetDistinctValuesRequestOptions = DistinctValuesRequestOptions<IModelConnection, Descriptor | DescriptorOverrides, KeySet, RulesetVariable> & ClientDiagnosticsAttribute;

// @internal (undocumented)
export const getFieldInfos: (field: Field) => Set<PropertyFullName>;

// @public
export type GetNodesRequestOptions = HierarchyRequestOptions<IModelConnection, NodeKey, RulesetVariable> & ClientDiagnosticsAttribute;

// @public @deprecated
export function getScopeId(scope: SelectionScope | string | undefined): string;

Expand Down Expand Up @@ -240,6 +246,11 @@ export interface ISelectionProvider {
selectionChange: SelectionChangeEvent;
}

// @public
export type MultipleValuesRequestOptions = Paged<{
maxParallelRequests?: number;
}>;

// @internal (undocumented)
export class NoopFavoritePropertiesStorage implements IFavoritePropertiesStorage {
// (undocumented)
Expand Down Expand Up @@ -314,32 +325,55 @@ export class PresentationManager implements IDisposable {
dispose(): void;
// @internal
ensureIModelInitialized(_: IModelConnection): Promise<void>;
getContent(requestOptions: Paged<ContentRequestOptions<IModelConnection, Descriptor | DescriptorOverrides, KeySet, RulesetVariable>> & ClientDiagnosticsAttribute): Promise<Content | undefined>;
getContentAndSize(requestOptions: Paged<ContentRequestOptions<IModelConnection, Descriptor | DescriptorOverrides, KeySet, RulesetVariable>> & ClientDiagnosticsAttribute): Promise<{
// @deprecated
getContent(requestOptions: GetContentRequestOptions & MultipleValuesRequestOptions): Promise<Content | undefined>;
// @deprecated
getContentAndSize(requestOptions: GetContentRequestOptions & MultipleValuesRequestOptions): Promise<{
content: Content;
size: number;
} | undefined>;
getContentDescriptor(requestOptions: ContentDescriptorRequestOptions<IModelConnection, KeySet, RulesetVariable> & ClientDiagnosticsAttribute): Promise<Descriptor | undefined>;
getContentInstanceKeys(requestOptions: ContentInstanceKeysRequestOptions<IModelConnection, KeySet, RulesetVariable> & ClientDiagnosticsAttribute): Promise<{
getContentInstanceKeys(requestOptions: ContentInstanceKeysRequestOptions<IModelConnection, KeySet, RulesetVariable> & ClientDiagnosticsAttribute & MultipleValuesRequestOptions): Promise<{
total: number;
items: () => AsyncGenerator<InstanceKey>;
}>;
getContentSetSize(requestOptions: ContentRequestOptions<IModelConnection, Descriptor | DescriptorOverrides, KeySet, RulesetVariable> & ClientDiagnosticsAttribute): Promise<number>;
getContentIterator(requestOptions: GetContentRequestOptions & MultipleValuesRequestOptions): Promise<{
descriptor: Descriptor;
total: number;
items: AsyncIterableIterator<Item>;
} | undefined>;
getContentSetSize(requestOptions: GetContentRequestOptions): Promise<number>;
getContentSources(requestOptions: ContentSourcesRequestOptions<IModelConnection> & ClientDiagnosticsAttribute): Promise<SelectClassInfo[]>;
getDisplayLabelDefinition(requestOptions: DisplayLabelRequestOptions<IModelConnection, InstanceKey> & ClientDiagnosticsAttribute): Promise<LabelDefinition>;
getDisplayLabelDefinitions(requestOptions: DisplayLabelsRequestOptions<IModelConnection, InstanceKey> & ClientDiagnosticsAttribute): Promise<LabelDefinition[]>;
// @deprecated
getDisplayLabelDefinitions(requestOptions: DisplayLabelsRequestOptions<IModelConnection, InstanceKey> & ClientDiagnosticsAttribute & MultipleValuesRequestOptions): Promise<LabelDefinition[]>;
getDisplayLabelDefinitionsIterator(requestOptions: DisplayLabelsRequestOptions<IModelConnection, InstanceKey> & ClientDiagnosticsAttribute & MultipleValuesRequestOptions): Promise<{
total: number;
items: AsyncIterableIterator<LabelDefinition>;
}>;
getDistinctValuesIterator(requestOptions: GetDistinctValuesRequestOptions & MultipleValuesRequestOptions): Promise<{
total: number;
items: AsyncIterableIterator<DisplayValueGroup>;
}>;
getElementProperties(requestOptions: SingleElementPropertiesRequestOptions<IModelConnection> & ClientDiagnosticsAttribute): Promise<ElementProperties | undefined>;
getFilteredNodePaths(requestOptions: FilterByTextHierarchyRequestOptions<IModelConnection, RulesetVariable> & ClientDiagnosticsAttribute): Promise<NodePathElement[]>;
getNodePaths(requestOptions: FilterByInstancePathsHierarchyRequestOptions<IModelConnection, RulesetVariable> & ClientDiagnosticsAttribute): Promise<NodePathElement[]>;
getNodes(requestOptions: Paged<HierarchyRequestOptions<IModelConnection, NodeKey, RulesetVariable>> & ClientDiagnosticsAttribute): Promise<Node_2[]>;
getNodesAndCount(requestOptions: Paged<HierarchyRequestOptions<IModelConnection, NodeKey, RulesetVariable>> & ClientDiagnosticsAttribute): Promise<{
// @deprecated
getNodes(requestOptions: GetNodesRequestOptions & MultipleValuesRequestOptions): Promise<Node_2[]>;
// @deprecated
getNodesAndCount(requestOptions: GetNodesRequestOptions & MultipleValuesRequestOptions): Promise<{
count: number;
nodes: Node_2[];
}>;
getNodesCount(requestOptions: HierarchyRequestOptions<IModelConnection, NodeKey, RulesetVariable> & ClientDiagnosticsAttribute): Promise<number>;
getNodesCount(requestOptions: GetNodesRequestOptions): Promise<number>;
// @beta
getNodesDescriptor(requestOptions: HierarchyLevelDescriptorRequestOptions<IModelConnection, NodeKey, RulesetVariable> & ClientDiagnosticsAttribute): Promise<Descriptor | undefined>;
getPagedDistinctValues(requestOptions: DistinctValuesRequestOptions<IModelConnection, Descriptor | DescriptorOverrides, KeySet, RulesetVariable> & ClientDiagnosticsAttribute): Promise<PagedResponse<DisplayValueGroup>>;
getNodesIterator(requestOptions: GetNodesRequestOptions & MultipleValuesRequestOptions): Promise<{
total: number;
items: AsyncIterableIterator<Node_2>;
}>;
// @deprecated
getPagedDistinctValues(requestOptions: GetDistinctValuesRequestOptions & MultipleValuesRequestOptions): Promise<PagedResponse<DisplayValueGroup>>;
// @internal (undocumented)
get ipcRequestsHandler(): IpcRequestsHandler | undefined;
// @alpha
Expand Down
4 changes: 3 additions & 1 deletion common/api/summary/presentation-frontend.exports.csv
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
sep=;
Release Tag;API Item
internal;BrowserLocalFavoritePropertiesStorage
internal;buildPagedArrayResponse:
beta;consoleDiagnosticsHandler(diagnostics: ClientDiagnostics): void
beta;createCombinedDiagnosticsHandler(handlers: ClientDiagnosticsHandler[]): (diagnostics: ClientDiagnostics) => void
public;createFavoritePropertiesStorage(type: DefaultFavoritePropertiesStorageTypes): IFavoritePropertiesStorage
Expand All @@ -15,7 +14,10 @@ public;FavoritePropertiesManager
public;FavoritePropertiesManagerProps
public;FavoritePropertiesOrderInfo
public;FavoritePropertiesScope
public;GetContentRequestOptions = ContentRequestOptions
public;GetDistinctValuesRequestOptions = DistinctValuesRequestOptions
internal;getFieldInfos: (field: Field) => Set
public;GetNodesRequestOptions = HierarchyRequestOptions
public;getScopeId(scope: SelectionScope | string | undefined): string
deprecated;getScopeId(scope: SelectionScope | string | undefined): string
internal;HILITE_RULESET: Ruleset
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/presentation-common",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/presentation-common"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/presentation-frontend",
"comment": "Add `getNodesIterator`, `getDistinctValuesIterator`, `getContentIterator` and `getDisplayLabelDefinitionIterator` methods to `PresentationManager` which return async iterators and load data using multiple parallel requests.",
"type": "none"
}
],
"packageName": "@itwin/presentation-frontend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/presentation-frontend",
"comment": "Deprecate `getNodes`, `getNodesAndCount`, `getContent`, `getContentAndSize`, `getPagedDistinctValues` and `getDisplayLabelDefinitions` in favor of the alternatives which return iterators.",
"type": "none"
}
],
"packageName": "@itwin/presentation-frontend"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/rpcinterface-full-stack-tests",
"comment": "",
"type": "none"
}
],
"packageName": "@itwin/rpcinterface-full-stack-tests"
}
35 changes: 35 additions & 0 deletions docs/changehistory/NextVersion.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Table of contents:
- [Seafloor terrain](#seafloor-terrain)
- [Electron 29 support](#electron-29-support)
- [Editor](#editor)
- [Presentation](#presentation)
- [Deprecation of async array results in favor of async iterators](#deprecation-of-async-array-results-in-favor-of-async-iterators)

## Display

Expand Down Expand Up @@ -47,3 +49,36 @@ Changes to @beta [CreateElementWithDynamicsTool]($editor-frontend) class:

- [CreateElementWithDynamicsTool.doCreateElement]($editor-frontend) no longer takes an optional [ElementGeometryBuilderParams]($common) as it will be set on the supplied [GeometricElementProps]($common).

## Presentation

### Deprecation of async array results in favor of async iterators

`PresentationManager` contains a number of methods to retrieve sets of results like nodes, content, etc. All of these methods have been deprecated in favor of new ones that return an async iterator instead of an array:

- Use `getContentIterator` instead of `getContent` and `getContentAndSize`.
- Use `getDisplayLabelDefinitionsIterator` instead of `getDisplayLabelDefinitions`.
- Use `getDistinctValuesIterator` instead of `getPagedDistinctValues`.
- Use `getNodesIterator` instead of `getNodes` and `getNodesAndCount`.

All of the above methods, including the deprecated ones, received ability to load large sets of results concurrently. Previously, when requesting a large set (page size > 1000), the result was created by sending a number of requests sequentially by requesting the first page, then the second, and so on, until the whole requested set was retrieved. Now, we send a request for the first page to determine total number of items and backend's page size limit, together with the first page of results, and then other requests are made all at once. At attribute `maxParallelRequests` was added to these methods to control how many parallel requests should be sent at a time.

While performance-wise deprecated methods should be in line with the newly added async iterator ones, the latter have two advantages:

1. Caller can start iterating over results as soon as we receive the first page, instead of having to wait for the whole set.
2. The iterator version stops sending requests as soon as the caller stops iterating over the async iterator.

> Example: Showing display labels for a large set of elements in a React component.
>
> The deprecated approach would be to use `PresentationManager.getDisplayLabelDefinitions` to retrieve labels of all elements. Because the API has to load all the data before returning it to the widget, user has to wait a long time to start seeing the results. Moreover, if he decides to close the widget (the component is unmounted), the `getDisplayLabelDefinitions` keeps building the array by sending requests to the backend - there's no way to cancel that.
>
> The new approach is to use `PresentationManager.getDisplayLabelDefinitionsIterator` to get the labels. The labels get streamed to the called as soon as the first results' page is retrieved, so user gets to see initial results quickly, while additional pages keep loading in the background. In addition, if the component is unmounted, iteration can be stopped, thus cancelling all further requests to the backend:
>
> ```ts
> const { items } = await manager.getDisplayLabelDefinitionsIterator(requestProps);
> for await (const label of items) {
> if (isComponentUnmounted) {
> break;
> }
> // update component's model to render the loaded label
> }
> ```
11 changes: 11 additions & 0 deletions full-stack-tests/presentation/src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,14 @@ export async function waitFor<T>(check: () => Promise<T> | T, timeout?: number):
} while (timer.current.milliseconds < timeout);
throw lastError;
}

/**
* Collects items of an async iterable to an array.
*/
export async function collect<T>(iter: AsyncIterable<T>): Promise<T[]> {
const result = new Array<T>();
for await (const item of iter) {
result.push(item);
}
return result;
}
Loading
Loading