diff --git a/packages/common/src/search/utils.ts b/packages/common/src/search/utils.ts index a441568ced9..5f10204fe45 100644 --- a/packages/common/src/search/utils.ts +++ b/packages/common/src/search/utils.ts @@ -9,8 +9,12 @@ import { ISearchGroupUsersOptions, ISearchOptions, } from "@esri/arcgis-rest-portal"; +import { isPageType } from "../content/_internal/internalContentUtils"; +import { IHubSite } from "../core"; +import { getProp } from "../objects/get-prop"; import { ISearchResponse } from "../types"; import { cloneObject } from "../util"; +import { IHubSearchResult } from "./types"; import { IPredicate, IQuery } from "./types/IHubCatalog"; import { IMatchOptions, @@ -294,3 +298,34 @@ export function getScopeGroupPredicate(scope: IQuery): IPredicate { ); return groupFilter && groupFilter.predicates.find(isGroupPredicate); } + +/** + * Determines the canonical siteRelative link for a search result. + * + * We need to pass in `site` specifically for Hub Page items. Unfortunately + * for us, Hub Page items have their canonical slug stored in the corresponding + * site's data.json, not within the Hub Page item itself. + * + * NOTE: The slugs generated by indexer for Hub Page items are not canonical + * and should not be used for link generation. + * + * @param searchResult the search result we're calculating the link for + * @param site IHubSite that is related to the result + * @returns a canonical siteRelative link + */ +export function getResultSiteRelativeLink( + searchResult: IHubSearchResult, + site?: IHubSite +): string { + const { id, type, typeKeywords } = searchResult; + let siteRelativeLink = searchResult.links?.siteRelative; + if (siteRelativeLink && isPageType(type, typeKeywords)) { + const pages = site?.pages || []; + const targetPage = pages.find((p) => p.id === id); + const slug = targetPage?.slug; + if (slug) { + siteRelativeLink = siteRelativeLink.replace(id, slug); + } + } + return siteRelativeLink; +} diff --git a/packages/common/test/search/utils.test.ts b/packages/common/test/search/utils.test.ts index 7cfc3295c44..ed8a0254382 100644 --- a/packages/common/test/search/utils.test.ts +++ b/packages/common/test/search/utils.test.ts @@ -1,5 +1,5 @@ import { IGroup, ISearchOptions, IUser } from "@esri/arcgis-rest-portal"; -import { ISearchResponse } from "../../src"; +import { IHubSite, ISearchResponse } from "../../src"; import { IHubSearchResult, IRelativeDate } from "../../src/search"; import { expandApis, @@ -9,6 +9,7 @@ import { getGroupThumbnailUrl, getNextFunction, migrateToCollectionKey, + getResultSiteRelativeLink, } from "../../src/search/utils"; import { MOCK_AUTH } from "../mocks/mock-auth"; import { mockUserSession } from "../test-helpers/fake-user-session"; @@ -336,4 +337,85 @@ describe("Search Utils:", () => { expect(result).toBe("appAndMap"); }); }); + + describe("getResultSiteRelativeLink", () => { + it("returns undefined if result.links isn't present", () => { + const searchResult = { + id: "9001", + type: "Feature Service", + } as IHubSearchResult; + const result = getResultSiteRelativeLink(searchResult, null); + expect(result).toBeUndefined(); + }); + it("returns undefined if result.links.siteRelative isn't present", () => { + const searchResult = { + id: "9001", + type: "Feature Service", + links: {}, + } as IHubSearchResult; + const result = getResultSiteRelativeLink(searchResult, null); + expect(result).toBeUndefined(); + }); + it("returns an unmodified siteRelative link if result isn't a Hub Page", () => { + const searchResult = { + id: "9001", + type: "Feature Service", + links: { + siteRelative: "/foo/9001", + }, + } as IHubSearchResult; + const result = getResultSiteRelativeLink(searchResult, null); + expect(result).toBe("/foo/9001"); + }); + it("returns a Hub Page result's unmodified siteRelative link if no site is included", () => { + const searchResult = { + id: "9001", + type: "Hub Page", + links: { + siteRelative: "/foo/9001", + }, + } as IHubSearchResult; + const result = getResultSiteRelativeLink(searchResult, null); + expect(result).toBe("/foo/9001"); + }); + it("returns a Hub Page result's unmodified siteRelative link if site has no pages", () => { + const searchResult = { + id: "9001", + type: "Hub Page", + links: { + siteRelative: "/foo/9001", + }, + } as IHubSearchResult; + const result = getResultSiteRelativeLink(searchResult, {} as IHubSite); + expect(result).toBe("/foo/9001"); + }); + it("returns a Hub Page result's unmodified siteRelative link if matching page doesn't have a slug", () => { + const searchResult = { + id: "9001", + type: "Hub Page", + links: { + siteRelative: "/foo/9001", + }, + } as IHubSearchResult; + const site = { + pages: [{ id: "9001" }], + } as IHubSite; + const result = getResultSiteRelativeLink(searchResult, site); + expect(result).toBe("/foo/9001"); + }); + it("substitutes the id in the siteRelative link for the matching page's slug", () => { + const searchResult = { + id: "9001", + type: "Hub Page", + links: { + siteRelative: "/foo/9001", + }, + } as IHubSearchResult; + const site = { + pages: [{ id: "9001", slug: "bar" }], + } as IHubSite; + const result = getResultSiteRelativeLink(searchResult, site); + expect(result).toBe("/foo/bar"); + }); + }); });