From d02f44c951f88f890ba6ee51f84acb10ba9f07fd Mon Sep 17 00:00:00 2001 From: Caleb Pomar Date: Fri, 13 Sep 2024 17:29:38 -0600 Subject: [PATCH 1/5] feat(hub-common): add first pass at simulating updating download caches and returning the status affects: @esri/hub-common --- .../fetchExportImageDownloadFile.ts | 1 - .../fetchHubApiDownloadFile.ts | 18 +++++++++++++++--- packages/common/src/downloads/types.ts | 3 +++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/common/src/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.ts b/packages/common/src/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.ts index caa6f2d9f43..5f5ec9c50ed 100644 --- a/packages/common/src/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.ts +++ b/packages/common/src/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.ts @@ -53,7 +53,6 @@ export async function fetchExportImageDownloadFile( // } const blob: Blob = await request(`${entity.url}/exportImage`, requestOptions); - progressCallback && progressCallback(DownloadOperationStatus.COMPLETED); return { type: "blob", blob, diff --git a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts index 68cee3c2bd0..d677f549e36 100644 --- a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts +++ b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts @@ -29,7 +29,13 @@ export async function fetchHubApiDownloadFile( validateOptions(options); const requestUrl = getDownloadApiRequestUrl(options); const { pollInterval, progressCallback } = options; - return pollDownloadApi(requestUrl, pollInterval, progressCallback); + const temp = await pollDownloadApi( + requestUrl, + pollInterval, + progressCallback + ); + temp.cacheStatus = options.updateCache ? "ready" : "ready_unknown"; + return temp; } function validateOptions(options: IFetchDownloadFileOptions) { @@ -61,7 +67,8 @@ function validateOptions(options: IFetchDownloadFileOptions) { * @returns a download api url that can be polled */ function getDownloadApiRequestUrl(options: IFetchDownloadFileOptions) { - const { entity, format, context, layers, geometry, where } = options; + const { entity, format, context, layers, geometry, where, updateCache } = + options; const searchParams = new URLSearchParams({ redirect: "false", // Needed to get the download URL instead of the file itself @@ -87,6 +94,8 @@ function getDownloadApiRequestUrl(options: IFetchDownloadFileOptions) { const token = getProp(context, "hubRequestOptions.authentication.token"); token && searchParams.append("token", token); + updateCache && searchParams.append("updateCache", "true"); + return `${context.hubUrl}/api/download/v1/items/${ entity.id }/${format}?${searchParams.toString()}`; @@ -122,7 +131,6 @@ async function pollDownloadApi( "Download operation failed with a 200" ); } - progressCallback && progressCallback(operationStatus, progressInPercent); // Operation complete, return the download URL if (resultUrl) { @@ -132,6 +140,8 @@ async function pollDownloadApi( }; } + progressCallback && progressCallback(operationStatus, progressInPercent); + // Operation still in progress, poll again await new Promise((resolve) => setTimeout(resolve, pollInterval)); return pollDownloadApi(requestUrl, pollInterval, progressCallback); @@ -223,4 +233,6 @@ interface IHubDownloadApiResponse { resultUrl?: string; recordCount?: number; progressInPercent?: number; + // TODO: Add docs, make type + cacheStatus?: "ready" | "ready_unknown" | "stale" | "not_ready"; } diff --git a/packages/common/src/downloads/types.ts b/packages/common/src/downloads/types.ts index 422baa1b027..67370bcfacb 100644 --- a/packages/common/src/downloads/types.ts +++ b/packages/common/src/downloads/types.ts @@ -133,6 +133,7 @@ export interface IFetchDownloadFileOptions { where?: string; // where clause to filter results by progressCallback?: downloadProgressCallback; pollInterval?: number; // interval in milliseconds to poll for job completion + updateCache?: boolean; // whether the request should also update the cache; only valid when targeting the hub download system } export type IFetchDownloadFileResponse = @@ -141,6 +142,8 @@ export type IFetchDownloadFileResponse = interface IBaseFetchDownloadFileResponse { type: "blob" | "url"; + // TODO: Add docs, make type + cacheStatus?: "ready" | "ready_unknown" | "stale" | "not_ready"; } export interface IFetchDownloadFileBlobResponse From 86521b20e9632b1b800cc78b50308d2873639e11 Mon Sep 17 00:00:00 2001 From: Caleb Pomar Date: Thu, 19 Sep 2024 13:58:38 -0600 Subject: [PATCH 2/5] feat(hub-common): add real cacheStatus detection and fix download-api polling affects: @esri/hub-common --- .../fetchHubApiDownloadFile.ts | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts index d677f549e36..8efd4392810 100644 --- a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts +++ b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts @@ -28,14 +28,20 @@ export async function fetchHubApiDownloadFile( ): Promise { validateOptions(options); const requestUrl = getDownloadApiRequestUrl(options); - const { pollInterval, progressCallback } = options; - const temp = await pollDownloadApi( + + const { pollInterval, updateCache, progressCallback } = options; + + let cacheQueryParam: CacheQueryParam; + if (updateCache) { + cacheQueryParam = "updateCache"; + } + + return pollDownloadApi( requestUrl, pollInterval, + cacheQueryParam, progressCallback ); - temp.cacheStatus = options.updateCache ? "ready" : "ready_unknown"; - return temp; } function validateOptions(options: IFetchDownloadFileOptions) { @@ -94,8 +100,6 @@ function getDownloadApiRequestUrl(options: IFetchDownloadFileOptions) { const token = getProp(context, "hubRequestOptions.authentication.token"); token && searchParams.append("token", token); - updateCache && searchParams.append("updateCache", "true"); - return `${context.hubUrl}/api/download/v1/items/${ entity.id }/${format}?${searchParams.toString()}`; @@ -112,9 +116,15 @@ function getDownloadApiRequestUrl(options: IFetchDownloadFileOptions) { async function pollDownloadApi( requestUrl: string, pollInterval: number, + cacheQueryParam?: CacheQueryParam, progressCallback?: downloadProgressCallback ): Promise { - const response = await fetch(requestUrl); + let withCacheQueryParam = requestUrl; + if (cacheQueryParam) { + withCacheQueryParam = `${requestUrl}&${cacheQueryParam}=true`; + } + + const response = await fetch(withCacheQueryParam); if (!response.ok) { const errorBody = await response.json(); // TODO: Add standarized messageId when available @@ -122,8 +132,12 @@ async function pollDownloadApi( rawMessage: errorBody.message, }); } - const { status, progressInPercent, resultUrl }: IHubDownloadApiResponse = - await response.json(); + const { + status, + progressInPercent, + resultUrl, + cacheStatus, + }: IHubDownloadApiResponse = await response.json(); const operationStatus = toDownloadOperationStatus(status); if (operationStatus === DownloadOperationStatus.FAILED) { throw new HubError( @@ -137,6 +151,7 @@ async function pollDownloadApi( return { type: "url", href: resultUrl, + cacheStatus, }; } @@ -144,7 +159,18 @@ async function pollDownloadApi( // Operation still in progress, poll again await new Promise((resolve) => setTimeout(resolve, pollInterval)); - return pollDownloadApi(requestUrl, pollInterval, progressCallback); + + let updatedCacheQueryParam: CacheQueryParam; + if (cacheQueryParam) { + updatedCacheQueryParam = "trackCacheUpdate"; + } + + return pollDownloadApi( + requestUrl, + pollInterval, + updatedCacheQueryParam, + progressCallback + ); } /** @@ -236,3 +262,5 @@ interface IHubDownloadApiResponse { // TODO: Add docs, make type cacheStatus?: "ready" | "ready_unknown" | "stale" | "not_ready"; } + +type CacheQueryParam = "updateCache" | "trackCacheUpdate"; From 6b92bba6576c2b03ee1f8329be920ec934b14756 Mon Sep 17 00:00:00 2001 From: Caleb Pomar Date: Thu, 19 Sep 2024 14:41:14 -0600 Subject: [PATCH 3/5] feat(hub-common): export DownloadCacheStatus type and add code comments affects: @esri/hub-common --- .../fetchHubApiDownloadFile.ts | 27 ++++++++++---- packages/common/src/downloads/types.ts | 35 +++++++++++++++++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts index 8efd4392810..913b8142bf9 100644 --- a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts +++ b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts @@ -2,6 +2,7 @@ import HubError from "../../../HubError"; import { getProp } from "../../../objects/get-prop"; import { ArcgisHubDownloadError, + DownloadCacheStatus, DownloadOperationStatus, IFetchDownloadFileOptions, IFetchDownloadFileResponse, @@ -11,7 +12,8 @@ import { /** * @private - * Fetches a download file url from the Hub Download API + * Fetches a download file url from the Hub Download API. If we know the file will be served from + * a cache, we also return the status of the cache. * * NOTE: The Hub Download API only works with a certain subset of Feature and Map services * and performs different operations (i.e., calling createReplica or paging the service's @@ -21,7 +23,7 @@ import { * interface for downloading data from any service supported by the Hub Download API. * * @param options options for refining / filtering the resulting download file - * @returns a url to download the file + * @returns the url to download the file and cache status */ export async function fetchHubApiDownloadFile( options: IFetchDownloadFileOptions @@ -33,6 +35,8 @@ export async function fetchHubApiDownloadFile( let cacheQueryParam: CacheQueryParam; if (updateCache) { + // The download api has 2 query params that can be used for controlling cache behavior. + // This one should only be used as part of an initial request to update the cache cacheQueryParam = "updateCache"; } @@ -110,6 +114,11 @@ function getDownloadApiRequestUrl(options: IFetchDownloadFileOptions) { * Polls the Hub Download API until the download is ready, then returns the download file URL * * @param requestUrl Hub Download Api URL to poll + * @param pollInterval interval in milliseconds to poll for job completion + * @param cacheQueryParam an optional query param to control cache behavior. 'updateCache' should + * only be used with the initial request when a download cache update is desired. All subsequent + * requests should use'trackCacheUpdate' to follow the progress of the cache update. + * for subsequent requests when polling the progress of a cache update. * @param progressCallback an optional callback to report download generation progress * @returns the final file URL */ @@ -119,12 +128,14 @@ async function pollDownloadApi( cacheQueryParam?: CacheQueryParam, progressCallback?: downloadProgressCallback ): Promise { + // If requested, append the cache query param to the request URL let withCacheQueryParam = requestUrl; if (cacheQueryParam) { withCacheQueryParam = `${requestUrl}&${cacheQueryParam}=true`; } const response = await fetch(withCacheQueryParam); + if (!response.ok) { const errorBody = await response.json(); // TODO: Add standarized messageId when available @@ -132,12 +143,14 @@ async function pollDownloadApi( rawMessage: errorBody.message, }); } + const { status, progressInPercent, resultUrl, cacheStatus, }: IHubDownloadApiResponse = await response.json(); + const operationStatus = toDownloadOperationStatus(status); if (operationStatus === DownloadOperationStatus.FAILED) { throw new HubError( @@ -146,8 +159,8 @@ async function pollDownloadApi( ); } - // Operation complete, return the download URL if (resultUrl) { + // Operation complete, return the download URL and cache status return { type: "url", href: resultUrl, @@ -155,13 +168,14 @@ async function pollDownloadApi( }; } + // Operation still in progress. Report progress if a callback was provided and poll again. progressCallback && progressCallback(operationStatus, progressInPercent); - - // Operation still in progress, poll again await new Promise((resolve) => setTimeout(resolve, pollInterval)); let updatedCacheQueryParam: CacheQueryParam; if (cacheQueryParam) { + // After an initial request with ?updateCache=true, we need to switch to ?trackCacheUpdate=true. + // This enables us to follow the progress of the cache update without causing an infinite update loop. updatedCacheQueryParam = "trackCacheUpdate"; } @@ -259,8 +273,7 @@ interface IHubDownloadApiResponse { resultUrl?: string; recordCount?: number; progressInPercent?: number; - // TODO: Add docs, make type - cacheStatus?: "ready" | "ready_unknown" | "stale" | "not_ready"; + cacheStatus?: DownloadCacheStatus; } type CacheQueryParam = "updateCache" | "trackCacheUpdate"; diff --git a/packages/common/src/downloads/types.ts b/packages/common/src/downloads/types.ts index 67370bcfacb..d7a68f81cc9 100644 --- a/packages/common/src/downloads/types.ts +++ b/packages/common/src/downloads/types.ts @@ -136,23 +136,54 @@ export interface IFetchDownloadFileOptions { updateCache?: boolean; // whether the request should also update the cache; only valid when targeting the hub download system } +/** + * Response object for the fetchDownloadFile operation. + * The response object will contain either a Blob or a URL, + * depending on the type of download operation that was performed. + */ export type IFetchDownloadFileResponse = | IFetchDownloadFileBlobResponse | IFetchDownloadFileUrlResponse; +/** + * Base interface for all fetchDownloadFile response objects. + */ interface IBaseFetchDownloadFileResponse { type: "blob" | "url"; - // TODO: Add docs, make type - cacheStatus?: "ready" | "ready_unknown" | "stale" | "not_ready"; + /** + * If the response comes from our cache rather than a live download, + * indicates the status of the cached file + */ + cacheStatus?: DownloadCacheStatus; } +/** + * Represents the status of a cached download file. + * - `ready` indicates that the file has up-to-date data + * - `ready_unknown` indicates that we don't know if the file is up-to-date + * - `stale` indicates that the file is out-of-date + */ +export type DownloadCacheStatus = "ready" | "ready_unknown" | "stale"; + +/** + * Response object for the fetchDownloadFile operation when the response is a Blob. + */ export interface IFetchDownloadFileBlobResponse extends IBaseFetchDownloadFileResponse { type: "blob"; + /** + * The Blob object that contains the download file. + */ blob: Blob; + /** + * The name to assign to the file when saving it to disk. + */ filename: string; } +/** + * Response object for the fetchDownloadFile operation when the response is a URL. + */ export interface IFetchDownloadFileUrlResponse extends IBaseFetchDownloadFileResponse { type: "url"; From 2e8f3dd9e845d07f5a8df4c75b94197f211f7f74 Mon Sep 17 00:00:00 2001 From: Caleb Pomar Date: Thu, 19 Sep 2024 16:56:29 -0600 Subject: [PATCH 4/5] test(hub-common): add tests for cacheStatus and updateCache affects: @esri/hub-common --- .../fetchHubApiDownloadFile.ts | 6 + .../fetchExportImageDownloadFile.test.ts | 7 +- .../fetchHubApiDownloadFile.test.ts | 177 ++++++++++++++++-- 3 files changed, 174 insertions(+), 16 deletions(-) diff --git a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts index 913b8142bf9..5bf9277635a 100644 --- a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts +++ b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts @@ -276,4 +276,10 @@ interface IHubDownloadApiResponse { cacheStatus?: DownloadCacheStatus; } +/** + * @private + * Query params that can be used to control cache behavior in the Hub Download API. + * 'updateCache' is used with the initial request when a download cache update is desired. + * 'trackCacheUpdate' is used for subsequent requests while polling the progress of a cache update. + */ type CacheQueryParam = "updateCache" | "trackCacheUpdate"; diff --git a/packages/common/test/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.test.ts b/packages/common/test/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.test.ts index 419a70249a1..ce293f06205 100644 --- a/packages/common/test/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.test.ts +++ b/packages/common/test/downloads/_internal/file-url-fetchers/fetchExportImageDownloadFile.test.ts @@ -8,7 +8,7 @@ import { fetchExportImageDownloadFile } from "../../../../src/downloads/_interna import { IHubEditableContent, IServiceExtendedProps } from "../../../../src"; describe("fetchExportImageDownloadFile", () => { - it("should call progressCallback with PENDING and COMPLETED statuses", async () => { + it("should call progressCallback with PENDING statuses", async () => { const requestSpy = spyOn(requestModule, "request").and.returnValue( Promise.resolve({ size: 1000 } as Blob) ); @@ -46,13 +46,10 @@ describe("fetchExportImageDownloadFile", () => { blob: { size: 1000 } as Blob, }); - expect(progressCallback).toHaveBeenCalledTimes(2); + expect(progressCallback).toHaveBeenCalledTimes(1); expect(progressCallback).toHaveBeenCalledWith( DownloadOperationStatus.PENDING ); - expect(progressCallback).toHaveBeenCalledWith( - DownloadOperationStatus.COMPLETED - ); expect(requestSpy).toHaveBeenCalledTimes(1); expect(requestSpy).toHaveBeenCalledWith( diff --git a/packages/common/test/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.test.ts b/packages/common/test/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.test.ts index 67fe9a7c35f..e2e3f71cec3 100644 --- a/packages/common/test/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.test.ts +++ b/packages/common/test/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.test.ts @@ -147,7 +147,11 @@ describe("fetchHubApiDownloadFile", () => { pollInterval: 0, }); - expect(result).toEqual({ type: "url", href: "fake-url" }); + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: undefined, + }); }); it("polls with a progress callback", async () => { fetchMock.once( @@ -182,7 +186,7 @@ describe("fetchHubApiDownloadFile", () => { const progressCallback = jasmine .createSpy("progressCallback") - .and.callFake((status: any, percent: any): any => null); + .and.callFake((_status: any, _percent: any): any => null); const result = await fetchHubApiDownloadFile({ entity: { id: "123" } as unknown as IHubEditableContent, format: ServiceDownloadFormat.CSV, @@ -195,8 +199,12 @@ describe("fetchHubApiDownloadFile", () => { progressCallback, }); - expect(result).toEqual({ type: "url", href: "fake-url" }); - expect(progressCallback).toHaveBeenCalledTimes(3); + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: undefined, + }); + expect(progressCallback).toHaveBeenCalledTimes(2); expect(progressCallback).toHaveBeenCalledWith( DownloadOperationStatus.PENDING, undefined @@ -205,10 +213,6 @@ describe("fetchHubApiDownloadFile", () => { DownloadOperationStatus.PROCESSING, 50 ); - expect(progressCallback).toHaveBeenCalledWith( - DownloadOperationStatus.COMPLETED, - undefined - ); }); it("handles geometry, token and where parameters", async () => { fetchMock.once( @@ -240,7 +244,11 @@ describe("fetchHubApiDownloadFile", () => { where: "1=1", }); - expect(result).toEqual({ type: "url", href: "fake-url" }); + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: undefined, + }); }); it("Explicitly sets the spatialRefId to 4326 for GeoJSON and KML", async () => { fetchMock.once( @@ -263,7 +271,11 @@ describe("fetchHubApiDownloadFile", () => { layers: [0], }); - expect(result).toEqual({ type: "url", href: "fake-url" }); + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: undefined, + }); fetchMock.once( "https://hubqa.arcgis.com/api/download/v1/items/123/kml?redirect=false&layers=0&spatialRefId=4326", @@ -285,6 +297,149 @@ describe("fetchHubApiDownloadFile", () => { layers: [0], }); - expect(result2).toEqual({ type: "url", href: "fake-url-2" }); + expect(result2).toEqual({ + type: "url", + href: "fake-url-2", + cacheStatus: undefined, + }); + }); + it("polls without a progress callback", async () => { + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0", + { + body: { + status: "Pending", + }, + } + ); + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0", + { + body: { + status: "InProgress", + recordCount: 100, + progressInPercent: 50, + }, + }, + { overwriteRoutes: false } + ); + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0", + { + body: { + status: "Completed", + resultUrl: "fake-url", + }, + }, + { overwriteRoutes: false } + ); + + const result = await fetchHubApiDownloadFile({ + entity: { id: "123" } as unknown as IHubEditableContent, + format: ServiceDownloadFormat.CSV, + context: { + hubUrl: "https://hubqa.arcgis.com", + hubRequestOptions: {}, + } as unknown as IArcGISContext, + layers: [0], + pollInterval: 0, + }); + + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: undefined, + }); + }); + it("returns the cacheStatus if present", async () => { + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0", + { + body: { + status: "Completed", + resultUrl: "fake-url", + cacheStatus: "ready_unknown", + }, + } + ); + + const result = await fetchHubApiDownloadFile({ + entity: { id: "123" } as unknown as IHubEditableContent, + format: ServiceDownloadFormat.CSV, + context: { + hubUrl: "https://hubqa.arcgis.com", + hubRequestOptions: {}, + } as unknown as IArcGISContext, + layers: [0], + pollInterval: 0, + }); + + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: "ready_unknown", + }); + }); + it("handles the updateCache option", async () => { + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0&updateCache=true", + { + body: { + status: "Pending", + }, + } + ); + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0&trackCacheUpdate=true", + { + body: { + status: "PagingData", + recordCount: 100, + progressInPercent: 50, + }, + }, + { overwriteRoutes: false } + ); + fetchMock.once( + "https://hubqa.arcgis.com/api/download/v1/items/123/csv?redirect=false&layers=0&trackCacheUpdate=true", + { + body: { + status: "Completed", + resultUrl: "fake-url", + }, + }, + { overwriteRoutes: false } + ); + + const progressCallback = jasmine + .createSpy("progressCallback") + .and.callFake((_status: any, _percent: any): any => null); + const result = await fetchHubApiDownloadFile({ + entity: { id: "123" } as unknown as IHubEditableContent, + format: ServiceDownloadFormat.CSV, + context: { + hubUrl: "https://hubqa.arcgis.com", + hubRequestOptions: {}, + } as unknown as IArcGISContext, + layers: [0], + pollInterval: 0, + progressCallback, + updateCache: true, + }); + + expect(result).toEqual({ + type: "url", + href: "fake-url", + cacheStatus: undefined, + }); + expect(progressCallback).toHaveBeenCalledTimes(2); + expect(progressCallback).toHaveBeenCalledWith( + DownloadOperationStatus.PENDING, + undefined + ); + expect(progressCallback).toHaveBeenCalledWith( + DownloadOperationStatus.PROCESSING, + 50 + ); }); }); From a7d6b51afdc0903590cd2eb41d0f5ea6c23b1376 Mon Sep 17 00:00:00 2001 From: Caleb Pomar Date: Mon, 23 Sep 2024 08:39:58 -0600 Subject: [PATCH 5/5] refactor(hub-common): switch to wait() helper for polling affects: @esri/hub-common --- .../_internal/file-url-fetchers/fetchHubApiDownloadFile.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts index 5bf9277635a..973b1961e17 100644 --- a/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts +++ b/packages/common/src/downloads/_internal/file-url-fetchers/fetchHubApiDownloadFile.ts @@ -1,5 +1,6 @@ import HubError from "../../../HubError"; import { getProp } from "../../../objects/get-prop"; +import { wait } from "../../../utils/wait"; import { ArcgisHubDownloadError, DownloadCacheStatus, @@ -170,7 +171,7 @@ async function pollDownloadApi( // Operation still in progress. Report progress if a callback was provided and poll again. progressCallback && progressCallback(operationStatus, progressInPercent); - await new Promise((resolve) => setTimeout(resolve, pollInterval)); + await wait(pollInterval); let updatedCacheQueryParam: CacheQueryParam; if (cacheQueryParam) {