From abc2121921a8ea9954a60f994eb476a29b0281a8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 10:19:52 -0500 Subject: [PATCH] Reimplement deprecated getViewThumbnail function to resolve regression (#3306) (#3307) * Reimplement deprecated getViewThumbnail function to resolve regression (cherry picked from commit 5d8c5eabf52f95aff09c6e4cf9c7d3c183afc236) Co-authored-by: Seamus Kirby <32379572+skirby1996@users.noreply.github.com> --- common/api/core-frontend.api.md | 2 +- ...t-get-view-thumbnail_2022-03-07-17-37.json | 10 ++++++ ...t-get-view-thumbnail_2022-03-07-17-37.json | 10 ++++++ ...t-get-view-thumbnail_2022-03-07-17-37.json | 10 ++++++ .../backend/src/rpc-impl/IModelReadRpcImpl.ts | 16 +++++++-- core/frontend/src/IModelConnection.ts | 18 +++++++--- .../frontend/standalone/ModelState.test.ts | 35 +++++++++++++++++++ .../src/frontend/IModelConnection.test.ts | 10 +++++- 8 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 common/changes/@itwin/core-backend/reimplement-get-view-thumbnail_2022-03-07-17-37.json create mode 100644 common/changes/@itwin/core-frontend/reimplement-get-view-thumbnail_2022-03-07-17-37.json create mode 100644 common/changes/@itwin/rpcinterface-full-stack-tests/reimplement-get-view-thumbnail_2022-03-07-17-37.json diff --git a/common/api/core-frontend.api.md b/common/api/core-frontend.api.md index 8d89cc98dc89..e5f7d8060c71 100644 --- a/common/api/core-frontend.api.md +++ b/common/api/core-frontend.api.md @@ -4954,7 +4954,7 @@ export namespace IModelConnection { export class Views { // @internal constructor(_iModel: IModelConnection); - // @deprecated (undocumented) + // @deprecated getThumbnail(_viewId: Id64String): Promise; getViewList(queryParams: ViewQueryParams): Promise; load(viewDefinitionId: Id64String): Promise; diff --git a/common/changes/@itwin/core-backend/reimplement-get-view-thumbnail_2022-03-07-17-37.json b/common/changes/@itwin/core-backend/reimplement-get-view-thumbnail_2022-03-07-17-37.json new file mode 100644 index 000000000000..99b35bb89b62 --- /dev/null +++ b/common/changes/@itwin/core-backend/reimplement-get-view-thumbnail_2022-03-07-17-37.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-backend", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-backend" +} \ No newline at end of file diff --git a/common/changes/@itwin/core-frontend/reimplement-get-view-thumbnail_2022-03-07-17-37.json b/common/changes/@itwin/core-frontend/reimplement-get-view-thumbnail_2022-03-07-17-37.json new file mode 100644 index 000000000000..ac11a63efe7e --- /dev/null +++ b/common/changes/@itwin/core-frontend/reimplement-get-view-thumbnail_2022-03-07-17-37.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-frontend", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-frontend" +} \ No newline at end of file diff --git a/common/changes/@itwin/rpcinterface-full-stack-tests/reimplement-get-view-thumbnail_2022-03-07-17-37.json b/common/changes/@itwin/rpcinterface-full-stack-tests/reimplement-get-view-thumbnail_2022-03-07-17-37.json new file mode 100644 index 000000000000..f4ac0f33aed4 --- /dev/null +++ b/common/changes/@itwin/rpcinterface-full-stack-tests/reimplement-get-view-thumbnail_2022-03-07-17-37.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/rpcinterface-full-stack-tests", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/rpcinterface-full-stack-tests" +} \ No newline at end of file diff --git a/core/backend/src/rpc-impl/IModelReadRpcImpl.ts b/core/backend/src/rpc-impl/IModelReadRpcImpl.ts index ff2ac1c6407b..04b896727d05 100644 --- a/core/backend/src/rpc-impl/IModelReadRpcImpl.ts +++ b/core/backend/src/rpc-impl/IModelReadRpcImpl.ts @@ -10,7 +10,7 @@ import { GuidString, Id64, Id64String, IModelStatus, Logger } from "@itwin/core- import { Code, CodeProps, DbBlobRequest, DbBlobResponse, DbQueryRequest, DbQueryResponse, ElementLoadOptions, ElementLoadProps, ElementProps, EntityMetaData, EntityQueryParams, FontMapProps, GeoCoordinatesRequestProps, GeoCoordinatesResponseProps, GeometryContainmentRequestProps, - GeometryContainmentResponseProps, GeometrySummaryRequestProps, IModel, IModelConnectionProps, IModelCoordinatesRequestProps, + GeometryContainmentResponseProps, GeometrySummaryRequestProps, ImageSourceFormat, IModel, IModelConnectionProps, IModelCoordinatesRequestProps, IModelCoordinatesResponseProps, IModelError, IModelReadRpcInterface, IModelRpcOpenProps, IModelRpcProps, MassPropertiesRequestProps, MassPropertiesResponseProps, ModelProps, NoContentError, RpcInterface, RpcManager, SnapRequestProps, SnapResponseProps, SyncMode, TextureData, TextureLoadProps, ViewStateLoadProps, ViewStateProps, @@ -200,9 +200,19 @@ export class IModelReadRpcImpl extends RpcInterface implements IModelReadRpcInte return (el === undefined) ? [] : el.getToolTipMessage(); } - /** @deprecated */ + /** Send a view thumbnail to the frontend. This is a binary transfer with the metadata in a 16-byte prefix header. + * @deprecated + */ public async getViewThumbnail(_tokenProps: IModelRpcProps, _viewId: string): Promise { - throw new NoContentError(); + const iModelDb = await RpcBriefcaseUtility.findOpenIModel(RpcTrace.expectCurrentActivity.accessToken, _tokenProps); + const thumbnail = iModelDb.views.getThumbnail(_viewId); + if (undefined === thumbnail || 0 === thumbnail.image.length) + throw new NoContentError(); + + const val = new Uint8Array(thumbnail.image.length + 16); // allocate a new buffer 16 bytes larger than the image size + new Uint32Array(val.buffer, 0, 4).set([thumbnail.image.length, thumbnail.format === "jpeg" ? ImageSourceFormat.Jpeg : ImageSourceFormat.Png, thumbnail.width, thumbnail.height]); // Put the metadata in the first 16 bytes. + val.set(thumbnail.image, 16); // put the image data at offset 16 after metadata + return val; } public async getDefaultViewId(tokenProps: IModelRpcProps): Promise { diff --git a/core/frontend/src/IModelConnection.ts b/core/frontend/src/IModelConnection.ts index 9be09e6b01f6..8ce573e42314 100644 --- a/core/frontend/src/IModelConnection.ts +++ b/core/frontend/src/IModelConnection.ts @@ -12,7 +12,7 @@ import { import { AxisAlignedBox3d, Cartographic, CodeProps, CodeSpec, DbQueryRequest, DbResult, EcefLocation, EcefLocationProps, ECSqlReader, ElementLoadOptions, ElementProps, EntityQueryParams, FontMap, GeoCoordStatus, GeometryContainmentRequestProps, GeometryContainmentResponseProps, - GeometrySummaryRequestProps, IModel, IModelConnectionProps, IModelError, IModelReadRpcInterface, IModelStatus, + GeometrySummaryRequestProps, ImageSourceFormat, IModel, IModelConnectionProps, IModelError, IModelReadRpcInterface, IModelStatus, mapToGeoServiceStatus, MassPropertiesRequestProps, MassPropertiesResponseProps, ModelProps, ModelQueryParams, NoContentError, Placement, Placement2d, Placement3d, QueryBinder, QueryOptions, QueryOptionsBuilder, QueryRowFormat, RpcManager, SnapRequestProps, SnapResponseProps, SnapshotIModelRpcInterface, TextureData, TextureLoadProps, ThumbnailProps, ViewDefinitionProps, ViewQueryParams, ViewStateLoadProps, @@ -1045,11 +1045,21 @@ export namespace IModelConnection { // eslint-disable-line no-redeclare return viewState; } - /** @deprecated - * @throws This function is deprecated and will always throw a "no content" error. + /** Get a thumbnail for a view. + * @param viewId The id of the view of the thumbnail. + * @returns A Promise of the ThumbnailProps. + * @throws "No content" error if invalid thumbnail. + * @deprecated */ public async getThumbnail(_viewId: Id64String): Promise { - throw new NoContentError(); + // eslint-disable-next-line deprecation/deprecation + const val = await IModelReadRpcInterface.getClientForRouting(this._iModel.routingContext.token).getViewThumbnail(this._iModel.getRpcProps(), _viewId.toString()); + const intValues = new Uint32Array(val.buffer, 0, 4); + + if (intValues[1] !== ImageSourceFormat.Jpeg && intValues[1] !== ImageSourceFormat.Png) + throw new NoContentError(); + + return { format: intValues[1] === ImageSourceFormat.Jpeg ? "jpeg" : "png", width: intValues[2], height: intValues[3], image: new Uint8Array(val.buffer, 16, intValues[0]) }; } } } diff --git a/full-stack-tests/core/src/frontend/standalone/ModelState.test.ts b/full-stack-tests/core/src/frontend/standalone/ModelState.test.ts index bd8ea671c6e5..c09160243bde 100644 --- a/full-stack-tests/core/src/frontend/standalone/ModelState.test.ts +++ b/full-stack-tests/core/src/frontend/standalone/ModelState.test.ts @@ -126,4 +126,39 @@ describe("ModelState", () => { assert.isTrue(range.low.isAlmostEqual({ x: 288874.1174466432, y: 3803761.1888925503, z: -0.0005 })); assert.isTrue(range.high.isAlmostEqual({ x: 289160.8417204395, y: 3803959.118535, z: 0.0005 })); }); + + it("view thumbnails", async () => { + // eslint-disable-next-line deprecation/deprecation + let thumbnail = await imodel3.views.getThumbnail("0x34"); + assert.equal(thumbnail.format, "png", "thumbnail format"); + assert.equal(thumbnail.height, 768, "thumbnail height"); + assert.equal(thumbnail.width, 768, "thumbnail width"); + assert.equal(thumbnail.image.length, 19086, "thumbnail length"); + const image = thumbnail.image; + assert.equal(image[0], 0x89); + assert.equal(image[1], 0x50); + assert.equal(image[2], 0x4E); + assert.equal(image[3], 0x47); + assert.equal(image[4], 0x0D); + assert.equal(image[5], 0x0A); + assert.equal(image[6], 0x1A); + assert.equal(image[7], 0x0A); + + // eslint-disable-next-line deprecation/deprecation + thumbnail = await imodel2.views.getThumbnail("0x24"); + assert.equal(thumbnail.format, "jpeg"); + assert.equal(thumbnail.height, 768); + assert.equal(thumbnail.width, 768); + assert.equal(thumbnail.image.length, 18062); + assert.equal(thumbnail.image[3], 224); + assert.equal(thumbnail.image[18061], 217); + + try { + // eslint-disable-next-line deprecation/deprecation + await imodel2.views.getThumbnail("0x25"); + } catch (_err) { + return; + } // thumbnail doesn't exist + assert.fail("getThumbnail should not return"); + }); }); diff --git a/full-stack-tests/rpc-interface/src/frontend/IModelConnection.test.ts b/full-stack-tests/rpc-interface/src/frontend/IModelConnection.test.ts index 27bd74fe780e..ef9a6e86c017 100644 --- a/full-stack-tests/rpc-interface/src/frontend/IModelConnection.test.ts +++ b/full-stack-tests/rpc-interface/src/frontend/IModelConnection.test.ts @@ -8,7 +8,7 @@ import { Matrix4d, Point3d, XYZProps, YawPitchRollAngles } from "@itwin/core-geo import { EcefLocation, GeoCoordStatus, IModelReadRpcInterface, IModelVersion, MassPropertiesOperation, MassPropertiesRequestProps, ModelQueryParams, } from "@itwin/core-common"; -import { CheckpointConnection, IModelApp, IModelConnection, SpatialModelState } from "@itwin/core-frontend"; +import { CheckpointConnection, IModelApp, IModelConnection, SpatialModelState, ViewState } from "@itwin/core-frontend"; import { TestFrontendAuthorizationClient } from "@itwin/oidc-signin-tool/lib/cjs/frontend"; import { TestContext } from "./setup/TestContext"; @@ -172,6 +172,14 @@ describe("IModelReadRpcInterface Methods from an IModelConnection", () => { expect(result).undefined; }); + it("getViewThumbnail should work as expected", async () => { + const modelQueryParams: ModelQueryParams = { limit: 10, from: ViewState.classFullName }; + const modelProps = await iModel.views.queryProps(modelQueryParams); + const viewId = modelProps[0].id!.toString(); + const result = await iModel.views.getThumbnail(viewId); + expect(result).to.not.be.undefined; + }); + it("getIModelCoordinatesFromGeoCoordinates should work as expected", async () => { const wgs84Converter = iModel.geoServices.getConverter("WGS84"); const nad27Converter = iModel.geoServices.getConverter("NAD27");