Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hub-common): consolidates entity location construction and depre… #1382

Merged
merged 13 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"program": "${workspaceRoot}/node_modules/.bin/jasmine",
"args": [
"--config=jasmine.json"
]
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"type": "node",
Expand Down
33 changes: 4 additions & 29 deletions packages/common/src/content/_internal/computeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { IRequestOptions } from "@esri/arcgis-rest-request";
import { UserSession } from "@esri/arcgis-rest-auth";
import { getItemThumbnailUrl } from "../../resources";
import { IModel } from "../../types";
import { bBoxToExtent, isBBox } from "../../extent";
import { IExtent } from "@esri/arcgis-rest-types";
import { getItemHomeUrl } from "../../urls/get-item-home-url";
import { getContentEditUrl, getHubRelativeUrl } from "./internalContentUtils";
import { IHubLocation } from "../../core/types/IHubLocation";
import { IHubEditableContent } from "../../core/types/IHubEditableContent";
import { getRelativeWorkspaceUrl } from "../../core/getRelativeWorkspaceUrl";
import { isDiscussable } from "../../discussions";
Expand All @@ -15,24 +12,7 @@ import {
ServiceCapabilities,
} from "../hostedServiceUtils";
import { IItemAndIServerEnrichments } from "../../items/_enrichments";

// if called and valid, set 3 things -- else just return type custom
export const getExtentObject = (itemExtent: number[][]): IExtent => {
return isBBox(itemExtent)
? ({ ...bBoxToExtent(itemExtent), type: "extent" } as unknown as IExtent)
: undefined;
};

export function deriveLocationFromItemExtent(itemExtent?: number[][]) {
const location: IHubLocation = { type: "custom" };
const geometry: any = getExtentObject(itemExtent);
if (geometry) {
location.geometries = [geometry];
location.spatialReference = geometry.spatialReference;
location.extent = itemExtent;
}
return location;
}
import { computeBaseProps } from "../../core/_internal/computeBaseProps";

