From 6557279ab9dfe23dc8a781cda41a48dbc880c5aa Mon Sep 17 00:00:00 2001 From: Vivian Zhang Date: Fri, 24 May 2024 14:17:42 -0700 Subject: [PATCH] fix: add siteRelativeEntityType link to multiple entities (#1522) --- .../src/content/_internal/computeProps.ts | 1 + .../content/_internal/internalContentUtils.ts | 29 ++++++-- .../common/src/core/types/IHubEntityBase.ts | 3 + .../src/events/_internal/computeLinks.ts | 1 + .../src/groups/_internal/computeLinks.ts | 2 + .../_internal/computeLinks.ts | 1 + .../src/initiatives/_internal/computeLinks.ts | 1 + .../src/pages/_internal/computeProps.ts | 2 + .../src/projects/_internal/computeLinks.ts | 1 + .../src/sites/_internal/computeLinks.ts | 1 + .../src/templates/_internal/computeLinks.ts | 1 + .../_internal/internalContentUtils.test.ts | 73 +++++++++++++++++++ .../events/_internal/PropertyMapper.test.ts | 1 + .../events/_internal/computeLinks.test.ts | 1 + .../groups/_internal/computeLinks.test.ts | 1 + .../_internal/computeLinks.test.ts | 1 + .../_internal/computeLinks.test.ts | 2 + .../projects/_internal/computeLinks.test.ts | 1 + .../eventToSearchResult.test.ts | 2 + .../test/sites/_internal/computeLinks.test.ts | 1 + 20 files changed, 119 insertions(+), 7 deletions(-) diff --git a/packages/common/src/content/_internal/computeProps.ts b/packages/common/src/content/_internal/computeProps.ts index b777c573299..7d373971680 100644 --- a/packages/common/src/content/_internal/computeProps.ts +++ b/packages/common/src/content/_internal/computeProps.ts @@ -45,6 +45,7 @@ export function computeProps( content.slug || content.id, content.typeKeywords ), + siteRelativeEntityType: getHubRelativeUrl(content.type), workspaceRelative: getRelativeWorkspaceUrl("content", content.id), thumbnail: thumbnailUrl, contentEditUrl: getContentEditUrl(model.item, requestOptions), diff --git a/packages/common/src/content/_internal/internalContentUtils.ts b/packages/common/src/content/_internal/internalContentUtils.ts index d19e75b77b6..88188acebf4 100644 --- a/packages/common/src/content/_internal/internalContentUtils.ts +++ b/packages/common/src/content/_internal/internalContentUtils.ts @@ -16,7 +16,12 @@ import { ISpatialReference, IUser, } from "@esri/arcgis-rest-types"; -import { IHubContent, IHubLocation, PublisherSource } from "../../core"; +import { + IHubContent, + IHubLocation, + PublisherSource, + getTypeFromEntity, +} from "../../core"; import { IHubGeography, GeographyProvenance, @@ -226,22 +231,25 @@ export const isProxiedCSV = ( /** * Get the relative URL to use for the item in a hub site * @param type - * @param identifier + * @param identifier optional, if not pass, will return a URL to the entities, + * e.g. /initiatives, /projects + * NOTE: not all entities have the entities route set up, in that case, we will + * not return an URL, so they will be redirected back to the site home * @param typeKeywords * @returns * @private */ export const getHubRelativeUrl = ( type: string, - identifier: string, + identifier?: string, typeKeywords?: string[] ): string => { // solution types have their own logic let contentUrl = getSolutionUrl(type, identifier, typeKeywords) || getInitiativeTemplateUrl(type, identifier, typeKeywords); + const family = getFamily(type); if (!contentUrl) { - const family = getFamily(type); const familiesWithPluralizedRoute = [ "app", "dataset", @@ -255,9 +263,9 @@ export const getHubRelativeUrl = ( ]; // default to the catchall content route let path = "/content"; + // the exception if (family === "feedback") { - // the exception - path = "/feedback/surveys"; + path = identifier ? "/feedback/surveys" : ""; } else if (isPageType(type, typeKeywords)) { // pages are in the document family, // but instead of showing the page's metadata on /documents/about @@ -267,7 +275,14 @@ export const getHubRelativeUrl = ( // the rule: route name is plural of family name path = `/${family}s`; } - contentUrl = `${path}/${identifier}`; + contentUrl = identifier ? `${path}/${identifier}` : `${path}`; + } + // TODO: once an entity has its entities route set up, add it to this list + const entitiesHaveEntitiesRoute = ["initiative", "project"]; + // if there is no identifier and the entity does not have the entities route + // set up, do not return an url + if (!identifier && !entitiesHaveEntitiesRoute.includes(family)) { + contentUrl = ""; } return contentUrl; }; diff --git a/packages/common/src/core/types/IHubEntityBase.ts b/packages/common/src/core/types/IHubEntityBase.ts index ff2933ee69c..8c836a0e648 100644 --- a/packages/common/src/core/types/IHubEntityBase.ts +++ b/packages/common/src/core/types/IHubEntityBase.ts @@ -31,6 +31,9 @@ export interface IHubEntityLinks { * Relative url of the entity, within a site */ siteRelative?: string; + + siteRelativeEntityType?: string; + /** * Relative workspace url of the entity, within a site */ diff --git a/packages/common/src/events/_internal/computeLinks.ts b/packages/common/src/events/_internal/computeLinks.ts index 685d41204be..a84ac9c599f 100644 --- a/packages/common/src/events/_internal/computeLinks.ts +++ b/packages/common/src/events/_internal/computeLinks.ts @@ -16,6 +16,7 @@ export function computeLinks(event: IEvent): IHubEntityLinks { return { self: siteRelative, siteRelative, + siteRelativeEntityType: getHubRelativeUrl("event"), workspaceRelative: getRelativeWorkspaceUrl("Event", event.id), // TODO // thumbnail: "", diff --git a/packages/common/src/groups/_internal/computeLinks.ts b/packages/common/src/groups/_internal/computeLinks.ts index bf45b4ffa50..80176b302d7 100644 --- a/packages/common/src/groups/_internal/computeLinks.ts +++ b/packages/common/src/groups/_internal/computeLinks.ts @@ -5,6 +5,7 @@ import { IHubEntityLinks } from "../../core/types"; import { getRelativeWorkspaceUrl } from "../../core/getRelativeWorkspaceUrl"; import { getGroupHomeUrl } from "../../urls/getGroupHomeUrl"; import { getGroupThumbnailUrl } from "../../search/utils"; +import { getHubRelativeUrl } from "../../content/_internal/internalContentUtils"; /** * Compute the links that get appended to a Hub Group @@ -26,6 +27,7 @@ export function computeLinks( return { self: getGroupHomeUrl(group.id, requestOptions), siteRelative: `/groups/${group.id}`, + siteRelativeEntityType: getHubRelativeUrl("Group"), workspaceRelative: getRelativeWorkspaceUrl("Group", group.id), thumbnail: getGroupThumbnailUrl(requestOptions.portal, group, token), }; diff --git a/packages/common/src/initiative-templates/_internal/computeLinks.ts b/packages/common/src/initiative-templates/_internal/computeLinks.ts index 7a26c4c2497..27ab6f85475 100644 --- a/packages/common/src/initiative-templates/_internal/computeLinks.ts +++ b/packages/common/src/initiative-templates/_internal/computeLinks.ts @@ -28,6 +28,7 @@ export function computeLinks( return { self: getItemHomeUrl(item.id, requestOptions), siteRelative: getHubRelativeUrl(item.type, getItemIdentifier(item)), + siteRelativeEntityType: getHubRelativeUrl(item.type), workspaceRelative: getRelativeWorkspaceUrl( item.type, getItemIdentifier(item) diff --git a/packages/common/src/initiatives/_internal/computeLinks.ts b/packages/common/src/initiatives/_internal/computeLinks.ts index 6949350d0c2..e69abb6ca1e 100644 --- a/packages/common/src/initiatives/_internal/computeLinks.ts +++ b/packages/common/src/initiatives/_internal/computeLinks.ts @@ -28,6 +28,7 @@ export function computeLinks( return { self: getItemHomeUrl(item.id, requestOptions), siteRelative: getHubRelativeUrl(item.type, getItemIdentifier(item)), + siteRelativeEntityType: getHubRelativeUrl(item.type), workspaceRelative: getRelativeWorkspaceUrl( item.type, getItemIdentifier(item) diff --git a/packages/common/src/pages/_internal/computeProps.ts b/packages/common/src/pages/_internal/computeProps.ts index 0f52346b106..8ee15bfedae 100644 --- a/packages/common/src/pages/_internal/computeProps.ts +++ b/packages/common/src/pages/_internal/computeProps.ts @@ -9,6 +9,7 @@ import { getItemHomeUrl } from "../../urls/get-item-home-url"; import { IHubPage } from "../../core/types/IHubPage"; import { getRelativeWorkspaceUrl } from "../../core/getRelativeWorkspaceUrl"; import { computeBaseProps } from "../../core/_internal/computeBaseProps"; +import { getHubRelativeUrl } from "../../content/_internal/internalContentUtils"; /** * Given a model and a page, set various computed properties that can't be directly mapped @@ -37,6 +38,7 @@ export function computeProps( page.links = { self: getItemHomeUrl(page.id, requestOptions), siteRelative: `/pages/${page.id}`, + siteRelativeEntityType: getHubRelativeUrl("page"), workspaceRelative: getRelativeWorkspaceUrl("page", page.id), layoutRelative: `/pages/${page.id}/edit`, thumbnail: thumbnailUrl, diff --git a/packages/common/src/projects/_internal/computeLinks.ts b/packages/common/src/projects/_internal/computeLinks.ts index 8f910ca5511..9c88350d549 100644 --- a/packages/common/src/projects/_internal/computeLinks.ts +++ b/packages/common/src/projects/_internal/computeLinks.ts @@ -28,6 +28,7 @@ export function computeLinks( return { self: getItemHomeUrl(item.id, requestOptions), siteRelative: getHubRelativeUrl(item.type, getItemIdentifier(item)), + siteRelativeEntityType: getHubRelativeUrl(item.type), workspaceRelative: getRelativeWorkspaceUrl( item.type, getItemIdentifier(item) diff --git a/packages/common/src/sites/_internal/computeLinks.ts b/packages/common/src/sites/_internal/computeLinks.ts index 7a3ec7f8226..8a9c7f6d6d9 100644 --- a/packages/common/src/sites/_internal/computeLinks.ts +++ b/packages/common/src/sites/_internal/computeLinks.ts @@ -27,6 +27,7 @@ export function computeLinks( return { self: item.url, siteRelative: getHubRelativeUrl(item.type, item.id, item.typeKeywords), + siteRelativeEntityType: getHubRelativeUrl(item.type), layoutRelative: "/edit", workspaceRelative: getRelativeWorkspaceUrl( item.type, diff --git a/packages/common/src/templates/_internal/computeLinks.ts b/packages/common/src/templates/_internal/computeLinks.ts index 84a8228b68d..fb3c8a05e26 100644 --- a/packages/common/src/templates/_internal/computeLinks.ts +++ b/packages/common/src/templates/_internal/computeLinks.ts @@ -37,6 +37,7 @@ export function computeLinks( return { self: itemHomeUrl, siteRelative: siteRelativeUrl, + siteRelativeEntityType: getHubRelativeUrl(item.type), workspaceRelative: isDeployed ? itemHomeUrl : getRelativeWorkspaceUrl(item.type, getItemIdentifier(item)), diff --git a/packages/common/test/content/_internal/internalContentUtils.test.ts b/packages/common/test/content/_internal/internalContentUtils.test.ts index dc2355d2391..94bf7320979 100644 --- a/packages/common/test/content/_internal/internalContentUtils.test.ts +++ b/packages/common/test/content/_internal/internalContentUtils.test.ts @@ -3,6 +3,7 @@ import { getContentEditUrl, getExtentObject, deriveLocationFromItem, + getHubRelativeUrl, } from "../../../src/content/_internal/internalContentUtils"; import * as internalContentUtils from "../../../src/content/_internal/internalContentUtils"; import * as Compose from "../../../src/content/compose"; @@ -487,3 +488,75 @@ describe("deriveLocationFromItem", () => { }); }); }); + +describe("getHubRelativeUrl", () => { + describe("handle when there is an identifier", () => { + const identifier = "a-slug"; + it("should handle a family that does not have a puralized route", () => { + // 'report template' should be in the 'content' family + const result = getHubRelativeUrl("report template", identifier); + expect(result).toBe(`/content/${identifier}`); + }); + it("should handle initiatives", () => { + const result = getHubRelativeUrl("Hub Initiative", identifier); + expect(result).toBe(`/initiatives/${identifier}`); + }); + it("should handle projects", () => { + const result = getHubRelativeUrl("Hub Project", identifier); + expect(result).toBe(`/projects/${identifier}`); + }); + it("should handle initiative templates", () => { + let result = getHubRelativeUrl("Hub Initiative", identifier, [ + "hubInitiativeTemplate", + ]); + expect(result).toBe(`/initiatives/templates/${identifier}/about`); + result = getHubRelativeUrl("Hub Initiative Template", identifier); + expect(result).toBe(`/initiatives/templates/${identifier}/about`); + }); + it("should handle solution templates", () => { + let result = getHubRelativeUrl("Web Mapping Application", identifier, [ + "hubSolutionTemplate", + ]); + expect(result).toBe(`/templates/${identifier}/about`); + result = getHubRelativeUrl("Web Mapping Application", identifier); + expect(result).toBe(`/apps/${identifier}`); + result = getHubRelativeUrl("Solution", identifier); + expect(result).toBe(`/templates/${identifier}/about`); + result = getHubRelativeUrl("Solution", identifier, ["Deployed"]); + expect(result).toBe(`/content/${identifier}/about`); + }); + it("should handle feedback", () => { + const result = getHubRelativeUrl("Form", identifier); + expect(result).toBe(`/feedback/surveys/${identifier}`); + }); + it("should handle discussion", () => { + const result = getHubRelativeUrl("Discussion", identifier); + expect(result).toBe(`/discussions/${identifier}`); + }); + }); + describe("handle when there is no identifier", () => { + it("should handle a family that does not have a puralized route", () => { + // 'report template' should be in the 'content' family + let result = getHubRelativeUrl("report template"); + expect(result).toBe(""); + result = getHubRelativeUrl("StoryMap"); + expect(result).toBe(""); + }); + it("should handle initiatives", () => { + const result = getHubRelativeUrl("Hub Initiative"); + expect(result).toBe(`/initiatives`); + }); + it("should handle projects", () => { + const result = getHubRelativeUrl("Hub Project"); + expect(result).toBe(`/projects`); + }); + it("should handle feedback", () => { + const result = getHubRelativeUrl("Form"); + expect(result).toBe(""); + }); + it("should handle discussion", () => { + const result = getHubRelativeUrl("Discussion"); + expect(result).toBe(""); + }); + }); +}); diff --git a/packages/common/test/events/_internal/PropertyMapper.test.ts b/packages/common/test/events/_internal/PropertyMapper.test.ts index 7c6e5825b8e..23323d2a57f 100644 --- a/packages/common/test/events/_internal/PropertyMapper.test.ts +++ b/packages/common/test/events/_internal/PropertyMapper.test.ts @@ -150,6 +150,7 @@ describe("PropertyMapper", () => { links: { self: "/events/event-title-31c", siteRelative: "/events/event-title-31c", + siteRelativeEntityType: "", workspaceRelative: "/workspace/events/31c", }, slug: "event-title-31c", diff --git a/packages/common/test/events/_internal/computeLinks.test.ts b/packages/common/test/events/_internal/computeLinks.test.ts index db6a0b9ea7f..2e2ae2ceed1 100644 --- a/packages/common/test/events/_internal/computeLinks.test.ts +++ b/packages/common/test/events/_internal/computeLinks.test.ts @@ -11,6 +11,7 @@ describe("computeLinks", () => { expect(results).toEqual({ self: "/events/my-events-are-awesome-123-31c", siteRelative: "/events/my-events-are-awesome-123-31c", + siteRelativeEntityType: "", workspaceRelative: "/workspace/events/31c", }); }); diff --git a/packages/common/test/groups/_internal/computeLinks.test.ts b/packages/common/test/groups/_internal/computeLinks.test.ts index f757189ad9e..501e8ca6ec7 100644 --- a/packages/common/test/groups/_internal/computeLinks.test.ts +++ b/packages/common/test/groups/_internal/computeLinks.test.ts @@ -31,6 +31,7 @@ describe("computeLinks", () => { const chk = computeLinks(group, authdCtxMgr.context.requestOptions); expect(chk.siteRelative).toBe("/groups/00c"); + expect(chk.siteRelativeEntityType).toBe(""); expect(chk.workspaceRelative).toBe("/workspace/groups/00c"); }); }); diff --git a/packages/common/test/initiative-templates/_internal/computeLinks.test.ts b/packages/common/test/initiative-templates/_internal/computeLinks.test.ts index 19c3e8ff34d..edbe9b1d199 100644 --- a/packages/common/test/initiative-templates/_internal/computeLinks.test.ts +++ b/packages/common/test/initiative-templates/_internal/computeLinks.test.ts @@ -33,6 +33,7 @@ describe("computeLinks", () => { const chk = computeLinks(item, authdCtxMgr.context.requestOptions); expect(chk.siteRelative).toBe("/initiatives/templates/mock-slug/about"); + expect(chk.siteRelativeEntityType).toBe(""); expect(chk.workspaceRelative).toBe( "/workspace/initiativeTemplates/mock-slug" ); diff --git a/packages/common/test/initiatives/_internal/computeLinks.test.ts b/packages/common/test/initiatives/_internal/computeLinks.test.ts index 55ebc16d35c..4f40f2259e8 100644 --- a/packages/common/test/initiatives/_internal/computeLinks.test.ts +++ b/packages/common/test/initiatives/_internal/computeLinks.test.ts @@ -33,12 +33,14 @@ describe("computeLinks", () => { const chk = computeLinks(item, authdCtxMgr.context.requestOptions); expect(chk.siteRelative).toBe("/initiatives/mock-slug"); + expect(chk.siteRelativeEntityType).toBe("/initiatives"); 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("/initiatives/00c"); + expect(chk.siteRelativeEntityType).toBe("/initiatives"); expect(chk.workspaceRelative).toBe("/workspace/initiatives/00c"); }); }); diff --git a/packages/common/test/projects/_internal/computeLinks.test.ts b/packages/common/test/projects/_internal/computeLinks.test.ts index fb5bfe6c3ec..8850031ede0 100644 --- a/packages/common/test/projects/_internal/computeLinks.test.ts +++ b/packages/common/test/projects/_internal/computeLinks.test.ts @@ -39,6 +39,7 @@ describe("computeLinks", () => { const chk = computeLinks(item, authdCtxMgr.context.requestOptions); expect(chk.siteRelative).toBe("/projects/00c"); + expect(chk.siteRelativeEntityType).toBe("/projects"); expect(chk.workspaceRelative).toBe("/workspace/projects/00c"); }); }); diff --git a/packages/common/test/search/_internal/hubEventsHelpers/eventToSearchResult.test.ts b/packages/common/test/search/_internal/hubEventsHelpers/eventToSearchResult.test.ts index 2e1da8317a3..6a035cc3746 100644 --- a/packages/common/test/search/_internal/hubEventsHelpers/eventToSearchResult.test.ts +++ b/packages/common/test/search/_internal/hubEventsHelpers/eventToSearchResult.test.ts @@ -59,6 +59,7 @@ describe("eventToSearchResult", () => { links: { self: `/events/my-event-title-${event.id}`, siteRelative: `/events/my-event-title-${event.id}`, + siteRelativeEntityType: "", workspaceRelative: `/workspace/events/${event.id}`, }, tags: event.tags, @@ -93,6 +94,7 @@ describe("eventToSearchResult", () => { links: { self: `/events/my-event-title-${event.id}`, siteRelative: `/events/my-event-title-${event.id}`, + siteRelativeEntityType: "", workspaceRelative: `/workspace/events/${event.id}`, }, tags: event.tags, diff --git a/packages/common/test/sites/_internal/computeLinks.test.ts b/packages/common/test/sites/_internal/computeLinks.test.ts index 20238067d2a..56f96cc4e2e 100644 --- a/packages/common/test/sites/_internal/computeLinks.test.ts +++ b/packages/common/test/sites/_internal/computeLinks.test.ts @@ -34,6 +34,7 @@ describe("computeLinks", () => { expect(chk.self).toBe("https://some-url.com"); expect(chk.siteRelative).toBe("/content/00c"); + expect(chk.siteRelativeEntityType).toBe(""); expect(chk.workspaceRelative).toBe("/workspace/sites/00c"); expect(chk.layoutRelative).toBe("/edit"); });