diff --git a/packages/arcgis-rest-feature-service/src/formatCodedValues.ts b/packages/arcgis-rest-feature-service/src/formatCodedValues.ts new file mode 100644 index 0000000000..a157bcbf67 --- /dev/null +++ b/packages/arcgis-rest-feature-service/src/formatCodedValues.ts @@ -0,0 +1,146 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { IRequestOptions } from "@esri/arcgis-rest-request"; +import { + IField, + ILayerDefinition, + IFeature +} from "@esri/arcgis-rest-common-types"; +import { IQueryFeaturesResponse } from "./query"; +import { getFeatureService } from "./getFeatureService"; + +/** + * Request options to fetch a feature by id. + */ +export interface IFormatCodedValuesRequestOptions extends IRequestOptions { + /** + * Layer service url. + */ + url: string; + /** + * Unique identifier of the feature. + */ + queryResponse: IQueryFeaturesResponse; + /** + * * If a fieldset is provided, no internal metadata check will be issued to gather info about coded value domains. + * + * getFeatureService(url) + * .then(metadata => { + * queryFeatures({ url }) + * .then(response => { + * formatCodedValues({ + * url, + * queryResponse, + * fields: metadata.fields + * }) + * .then(formattedResponse) + * }) + * }) + */ + fields?: IField[]; +} + +/** + * Replaces the raw coded domain values in a query response with descriptions (for legibility). + * + * ```js + * import { queryFeatures, formatCodedValues } from '@esri/arcgis-rest-feature-service'; + * + * const url = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/ServiceRequest/FeatureServer/0"; + * + * queryFeatures({ url }) + * .then(queryResponse => { + * formatCodedValues({ + * url, + * queryResponse + * }) + * .then(formattedResponse) + * }) + * ``` + * + * @param requestOptions - Options for the request. + * @returns A Promise that will resolve with the addFeatures response. + */ +export function formatCodedValues( + requestOptions: IFormatCodedValuesRequestOptions +): Promise { + return new Promise(resolve => { + if (!requestOptions.fields) { + return getFeatureService(requestOptions.url, requestOptions).then( + (metadata: ILayerDefinition) => { + resolve((requestOptions.fields = metadata.fields)); + } + ); + } else { + resolve(requestOptions.fields); + } + }).then(fields => { + // turn the fields array into a POJO to avoid multiple calls to Array.find() + const fieldsObject: any = {}; + const fieldsArray = fields as IField[]; + fieldsArray.forEach((field: IField) => { + fieldsObject[field.name] = field; + }); + + // dont mutate original response + const clonedResponse = JSON.parse( + JSON.stringify(requestOptions.queryResponse) + ); + + clonedResponse.features.forEach((feature: IFeature) => { + for (const key in feature.attributes) { + /* istanbul ignore next */ + if (!feature.attributes.hasOwnProperty(key)) continue; + feature.attributes[key] = convertAttribute( + feature.attributes, + fieldsObject[key] + ); + } + }); + return clonedResponse; + }); +} + +/** + * ripped off from https://github.com/GeoXForm/esri-to-geojson/blob/55d32955d8ef0acb26de70025539e7c7a37d838e/src/index.js#L193-L220 + * + * Decodes an attributes CVD and standardizes any date fields + * + * @params {object} attribute - a single esri feature attribute + * @params {object} field - the field metadata describing that attribute + * @returns {object} outAttribute - the converted attribute + * @private + */ + +function convertAttribute(attribute: any, field: IField) { + const inValue = attribute[field.name]; + let value; + + if (inValue === null) return inValue; + + if (field.domain && field.domain.type === "codedValue") { + value = cvd(inValue, field); + } else { + value = inValue; + } + return value; +} + +/** + * also ripped off from https://github.com/GeoXForm/esri-to-geojson/blob/55d32955d8ef0acb26de70025539e7c7a37d838e/src/index.js#L222-L235 + * + * Looks up a value from a coded domain + * + * @params {integer} value - The original field value + * @params {object} field - metadata describing the attribute field + * @returns {string/integerfloat} - The decoded field value + * @private + */ + +function cvd(value: any, field: IField) { + const domain = field.domain.codedValues.find((d: any) => { + return value === d.code; + }); + return domain ? domain.name : value; +} diff --git a/packages/arcgis-rest-feature-service/src/getFeatureService.ts b/packages/arcgis-rest-feature-service/src/getFeatureService.ts index c3763e9f7a..8e58c494b0 100644 --- a/packages/arcgis-rest-feature-service/src/getFeatureService.ts +++ b/packages/arcgis-rest-feature-service/src/getFeatureService.ts @@ -1,10 +1,7 @@ /* Copyright (c) 2018 Environmental Systems Research Institute, Inc. * Apache-2.0 */ -import { - request, - IRequestOptions -} from "@esri/arcgis-rest-request"; +import { request, IRequestOptions } from "@esri/arcgis-rest-request"; import { ILayerDefinition } from "@esri/arcgis-rest-common-types"; @@ -25,7 +22,7 @@ import { ILayerDefinition } from "@esri/arcgis-rest-common-types"; */ export function getFeatureService( url: string, - requestOptions: IRequestOptions + requestOptions?: IRequestOptions ): Promise { return request(url, requestOptions); } diff --git a/packages/arcgis-rest-feature-service/src/index.ts b/packages/arcgis-rest-feature-service/src/index.ts index 69257bd19d..a5da69b5d6 100644 --- a/packages/arcgis-rest-feature-service/src/index.ts +++ b/packages/arcgis-rest-feature-service/src/index.ts @@ -11,3 +11,4 @@ export * from "./updateAttachment"; export * from "./deleteAttachments"; export * from "./queryRelated"; export * from "./getFeatureService"; +export * from "./formatCodedValues"; diff --git a/packages/arcgis-rest-feature-service/src/query.ts b/packages/arcgis-rest-feature-service/src/query.ts index f85c1a2ccf..dcc8b0651e 100644 --- a/packages/arcgis-rest-feature-service/src/query.ts +++ b/packages/arcgis-rest-feature-service/src/query.ts @@ -1,4 +1,4 @@ -/* Copyright (c) 2017 Environmental Systems Research Institute, Inc. +/* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc. * Apache-2.0 */ import { @@ -11,12 +11,9 @@ import { IFeatureSet, IFeature, esriUnits, - IExtent, - IField, - ILayerDefinition + IExtent } from "@esri/arcgis-rest-common-types"; -import { getFeatureService } from "./getFeatureService"; import { ISharedQueryParams } from "./helpers"; /** @@ -92,11 +89,12 @@ export interface IQueryFeaturesRequestOptions returnTrueCurves?: false; sqlFormat?: "none" | "standard" | "native"; returnExceededLimitFeatures?: boolean; - // if fields false, skip metadata check, dont massage query response - // if fields populated, skip metadata check, make cvds and dates readable in response - // if fields true, fetch metadata and make cvds and dates readable - // if fields missing, fetch metadata and make cvds and dates readable - fields?: any; + /** + * someday... + * + * If 'true' the query will be preceded by a metadata check to gather info about coded value domains and result values will be decoded. If a fieldset is provided it will be used to decode values and no internal metadata request will be issued. + */ + // formatCodedValues?: boolean | IField[]; } export interface IQueryFeaturesResponse extends IFeatureSet { @@ -163,37 +161,11 @@ export function getFeature( */ export function queryFeatures( requestOptions: IQueryFeaturesRequestOptions -): Promise { - if ( - typeof requestOptions.fields === "undefined" || - (typeof requestOptions.fields === "boolean" && requestOptions.fields) - ) { - // ensure custom fetch and authentication are passed through - const metadataOptions: IRequestOptions = { - httpMethod: "GET", - authentication: requestOptions.authentication || null, - fetch: requestOptions.fetch || null - }; - // fetch metadata to retrieve information about coded value domains - return getFeatureService(requestOptions.url, metadataOptions).then( - (metadata: ILayerDefinition) => { - requestOptions.fields = metadata.fields; - return _queryFeatures(requestOptions); - } - ); - } else { - return _queryFeatures(requestOptions); - } -} - -function _queryFeatures( - requestOptions: IQueryFeaturesRequestOptions ): Promise { const queryOptions: IQueryFeaturesRequestOptions = { params: {}, httpMethod: "GET", url: requestOptions.url, - fields: false, ...requestOptions }; @@ -207,75 +179,5 @@ function _queryFeatures( queryOptions.params.outFields = "*"; } - if (!requestOptions.fields) { - return request(`${queryOptions.url}/query`, queryOptions); - } else { - // turn the fields array into a POJO to avoid multiple calls to Array.find() - const fieldsObject: any = {}; - queryOptions.fields.forEach((field: IField) => { - fieldsObject[field.name] = field; - }); - - return request(`${queryOptions.url}/query`, queryOptions).then(response => { - response.features.forEach((feature: IFeature) => { - for (const key in feature.attributes) { - if (!feature.attributes.hasOwnProperty(key)) continue; - feature.attributes[key] = convertAttribute( - feature.attributes, - fieldsObject[key] - ); - } - }); - return response; - }); - } -} - -/** - * ripped off from https://github.com/GeoXForm/esri-to-geojson/blob/55d32955d8ef0acb26de70025539e7c7a37d838e/src/index.js#L193-L220 - * - * Decodes an attributes CVD and standardizes any date fields - * - * @params {object} attribute - a single esri feature attribute - * @params {object} field - the field metadata describing that attribute - * @returns {object} outAttribute - the converted attribute - * @private - */ - -function convertAttribute(attribute: any, field: IField) { - const inValue = attribute[field.name]; - let value; - - if (inValue === null) return inValue; - - if (field.domain && field.domain.type === "codedValue") { - value = cvd(inValue, field); - } else if (field.type === "esriFieldTypeDate") { - try { - value = new Date(inValue).toISOString(); - } catch (e) { - value = inValue; - } - } else { - value = inValue; - } - return value; -} - -/** - * also ripped off from https://github.com/GeoXForm/esri-to-geojson/blob/55d32955d8ef0acb26de70025539e7c7a37d838e/src/index.js#L222-L235 - * - * Looks up a value from a coded domain - * - * @params {integer} value - The original field value - * @params {object} field - metadata describing the attribute field - * @returns {string/integerfloat} - The decoded field value - * @private - */ - -function cvd(value: any, field: IField) { - const domain = field.domain.codedValues.find((d: any) => { - return value === d.code; - }); - return domain ? domain.name : value; + return request(`${queryOptions.url}/query`, queryOptions); }