Skip to content

Commit

Permalink
Merge pull request #744 from Esri/f/recordCount
Browse files Browse the repository at this point in the history
feat(hub-common): add viewDefinition and recordCount to fetchContent(…
  • Loading branch information
tomwayson authored Mar 16, 2022
2 parents 23bf085 + 4f7ed57 commit 3298518
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 48 deletions.
3 changes: 1 addition & 2 deletions packages/common/src/content/_fetch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { IItem } from "@esri/arcgis-rest-portal";
import { BBox } from "..";
import { ItemOrServerEnrichment } from "../items/_enrichments";
import { hubApiRequest } from "../request";
import { IHubRequestOptions, IHubGeography } from "../types";
import { BBox, IHubRequestOptions, IHubGeography } from "../types";
import { isMapOrFeatureServerUrl } from "../urls";
import { cloneObject } from "../util";
import { includes } from "../utils";
Expand Down
70 changes: 57 additions & 13 deletions packages/common/src/content/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,51 @@ export interface IComposeContentOptions extends IHubContentEnrichments {
searchDescription?: string;
}

/**
* get the layer object for
* - an item that refers to a specific layer of a service
* - a multi-layer services (if a layer id was passed in)
* - a single layer feature service
* @param item
* @param layers the layers and tables returned from the service
* @param layerId a specific id
* @returns layer definition
* @private
*/
export const getItemLayer = (
item: IItem,
layers: ILayerDefinition[],
layerId?: number
) => {
// if item refers to a layer we always want to use that layer id
// otherwise use the layer id that was passed in (if any)
const _layerIdFromUrl = getLayerIdFromUrl(item.url);
const _layerId = _layerIdFromUrl ? parseInt(_layerIdFromUrl, 10) : layerId;
return (
layers &&
(!isNil(_layerId)
? // find the explicitly set layer id
layers.find((_layer) => _layer.id === _layerId)
: // for feature servers with a single layer always show the layer
isFeatureService(item.type) && getOnlyQueryLayer(layers))
);
};

// TODO: we should re-define ILayerDefinition
// in IServerEnrichments.ts to include isView
interface ILayerViewDefinition extends ILayerDefinition {
isView?: boolean;
}

/**
* determine if a layer is a layer view
* @param layer
* @returns
* @private
*/
export const isLayerView = (layer: ILayerDefinition) =>
(layer as ILayerViewDefinition).isView;

/**
* Compose a new content object out of an item, enrichments, and context
* @param item
Expand Down Expand Up @@ -528,19 +573,7 @@ export const composeContent = (
} = options || {};

// set common variables that we will use in the derived properties below
// if item refers to a layer we always want to use that layer id
// otherwise use the layer id that was passed in (if any)
const _layerIdFromUrl = getLayerIdFromUrl(item.url);
const layerId = _layerIdFromUrl
? parseInt(_layerIdFromUrl, 10)
: options?.layerId;
const layer =
layers &&
(!isNil(layerId)
? // find the explicitly set layer id
layers.find((_layer) => _layer.id === layerId)
: // for feature servers with a single layer always show the layer
isFeatureService(item.type) && getOnlyQueryLayer(layers));
const layer = getItemLayer(item, layers, options?.layerId);
// NOTE: we only set hubId for public items in online
const hubId = canUseHubApiForItem(item, requestOptions)
? layer
Expand Down Expand Up @@ -750,6 +783,17 @@ export const composeContent = (
null
);
},
get viewDefinition() {
// if this is a layer view and we have the item data
// find the definition that corresponds to the current layer
const dataLayer =
layer &&
isLayerView(layer) &&
data &&
Array.isArray(data.layers) &&
data.layers.find((_layer) => _layer.id === layer.id);
return dataLayer ? dataLayer.layerDefinition : undefined;
},
get orgId() {
// NOTE: it's undocumented, but the portal API will return orgId for items... sometimes
return org ? org.id : item.orgId || ownerUser?.orgId;
Expand Down
63 changes: 51 additions & 12 deletions packages/common/src/content/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { queryFeatures } from "@esri/arcgis-rest-feature-layer";
import { getItem } from "@esri/arcgis-rest-portal";
import { composeContent } from "./compose";
import { IHubContent } from "../core";
import {
ItemOrServerEnrichment,
fetchItemEnrichments,
IItemAndEnrichments,
} from "../items/_enrichments";
import { IHubRequestOptions } from "../types";
import { isNil } from "../util";
Expand All @@ -14,6 +16,7 @@ import {
getContentEnrichments,
} from "./_fetch";
import { canUseHubApiForItem } from "./_internal";
import { composeContent, getItemLayer, isLayerView } from "./compose";

interface IFetchItemAndEnrichmentsOptions extends IHubRequestOptions {
enrichments?: ItemOrServerEnrichment[];
Expand All @@ -24,21 +27,42 @@ export interface IFetchContentOptions extends IFetchItemAndEnrichmentsOptions {
siteOrgKey?: string;
}

const maybeFetchLayerEnrichments = async (
itemAndEnrichments: IItemAndEnrichments,
options?: IFetchContentOptions
) => {
// determine if this is a client-side feature layer view
const { item, data, layers } = itemAndEnrichments;
const layer =
layers && getItemLayer(item, layers, options && options.layerId);
// TODO: add recordCount here too?
const layerEnrichments =
layer && isLayerView(layer) && !data
? // NOTE: I'm not sure what conditions causes a layer view
// to store (at least part of) it's view definition in item data
// it seems that most do not, but until we have a reliable signal
// we just fetch the item data for all layer views
await fetchItemEnrichments(item, ["data"], options)
: undefined;
// TODO: merge errors
return { ...itemAndEnrichments, ...layerEnrichments };
};

const fetchItemAndEnrichments = async (
itemId: string,
options?: IFetchItemAndEnrichmentsOptions
options?: IFetchContentOptions
) => {
// TODO: add error handling
// fetch the item
const item = await getItem(itemId, options);
// TODO: allow override enrichments
// fetch the enrichments
const enrichmentsToFetch =
options?.enrichments || getContentEnrichments(item);
const enrichments = await fetchItemEnrichments(
item,
enrichmentsToFetch,
options
);
return { ...enrichments, item };
return maybeFetchLayerEnrichments({ ...enrichments, item }, options);
};

const fetchContentById = async (
Expand All @@ -52,8 +76,7 @@ const fetchContentById = async (
options
);
// did the caller request a specific layer
const specifiedLayerId =
options && !isNil(options.layerId) && options.layerId;
const specifiedLayerId = options && options.layerId;
// if this is a public item and we're not in enterprise
// fetch the slug and remaining enrichments from the Hub API
const { slug, layerId, boundary, extent, searchDescription, statistics } =
Expand All @@ -65,7 +88,7 @@ const fetchContentById = async (
requestOptions: options,
...itemEnrichments,
slug,
layerId: specifiedLayerId || layerId,
layerId: isNil(specifiedLayerId) ? layerId : specifiedLayerId,
boundary,
extent,
searchDescription,
Expand Down Expand Up @@ -119,6 +142,17 @@ const fetchContentBySlug = async (
});
};

const fetchContentRecordCount = async (content: IHubContent) => {
const { url, viewDefinition } = content;
const where = viewDefinition?.definitionExpression;
const response: any = await queryFeatures({
url,
where,
returnCountOnly: true,
});
return response.count;
};

/**
* Fetch enriched content from the Portal and Hub APIs.
* @param identifier content slug or id
Expand All @@ -131,14 +165,19 @@ const fetchContentBySlug = async (
* const content = await fetchContent('my-org::item-name')
* ```
*/
export const fetchContent = (
export const fetchContent = async (
identifier: string,
options?: IFetchContentOptions
) => {
return isSlug(identifier)
? fetchContentBySlug(
const content = isSlug(identifier)
? await fetchContentBySlug(
addContextToSlug(identifier, options?.siteOrgKey),
options
)
: fetchContentById(identifier, options);
: await fetchContentById(identifier, options);
// fetch record count for layers, tables, or proxied CSVs
const { isProxied, layer } = content;
content.recordCount =
isProxied || !!layer ? await fetchContentRecordCount(content) : undefined;
return content;
};
7 changes: 5 additions & 2 deletions packages/common/src/core/types/IHubContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ export interface IHubContent
/** links to additional resources specified in the formal item metadata */
additionalResources?: IHubAdditionalResource[];

/** definition for content that refers to a client-side layer view */
viewDefinition?: { definitionExpression?: string };

// TODO: metrics, urls, publisher, etc?

///////////
// TODO: remove these deprecated props at the next breaking version
//////////
Expand All @@ -126,6 +131,4 @@ export interface IHubContent

/* DEPRECATED: use org.id instead */
orgId?: string;

// TODO: metrics, urls, publisher, etc?
}
Loading

0 comments on commit 3298518

Please sign in to comment.