diff --git a/packages/common/src/core/types/IHubEntityBase.ts b/packages/common/src/core/types/IHubEntityBase.ts index 8d1359d988c..ff2933ee69c 100644 --- a/packages/common/src/core/types/IHubEntityBase.ts +++ b/packages/common/src/core/types/IHubEntityBase.ts @@ -12,6 +12,39 @@ export interface ILink { [key: string]: string; } +/** + * Interface for entity links + */ +export interface IHubEntityLinks { + /** + * Url to Thumbnail. May not include a token + * TODO: Why should we not include the token? + */ + thumbnail?: string; + /** + * Url to the entities canonical "self" + * For Items/Groups/Users, this will be the Home App url + * For other entities, it will be the canonical url + */ + self: string; + /** + * Relative url of the entity, within a site + */ + siteRelative?: string; + /** + * Relative workspace url of the entity, within a site + */ + workspaceRelative?: string; + /** + * Relative url to the entity's layout editing experience + */ + layoutRelative?: string; + /** + * Additional urls + */ + [key: string]: string | ILink; +} + /** * Base properties for Hub Entities * This includes a subset of `IItem`, that can apply to @@ -68,33 +101,5 @@ export interface IHubEntityBase { /** * Links to related things */ - links?: { - /** - * Url to Thumbnail. May not include a token - * TODO: Why should we not include the token? - */ - thumbnail?: string; - /** - * Url to the entities canonical "self" - * For Items/Groups/Users, this will be the Home App url - * For other entities, it will be the canonical url - */ - self: string; - /** - * Relative url of the entity, within a site - */ - siteRelative?: string; - /** - * Relative workspace url of the entity, within a site - */ - workspaceRelative?: string; - /** - * Relative url to the entity's layout editing experience - */ - layoutRelative?: string; - /** - * Additional urls - */ - [key: string]: string | ILink; - }; + links?: IHubEntityLinks; } diff --git a/packages/common/src/groups/HubGroups.ts b/packages/common/src/groups/HubGroups.ts index 621e840313d..355f98bab82 100644 --- a/packages/common/src/groups/HubGroups.ts +++ b/packages/common/src/groups/HubGroups.ts @@ -1,10 +1,8 @@ import { IGroup } from "@esri/arcgis-rest-types"; import { fetchGroupEnrichments } from "./_internal/enrichments"; import { getProp, setProp } from "../objects"; -import { getGroupThumbnailUrl } from "../search/utils"; import { parseInclude } from "../search/_internal/parseInclude"; import { IHubRequestOptions } from "../types"; -import { getGroupHomeUrl } from "../urls"; import { unique } from "../util"; import { mapBy } from "../utils"; import { @@ -19,8 +17,8 @@ import { IUserRequestOptions } from "@esri/arcgis-rest-auth"; import { DEFAULT_GROUP } from "./defaults"; import { convertHubGroupToGroup } from "./_internal/convertHubGroupToGroup"; import { convertGroupToHubGroup } from "./_internal/convertGroupToHubGroup"; -import { getRelativeWorkspaceUrl } from "../core/getRelativeWorkspaceUrl"; import { IHubSearchResult } from "../search/types/IHubSearchResult"; +import { computeLinks } from "./_internal/computeLinks"; /** * Enrich a generic search result @@ -83,10 +81,7 @@ export async function enrichGroupSearchResult( }); // Handle links - result.links.thumbnail = getGroupThumbnailUrl(requestOptions.portal, group); - result.links.self = getGroupHomeUrl(result.id, requestOptions); - result.links.siteRelative = `/teams/${result.id}`; - result.links.workspaceRelative = getRelativeWorkspaceUrl("Group", result.id); + result.links = computeLinks(group, requestOptions); return result; } diff --git a/packages/common/src/groups/_internal/computeLinks.ts b/packages/common/src/groups/_internal/computeLinks.ts new file mode 100644 index 00000000000..6ec96c1a65c --- /dev/null +++ b/packages/common/src/groups/_internal/computeLinks.ts @@ -0,0 +1,32 @@ +import { IGroup } from "@esri/arcgis-rest-types"; +import { IRequestOptions } from "@esri/arcgis-rest-request"; +import { UserSession } from "@esri/arcgis-rest-auth"; +import { IHubEntityLinks } from "../../core/types"; +import { getRelativeWorkspaceUrl } from "../../core/getRelativeWorkspaceUrl"; +import { getGroupHomeUrl } from "../../urls/getGroupHomeUrl"; +import { getGroupThumbnailUrl } from "../../search/utils"; + +/** + * Compute the links that get appended to a Hub Group + * search result and entity + * + * @param group + * @param requestOptions + */ +export function computeLinks( + group: IGroup, + requestOptions: IRequestOptions +): IHubEntityLinks { + let token: string; + if (requestOptions.authentication) { + const session: UserSession = requestOptions.authentication as UserSession; + token = session.token; + } + + return { + self: getGroupHomeUrl(group.id, requestOptions), + siteRelative: `/teams/${group.id}`, + workspaceRelative: getRelativeWorkspaceUrl("Group", group.id), + thumbnail: getGroupThumbnailUrl(requestOptions.portal, group, token), + }; +} diff --git a/packages/common/src/groups/_internal/computeProps.ts b/packages/common/src/groups/_internal/computeProps.ts index 783f01e1584..064b1b14e81 100644 --- a/packages/common/src/groups/_internal/computeProps.ts +++ b/packages/common/src/groups/_internal/computeProps.ts @@ -5,6 +5,7 @@ import { IHubGroup } from "../../core/types/IHubGroup"; import { IGroup } from "@esri/arcgis-rest-types"; import { isDiscussable } from "../../discussions"; import { getGroupThumbnailUrl } from "../../search/utils"; +import { computeLinks } from "./computeLinks"; /** * Given a model and a group, set various computed properties that can't be directly mapped @@ -62,6 +63,8 @@ export function computeProps( group.userMembership?.memberType === "admin"; hubGroup.canDelete = hubGroup.canEdit; + hubGroup.links = computeLinks(group, requestOptions); + // cast b/c this takes a partial but returns a full group return hubGroup as IHubGroup; } diff --git a/packages/common/src/initiatives/HubInitiatives.ts b/packages/common/src/initiatives/HubInitiatives.ts index 53665e0eb39..9fb5487e3b9 100644 --- a/packages/common/src/initiatives/HubInitiatives.ts +++ b/packages/common/src/initiatives/HubInitiatives.ts @@ -17,13 +17,11 @@ import { import { isGuid, cloneObject, - getItemThumbnailUrl, unique, mapBy, getProp, getFamily, IHubRequestOptions, - getItemHomeUrl, setDiscussableKeyword, IModel, } from "../index"; @@ -41,17 +39,16 @@ import { IEntityInfo, IHubInitiative } from "../core/types"; import { IHubSearchResult } from "../search"; import { parseInclude } from "../search/_internal/parseInclude"; import { fetchItemEnrichments } from "../items/_enrichments"; -import { getHubRelativeUrl } from "../content/_internal/internalContentUtils"; import { DEFAULT_INITIATIVE, DEFAULT_INITIATIVE_MODEL } from "./defaults"; import { getPropertyMap } from "./_internal/getPropertyMap"; import { computeProps } from "./_internal/computeProps"; import { applyInitiativeMigrations } from "./_internal/applyInitiativeMigrations"; -import { getRelativeWorkspaceUrl } from "../core/getRelativeWorkspaceUrl"; import { combineQueries } from "../search/_internal/combineQueries"; import { portalSearchItemsAsItems } from "../search/_internal/portalSearchItems"; import { getTypeWithKeywordQuery } from "../associations/internal/getTypeWithKeywordQuery"; -import { getTypeWithoutKeywordQuery } from "../associations/internal/getTypeWithoutKeywordQuery"; import { negateGroupPredicates } from "../search/_internal/negateGroupPredicates"; +import { computeLinks } from "./_internal/computeLinks"; +import { getHubRelativeUrl } from "../content/_internal/internalContentUtils"; /** * @private @@ -260,23 +257,12 @@ export async function enrichInitiativeSearchResult( // Handle links // TODO: Link handling should be an enrichment - result.links.thumbnail = getItemThumbnailUrl(item, requestOptions); - result.links.self = getItemHomeUrl(result.id, requestOptions); - const identifier = item.id; - // Until the new initiatives route is in place we need to - // route using the id. Once the route is in place we can - // un-comment this - // const identifier = getItemIdentifier(item); - - result.links.siteRelative = getHubRelativeUrl( - result.type, - identifier, - item.typeKeywords - ); - result.links.workspaceRelative = getRelativeWorkspaceUrl( - result.type, - identifier - ); + result.links = { + ...computeLinks(item, requestOptions), + // TODO: remove once sites are separated from initiatives and + // the initiatives view route is released + siteRelative: getHubRelativeUrl(result.type, result.id), + }; return result; } diff --git a/packages/common/src/initiatives/_internal/computeLinks.ts b/packages/common/src/initiatives/_internal/computeLinks.ts new file mode 100644 index 00000000000..a9815a0e386 --- /dev/null +++ b/packages/common/src/initiatives/_internal/computeLinks.ts @@ -0,0 +1,39 @@ +import { IItem } from "@esri/arcgis-rest-types"; +import { IRequestOptions } from "@esri/arcgis-rest-request"; +import { UserSession } from "@esri/arcgis-rest-auth"; +import { getItemHomeUrl } from "../../urls"; +import { IHubEntityLinks } from "../../core/types"; +import { getItemIdentifier } from "../../items"; +import { getRelativeWorkspaceUrl } from "../../core/getRelativeWorkspaceUrl"; +import { getItemThumbnailUrl } from "../../resources/get-item-thumbnail-url"; + +/** + * Compute the links that get appended to a Hub Initiative + * search result and entity + * + * @param item + * @param requestOptions + */ +export function computeLinks( + item: IItem, + requestOptions: IRequestOptions +): IHubEntityLinks { + let token: string; + if (requestOptions.authentication) { + const session: UserSession = requestOptions.authentication as UserSession; + token = session.token; + } + + return { + self: getItemHomeUrl(item.id, requestOptions), + // TODO: once the initiative view is moved to initiatives/:id, + // update and leverage the getHubRelativeUrl util to construct + // this url. For now, we hard-code to the initiatives2/:id route + siteRelative: `/initiatives2/${getItemIdentifier(item)}`, + workspaceRelative: getRelativeWorkspaceUrl( + item.type, + getItemIdentifier(item) + ), + thumbnail: getItemThumbnailUrl(item, requestOptions, token), + }; +} diff --git a/packages/common/src/initiatives/_internal/computeProps.ts b/packages/common/src/initiatives/_internal/computeProps.ts index cd4dd4f82c0..3cc20f12e85 100644 --- a/packages/common/src/initiatives/_internal/computeProps.ts +++ b/packages/common/src/initiatives/_internal/computeProps.ts @@ -8,6 +8,7 @@ import { IHubInitiative } from "../../core"; import { isDiscussable } from "../../discussions"; import { processEntityFeatures } from "../../permissions/_internal/processEntityFeatures"; import { InitiativeDefaultFeatures } from "./InitiativeBusinessRules"; +import { computeLinks } from "./computeLinks"; /** * Given a model and an Initiative, set various computed properties that can't be directly mapped @@ -49,6 +50,8 @@ export function computeProps( InitiativeDefaultFeatures ); + initiative.links = computeLinks(model.item, requestOptions); + // cast b/c this takes a partial but returns a full object return initiative as IHubInitiative; } diff --git a/packages/common/src/projects/_internal/computeLinks.ts b/packages/common/src/projects/_internal/computeLinks.ts new file mode 100644 index 00000000000..8f910ca5511 --- /dev/null +++ b/packages/common/src/projects/_internal/computeLinks.ts @@ -0,0 +1,37 @@ +import { IItem } from "@esri/arcgis-rest-types"; +import { IRequestOptions } from "@esri/arcgis-rest-request"; +import { UserSession } from "@esri/arcgis-rest-auth"; +import { getItemHomeUrl } from "../../urls"; +import { IHubEntityLinks } from "../../core/types"; +import { getItemIdentifier } from "../../items"; +import { getHubRelativeUrl } from "../../content/_internal/internalContentUtils"; +import { getRelativeWorkspaceUrl } from "../../core/getRelativeWorkspaceUrl"; +import { getItemThumbnailUrl } from "../../resources/get-item-thumbnail-url"; + +/** + * Compute the links that get appended to a Hub Project + * search result and entity + * + * @param item + * @param requestOptions + */ +export function computeLinks( + item: IItem, + requestOptions: IRequestOptions +): IHubEntityLinks { + let token: string; + if (requestOptions.authentication) { + const session: UserSession = requestOptions.authentication as UserSession; + token = session.token; + } + + return { + self: getItemHomeUrl(item.id, requestOptions), + siteRelative: getHubRelativeUrl(item.type, getItemIdentifier(item)), + workspaceRelative: getRelativeWorkspaceUrl( + item.type, + getItemIdentifier(item) + ), + thumbnail: getItemThumbnailUrl(item, requestOptions, token), + }; +} diff --git a/packages/common/src/projects/_internal/computeProps.ts b/packages/common/src/projects/_internal/computeProps.ts index c0f4b50bb78..2d5b8961e80 100644 --- a/packages/common/src/projects/_internal/computeProps.ts +++ b/packages/common/src/projects/_internal/computeProps.ts @@ -3,10 +3,10 @@ import { UserSession } from "@esri/arcgis-rest-auth"; import { getItemThumbnailUrl } from "../../resources"; import { IHubProject } from "../../core"; import { IModel } from "../../types"; - import { isDiscussable } from "../../discussions"; import { processEntityFeatures } from "../../permissions/_internal/processEntityFeatures"; import { ProjectDefaultFeatures } from "./ProjectBusinessRules"; +import { computeLinks } from "./computeLinks"; /** * Given a model and a project, set various computed properties that can't be directly mapped @@ -44,6 +44,8 @@ export function computeProps( ProjectDefaultFeatures ); + project.links = computeLinks(model.item, requestOptions); + // cast b/c this takes a partial but returns a full project return project as IHubProject; } diff --git a/packages/common/src/projects/fetch.ts b/packages/common/src/projects/fetch.ts index ef640a69cf0..f916425603b 100644 --- a/packages/common/src/projects/fetch.ts +++ b/packages/common/src/projects/fetch.ts @@ -2,7 +2,6 @@ import { IRequestOptions } from "@esri/arcgis-rest-request"; import { getItem, IItem } from "@esri/arcgis-rest-portal"; import { getFamily } from "../content/get-family"; -import { getHubRelativeUrl } from "../content/_internal/internalContentUtils"; import { IHubProject } from "../core/types"; import { PropertyMapper } from "../core/_internal/PropertyMapper"; import { getItemBySlug } from "../items/slugs"; @@ -18,12 +17,9 @@ import { computeProps } from "./_internal/computeProps"; import { getPropertyMap } from "./_internal/getPropertyMap"; import { unique } from "../util"; import { getProp } from "../objects/get-prop"; -import { getItemThumbnailUrl } from "../resources/get-item-thumbnail-url"; -import { getItemHomeUrl } from "../urls/get-item-home-url"; -import { getItemIdentifier } from "../items"; -import { getRelativeWorkspaceUrl } from "../core/getRelativeWorkspaceUrl"; import { listAssociations } from "../associations"; import { getTypeByIdsQuery } from "../associations/internal/getTypeByIdsQuery"; +import { computeLinks } from "./_internal/computeLinks"; /** * @private @@ -127,18 +123,7 @@ export async function enrichProjectSearchResult( // Handle links // TODO: Link handling should be an enrichment - result.links.thumbnail = getItemThumbnailUrl(item, requestOptions); - result.links.self = getItemHomeUrl(result.id, requestOptions); - const identifier = getItemIdentifier(item); - result.links.siteRelative = getHubRelativeUrl( - result.type, - identifier, - item.typeKeywords - ); - result.links.workspaceRelative = getRelativeWorkspaceUrl( - result.type, - identifier - ); + result.links = computeLinks(item, requestOptions); return result; } diff --git a/packages/common/test/groups/_internal/computeLinks.test.ts b/packages/common/test/groups/_internal/computeLinks.test.ts new file mode 100644 index 00000000000..a85613f90e5 --- /dev/null +++ b/packages/common/test/groups/_internal/computeLinks.test.ts @@ -0,0 +1,36 @@ +import { IGroup, IUser } from "@esri/arcgis-rest-types"; +import { computeLinks } from "../../../src/groups/_internal/computeLinks"; +import { ArcGISContextManager } from "../../../src"; +import { MOCK_AUTH } from "../../mocks/mock-auth"; +import { IPortal } from "@esri/arcgis-rest-portal"; + +describe("computeLinks", () => { + let authdCtxMgr: ArcGISContextManager; + let group: IGroup; + + beforeEach(async () => { + group = { + id: "00c", + } as IGroup; + authdCtxMgr = await ArcGISContextManager.create({ + authentication: MOCK_AUTH, + currentUser: {} as IUser, + portal: { + name: "DC R&D Center", + id: "BRXFAKE", + urlKey: "fake-org", + properties: { + hub: { enabled: true }, + }, + } as unknown as IPortal, + portalUrl: "https://org.maps.arcgis.com", + }); + }); + + it("generates a links hash", () => { + const chk = computeLinks(group, authdCtxMgr.context.requestOptions); + + expect(chk.siteRelative).toBe("/teams/00c"); + expect(chk.workspaceRelative).toBe("/workspace/groups/00c"); + }); +}); diff --git a/packages/common/test/groups/_internal/computeProps.test.ts b/packages/common/test/groups/_internal/computeProps.test.ts index e57236e0376..5f49b3d5041 100644 --- a/packages/common/test/groups/_internal/computeProps.test.ts +++ b/packages/common/test/groups/_internal/computeProps.test.ts @@ -3,6 +3,7 @@ import { MOCK_AUTH } from "../../mocks/mock-auth"; import { ArcGISContextManager } from "../../../src/ArcGISContextManager"; import { computeProps } from "../../../src/groups/_internal/computeProps"; import { IHubGroup } from "../../../src/core/types/IHubGroup"; +import * as computeLinksModule from "../../../src/groups/_internal/computeLinks"; describe("groups: computeProps:", () => { let group: IGroup; @@ -86,6 +87,19 @@ describe("groups: computeProps:", () => { "https://org.maps.arcgis.com/sharing/rest/community/groups/3ef/info/group.jpg" ); }); + it("generates a links hash", () => { + const computeLinksSpy = spyOn( + computeLinksModule, + "computeLinks" + ).and.returnValue({ self: "some-link" }); + const chk = computeProps( + group, + hubGroup, + authdCtxMgr.context.requestOptions + ); + expect(computeLinksSpy).toHaveBeenCalledTimes(1); + expect(chk.links).toEqual({ self: "some-link" }); + }); it("computes the correct props when no userMembership", () => { group = { id: "3ef", diff --git a/packages/common/test/initiatives/_internal/computeLinks.test.ts b/packages/common/test/initiatives/_internal/computeLinks.test.ts new file mode 100644 index 00000000000..31ee9fdde1b --- /dev/null +++ b/packages/common/test/initiatives/_internal/computeLinks.test.ts @@ -0,0 +1,44 @@ +import { IItem, IUser } from "@esri/arcgis-rest-types"; +import { computeLinks } from "../../../src/initiatives/_internal/computeLinks"; +import { ArcGISContextManager, setProp } from "../../../src"; +import { MOCK_AUTH } from "../../mocks/mock-auth"; +import { IPortal } from "@esri/arcgis-rest-portal"; + +describe("computeLinks", () => { + let authdCtxMgr: ArcGISContextManager; + let item: IItem; + + beforeEach(async () => { + item = { + type: "Hub Initiative", + id: "00c", + } as IItem; + authdCtxMgr = await ArcGISContextManager.create({ + authentication: MOCK_AUTH, + currentUser: {} as IUser, + portal: { + name: "DC R&D Center", + id: "BRXFAKE", + urlKey: "fake-org", + properties: { + hub: { enabled: true }, + }, + } as unknown as IPortal, + portalUrl: "https://org.maps.arcgis.com", + }); + }); + + it("generates a links hash using the initiative's slug", () => { + setProp("properties.slug", "mock-slug", item); + const chk = computeLinks(item, authdCtxMgr.context.requestOptions); + + expect(chk.siteRelative).toBe("/initiatives2/mock-slug"); + expect(chk.workspaceRelative).toBe("/workspace/initiatives/mock-slug"); + }); + it("generates a links hash using the initiative's id when no slug is available", () => { + const chk = computeLinks(item, authdCtxMgr.context.requestOptions); + + expect(chk.siteRelative).toBe("/initiatives2/00c"); + expect(chk.workspaceRelative).toBe("/workspace/initiatives/00c"); + }); +}); diff --git a/packages/common/test/initiatives/_internal/computeProps.test.ts b/packages/common/test/initiatives/_internal/computeProps.test.ts index 1c30903f148..59e3fedfccf 100644 --- a/packages/common/test/initiatives/_internal/computeProps.test.ts +++ b/packages/common/test/initiatives/_internal/computeProps.test.ts @@ -5,10 +5,28 @@ import { computeProps } from "../../../src/initiatives/_internal/computeProps"; import { IHubInitiative, IModel } from "../../../src"; import * as processEntitiesModule from "../../../src/permissions/_internal/processEntityFeatures"; import { InitiativeDefaultFeatures } from "../../../src/initiatives/_internal/InitiativeBusinessRules"; +import * as computeLinksModule from "../../../src/initiatives/_internal/computeLinks"; describe("initiatives: computeProps:", () => { let authdCtxMgr: ArcGISContextManager; + let model: IModel; + let initiative: Partial; + beforeEach(async () => { + model = { + item: { + type: "Hub Initiative", + id: "00c", + created: new Date().getTime(), + modified: new Date().getTime(), + }, + data: {}, + } as IModel; + initiative = { + type: "Hub Initiative", + id: "00c", + slug: "mock-slug", + }; // When we pass in all this information, the context // manager will not try to fetch anything, so no need // to mock those calls @@ -44,60 +62,54 @@ describe("initiatives: computeProps:", () => { expect(spy.calls.argsFor(0)[1]).toEqual(InitiativeDefaultFeatures); }); it("handles missing settings hash", () => { - const model: IModel = { - item: { - created: new Date().getTime(), - modified: new Date().getTime(), - }, - data: {}, - } as IModel; - const init: Partial = {}; - const chk = computeProps(model, init, authdCtxMgr.context.requestOptions); + const chk = computeProps( + model, + initiative, + authdCtxMgr.context.requestOptions + ); expect(chk.features?.details).toBeTruthy(); expect(chk.features?.settings).toBeFalsy(); expect(spy.calls.argsFor(0)[0]).toEqual({}); }); it("handles missing capabilities hash", () => { - const model: IModel = { - item: { - id: "3ef", - type: "Hub Initiative", - created: new Date().getTime(), - modified: new Date().getTime(), - }, - data: { - settings: {}, - }, - } as unknown as IModel; - const init: Partial = {}; - const chk = computeProps(model, init, authdCtxMgr.context.requestOptions); + const chk = computeProps( + model, + initiative, + authdCtxMgr.context.requestOptions + ); expect(chk.features?.details).toBeTruthy(); expect(chk.features?.settings).toBeFalsy(); expect(spy.calls.argsFor(0)[0]).toEqual({}); }); it("passes features hash", () => { - const model: IModel = { - item: { - id: "3ef", - type: "Hub Initiative", - created: new Date().getTime(), - modified: new Date().getTime(), + model.data = { + settings: { + features: { details: true }, }, - data: { - settings: { - features: { - details: true, - }, - }, - }, - } as unknown as IModel; - const init: Partial = {}; - const chk = computeProps(model, init, authdCtxMgr.context.requestOptions); + }; + const chk = computeProps( + model, + initiative, + authdCtxMgr.context.requestOptions + ); expect(chk.features?.details).toBeTruthy(); expect(chk.features?.settings).toBeFalsy(); expect(spy.calls.argsFor(0)[0]).toEqual({ details: true }); }); + it("generates a links hash", () => { + const computeLinksSpy = spyOn( + computeLinksModule, + "computeLinks" + ).and.returnValue({ self: "some-link" }); + const chk = computeProps( + model, + initiative, + authdCtxMgr.context.requestOptions + ); + expect(computeLinksSpy).toHaveBeenCalledTimes(1); + expect(chk.links).toEqual({ self: "some-link" }); + }); }); }); diff --git a/packages/common/test/projects/_internal/computeLinks.test.ts b/packages/common/test/projects/_internal/computeLinks.test.ts new file mode 100644 index 00000000000..fb5bfe6c3ec --- /dev/null +++ b/packages/common/test/projects/_internal/computeLinks.test.ts @@ -0,0 +1,44 @@ +import { IItem, IUser } from "@esri/arcgis-rest-types"; +import { computeLinks } from "../../../src/projects/_internal/computeLinks"; +import { ArcGISContextManager, setProp } from "../../../src"; +import { MOCK_AUTH } from "../../mocks/mock-auth"; +import { IPortal } from "@esri/arcgis-rest-portal"; + +describe("computeLinks", () => { + let authdCtxMgr: ArcGISContextManager; + let item: IItem; + + beforeEach(async () => { + item = { + type: "Hub Project", + id: "00c", + } as IItem; + authdCtxMgr = await ArcGISContextManager.create({ + authentication: MOCK_AUTH, + currentUser: {} as IUser, + portal: { + name: "DC R&D Center", + id: "BRXFAKE", + urlKey: "fake-org", + properties: { + hub: { enabled: true }, + }, + } as unknown as IPortal, + portalUrl: "https://org.maps.arcgis.com", + }); + }); + + it("generates a links hash using the project's slug", () => { + setProp("properties.slug", "mock-slug", item); + const chk = computeLinks(item, authdCtxMgr.context.requestOptions); + + expect(chk.siteRelative).toBe("/projects/mock-slug"); + expect(chk.workspaceRelative).toBe("/workspace/projects/mock-slug"); + }); + it("generates a links hash using the project's id when no slug is available", () => { + const chk = computeLinks(item, authdCtxMgr.context.requestOptions); + + expect(chk.siteRelative).toBe("/projects/00c"); + expect(chk.workspaceRelative).toBe("/workspace/projects/00c"); + }); +}); diff --git a/packages/common/test/projects/_internal/computeProps.test.ts b/packages/common/test/projects/_internal/computeProps.test.ts index e27cecf2a7d..f364a6bb10f 100644 --- a/packages/common/test/projects/_internal/computeProps.test.ts +++ b/packages/common/test/projects/_internal/computeProps.test.ts @@ -5,9 +5,28 @@ import { computeProps } from "../../../src/projects/_internal/computeProps"; import { IHubProject, IModel } from "../../../src"; import * as processEntitiesModule from "../../../src/permissions/_internal/processEntityFeatures"; import { ProjectDefaultFeatures } from "../../../src/projects/_internal/ProjectBusinessRules"; +import * as computeLinksModule from "../../../src/projects/_internal/computeLinks"; + describe("projects: computeProps:", () => { let authdCtxMgr: ArcGISContextManager; + let model: IModel; + let project: Partial; + beforeEach(async () => { + model = { + item: { + type: "Hub Project", + id: "00c", + created: new Date().getTime(), + modified: new Date().getTime(), + }, + data: {}, + } as IModel; + project = { + type: "Hub Project", + id: "00c", + slug: "mock-slug", + }; // When we pass in all this information, the context // manager will not try to fetch anything, so no need // to mock those calls @@ -43,60 +62,54 @@ describe("projects: computeProps:", () => { expect(spy.calls.argsFor(0)[1]).toEqual(ProjectDefaultFeatures); }); it("handles missing settings hash", () => { - const model: IModel = { - item: { - created: new Date().getTime(), - modified: new Date().getTime(), - }, - data: {}, - } as IModel; - const init: Partial = {}; - const chk = computeProps(model, init, authdCtxMgr.context.requestOptions); + const chk = computeProps( + model, + project, + authdCtxMgr.context.requestOptions + ); expect(chk.features?.details).toBeTruthy(); expect(chk.features?.settings).toBeFalsy(); expect(spy.calls.argsFor(0)[0]).toEqual({}); }); it("handles missing capabilities hash", () => { - const model: IModel = { - item: { - id: "3ef", - type: "Hub Project", - created: new Date().getTime(), - modified: new Date().getTime(), - }, - data: { - settings: {}, - }, - } as unknown as IModel; - const init: Partial = {}; - const chk = computeProps(model, init, authdCtxMgr.context.requestOptions); + const chk = computeProps( + model, + project, + authdCtxMgr.context.requestOptions + ); expect(chk.features?.details).toBeTruthy(); expect(chk.features?.settings).toBeFalsy(); expect(spy.calls.argsFor(0)[0]).toEqual({}); }); it("passes features hash", () => { - const model: IModel = { - item: { - id: "3ef", - type: "Hub Project", - created: new Date().getTime(), - modified: new Date().getTime(), - }, - data: { - settings: { - features: { - details: true, - }, - }, + model.data = { + settings: { + features: { details: true }, }, - } as unknown as IModel; - const init: Partial = {}; - const chk = computeProps(model, init, authdCtxMgr.context.requestOptions); + }; + const chk = computeProps( + model, + project, + authdCtxMgr.context.requestOptions + ); expect(chk.features?.details).toBeTruthy(); expect(chk.features?.settings).toBeFalsy(); expect(spy.calls.argsFor(0)[0]).toEqual({ details: true }); }); + it("generates a links hash", () => { + const computeLinksSpy = spyOn( + computeLinksModule, + "computeLinks" + ).and.returnValue({ self: "some-link" }); + const chk = computeProps( + model, + project, + authdCtxMgr.context.requestOptions + ); + expect(computeLinksSpy).toHaveBeenCalledTimes(1); + expect(chk.links).toEqual({ self: "some-link" }); + }); }); });