From c2e2623b7dd71735ea4e6572879d77e98af175ff Mon Sep 17 00:00:00 2001 From: Tom Wayson Date: Tue, 1 Sep 2020 16:18:44 -0700 Subject: [PATCH] feat(getContent): add support for slugs as well as ids AFFECTS PACKAGES: @esri/hub-content ISSUES CLOSED: #320 --- packages/content/src/content.ts | 19 +++--- packages/content/src/hub.ts | 19 ++++-- packages/content/test/content.test.ts | 94 ++++++++++++++++----------- packages/content/test/hub.test.ts | 21 +++++- 4 files changed, 103 insertions(+), 50 deletions(-) diff --git a/packages/content/src/content.ts b/packages/content/src/content.ts index a9fa3b8286c..4d1dd8597c7 100644 --- a/packages/content/src/content.ts +++ b/packages/content/src/content.ts @@ -5,25 +5,28 @@ import { request } from "@esri/arcgis-rest-request"; import { IHubContent, IHubRequestOptions } from "@esri/hub-common"; import { getContentFromHub } from "./hub"; import { getContentFromPortal } from "./portal"; +import { isSlug } from "./slugs"; /** * Fetch content using either the Hub API or the ArcGIS REST API - * @param id - content (item) id + * @param identifier Hub API slug ({orgKey}::{title-as-slug} or {title-as-slug}) + * or record id ((itemId}_{layerId} or {itemId}) * @param requestOptions - request options that may include authentication */ export function getContent( - // TODO: this should take a slug as well - id: string, + identifier: string, requestOptions?: IHubRequestOptions ): Promise { if (requestOptions && requestOptions.isPortal) { - return getContentFromPortal(id, requestOptions); + return getContentFromPortal(identifier, requestOptions); } else { - return getContentFromHub(id, requestOptions).catch(() => { - // TODO: inspect error? + return getContentFromHub(identifier, requestOptions).catch(e => { // dataset is not in index (i.e. might be a private item) - // try fetching from portal instead - return getContentFromPortal(id, requestOptions); + if (!isSlug(identifier)) { + // try fetching from portal instead + return getContentFromPortal(identifier, requestOptions); + } + return Promise.reject(e); }); } } diff --git a/packages/content/src/hub.ts b/packages/content/src/hub.ts index a11d6c11600..55163330cae 100644 --- a/packages/content/src/hub.ts +++ b/packages/content/src/hub.ts @@ -6,6 +6,8 @@ import { hubApiRequest } from "@esri/hub-common"; import { itemToContent, withPortalUrls } from "./portal"; +import { isSlug } from "./slugs"; +import { cloneObject } from "@esri/hub-common"; /** * Parse item ID and layer ID (if any) from dataset record ID @@ -36,15 +38,24 @@ export type DatasetResource = ResourceObject< /** * Fetch a dataset resource with the given ID from the Hub API * - * @param id Hub API ID - * @param requestOptions + * @param identifier Hub API slug ({orgKey}::{title-as-slug} or {title-as-slug}) + * or record id ((itemId}_{layerId} or {itemId}) + * @param requestOptions - request options that may include authentication * @export */ export function getContentFromHub( - id: string, + identifier: string, requestOptions?: IHubRequestOptions ): Promise { - return hubApiRequest(`/datasets/${id}`, requestOptions).then(resp => { + let request; + if (isSlug(identifier)) { + const options = cloneObject(requestOptions); + options.params = { ...options.params, "filter[slug]": identifier }; + request = hubApiRequest(`/datasets`, options); + } else { + request = hubApiRequest(`/datasets/${identifier}`, requestOptions); + } + return request.then((resp: any) => { const dataset = resp && resp.data; return dataset && withPortalUrls(datasetToContent(dataset), requestOptions); }); diff --git a/packages/content/test/content.test.ts b/packages/content/test/content.test.ts index 3b7719ce661..ad2172548fc 100644 --- a/packages/content/test/content.test.ts +++ b/packages/content/test/content.test.ts @@ -22,47 +22,67 @@ describe("get content", () => { }); afterEach(fetchMock.restore); describe("from hub", () => { - it("should call getContentFromHub", done => { + beforeEach(() => { requestOpts.isPortal = false; requestOpts.portalSelf.isPortal = false; - const id = "foo"; - const getContentFromHubSpy = spyOn( - hubModule, - "getContentFromHub" - ).and.returnValue(Promise.resolve({})); - getContent(id, requestOpts).then(() => { - expect(getContentFromHubSpy.calls.count()).toBe(1); - expect(getContentFromHubSpy.calls.argsFor(0)).toEqual([ - id, - requestOpts - ]); - done(); + }); + describe("with an id", () => { + const id = "7a153563b0c74f7eb2b3eae8a66f2fbb"; + it("should call getContentFromHub", done => { + const getContentFromHubSpy = spyOn( + hubModule, + "getContentFromHub" + ).and.returnValue(Promise.resolve({})); + getContent(id, requestOpts).then(() => { + expect(getContentFromHubSpy.calls.count()).toBe(1); + expect(getContentFromHubSpy.calls.argsFor(0)).toEqual([ + id, + requestOpts + ]); + done(); + }); + }); + it("handles private items", done => { + const getContentFromHubSpy = spyOn( + hubModule, + "getContentFromHub" + ).and.returnValue(Promise.reject({})); + const getContentFromPortalSpy = spyOn( + portalModule, + "getContentFromPortal" + ).and.returnValue(Promise.resolve({})); + getContent(id, requestOpts).then(() => { + expect(getContentFromHubSpy.calls.count()).toBe(1); + expect(getContentFromHubSpy.calls.argsFor(0)).toEqual([ + id, + requestOpts + ]); + expect(getContentFromPortalSpy.calls.count()).toBe(1); + expect(getContentFromPortalSpy.calls.argsFor(0)).toEqual([ + id, + requestOpts + ]); + done(); + }); }); }); - it("handles private items", done => { - requestOpts.isPortal = false; - requestOpts.portalSelf.isPortal = false; - const id = "foo"; - const getContentFromHubSpy = spyOn( - hubModule, - "getContentFromHub" - ).and.returnValue(Promise.reject({})); - const getContentFromPortalSpy = spyOn( - portalModule, - "getContentFromPortal" - ).and.returnValue(Promise.resolve({})); - getContent(id, requestOpts).then(() => { - expect(getContentFromHubSpy.calls.count()).toBe(1); - expect(getContentFromHubSpy.calls.argsFor(0)).toEqual([ - id, - requestOpts - ]); - expect(getContentFromPortalSpy.calls.count()).toBe(1); - expect(getContentFromPortalSpy.calls.argsFor(0)).toEqual([ - "foo", - requestOpts - ]); - done(); + describe("with a slug", () => { + const slug = "foo"; + it("rejects when not in the index", done => { + const err = new Error("test"); + const getContentFromHubSpy = spyOn( + hubModule, + "getContentFromHub" + ).and.returnValue(Promise.reject(err)); + getContent(slug, requestOpts).catch(e => { + expect(getContentFromHubSpy.calls.count()).toBe(1); + expect(getContentFromHubSpy.calls.argsFor(0)).toEqual([ + slug, + requestOpts + ]); + expect(e).toEqual(err); + done(); + }); }); }); }); diff --git a/packages/content/test/hub.test.ts b/packages/content/test/hub.test.ts index b472ada8c66..1368455f9dc 100644 --- a/packages/content/test/hub.test.ts +++ b/packages/content/test/hub.test.ts @@ -158,7 +158,7 @@ describe("hub", () => { }; }); afterEach(fetchMock.restore); - it("should fetch a dataset record and return content", done => { + it("should fetch a dataset record by id and return content", done => { fetchMock.once("*", featureLayerJson); const dataset = featureLayerJson.data as DatasetResource; const id = dataset.id; @@ -173,5 +173,24 @@ describe("hub", () => { done(); }); }); + it("should fetch a dataset record by slug and return content", done => { + fetchMock.once("*", featureLayerJson); + const dataset = featureLayerJson.data as DatasetResource; + const slug = "Wigan::out-of-work-benefit-claims"; + getContentFromHub(slug, requestOpts).then(content => { + // verify that we attempted to fetch from the portal API + const [url, opts] = fetchMock.calls()[0]; + expect(url).toBe( + `https://some.url.com/api/v3/datasets?${encodeURIComponent( + "filter[slug]" + )}=${encodeURIComponent(slug)}` + ); + expect(opts.method).toBe("GET"); + validateContentFromDataset(content, dataset, "dataset"); + // TODO: content type specific properties + // expect(content.recordCount).toBe(attributes.recordCount); + done(); + }); + }); }); });