Skip to content

Commit

Permalink
feat: by default fetch metadata and make query response readable
Browse files Browse the repository at this point in the history
AFFECTS PACKAGES:
@esri/arcgis-rest-feature-service
@esri/arcgis-rest-request

ISSUES CLOSED: #375
  • Loading branch information
jgravois committed Nov 12, 2018
1 parent ee76650 commit 3c96fce
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 137 deletions.
244 changes: 122 additions & 122 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"shelljs": "^0.7.8",
"slug": "^0.9.1",
"ts-node": "^3.3.0",
"tslint": "^5.8.0",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.6.0",
"tslint-config-standard": "^6.0.1",
"typedoc": "^0.12.0",
Expand Down
31 changes: 31 additions & 0 deletions packages/arcgis-rest-feature-service/src/getFeatureService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* Copyright (c) 2018 Environmental Systems Research Institute, Inc.
* Apache-2.0 */

import {
request,
IRequestOptions
} from "@esri/arcgis-rest-request";

import { ILayerDefinition } from "@esri/arcgis-rest-common-types";

/**
* Layer (Feature Service) request. See the [REST Documentation](https://developers.arcgis.com/rest/services-reference/layer-feature-service-.htm) for more information.
*
* ```js
* import { getFeatureService } from '@esri/arcgis-rest-feature-service';
*
* const url = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/ServiceRequest/FeatureServer/0";
*
* getFeatureService(url)
* .then(response) // { name: "311", id: 0, ... }
* ```
*
* @param requestOptions - Options for the request.
* @returns A Promise that will resolve with the addFeatures response.
*/
export function getFeatureService(
url: string,
requestOptions: IRequestOptions
): Promise<ILayerDefinition> {
return request(url, requestOptions);
}
1 change: 1 addition & 0 deletions packages/arcgis-rest-feature-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from "./addAttachment";
export * from "./updateAttachment";
export * from "./deleteAttachments";
export * from "./queryRelated";
export * from "./getFeatureService";
125 changes: 115 additions & 10 deletions packages/arcgis-rest-feature-service/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ import {
IFeatureSet,
IFeature,
esriUnits,
IExtent
IExtent,
IField,
ILayerDefinition
} from "@esri/arcgis-rest-common-types";

import { getFeatureService } from "./getFeatureService";
import { ISharedQueryParams } from "./helpers";

/**
Expand Down Expand Up @@ -88,6 +92,11 @@ 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;
}

export interface IQueryFeaturesResponse extends IFeatureSet {
Expand Down Expand Up @@ -143,7 +152,7 @@ export function getFeature(
*
* queryFeatures({
* url,
* where: "STATE_NAME = 'Alaska"
* where: "STATE_NAME = 'Alaska'"
* }).then(result => {
* console.log(result.features); // array of features
* });
Expand All @@ -155,22 +164,118 @@ export function getFeature(
export function queryFeatures(
requestOptions: IQueryFeaturesRequestOptions
): Promise<IQueryFeaturesResponse | IQueryResponse> {
// default to a GET request
const options: IQueryFeaturesRequestOptions = {
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<IQueryFeaturesResponse | IQueryResponse> {
const queryOptions: IQueryFeaturesRequestOptions = {
params: {},
httpMethod: "GET",
url: requestOptions.url,
fields: false,
...requestOptions
};

appendCustomParams(requestOptions, options);
appendCustomParams(requestOptions, queryOptions);

// set default query parameters
if (!options.params.where) {
options.params.where = "1=1";
if (!queryOptions.params.where) {
queryOptions.params.where = "1=1";
}
if (!options.params.outFields) {
options.params.outFields = "*";
if (!queryOptions.params.outFields) {
queryOptions.params.outFields = "*";
}
return request(`${options.url}/query`, options);

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;
}
6 changes: 4 additions & 2 deletions packages/arcgis-rest-feature-service/test/features.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ describe("feature", () => {

it("should supply default query parameters", done => {
const requestOptions = {
url: serviceUrl
url: serviceUrl,
fields: false
};
fetchMock.once("*", queryResponse);
queryFeatures(requestOptions)
Expand All @@ -74,7 +75,8 @@ describe("feature", () => {
const requestOptions = {
url: serviceUrl,
where: "Condition='Poor'",
outFields: ["FID", "Tree_ID", "Cmn_Name", "Condition"]
outFields: ["FID", "Tree_ID", "Cmn_Name", "Condition"],
fields: false
};
fetchMock.once("*", queryResponse);
queryFeatures(requestOptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export function appendCustomParams(
key !== "fetch" &&
key !== "portal" &&
key !== "maxUrlLength" &&
key !== "endpoint"
key !== "endpoint" &&
key !== "fields"
) {
newOptions.params[key] = (oldOptions as { [key: string]: any })[key];
}
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"only-arrow-functions": [false],
"object-literal-sort-keys": false,
"interface-name": [true, "always-prefix"],
"no-string-literal": false
"no-string-literal": false,
"no-console": false
}
}

0 comments on commit 3c96fce

Please sign in to comment.