export function computeProps(
model: IModel,
Expand All @@ -46,6 +26,9 @@ export function computeProps(
token = session.token;
}

// compute base properties on content
content = computeBaseProps(model.item, content);

// thumbnail url
const thumbnailUrl = getItemThumbnailUrl(model.item, requestOptions, token);
// TODO: Remove this once opendata-ui starts using `links.thumbnail` instead
Expand All @@ -65,14 +48,6 @@ export function computeProps(
// error that doesn't let us save the form
content.licenseInfo = model.item.licenseInfo || "";

if (!content.location) {
// build location if one does not exist based off of the boundary and the item's extent
content.location =
model.item.properties?.boundary === "none"
? { type: "none" }
: deriveLocationFromItemExtent(model.item.extent);
}

content.isDiscussable = isDiscussable(content);

if (enrichments.server) {
Expand Down
53 changes: 52 additions & 1 deletion packages/common/src/content/_internal/internalContentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
import { parseServiceUrl } from "@esri/arcgis-rest-feature-layer";
import { IItem, IPortal } from "@esri/arcgis-rest-portal";
import {
IExtent,
ILayerDefinition,
ISpatialReference,
IUser,
} from "@esri/arcgis-rest-types";
import { IHubContent, PublisherSource } from "../../core";
import { IHubContent, IHubLocation, PublisherSource } from "../../core";
import {
IHubGeography,
GeographyProvenance,
Expand Down Expand Up @@ -95,6 +96,56 @@ export const getContentBoundary = (item: IItem): IHubGeography => {
return boundary;
};

/**
* Constructs IExtent from numeric item extent array
* @param extent Raw item extent array
* @returns IExtent
*/
export const getExtentObject = (extent: number[][]): IExtent => {
return isBBox(extent)
? ({ ...bBoxToExtent(extent), type: "extent" } as unknown as IExtent)
: undefined;
};

/**
* Derives proper IHubLocation given an ArcGIS Item. If no
* location (item.properties.location) is present, one will be
* constructed from the item's extent.
* @param item ArcGIS Item
* @returns IHubLocation
*/
export const deriveLocationFromItem = (item: IItem): IHubLocation => {
const { properties, extent } = item;
const location: IHubLocation = properties?.location;

if (location) {
// IHubLocation already exists, so return it
return location;
}

if (properties?.boundary === "none") {
// Per https://confluencewikidev.esri.com/display/Hub/Hub+Location+Management
// bounds = 'none' -> specifies not to show on map. If this is true and
// no location is already present, opt to not generate location from item extent
return { type: "none" };
}

// IHubLocation does not exist on item properties, so construct it
// from item extent
const geometry: any = getExtentObject(extent);
if (geometry) {
return {
type: "custom",
extent,
geometries: [geometry],
spatialReference: geometry.spatialReference,
};
} else {
// Could not construct extent object, so return none
return { type: "none" };
}
};

/**
* Determine if we are in an enterprise environment
* NOTE: when no request options are provided, the underlying
Expand Down
20 changes: 5 additions & 15 deletions packages/common/src/content/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { getItemHomeUrl } from "../urls";
import { unique } from "../util";
import { mapBy } from "../utils";
import { getFamily } from "./get-family";
import { getHubRelativeUrl } from "./_internal/internalContentUtils";
import { bBoxToExtent, extentToPolygon, isBBox } from "../extent";
import {
deriveLocationFromItem,
getHubRelativeUrl,
} from "./_internal/internalContentUtils";

/**
* Enrich a generic search result
Expand Down Expand Up @@ -45,22 +47,10 @@ export async function enrichContentSearchResult(
siteRelative: "not-implemented",
thumbnail: "not-implemented",
},
location: item.properties?.location,
location: deriveLocationFromItem(item),
tannerjt marked this conversation as resolved.
Show resolved Hide resolved
rawResult: item,
};

// Include geometry in IHubSearchResult
if (isBBox(item.extent)) {
// PR Reference: https://github.com/Esri/hub.js/pull/987
const extent = bBoxToExtent(item.extent);
const geometryPolygon = extentToPolygon(extent);
result.geometry = {
geometry: { type: "polygon", ...geometryPolygon },
provenance: "item",
spatialReference: extent.spatialReference,
};
}

// default includes
const DEFAULTS: string[] = [];

Expand Down
20 changes: 20 additions & 0 deletions packages/common/src/core/_internal/computeBaseProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { IItem } from "@esri/arcgis-rest-types";
import { IHubItemEntity } from "../types";
import { deriveLocationFromItem } from "../../content/_internal/internalContentUtils";

/**
* Base property mapping for item backed entity types
* @param item IItem
* @param entity IHubItemEntity
* @returns
*/
export function computeBaseProps<T extends Partial<IHubItemEntity>>(
item: IItem,
entity: T
): T {
// TODO: Currently only location is determined for base
// properties, but all properties that are commonly shared
// across items should be moved here.
entity.location = entity.location || deriveLocationFromItem(item);
tannerjt marked this conversation as resolved.
Show resolved Hide resolved
return entity;
}
3 changes: 3 additions & 0 deletions packages/common/src/discussions/_internal/computeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IModel } from "../../types";
import { IHubDiscussion } from "../../core";

import { isDiscussable } from "../utils";
import { computeBaseProps } from "../../core/_internal/computeBaseProps";

/**
* Given a model and a Discussion, set various computed properties that can't be directly mapped
Expand All @@ -25,6 +26,8 @@ export function computeProps(
const session: UserSession = requestOptions.authentication as UserSession;
token = session.token;
}
// compute base properties on discussion
discussion = computeBaseProps(model.item, discussion);
tannerjt marked this conversation as resolved.
Show resolved Hide resolved

// thumbnail url
discussion.thumbnailUrl = getItemThumbnailUrl(
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/discussions/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export async function updateDiscussion(
let allowedLocations: Polygon[];
try {
allowedLocations =
updatedDiscussion.location?.geometries?.map(
updatedDiscussion.location.geometries?.map(
(geometry) => arcgisToGeoJSON(geometry) as any as Polygon
) || null;
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getItemThumbnailUrl } from "../../resources";
import { IModel } from "../../types";
import { InitiativeTemplateDefaultFeatures } from "./InitiativeTemplateBusinessRules";
import { computeLinks } from "./computeLinks";
import { computeBaseProps } from "../../core/_internal/computeBaseProps";

/**
* Given a model and an initiative template, set various computed properties that can't be directly mapped
Expand All @@ -27,6 +28,9 @@ export function computeProps(
token = session.token;
}

// compute base properties on initiativeTemplate
initiativeTemplate = computeBaseProps(model.item, initiativeTemplate);

// thumbnail url
initiativeTemplate.thumbnailUrl = getItemThumbnailUrl(
model.item,
Expand Down
6 changes: 5 additions & 1 deletion packages/common/src/initiative-templates/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ 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 {
deriveLocationFromItem,
getHubRelativeUrl,
} from "../content/_internal/internalContentUtils";
import { IHubInitiativeTemplate } from "../core/types";
import { PropertyMapper } from "../core/_internal/PropertyMapper";
import { getItemBySlug } from "../items/slugs";
Expand Down Expand Up @@ -82,6 +85,7 @@ export async function enrichInitiativeTemplateSearchResult(
thumbnail: "not-implemented",
workspaceRelative: "not-implemented",
},
location: deriveLocationFromItem(item),
tannerjt marked this conversation as resolved.
Show resolved Hide resolved
rawResult: item,
};

Expand Down
6 changes: 5 additions & 1 deletion packages/common/src/initiatives/HubInitiatives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ import { portalSearchItemsAsItems } from "../search/_internal/portalSearchItems"
import { getTypeWithKeywordQuery } from "../associations/internal/getTypeWithKeywordQuery";
import { negateGroupPredicates } from "../search/_internal/negateGroupPredicates";
import { computeLinks } from "./_internal/computeLinks";
import { getHubRelativeUrl } from "../content/_internal/internalContentUtils";
import {
deriveLocationFromItem,
getHubRelativeUrl,
} from "../content/_internal/internalContentUtils";
import { setEntityStatusKeyword } from "../utils/internal/setEntityStatusKeyword";

/**
Expand Down Expand Up @@ -269,6 +272,7 @@ export async function enrichInitiativeSearchResult(
thumbnail: "not-implemented",
workspaceRelative: "not-implemented",
},
location: deriveLocationFromItem(item),
tannerjt marked this conversation as resolved.
Show resolved Hide resolved
rawResult: item,
};

Expand Down
4 changes: 4 additions & 0 deletions packages/common/src/initiatives/_internal/computeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { processEntityFeatures } from "../../permissions/_internal/processEntity
import { InitiativeDefaultFeatures } from "./InitiativeBusinessRules";
import { computeLinks } from "./computeLinks";
import { getAuthedImageUrl } from "../../core/_internal/getAuthedImageUrl";
import { computeBaseProps } from "../../core/_internal/computeBaseProps";

/**
* Given a model and an Initiative, set various computed properties that can't be directly mapped
Expand All @@ -30,6 +31,9 @@ export function computeProps(
token = session.token;
}

// compute base properties on initiative
initiative = computeBaseProps(model.item, initiative);

// thumbnail url
initiative.thumbnailUrl = getItemThumbnailUrl(
model.item,
Expand Down
6 changes: 5 additions & 1 deletion packages/common/src/pages/HubPages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { getFamily } from "../content/get-family";
import { getHubRelativeUrl } from "../content/_internal/internalContentUtils";
import {
deriveLocationFromItem,
getHubRelativeUrl,
} from "../content/_internal/internalContentUtils";
import { fetchItemEnrichments } from "../items/_enrichments";
import { getProp } from "../objects";
import { getItemThumbnailUrl } from "../resources";
Expand Down Expand Up @@ -214,6 +217,7 @@ export async function enrichPageSearchResult(
siteRelative: "not-implemented",
thumbnail: "not-implemented",
},
location: deriveLocationFromItem(item),
rawResult: item,
};

Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/pages/_internal/computeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PageDefaultFeatures } from "./PageBusinessRules";
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";

/**
* Given a model and a page, set various computed properties that can't be directly mapped
Expand All @@ -27,6 +28,8 @@ export function computeProps(
const session: UserSession = requestOptions.authentication as UserSession;
token = session.token;
}
// compute base properties on page
page = computeBaseProps(model.item, page);
// thumbnail url
const thumbnailUrl = getItemThumbnailUrl(model.item, requestOptions, token);
// TODO: Remove this once opendata-ui starts using `links.thumbnail` instead
Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/projects/_internal/computeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { processEntityFeatures } from "../../permissions/_internal/processEntity
import { ProjectDefaultFeatures } from "./ProjectBusinessRules";
import { computeLinks } from "./computeLinks";
import { getAuthedImageUrl } from "../../core/_internal/getAuthedImageUrl";
import { computeBaseProps } from "../../core/_internal/computeBaseProps";

/**
* Given a model and a project, set various computed properties that can't be directly mapped
Expand All @@ -27,6 +28,8 @@ export function computeProps(
const session: UserSession = requestOptions.authentication as UserSession;
token = session.token;
}
// compute base properties on project
project = computeBaseProps(model.item, project);
// thumbnail url
project.thumbnailUrl = getItemThumbnailUrl(model.item, requestOptions, token);

Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/projects/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getProp } from "../objects/get-prop";
import { listAssociations } from "../associations";
import { getTypeByIdsQuery } from "../associations/internal/getTypeByIdsQuery";
import { computeLinks } from "./_internal/computeLinks";
import { deriveLocationFromItem } from "../content/_internal/internalContentUtils";

/**
* @private
Expand Down Expand Up @@ -100,6 +101,7 @@ export async function enrichProjectSearchResult(
thumbnail: "not-implemented",
workspaceRelative: "not-implemented",
},
location: deriveLocationFromItem(item),
rawResult: item,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,8 @@ export async function ogcItemToSearchResult(
// as `license` and `source` if the OgcItem came from the index.
const pseudoItem = ogcItem.properties as IItem;
const result = await itemToSearchResult(pseudoItem, includes, requestOptions);
// Expose extraneous members like `license`, `source`, `properties.location` and `geometry`
// Expose extraneous members like `license` and `source`
result.source = ogcItem.properties.source;
result.license = ogcItem.properties.license;
result.location = ogcItem.properties.properties?.location;
// Add IHubGeography to result
if (ogcItem.geometry) {
try {
result.geometry = {
geometry: geojsonToArcGIS(ogcItem.geometry) as IPolygonProperties,
};
} catch {
// If geojsonToArcGIS throws an error from an invalid input geometry,
// just ignore for now
}
}

return result;
}
Loading