diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd24a9eca..69f5f7aaa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,125 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + + +## [Unreleased][head] + +## [2.14.1] - July 23rd 2020 + +### @esri/arcgis-rest-portal + +* Bug Fixes + * added `exportItem` allows `targetSR` to be a `string` or an `ISpatialReference` + +## [2.14.0] - July 20th 2020 + +### @esri/arcgis-rest-auth + +* Bug Fixes + * **arcgis-rest-auth**: enable oAuth from within an IFrame [`e6538d5d`](https://github.com/Esri/arcgis-rest-js/commit/e6538d5d38c9b2d0f31c6392d257f0d9263170bd) [#711](https://github.com/Esri/arcgis-rest-js/issues/711) + +### @esri/arcgis-rest-portal + +* New Features + * added `exportItem` and `getUserContent` functions + +## [2.13.2] - June 11th 2020 + +### @esri/arcgis-rest-service-admin +* Optimization + * **arcgis-rest-service-admin**: Function `createFeatureService` creates the feature service directly in a specified folder rather than creating it at the root and moving it to the folder [aa478ca]( https://github.com/Esri/arcgis-rest-js/pull/689/commits/aa478caf5dfd9290d4adce2ad704973008d68887) + +## [2.13.1] - May 12th 2020 + +### @esri/arcgis-rest-request + +* Bug Fixes + * **request**: Patch hideToken for browser CORS support [b97860](https://github.com/Esri/arcgis-rest-js/commit/b978605f08810bbd5f0b568b36afd3f8f2adbdb2) + +## [2.13.0] - May 6th 2020 + +### @esri/arcgis-rest-request + +* New Features + * **request**: Add `hideToken` option to prevent passing token in query parameters [8595fab](https://github.com/Esri/arcgis-rest-js/commit/8595fabe347a4b0d1718060b12956624308e8ab1) + +## [2.12.1] - May 3rd 2020 + +### @esri/arcgis-rest-types + +* Bug Fixes + * Add the missing `s` in IStatisticDefinition. [c807192](https://github.com/Esri/arcgis-rest-js/commit/c8071921df9424961a325ef2875591a3a3809d94) + +## [2.12.0] - April 27th 2020 + +### @esri/arcgis-rest-portal + +* New Features + * **arcgis-rest-portal**: add getPortalSettings function [`e956dc56`](https://github.com/Esri/arcgis-rest-js/commit/e956dc56e2fb925478767d989e4cf42cb8ac1a1c) + +## [2.11.0] - April 6th 2020 + +### @esri/arcgis-rest-auth +* New Features + * Added getGroupCategorySchema [66ce599](https://github.com/Esri/arcgis-rest-js/commit/66ce5997911b78283db95affcf08333ed4574f3e) + * Added support for group contents search [ef4e404](https://github.com/Esri/arcgis-rest-js/commit/ef4e40496711ed43cde24ababaefe0f1feb7763d) + +### @esri/arcgis-rest-types + +* Bug Fixes + * Revert Merge pull request #656 to remove const enums usage. [ea218f0](https://github.com/Esri/arcgis-rest-js/commit/ea218f0f6e898308109f7fda6daa70464ac21f79) + +## [2.10.1] - April 3rd 2020 + +### @esri/arcgis-rest-auth +* New Features + * **UserSession** Add support for unmanaged sessions, async determineOwner [b8d099a](https://github.com/Esri/arcgis-rest-js/commit/b8d099ab863701cb10e7692c3817840ee6c0c8ec) + +### @esri/arcgis-rest-portal + +* New Features + * **portal** Add resourcesPrefix parameter to addItemResource [c368232](https://github.com/Esri/arcgis-rest-js/pull/684/commits/c3682322f7aca69c0dd3907a603304d232d8b43c) + +* Bug Fixes + * If the data returned by `getItemData()` is empty, return null [05627f8](https://github.com/Esri/arcgis-rest-js/commit/05627f89c517dd3a69b9b92dd9f313144f266190) + +## [2.10.0] - March 17th 2020 + +### @esri/arcgis-rest-portal + +* New Features + * **updateinfo** Added function for updateinfo [0c068fc](https://github.com/Esri/arcgis-rest-js/commit/0c068fcd6daa5d2aad6a28c29653eff71cdddbd9) + +* Bug Fixes + * **shareToGroupAsNonOwner**: No longer trying to promote group owner to group admin [84a7d41](https://github.com/Esri/arcgis-rest-js/commit/84a7d41719db0da46518163b1de14eb822d9f071) + +## [2.9.0] - March 1st 2020 + +### @esri/arcgis-rest-feature-layer + +* New Features + * **portal**: add updateGroupMembership, isItemSharedWithGroup [`14848dbd`](https://github.com/Esri/arcgis-rest-js/commit/14848dbd6034362628ef99c8d57d445c8ed37776) + +### @esri/arcgis-rest-portal + +* Documentation + * **portal**: fix comment in code [`5afe3be9`](https://github.com/Esri/arcgis-rest-js/commit/5afe3be91121223d89f4db2df43553ce1082641d) +* New Features + * **portal**: add reassignItem [`1756cc48`](https://github.com/Esri/arcgis-rest-js/commit/1756cc48f43436f3041e44ed14827f415b106a90) + * **portal**: add updateGroupMembership, isItemSharedWithGroup [`14848dbd`](https://github.com/Esri/arcgis-rest-js/commit/14848dbd6034362628ef99c8d57d445c8ed37776) + +### @esri/arcgis-rest-service-admin + +* New Features + * **portal**: add updateGroupMembership, isItemSharedWithGroup [`14848dbd`](https://github.com/Esri/arcgis-rest-js/commit/14848dbd6034362628ef99c8d57d445c8ed37776) + +### Other Changes + +* New Features + * **portal**: add reassignItem [`2fe62ae7`](https://github.com/Esri/arcgis-rest-js/commit/2fe62ae7897506c3133f18aeed94ce4f3e793aca) + * **portal**: add updateGroupMembership, isItemSharedWithGroup [`ab3fefc0`](https://github.com/Esri/arcgis-rest-js/commit/ab3fefc0c6751ae84c69bd9a84797e53abf57e83) + ## [2.8.2] - February 24th 2020 ### @esri/arcgis-rest-portal @@ -18,10 +137,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * Bug Fixes * **getItemResources**: do not mutate requestOptions in getItemResources [`47841cd3`](https://github.com/Esri/arcgis-rest-js/commit/47841cd39b37a4d18ea0c2acf95a5844543045a3) -## [Unreleased][head] - - - ## [2.8.1] - January 29th 2020 ### @esri/arcgis-rest-auth @@ -36,7 +151,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - New Features - - **Add properties to IStatisticDefinition to support percentiles**: Added optional `statisticParameter` property to support new `&quot;percentile_cont&quot; | &quot;percentile_disc&quot;` types. [8edec3ff](https://github.com/Esri/arcgis-rest-js/pull/650/commits/8edec3ff7d91bc583dfd30db23067bee0e86887e) + - **Add properties to IStatisticDefinition to support percentiles**: Added optional `statisticParameter` property to support new `percentile_disc` types. [8edec3ff](https://github.com/Esri/arcgis-rest-js/pull/650/commits/8edec3ff7d91bc583dfd30db23067bee0e86887e) - **Export `IStatisticDefinition` interface**: [a6ac91b7](https://github.com/Esri/arcgis-rest-js/pull/651/commits/a6ac91b713510391e86819fa51595440cc1533ce) @@ -1559,4 +1674,16 @@ Initial Public Release [2.8.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.7.2...v2.8.0 "v2.8.0" [2.8.1]: https://github.com/Esri/arcgis-rest-js/compare/v2.8.0...v2.8.1 "v2.8.1" [2.8.2]: https://github.com/Esri/arcgis-rest-js/compare/v2.8.1...v2.8.2 "v2.8.2" -[HEAD]: https://github.com/Esri/arcgis-rest-js/compare/v2.8.2...HEAD "Unreleased Changes" +[2.9.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.8.2...v2.9.0 "v2.9.0" +[2.10.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.9.0...v2.10.0 "v2.10.0" +[2.10.1]: https://github.com/Esri/arcgis-rest-js/compare/v2.10.0...v2.10.1 "v2.10.1" +[2.10.2]: https://github.com/Esri/arcgis-rest-js/compare/v2.10.1...v2.10.2 "v2.10.2" +[2.11.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.10.2...v2.11.0 "v2.11.0" +[2.12.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.11.0...v2.12.0 "v2.12.0" +[2.12.1]: https://github.com/Esri/arcgis-rest-js/compare/v2.12.0...v2.12.1 "v2.12.1" +[2.13.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.12.1...v2.13.0 "v2.13.0" +[2.13.1]: https://github.com/Esri/arcgis-rest-js/compare/v2.13.0...v2.13.1 "v2.13.1" +[2.13.2]: https://github.com/Esri/arcgis-rest-js/compare/v2.13.1...v2.13.2 "v2.13.2" +[2.14.0]: https://github.com/Esri/arcgis-rest-js/compare/v2.13.2...v2.14.0 "v2.14.0" +[2.14.1]: https://github.com/Esri/arcgis-rest-js/compare/v2.14.0...v2.14.1 "v2.14.1" +[HEAD]: https://github.com/Esri/arcgis-rest-js/compare/v2.14.1...HEAD "Unreleased Changes" diff --git a/README.md b/README.md index 2ef94de9fe..4054d963e2 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Some useful commands include: * [`@esri/arcgis-rest-auth`](./packages/arcgis-rest-auth) - Provides methods for authenticating named users and applications. * [`@esri/arcgis-rest-portal`](./packages/arcgis-rest-portal) - Methods for working with ArcGIS Online/Enterprise content and users. * [`@esri/arcgis-rest-feature-layer`](./packages/arcgis-rest-feature-layer) - Functions for querying and editing features inside of hosted feature layers. -* [`@esri/arcgis-rest-service-admin`](./packages/arcgis-rest-feature-service-admin) - Functions for administering hosted services. +* [`@esri/arcgis-rest-service-admin`](./packages/arcgis-rest-service-admin) - Functions for administering hosted feature services. * [`@esri/arcgis-rest-geocoding`](./packages/arcgis-rest-geocoding) - Geocoding wrapper for `@esri/arcgis-rest-js` * [`@esri/arcgis-rest-routing`](./packages/arcgis-rest-routing) - Routing and directions wrapper for `@esri/arcgis-rest-js`. * [`@esri/arcgis-rest-types`](./packages/arcgis-rest-types/) - Common Typings for TypeScript developers. diff --git a/RELEASE.md b/RELEASE.md index 3cb6304794..15b92f3683 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,18 +8,25 @@ The command below bumps the version in each individual package.json file and par npm run release:prepare ``` -I don't know why, but sometimes lerna fails to increment a new version number for individual packages (like @esri/arcgis-rest-auth). **When this happens, it is necessary to increment the `version` number in the package (and anything that depends on it) manually.** - -You should **not** increment `peerDependency` version numbers manually _unless you know that your new version of the package needs to use the updated peer dependency_. They should remain as loose as possible. - -For some reason, in CHANGELOG.md, the unreleased section appears below this release. So please move it to the top. +Sometimes this step fails due to errors while parsing commit messages. See [these inline comments](https://github.com/Esri/arcgis-rest-js/blob/d8566a99dd1534e5eeae2ebfc5bfbffc679426d8/support/changelog.js#L78-L81) for how to modify the script and re-run the changelog generation script. Afterwards, you can display a diff to give you a sense of what will be committed to master when you actually publish. +**Note:** Lerna only bumps the versions of packages that have changes or depends on packages that have changes. Not all packages will be bumped. This is expected. + ```bash npm run release:review ``` +**IMPORTANT** + +Before publishing, you will likely need to make a few chages to `CHANGELOG.md` b/c the system to automates this is [far from perfect](https://github.com/Esri/arcgis-rest-js/issues/688). For example: +- It is very rare that anyone uses `npm run c`, so it is very likely that you will have to manually add changelog entries for whatever has changed since the last release +- Often the Unreleased section appears below the current release. So please move it to the top. + + +Once the changelog looks good, run `git add .` to stage it, and proceed. + The last command increments the version in the root package.json, pushes the new tag to GitHub and publishes a release of each individual package on npm. ```bash diff --git a/codecov.yml b/codecov.yml index b0510506e3..3b6b36c5b8 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,7 @@ +codecov: + require_ci_to_pass: yes + coverage: round: up - range: 0..10 \ No newline at end of file + range: 90..95 + precision: 2 \ No newline at end of file diff --git a/demos/ago-node-cli/package.json b/demos/ago-node-cli/package.json index c70797c72f..c10da38d8e 100644 --- a/demos/ago-node-cli/package.json +++ b/demos/ago-node-cli/package.json @@ -1,6 +1,6 @@ { "name": "node-cli", - "version": "2.8.2", + "version": "2.14.1", "description": "arcgis-rest-js node command-line item search example", "main": "ago.js", "scripts": { @@ -18,9 +18,9 @@ "author": "Dave Bouwman ", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "chalk": "^2.3.0", "commander": "^2.12.2", "cross-fetch": "^3.0.0", diff --git a/demos/attachments/package.json b/demos/attachments/package.json index c3d592c5d7..6209281a2d 100644 --- a/demos/attachments/package.json +++ b/demos/attachments/package.json @@ -1,13 +1,13 @@ { "name": "attachments", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "Vanilla JavaScript demo of attachment methods of @esri/arcgis-rest-feature-service", "author": "", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-feature-layer": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-feature-layer": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "devDependencies": { "http-server": "*" diff --git a/demos/batch-geocoder-node/package.json b/demos/batch-geocoder-node/package.json index 2e89b67913..12cb2d4413 100644 --- a/demos/batch-geocoder-node/package.json +++ b/demos/batch-geocoder-node/package.json @@ -1,6 +1,6 @@ { "name": "batch-geocoder", - "version": "2.8.2", + "version": "2.14.1", "description": "arcgis-rest-js batch geocode sample", "main": "batch-geocode.js", "scripts": { @@ -27,9 +27,9 @@ }, "homepage": "https://github.com/Esri/arcgis-rest-js#readme", "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-geocoding": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-geocoding": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "cross-fetch": "^3.0.0", "isomorphic-form-data": "^2.0.0", "papaparse": "^4.6.0" diff --git a/demos/express/package.json b/demos/express/package.json index 1c770de566..e662639fa5 100644 --- a/demos/express/package.json +++ b/demos/express/package.json @@ -1,13 +1,13 @@ { "name": "@esri/arcgis-rest-demo-express", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "Demo of @esri/arcgis-rest-* packages in an Express server", "author": "", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "cross-fetch": "^3.0.0", "express": "^4.16.3", "isomorphic-form-data": "^2.0.0" diff --git a/demos/feature-service-browser/package.json b/demos/feature-service-browser/package.json index 8ee2cd9058..80ff92cf63 100644 --- a/demos/feature-service-browser/package.json +++ b/demos/feature-service-browser/package.json @@ -1,13 +1,13 @@ { "name": "feature-service-browser", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "Vanilla JavaScript demo of @esri/arcgis-rest-feature-service", "author": "", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-feature-layer": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-feature-layer": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "devDependencies": { "http-server": "*" diff --git a/demos/geocoder-browser/package.json b/demos/geocoder-browser/package.json index 7a6e1a3822..9f98dee2ab 100644 --- a/demos/geocoder-browser/package.json +++ b/demos/geocoder-browser/package.json @@ -1,14 +1,14 @@ { "name": "@esri/arcgis-rest-geocoder-vanilla", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "Vanilla JavaScript demo of @esri/arcgis-rest-geocoder", "author": "", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-geocoding": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-geocoding": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "devDependencies": { "http-server": "0.11.1" diff --git a/demos/jsapi-integration/package.json b/demos/jsapi-integration/package.json index cc0ba058af..d1fc64f698 100644 --- a/demos/jsapi-integration/package.json +++ b/demos/jsapi-integration/package.json @@ -1,14 +1,14 @@ { "name": "@esri/jsapi-integration", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "to do", "author": "", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "devDependencies": { "http-server": "*" diff --git a/demos/node-cli-item-management/package.json b/demos/node-cli-item-management/package.json index e8c001fba5..2b163c1fde 100644 --- a/demos/node-cli-item-management/package.json +++ b/demos/node-cli-item-management/package.json @@ -1,6 +1,6 @@ { "name": "cli-item-management", - "version": "2.8.2", + "version": "2.14.1", "description": "Manage items in ArcGIS Online with a Node JS CLI and ArcGIS Rest JS", "main": "index.js", "scripts": { @@ -16,9 +16,9 @@ "arcgis-rest-js" ], "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "chalk": "^2.3.0", "cross-fetch": "^3.0.0", "isomorphic-form-data": "^1.0.0", diff --git a/demos/oauth2-browser/package-lock.json b/demos/oauth2-browser/package-lock.json index 6cc704a686..bbb4751a8f 100644 --- a/demos/oauth2-browser/package-lock.json +++ b/demos/oauth2-browser/package-lock.json @@ -1,26 +1,63 @@ { - "requires": true, + "name": "@esri/arcgis-rest-demo-vanilla", + "version": "2.11.0", "lockfileVersion": 1, + "requires": true, "dependencies": { + "@esri/arcgis-rest-auth": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@esri/arcgis-rest-auth/-/arcgis-rest-auth-2.11.0.tgz", + "integrity": "sha512-T4OnGmw5OIOEx9N0y1HeIpyU8cNc/UZKSwPWiH9yt8i2CNko5FS7F3uO4VoLgmarrVnc3v59FpT1ZctwIuTKeA==", + "requires": { + "@esri/arcgis-rest-types": "^2.11.0", + "tslib": "^1.9.3" + } + }, + "@esri/arcgis-rest-portal": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@esri/arcgis-rest-portal/-/arcgis-rest-portal-2.11.0.tgz", + "integrity": "sha512-bqs8TYkqZvtH7RbQy8F6Qk5K2EmQb1lTrS0KZ3snOdyeUYhV+DR7QEw1chiTtNRVoeZwX3hB0xTz8E67Ddmi1A==", + "requires": { + "@esri/arcgis-rest-types": "^2.11.0", + "tslib": "^1.9.3" + } + }, + "@esri/arcgis-rest-request": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@esri/arcgis-rest-request/-/arcgis-rest-request-2.11.0.tgz", + "integrity": "sha512-cIot16FUYc0coR2cSRTxqsOsfUZOL9U9br1PlCB+T95/HbCDJrL4dnA19wdiXLIfPunt1Xr7yr547PYPAZXU3w==", + "requires": { + "tslib": "^1.9.3" + } + }, + "@esri/arcgis-rest-types": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@esri/arcgis-rest-types/-/arcgis-rest-types-2.11.0.tgz", + "integrity": "sha512-4rbvFWxXHgJO/ePYdQ6razw7D8xU8ktVwJ8Fhk58GoRJr+5R99i/YtmGunk/TAEAssssaAt11W5WoQqVNoEAMA==" + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true }, "colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true }, "corser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", - "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=" + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "dev": true }, "debug": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, "requires": { "ms": "2.0.0" } @@ -29,6 +66,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-2.2.1.tgz", "integrity": "sha512-ztE4WqheoWLh3wv+HQwy7dACnvNY620coWpa+XqY6R2cVWgaAT2lUISU1Uf7JpdLLJCURktJOaA9av2AOzsyYQ==", + "dev": true, "requires": { "he": "^1.1.1", "mime": "^1.2.11", @@ -39,17 +77,20 @@ "eventemitter3": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=" + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", + "dev": true }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true }, "http-proxy": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", + "dev": true, "requires": { "eventemitter3": "1.x.x", "requires-port": "1.x.x" @@ -59,6 +100,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.10.0.tgz", "integrity": "sha1-sqRGsWqduH7TxiK6m+sbCFsSNKc=", + "dev": true, "requires": { "colors": "1.0.3", "corser": "~2.0.0", @@ -73,17 +115,20 @@ "mime": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", - "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=" + "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=", + "dev": true }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" }, @@ -91,24 +136,28 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true } } }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "opener": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", - "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=" + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "dev": true }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -117,7 +166,8 @@ "minimist": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true } } }, @@ -125,6 +175,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", + "dev": true, "requires": { "async": "^1.5.2", "debug": "^2.2.0", @@ -134,17 +185,25 @@ "qs": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", - "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=" + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "dev": true }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" }, "union": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz", "integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=", + "dev": true, "requires": { "qs": "~2.3.3" } @@ -152,12 +211,14 @@ "url-join": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.2.tgz", - "integrity": "sha1-wHJ1aWetJLi1nldBVRyqx49QuLc=" + "integrity": "sha1-wHJ1aWetJLi1nldBVRyqx49QuLc=", + "dev": true }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true } } } diff --git a/demos/oauth2-browser/package.json b/demos/oauth2-browser/package.json index 708aaf8789..2857eb864f 100644 --- a/demos/oauth2-browser/package.json +++ b/demos/oauth2-browser/package.json @@ -1,13 +1,13 @@ { "name": "@esri/arcgis-rest-demo-vanilla", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "Vanilla JavaScript demo of @esri/arcgis-rest-* packages", "author": "", "license": "Apache-2.0", "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "devDependencies": { "http-server": "*" diff --git a/demos/stream-response-to-file/package.json b/demos/stream-response-to-file/package.json index c3f6ee6941..b0ca2211f2 100644 --- a/demos/stream-response-to-file/package.json +++ b/demos/stream-response-to-file/package.json @@ -1,6 +1,6 @@ { "name": "stream-response-to-file", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "stream response to file with arcgis-rest-request", "main": "index.js", @@ -25,8 +25,8 @@ }, "homepage": "https://github.com/Esri/arcgis-rest-js#readme", "dependencies": { - "@esri/arcgis-rest-feature-layer": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-feature-layer": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "isomorphic-form-data": "^2.0.0", "node-fetch": "^2.3.0" } diff --git a/demos/tree-shaking-rollup/package.json b/demos/tree-shaking-rollup/package.json index 0f45a8c7df..318a7b1403 100644 --- a/demos/tree-shaking-rollup/package.json +++ b/demos/tree-shaking-rollup/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-tree-shaking-rollup", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "", "scripts": { @@ -19,7 +19,7 @@ "rollup-plugin-node-resolve": "^4.0.1" }, "dependencies": { - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" } } diff --git a/demos/tree-shaking-webpack/package.json b/demos/tree-shaking-webpack/package.json index afa222c772..f02f45eee7 100644 --- a/demos/tree-shaking-webpack/package.json +++ b/demos/tree-shaking-webpack/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-tree-shaking-webpack", - "version": "2.8.2", + "version": "2.14.1", "private": true, "description": "", "scripts": { @@ -18,7 +18,7 @@ "webpack-cli": "^3.3.0" }, "dependencies": { - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" } } diff --git a/demos/vue/package.json b/demos/vue/package.json index e617117f2d..bff294eebb 100644 --- a/demos/vue/package.json +++ b/demos/vue/package.json @@ -1,7 +1,7 @@ { "name": "@esri/arcgis-rest-demo-vue-with-popup", "description": "VueJS demo of @esri/arcgis-rest-* packages.", - "version": "2.8.2", + "version": "2.14.1", "author": "", "license": "Apache-2.0", "private": true, @@ -12,8 +12,8 @@ "start": "npm run serve" }, "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "vue": "^2.5.17", "vue-router": "^3.0.1", "vuex": "^3.0.1" diff --git a/demos/webmap-checker-sapper/package.json b/demos/webmap-checker-sapper/package.json index 900ba50219..0175ee971b 100644 --- a/demos/webmap-checker-sapper/package.json +++ b/demos/webmap-checker-sapper/package.json @@ -2,7 +2,7 @@ "name": "sapper-webmap-checker", "description": "this thing is cray cray", "private": true, - "version": "2.8.2", + "version": "2.14.1", "scripts": { "dev": "sapper dev", "build:legacy": "sapper build --legacy", @@ -13,9 +13,9 @@ "test": "run-p --race dev cy:run" }, "dependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2", + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1", "compression": "^1.7.1", "cookies": "^0.7.3", "cross-fetch": "^3.0.0", diff --git a/docs/src/srihashes.json b/docs/src/srihashes.json index 99e30dfae7..e88ee78909 100644 --- a/docs/src/srihashes.json +++ b/docs/src/srihashes.json @@ -1,12 +1,12 @@ { - "version": "2.8.2", + "version": "2.14.1", "packages": { - "@esri/arcgis-rest-auth": "sha384-/6idH5udu7WoPydfhFvnpUhR5o96YBNfgB8dA3zBLD/GhHKLjX/tPKlKnVwrca7T", - "@esri/arcgis-rest-feature-layer": "sha384-IWRnOXMdxFDADhc2O7XEnxCWrKKlDmNRbVt91YTecvJMaNK0h2kv3aNBhtiH9Bvq", - "@esri/arcgis-rest-geocoding": "sha384-mVU7gz/+b2J9zil9V3IGi1nNeYnYC6ppjKH6ztQIUBhfWB2lDawYWmCEFx0cDdG6", - "@esri/arcgis-rest-portal": "sha384-tipffgxouWmL2JMOu74K3Fdu8MYiuOsDx5eyxIVBr6WqVkf19p7kfkhLJnx3LtXb", - "@esri/arcgis-rest-request": "sha384-6qJUmGMTMBNnRab24nTr5YStMhtFKeOcqj9uUwly49S1Ol/5pv1R2i6hJlPkFNOc", - "@esri/arcgis-rest-routing": "sha384-QGEJXFi6Rjop4the1+Gusds8wFtZCOH+rdGiHQH0p66ksL0JF6wP80pOLne8LKs0", - "@esri/arcgis-rest-service-admin": "sha384-L7QcKdKxM+eLc2824X3wz/gSxBf+wD2MD6UhJ0sifPlBwqpfc2vMRBR4Oy4CL0Cw" + "@esri/arcgis-rest-auth": "sha384-mSj6TbxT3Aq9x0MoNQxif5CNXmCMrkvt+AR2HXnWC+RxRB20rQ4uBYYzOMx2NvpY", + "@esri/arcgis-rest-feature-layer": "sha384-lWNBSoUJqAEjCV5NwEexfHBrk7LYlh83j1qTdAhBkdEdTlFTIc/s/7Q8Mit3ANhi", + "@esri/arcgis-rest-geocoding": "sha384-oaOLPrBuIWQKn9FLT9svXku7hkQm/5wkmq4kb3RKnnbkTI84MTqwgkp1Wj4XqQT3", + "@esri/arcgis-rest-portal": "sha384-reDCcXiSC5qsE4Q4z3tMEUZCdtirqcGqzGLf5nDf8QhRhTbIKOOsYKtvcsnBJtv9", + "@esri/arcgis-rest-request": "sha384-cpPHBAeeBMJwOFO1/aEp4asw1SnWOLKspDuDMioo2S4SBTAcLEzfmD24oiL9ckaL", + "@esri/arcgis-rest-routing": "sha384-MnoZxNl/LGyI0lgH2x0MTbKD4RicYNcGAL+00D+131pnvFViqAB5xMY9/sRg/WyM", + "@esri/arcgis-rest-service-admin": "sha384-RpEUKaNPXtYKnjzM3n27FSLD3NKg32J34Vo/MInecH2duWq/9jISUNhgDzOzz95D" } } \ No newline at end of file diff --git a/lerna.json b/lerna.json index 3bf54cf924..d77d3eaa40 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ "packages/*", "demos/*" ], - "version": "2.8.2" + "version": "2.14.1" } diff --git a/package-lock.json b/package-lock.json index 423c79c419..7083a76aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-js", - "version": "2.8.2", + "version": "2.14.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -61,6 +61,23 @@ } } }, + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, "@improved/node": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@improved/node/-/node-1.1.1.tgz", @@ -77,9 +94,15 @@ } }, "@tootallnate/once": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz", - "integrity": "sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, "@types/estree": { @@ -174,6 +197,12 @@ "integrity": "sha512-YbPXbaynBTe0pVExPhL76TsWnxSPeFAvImIsmylpBWn/yfw+lHy+Q68aawvZHsgskT44ZAoeE67GM5f+Brekew==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -2062,6 +2091,12 @@ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", "dev": true }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", @@ -2296,9 +2331,9 @@ "dev": true }, "codecov": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.6.5.tgz", - "integrity": "sha512-v48WuDMUug6JXwmmfsMzhCHRnhUf8O3duqXvltaYJKrO1OekZWpB/eH6iIoaxMl8Qli0+u3OxptdsBOYiD7VAQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.0.tgz", + "integrity": "sha512-uIixKofG099NbUDyzRk1HdGtaG8O+PBUAg3wfmjwXw2+ek+PZp+puRvbTohqrVfuudaezivJHFgTtSC3M8MXww==", "dev": true, "requires": { "argv": "0.0.2", @@ -2487,6 +2522,12 @@ } } }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -5084,6 +5125,15 @@ "pinkie-promise": "^2.0.0" } }, + "find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "requires": { + "semver-regex": "^2.0.0" + } + }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -6941,27 +6991,115 @@ } }, "husky": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", - "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", - "dev": true, - "requires": { - "is-ci": "^1.0.10", - "normalize-path": "^1.0.0", - "strip-indent": "^2.0.0" + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz", + "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.5.1", + "cosmiconfig": "^6.0.0", + "find-versions": "^3.2.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" }, "dependencies": { - "normalize-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", - "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", - "dev": true + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } }, - "strip-indent": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -7059,6 +7197,16 @@ "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=", "dev": true }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -9030,6 +9178,12 @@ "integrity": "sha1-xDkrWH3qOFgMlnhXDm6OSfzlJiI=", "dev": true }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -10447,6 +10601,12 @@ "mimic-fn": "^1.0.0" } }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true + }, "openurl": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", @@ -10633,6 +10793,15 @@ "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-asn1": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", @@ -10782,9 +10951,9 @@ "dev": true }, "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, "requires": { "isarray": "0.0.1" @@ -10870,12 +11039,81 @@ "pinkie": "^2.0.0" } }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, "pkginfo": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", "dev": true }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", @@ -11601,6 +11839,12 @@ "global-modules": "^1.0.0" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -12111,6 +12355,12 @@ "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", @@ -12120,6 +12370,12 @@ "semver": "^5.0.3" } }, + "semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true + }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -14435,6 +14691,15 @@ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true }, + "yaml": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", + "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.7" + } + }, "yargs": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", diff --git a/package.json b/package.json index dd13fb3135..4e48a594c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-js", - "version": "2.8.2", + "version": "2.14.1", "description": "Compact, modular JavaScript wrappers for the ArcGIS REST API that run in Node.js and modern browsers.", "devDependencies": { "@types/fetch-mock": "^6.0.4", @@ -10,7 +10,7 @@ "acetate-cli": "^1.0.1", "changelog-parser": "^2.7.0", "cheerio": "^1.0.0-rc.3", - "codecov": "^3.3.0", + "codecov": "^3.7.0", "commitizen": "^3.1.1", "concurrently": "^3.5.1", "cpx": "^1.5.0", @@ -21,7 +21,7 @@ "fetch-mock": "^5.13.1", "gh-pages": "^1.1.0", "gh-release": "^3.4.0", - "husky": "^0.14.3", + "husky": "^4.2.3", "inspect-process": "^0.5.0", "jasmine": "^2.8.0", "jasmine-core": "^2.8.0", diff --git a/packages/arcgis-rest-auth/package.json b/packages/arcgis-rest-auth/package.json index b1a6b5cdcc..7c0c813e1a 100644 --- a/packages/arcgis-rest-auth/package.json +++ b/packages/arcgis-rest-auth/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-auth", - "version": "2.8.2", + "version": "2.14.1", "description": "Authentication helpers for @esri/arcgis-rest-js.", "main": "dist/node/index.js", "unpkg": "dist/umd/auth.umd.js", @@ -13,11 +13,11 @@ "dist/**" ], "dependencies": { - "@esri/arcgis-rest-types": "^2.8.2", + "@esri/arcgis-rest-types": "^2.14.1", "tslib": "^1.9.3" }, "devDependencies": { - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-request": "^2.14.1" }, "peerDependencies": { "@esri/arcgis-rest-request": "^2.0.0" diff --git a/packages/arcgis-rest-auth/src/UserSession.ts b/packages/arcgis-rest-auth/src/UserSession.ts index 68b3c7ba0f..da8d9a5a69 100644 --- a/packages/arcgis-rest-auth/src/UserSession.ts +++ b/packages/arcgis-rest-auth/src/UserSession.ts @@ -355,39 +355,33 @@ export class UserSession implements IAuthenticationManager { function completeSignIn(error: any, oauthInfo?: IFetchTokenResponse) { try { - if ( - popup && - win.opener && - win.opener.parent && - win.opener.parent[`__ESRI_REST_AUTH_HANDLER_${clientId}`] - ) { - const handlerFn = - win.opener.parent[`__ESRI_REST_AUTH_HANDLER_${clientId}`]; - if (handlerFn) { - handlerFn( - error ? JSON.stringify(error) : undefined, - JSON.stringify(oauthInfo) - ); + let handlerFn; + const handlerFnName = `__ESRI_REST_AUTH_HANDLER_${clientId}`; + + if (popup) { + // Guard b/c IE does not support window.opener + if (win.opener) { + if (win.opener.parent && win.opener.parent[handlerFnName]) { + handlerFn = win.opener.parent[handlerFnName]; + } else if (win.opener && win.opener[handlerFnName]) { + // support pop-out oauth from within an iframe + handlerFn = win.opener[handlerFnName]; + } + } else { + // IE + if (win !== win.parent && win.parent && win.parent[handlerFnName]) { + handlerFn = win.parent[handlerFnName]; + } } - win.close(); - return undefined; - } - - if ( - popup && - win !== win.parent && - win.parent && - win.parent[`__ESRI_REST_AUTH_HANDLER_${clientId}`] - ) { - const handlerFn = win.parent[`__ESRI_REST_AUTH_HANDLER_${clientId}`]; + // if we have a handler fn, call it and close the window if (handlerFn) { handlerFn( error ? JSON.stringify(error) : undefined, JSON.stringify(oauthInfo) ); + win.close(); + return undefined; } - win.close(); - return undefined; } } catch (e) { throw new ArcGISAuthError( @@ -616,6 +610,7 @@ export class UserSession implements IAuthenticationManager { private _tokenExpires: Date; private _refreshToken: string; private _refreshTokenExpires: Date; + private _pendingUserRequest: Promise; /** * Internal object to keep track of pending token requests. Used to prevent @@ -728,12 +723,12 @@ export class UserSession implements IAuthenticationManager { * @returns A Promise that will resolve with the data from the response. */ public getUser(requestOptions?: IRequestOptions): Promise { - if (this._user && this._user.username === this.username) { + if (this._pendingUserRequest) { + return this._pendingUserRequest; + } else if (this._user) { return Promise.resolve(this._user); } else { - const url = `${this.portal}/community/users/${encodeURIComponent( - this.username - )}`; + const url = `${this.portal}/community/self`; const options = { httpMethod: "GET", @@ -741,10 +736,36 @@ export class UserSession implements IAuthenticationManager { ...requestOptions, rawResponse: false } as IRequestOptions; - return request(url, options).then(response => { + + this._pendingUserRequest = request(url, options).then(response => { this._user = response; + this._pendingUserRequest = null; return response; }); + + return this._pendingUserRequest; + } + } + + /** + * Returns the username for the currently logged in [user](https://developers.arcgis.com/rest/users-groups-and-items/user.htm). Subsequent calls will *not* result in additional web traffic. This is also used internally when a username is required for some requests but is not present in the options. + * + * * ```js + * session.getUsername() + * .then(response => { + * console.log(response); // "casey_jones" + * }) + * ``` + */ + public getUsername() { + if (this.username) { + return Promise.resolve(this.username); + } else if (this._user) { + return Promise.resolve(this._user.username); + } else { + return this.getUser().then(user => { + return user.username; + }); } } @@ -794,6 +815,7 @@ export class UserSession implements IAuthenticationManager { ): Promise { // make sure subsequent calls to getUser() don't returned cached metadata this._user = null; + if (this.username && this.password) { return this.refreshWithUsernameAndPassword(requestOptions); } @@ -931,6 +953,10 @@ export class UserSession implements IAuthenticationManager { * Returns an unexpired token for the current `portal`. */ private getFreshToken(requestOptions?: ITokenRequestOptions) { + if (this.token && !this.tokenExpires) { + return Promise.resolve(this.token); + } + if ( this.token && this.tokenExpires && diff --git a/packages/arcgis-rest-auth/test/UserSession.test.ts b/packages/arcgis-rest-auth/test/UserSession.test.ts index 4cb4c1a403..0cf45b7257 100644 --- a/packages/arcgis-rest-auth/test/UserSession.test.ts +++ b/packages/arcgis-rest-auth/test/UserSession.test.ts @@ -270,6 +270,22 @@ describe("UserSession", () => { }); }); + it("should pass through a token when no token expiration is present", done => { + const session = new UserSession({ + token: "token" + }); + + session + .getToken("https://www.arcgis.com/sharing/rest/portals/self") + .then(token1 => { + expect(token1).toBe("token"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should generate a token for an untrusted, federated server", done => { const session = new UserSession({ clientId: "id", @@ -920,7 +936,7 @@ describe("UserSession", () => { expect(session.ssl).toBe(false); }); - it("should callback to create a new user session if finds a valid opener", done => { + it("should callback to create a new user session if finds a valid opener.parent", done => { const MockWindow = { opener: { parent: { @@ -956,6 +972,40 @@ describe("UserSession", () => { ); }); + it("should callback to create a new user session if finds a valid opener (Iframe support)", done => { + const MockWindow = { + opener: { + __ESRI_REST_AUTH_HANDLER_clientId( + errorString: string, + oauthInfoString: string + ) { + const oauthInfo = JSON.parse(oauthInfoString); + expect(oauthInfo.token).toBe("token"); + expect(oauthInfo.username).toBe("c@sey"); + expect(oauthInfo.ssl).toBe(true); + expect(new Date(oauthInfo.expires).getTime()).toBeGreaterThan( + Date.now() + ); + } + }, + close() { + done(); + }, + location: { + href: + "https://example-app.com/redirect-uri#access_token=token&expires_in=1209600&username=c%40sey&ssl=true" + } + }; + + UserSession.completeOAuth2( + { + clientId: "clientId", + redirectUri: "https://example-app.com/redirect-uri" + }, + MockWindow + ); + }); + it("should callback to create a new user session if finds a valid parent", done => { const MockWindow = { parent: { @@ -1114,7 +1164,7 @@ describe("UserSession", () => { it("should cache metadata about the user", done => { // we intentionally only mock one response fetchMock.once( - "https://www.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=token", + "https://www.arcgis.com/sharing/rest/community/self?f=json&token=token", { username: "jsmith", fullName: "John Smith", @@ -1152,6 +1202,93 @@ describe("UserSession", () => { fail(e); }); }); + + it("should never make more then 1 request", done => { + // we intentionally only mock one response + fetchMock.once( + "https://www.arcgis.com/sharing/rest/community/self?f=json&token=token", + { + username: "jsmith", + fullName: "John Smith", + role: "org_publisher" + } + ); + + const session = new UserSession({ + clientId: "clientId", + redirectUri: "https://example-app.com/redirect-uri", + token: "token", + tokenExpires: TOMORROW, + refreshToken: "refreshToken", + refreshTokenExpires: TOMORROW, + refreshTokenTTL: 1440, + username: "jsmith", + password: "123456" + }); + + Promise.all([session.getUser(), session.getUser()]) + .then(() => { + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); + + describe(".getUsername()", () => { + afterEach(fetchMock.restore); + + it("should fetch the username via getUser()", done => { + // we intentionally only mock one response + fetchMock.once( + "https://www.arcgis.com/sharing/rest/community/self?f=json&token=token", + { + username: "jsmith" + } + ); + + const session = new UserSession({ + token: "token" + }); + + session + .getUsername() + .then(response => { + expect(response).toEqual("jsmith"); + + // also test getting it from the cache. + session + .getUsername() + .then(username => { + done(); + + expect(username).toEqual("jsmith"); + }) + .catch(e => { + fail(e); + }); + }) + .catch(e => { + fail(e); + }); + }); + + it("should use a username if passed in the session", done => { + const session = new UserSession({ + username: "jsmith" + }); + + session + .getUsername() + .then(response => { + expect(response).toEqual("jsmith"); + done(); + }) + .catch(e => { + fail(e); + }); + }); }); describe("to/fromCredential()", () => { @@ -1251,7 +1388,7 @@ describe("UserSession", () => { }); describe("non-federated server", () => { - it("shouldnt fetch a fresh token if the current one isnt expired.", done => { + it("shouldnt fetch a fresh token if the current one isn't expired.", done => { const MOCK_USER_SESSION = new UserSession({ username: "c@sey", password: "123456", @@ -1377,7 +1514,7 @@ describe("UserSession", () => { }) .catch(err => { expect(err.code).toBe("NOT_FEDERATED"); - expect(err.originalMessage).toBe( + expect(err.originalMessage).toEqual( "https://fakeserver2.com/arcgis/rest/services/Fake/MapServer/ is not federated with any portal and is not explicitly trusted." ); done(); diff --git a/packages/arcgis-rest-feature-layer/package.json b/packages/arcgis-rest-feature-layer/package.json index 596f0bd56f..c6d02ab33a 100644 --- a/packages/arcgis-rest-feature-layer/package.json +++ b/packages/arcgis-rest-feature-layer/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-feature-layer", - "version": "2.8.2", + "version": "2.14.1", "description": "Feature layer query and edit helpers for @esri/arcgis-rest-js", "main": "dist/node/index.js", "unpkg": "dist/umd/feature-layer.umd.js", @@ -13,12 +13,12 @@ "dist/**" ], "dependencies": { - "@esri/arcgis-rest-types": "^2.8.2", + "@esri/arcgis-rest-types": "^2.14.1", "tslib": "^1.9.3" }, "devDependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "peerDependencies": { "@esri/arcgis-rest-auth": "^2.0.0", diff --git a/packages/arcgis-rest-feature-layer/test/attachments.test.ts b/packages/arcgis-rest-feature-layer/test/attachments.test.ts index ca054b31bd..71a5a8b1e3 100644 --- a/packages/arcgis-rest-feature-layer/test/attachments.test.ts +++ b/packages/arcgis-rest-feature-layer/test/attachments.test.ts @@ -53,9 +53,7 @@ describe("attachment methods", () => { expect(fetchMock.called()).toBeTruthy(); const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual( - `${requestOptions.url}/${ - requestOptions.featureId - }/attachments?f=json&gdbVersion=SDE.DEFAULT` + `${requestOptions.url}/${requestOptions.featureId}/attachments?f=json&gdbVersion=SDE.DEFAULT` ); expect(options.method).toBe("GET"); expect(getAttachmentsResponse.attachmentInfos.length).toEqual(2); diff --git a/packages/arcgis-rest-feature-layer/test/query.test.ts b/packages/arcgis-rest-feature-layer/test/query.test.ts index 7ce341817f..403581bf99 100644 --- a/packages/arcgis-rest-feature-layer/test/query.test.ts +++ b/packages/arcgis-rest-feature-layer/test/query.test.ts @@ -106,9 +106,7 @@ describe("getFeature() and queryFeatures()", () => { expect(fetchMock.called()).toBeTruthy(); const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual( - `${ - requestOptions.url - }/query?f=json&where=Condition%3D'Poor'&outFields=FID%2CTree_ID%2CCmn_Name%2CCondition&geometry=%7B%7D&geometryType=esriGeometryPolygon&orderByFields=test` + `${requestOptions.url}/query?f=json&where=Condition%3D'Poor'&outFields=FID%2CTree_ID%2CCmn_Name%2CCondition&geometry=%7B%7D&geometryType=esriGeometryPolygon&orderByFields=test` ); expect(options.method).toBe("GET"); // expect(response.attributes.FID).toEqual(42); @@ -129,9 +127,7 @@ describe("getFeature() and queryFeatures()", () => { expect(fetchMock.called()).toBeTruthy(); const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); expect(url).toEqual( - `${ - requestOptions.url - }/queryRelatedRecords?f=json&definitionExpression=1%3D1&outFields=*&relationshipId=0` + `${requestOptions.url}/queryRelatedRecords?f=json&definitionExpression=1%3D1&outFields=*&relationshipId=0` ); expect(options.method).toBe("GET"); done(); diff --git a/packages/arcgis-rest-geocoding/package.json b/packages/arcgis-rest-geocoding/package.json index 728ef75d60..211e639fdc 100644 --- a/packages/arcgis-rest-geocoding/package.json +++ b/packages/arcgis-rest-geocoding/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-geocoding", - "version": "2.8.2", + "version": "2.14.1", "description": "Geocoding helpers for @esri/arcgis-rest-js", "main": "dist/node/index.js", "unpkg": "dist/umd/geocoding.umd.js", @@ -13,12 +13,12 @@ "dist/**" ], "dependencies": { - "@esri/arcgis-rest-types": "^2.8.2", + "@esri/arcgis-rest-types": "^2.14.1", "tslib": "^1.9.3" }, "devDependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "peerDependencies": { "@esri/arcgis-rest-auth": "^2.0.0", diff --git a/packages/arcgis-rest-portal/package.json b/packages/arcgis-rest-portal/package.json index 18268a2840..1be75fbc5d 100644 --- a/packages/arcgis-rest-portal/package.json +++ b/packages/arcgis-rest-portal/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-portal", - "version": "2.8.2", + "version": "2.14.1", "description": "ArcGIS Online and Enterprise content and user helpers for @esri/arcgis-rest-request", "main": "dist/node/index.js", "unpkg": "dist/umd/portal.umd.js", @@ -13,12 +13,12 @@ "dist/**" ], "dependencies": { - "@esri/arcgis-rest-types": "^2.8.2", + "@esri/arcgis-rest-types": "^2.14.1", "tslib": "^1.9.3" }, "devDependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "peerDependencies": { "@esri/arcgis-rest-auth": "^2.0.0", diff --git a/packages/arcgis-rest-portal/src/groups/get.ts b/packages/arcgis-rest-portal/src/groups/get.ts index f485261f2c..14e5caab7e 100644 --- a/packages/arcgis-rest-portal/src/groups/get.ts +++ b/packages/arcgis-rest-portal/src/groups/get.ts @@ -9,6 +9,16 @@ import { import { IPagingParams, IGroup, IItem, IUser } from "@esri/arcgis-rest-types"; import { getPortalUrl } from "../util/get-portal-url"; +export interface IGroupCategorySchema { + categorySchema: IGroupCategory[]; +} + +export interface IGroupCategory { + title: string; + description?: string; + categories?: IGroupCategory[]; +} + export interface IGetGroupContentOptions extends IRequestOptions { paging: IPagingParams; } @@ -53,6 +63,30 @@ export function getGroup( return request(url, options); } +/** + * Gets the category schema set on a group + * + * @param id - Group Id + * @param requestOptions - Options for the request + * @returns A promise that will resolve with JSON of group's category schema + * @see https://developers.arcgis.com/rest/users-groups-and-items/group-category-schema.htm + */ +export function getGroupCategorySchema( + id: string, + requestOptions?: IRequestOptions +): Promise { + const url = `${getPortalUrl( + requestOptions + )}/community/groups/${id}/categorySchema`; + + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + return request(url, options); +} + /** * Returns the content of a Group. Since the group may contain 1000s of items * the requestParams allow for paging. diff --git a/packages/arcgis-rest-portal/src/groups/search.ts b/packages/arcgis-rest-portal/src/groups/search.ts index 99d78a83aa..9f4dd9748f 100644 --- a/packages/arcgis-rest-portal/src/groups/search.ts +++ b/packages/arcgis-rest-portal/src/groups/search.ts @@ -1,9 +1,13 @@ /* Copyright (c) 2018-2019 Environmental Systems Research Institute, Inc. * Apache-2.0 */ -import { IGroup } from "@esri/arcgis-rest-types"; +import { IItem, IGroup } from "@esri/arcgis-rest-types"; import { SearchQueryBuilder } from "../util/SearchQueryBuilder"; -import { ISearchOptions, ISearchResult } from "../util/search"; +import { + ISearchOptions, + ISearchGroupContentOptions, + ISearchResult +} from "../util/search"; import { genericSearch } from "../util/generic-search"; /** @@ -23,3 +27,21 @@ export function searchGroups( ): Promise> { return genericSearch(search, "group"); } + +/** + * ```js + * import { searchGroupContent } from "@esri/arcgis-rest-portal"; + * // + * searchGroupContent('water') + * .then(response) // response.total => 355 + * ``` + * Search a portal for items in a group. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/group-content-search.htm) for more information. + * + * @param options - RequestOptions object amended with search parameters. + * @returns A Promise that will resolve with the data from the response. + */ +export function searchGroupContent( + options: ISearchGroupContentOptions +): Promise> { + return genericSearch(options, "groupContent"); +} diff --git a/packages/arcgis-rest-portal/src/groups/update-user-membership.ts b/packages/arcgis-rest-portal/src/groups/update-user-membership.ts new file mode 100644 index 0000000000..bcb3378005 --- /dev/null +++ b/packages/arcgis-rest-portal/src/groups/update-user-membership.ts @@ -0,0 +1,63 @@ +/* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ +import { IUserRequestOptions } from "@esri/arcgis-rest-auth"; +import { getPortalUrl } from "../util/get-portal-url"; +import { request } from "@esri/arcgis-rest-request"; + +export interface IUpdateGroupUsersResult { + /** + * Array of results + */ + results: any[]; +} + +export interface IUpdateGroupUsersOptions extends IUserRequestOptions { + /** + * Group ID + */ + id: string; + /** + * An array of usernames to be updated + */ + users: string[]; + /** + * Membership Type to update to + */ + newMemberType: "member" | "admin"; +} + +/** + * ```js + * import { updateUserMemberships } from "@esri/arcgis-rest-portal"; + * // + * updateUserMemberships({ + * id: groupId, + * admins: ["username3"], + * authentication + * }) + * .then(response); + * ``` + * Change the user membership levels of existing users in a group + * + * @param requestOptions - Options for the request + * @returns A Promise + */ +export function updateUserMemberships( + requestOptions: IUpdateGroupUsersOptions +): Promise { + const url = `${getPortalUrl(requestOptions)}/community/groups/${ + requestOptions.id + }/updateUsers`; + const opts: any = { + authentication: requestOptions.authentication, + params: {} + }; + // add the correct params depending on the type of membership we are changing to + if (requestOptions.newMemberType === "admin") { + opts.params.admins = requestOptions.users; + } else { + opts.params.users = requestOptions.users; + } + // make the request + return request(url, opts); +} diff --git a/packages/arcgis-rest-portal/src/index.ts b/packages/arcgis-rest-portal/src/index.ts index ba8b0ae081..df75ae2103 100644 --- a/packages/arcgis-rest-portal/src/index.ts +++ b/packages/arcgis-rest-portal/src/index.ts @@ -2,7 +2,9 @@ * Apache-2.0 */ export * from "./items/add"; +export * from "./items/content"; export * from "./items/create"; +export * from "./items/export"; export * from "./items/get"; export * from "./items/protect"; export * from "./items/reassign"; @@ -21,6 +23,7 @@ export * from "./groups/protect"; export * from "./groups/remove"; export * from "./groups/search"; export * from "./groups/update"; +export * from "./groups/update-user-membership"; export * from "./groups/join"; export * from "./users/get-user"; @@ -36,6 +39,7 @@ export * from "./sharing/group-sharing"; export * from "./sharing/helpers"; export * from "./util/get-portal"; +export * from "./util/get-portal-settings"; export * from "./util/get-portal-url"; export * from "./util/search"; export * from "./util/SearchQueryBuilder"; @@ -43,10 +47,12 @@ export * from "./util/SearchQueryBuilder"; // export * from "./util/generic-search"; because its an internal utility method export { IPagingParams, + IPagedResponse, IUser, IItemAdd, IItemUpdate, IItem, + IFolder, IGroupAdd, IGroup, GroupMembership diff --git a/packages/arcgis-rest-portal/src/items/add.ts b/packages/arcgis-rest-portal/src/items/add.ts index af92732f6a..80c6c13b6c 100644 --- a/packages/arcgis-rest-portal/src/items/add.ts +++ b/packages/arcgis-rest-portal/src/items/add.ts @@ -42,7 +42,6 @@ export interface IAddItemDataOptions extends IUserItemOptions { export function addItemData( requestOptions: IAddItemDataOptions ): Promise { - const owner = determineOwner(requestOptions); const options: any = { item: { id: requestOptions.id, @@ -77,18 +76,18 @@ export function addItemData( export function addItemRelationship( requestOptions: IManageItemRelationshipOptions ): Promise<{ success: boolean }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl( - requestOptions - )}/content/users/${owner}/addRelationship`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl( + requestOptions + )}/content/users/${owner}/addRelationship`; - const options = appendCustomParams( - requestOptions, - ["originItemId", "destinationItemId", "relationshipType"], - { params: { ...requestOptions.params } } - ); - - return request(url, options); + const options = appendCustomParams( + requestOptions, + ["originItemId", "destinationItemId", "relationshipType"], + { params: { ...requestOptions.params } } + ); + return request(url, options); + }); } /** @@ -121,20 +120,22 @@ export function addItemRelationship( export function addItemResource( requestOptions: IItemResourceOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/addResources`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/addResources`; - requestOptions.params = { - file: requestOptions.resource, - fileName: requestOptions.name, - text: requestOptions.content, - access: requestOptions.private ? "private" : "inherit", - ...requestOptions.params - }; + requestOptions.params = { + file: requestOptions.resource, + fileName: requestOptions.name, + resourcesPrefix: requestOptions.prefix, + text: requestOptions.content, + access: requestOptions.private ? "private" : "inherit", + ...requestOptions.params + }; - return request(url, requestOptions); + return request(url, requestOptions); + }); } /** @@ -158,16 +159,17 @@ export function addItemResource( export function addItemPart( requestOptions?: IItemPartOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/addPart`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/addPart`; - const options = appendCustomParams( - requestOptions, - ["file", "partNum"], - { params: { ...requestOptions.params } } - ); + const options = appendCustomParams( + requestOptions, + ["file", "partNum"], + { params: { ...requestOptions.params } } + ); - return request(url, options); + return request(url, options); + }); } diff --git a/packages/arcgis-rest-portal/src/items/content.ts b/packages/arcgis-rest-portal/src/items/content.ts new file mode 100644 index 0000000000..2b1af05c22 --- /dev/null +++ b/packages/arcgis-rest-portal/src/items/content.ts @@ -0,0 +1,67 @@ +import { request, IRequestOptions } from "@esri/arcgis-rest-request"; +import { + IPagingParams, + IItem, + IFolder, + IPagedResponse, +} from "@esri/arcgis-rest-types"; +import { getPortalUrl } from "../util/get-portal-url"; +import { determineOwner } from "./helpers"; + +export type UnixTime = number; + +export interface IUserContentRequestOptions + extends IPagingParams, + IRequestOptions { + owner?: string; + folderId?: string; +} + +export interface IUserContentResponse extends IPagedResponse { + username: string; + currentFolder?: IFolder; + items: IItem[]; + folders: IFolder[]; +} + +/** + * ```js + * import { getUserContent } from "@esri/arcgis-rest-portal"; + * // + * getUserContent({ + * owner: 'geemike', + * folderId: 'bao7', + * start: 1, + * num: 20, + * authentication + * }) + * ``` + * Returns a listing of the user's content. If the `username` is not supplied, it defaults to the username of the authenticated user. If `start` is not specificed it defaults to the first page. + * If the `num` is not supplied it is defaulted to 10. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/user-content.htm) for more information. + * + * @param requestOptions - Options for the request + * @returns A Promise + */ +export const getUserContent = ( + requestOptions: IUserContentRequestOptions +): Promise => { + const { + folderId: folder, + start = 1, + num = 10, + authentication, + } = requestOptions; + const suffix = folder ? `/${folder}` : ""; + + return determineOwner(requestOptions) + .then((owner) => `${getPortalUrl(requestOptions)}/content/users/${owner}${suffix}`) + .then((url) => request(url, { + httpMethod: "GET", + authentication, + params: { + start, + num, + }, + }) + ); +}; diff --git a/packages/arcgis-rest-portal/src/items/create.ts b/packages/arcgis-rest-portal/src/items/create.ts index 7fd1076909..9b1d41d3a7 100644 --- a/packages/arcgis-rest-portal/src/items/create.ts +++ b/packages/arcgis-rest-portal/src/items/create.ts @@ -46,17 +46,17 @@ export interface ICreateItemResponse extends IUpdateItemResponse { export function createFolder( requestOptions: ICreateFolderOptions ): Promise { - const owner = determineOwner(requestOptions); + return determineOwner(requestOptions).then(owner => { + const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`; + const url = `${baseUrl}/createFolder`; - const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`; - const url = `${baseUrl}/createFolder`; + requestOptions.params = { + title: requestOptions.title, + ...requestOptions.params + }; - requestOptions.params = { - title: requestOptions.title, - ...requestOptions.params - }; - - return request(url, requestOptions); + return request(url, requestOptions); + }); } /** @@ -91,39 +91,40 @@ export function createItemInFolder( ); } - const owner = determineOwner(requestOptions); - const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`; - let url = `${baseUrl}/addItem`; - - if (requestOptions.folderId) { - url = `${baseUrl}/${requestOptions.folderId}/addItem`; - } + return determineOwner(requestOptions).then(owner => { + const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`; + let url = `${baseUrl}/addItem`; - requestOptions.params = { - ...requestOptions.params, - ...serializeItem(requestOptions.item) - }; - - // serialize the item into something Portal will accept - const options = appendCustomParams( - requestOptions, - [ - "owner", - "folderId", - "file", - "dataUrl", - "text", - "async", - "multipart", - "filename", - "overwrite" - ], - { - params: { ...requestOptions.params } + if (requestOptions.folderId) { + url = `${baseUrl}/${requestOptions.folderId}/addItem`; } - ); - return request(url, options); + requestOptions.params = { + ...requestOptions.params, + ...serializeItem(requestOptions.item) + }; + + // serialize the item into something Portal will accept + const options = appendCustomParams( + requestOptions, + [ + "owner", + "folderId", + "file", + "dataUrl", + "text", + "async", + "multipart", + "filename", + "overwrite" + ], + { + params: { ...requestOptions.params } + } + ); + + return request(url, options); + }); } /** diff --git a/packages/arcgis-rest-portal/src/items/export.ts b/packages/arcgis-rest-portal/src/items/export.ts new file mode 100644 index 0000000000..a6bfa61599 --- /dev/null +++ b/packages/arcgis-rest-portal/src/items/export.ts @@ -0,0 +1,80 @@ +import { request } from "@esri/arcgis-rest-request"; +import { determineOwner, IUserItemOptions } from './helpers'; +import { getPortalUrl } from "../util/get-portal-url"; +import { ISpatialReference } from '@esri/arcgis-rest-types'; + +type ExportFormat = 'Shapefile' | 'CSV' | 'File Geodatabase' | 'Feature Collection' | 'GeoJson' | 'Scene Package' | 'KML' | 'Excel'; + +export interface IExportLayerInfo { + id: number; + where?: string; + includeGeometry?: boolean; + xColumnName?: string; + yColumnName?: string; +} + +export interface IExportParameters { + layers: IExportLayerInfo[]; + targetSR?: ISpatialReference | string; +} + +export interface IExportItemRequestOptions extends IUserItemOptions { + title?: string; + exportFormat: ExportFormat; + exportParameters: IExportParameters; +} + +export interface IExportItemResponse { + type: string; + size: number; + jobId: string; + exportItemId: string; + serviceItemId: string; + exportFormat: ExportFormat; +} + +/** + * ```js + * import { exportItem } from "@esri/arcgis-rest-portal"; + * // + * exportItem({ + * id: '3daf', + * owner: 'geemike', + * exportFormat: 'CSV', + * exportParameters: { + * layers: [ + * { id: 0 }, + * { id: 1, where: 'POP1999 > 100000' } + * ] + * }, + * authentication, + * }) + * ``` + * Exports an item from the portal. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/export-item.htm) for more information. + * + * @param requestOptions - Options for the request + * @returns A Promise + */ +export const exportItem = (requestOptions: IExportItemRequestOptions) : Promise => { + const { + authentication, + id: itemId, + title, + exportFormat, + exportParameters + } = requestOptions; + + return determineOwner(requestOptions) + .then(owner => `${getPortalUrl(requestOptions)}/content/users/${owner}/export`) + .then(url => request(url, { + httpMethod: 'POST', + authentication, + params: { + itemId, + title, + exportFormat, + exportParameters + } + }) + ); +} diff --git a/packages/arcgis-rest-portal/src/items/get.ts b/packages/arcgis-rest-portal/src/items/get.ts index 6c26304128..de5918d022 100644 --- a/packages/arcgis-rest-portal/src/items/get.ts +++ b/packages/arcgis-rest-portal/src/items/get.ts @@ -80,7 +80,7 @@ export function getItemData( /* if the item doesn't include data, the response will be empty and the internal call to response.json() will fail */ const emptyResponseErr = RegExp( - /Unexpected end of (JSON input|data at line 1 column 1)/i + /The string did not match the expected pattern|(Unexpected end of (JSON input|data at line 1 column 1))/i ); /* istanbul ignore else */ if (emptyResponseErr.test(err.message)) { @@ -228,18 +228,19 @@ export interface IGetItemStatusResponse { export function getItemStatus( requestOptions: IItemStatusOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/status`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/status`; - const options = appendCustomParams( - requestOptions, - ["jobId", "jobType"], - { params: { ...requestOptions.params } } - ); + const options = appendCustomParams( + requestOptions, + ["jobId", "jobType"], + { params: { ...requestOptions.params } } + ); - return request(url, options); + return request(url, options); + }); } export interface IGetItemPartsResponse { @@ -265,9 +266,10 @@ export interface IGetItemPartsResponse { export function getItemParts( requestOptions: IUserItemOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/parts`; - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/parts`; + return request(url, requestOptions); + }); } diff --git a/packages/arcgis-rest-portal/src/items/helpers.ts b/packages/arcgis-rest-portal/src/items/helpers.ts index 93ee193a47..b96a28ddb8 100644 --- a/packages/arcgis-rest-portal/src/items/helpers.ts +++ b/packages/arcgis-rest-portal/src/items/helpers.ts @@ -1,7 +1,7 @@ /* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc. * Apache-2.0 */ -import { IRequestOptions } from "@esri/arcgis-rest-request"; +import { IRequestOptions, ArcGISRequestError } from "@esri/arcgis-rest-request"; import { IItemAdd, IItemUpdate, IItem } from "@esri/arcgis-rest-types"; import { IUserRequestOptions } from "@esri/arcgis-rest-auth"; @@ -74,11 +74,26 @@ export interface IManageItemRelationshipOptions extends IUserRequestOptions { relationshipType: ItemRelationshipType; } +export interface IItemInfoOptions extends IUserItemOptions { + /** + * Subfolder for added information. + */ + folderName?: string; + /** + * Object to store + */ + file: any; +} + export interface IItemResourceOptions extends IUserItemOptions { /** * New resource filename. */ name?: string; + /** + * Folder in which to store the new resource. + */ + prefix?: string; /** * Text input to be added as a file resource. */ @@ -155,6 +170,13 @@ export interface IUpdateItemResponse { id: string; } +export interface IItemInfoResponse { + success: boolean; + itemId: string; + owner: string; + folder: string; +} + export interface IItemResourceResponse { success: boolean; itemId: string; @@ -220,15 +242,20 @@ export function serializeItem(item: IItemAdd | IItemUpdate | IItem): any { } /** - * requestOptions.owner is given priority, requestOptions.item.owner will be checked next. If neither are present, authentication.username will be assumed. + * `requestOptions.owner` is given priority, `requestOptions.item.owner` will be checked next. If neither are present, `authentication.getUserName()` will be used instead. */ -export function determineOwner(requestOptions: any): string { +export function determineOwner(requestOptions: any): Promise { if (requestOptions.owner) { - return requestOptions.owner; - } - if (requestOptions.item && requestOptions.item.owner) { - return requestOptions.item.owner; + return Promise.resolve(requestOptions.owner); + } else if (requestOptions.item && requestOptions.item.owner) { + return Promise.resolve(requestOptions.item.owner); + } else if (requestOptions.authentication && requestOptions.authentication.getUsername) { + return requestOptions.authentication.getUsername(); } else { - return requestOptions.authentication.username; + return Promise.reject( + new Error( + "Could not determine the owner of this item. Pass the `owner`, `item.owner`, or `authentication` option." + ) + ); } } diff --git a/packages/arcgis-rest-portal/src/items/protect.ts b/packages/arcgis-rest-portal/src/items/protect.ts index 46f634b271..8f4328d5f2 100644 --- a/packages/arcgis-rest-portal/src/items/protect.ts +++ b/packages/arcgis-rest-portal/src/items/protect.ts @@ -15,11 +15,12 @@ import { IUserItemOptions, determineOwner } from "./helpers"; export function protectItem( requestOptions: IUserItemOptions ): Promise<{ success: boolean }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/protect`; - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/protect`; + return request(url, requestOptions); + }); } /** @@ -31,9 +32,10 @@ export function protectItem( export function unprotectItem( requestOptions: IUserItemOptions ): Promise<{ success: boolean }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/unprotect`; - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/unprotect`; + return request(url, requestOptions); + }); } diff --git a/packages/arcgis-rest-portal/src/items/remove.ts b/packages/arcgis-rest-portal/src/items/remove.ts index c0826aa0a7..efc6d7d39d 100644 --- a/packages/arcgis-rest-portal/src/items/remove.ts +++ b/packages/arcgis-rest-portal/src/items/remove.ts @@ -29,11 +29,12 @@ import { export function removeItem( requestOptions: IUserItemOptions ): Promise<{ success: boolean; itemId: string }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/delete`; - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/delete`; + return request(url, requestOptions); + }); } /** @@ -56,18 +57,19 @@ export function removeItem( export function removeItemRelationship( requestOptions: IManageItemRelationshipOptions ): Promise<{ success: boolean }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl( - requestOptions - )}/content/users/${owner}/removeRelationship`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl( + requestOptions + )}/content/users/${owner}/removeRelationship`; - const options = appendCustomParams( - requestOptions, - ["originItemId", "destinationItemId", "relationshipType"], - { params: { ...requestOptions.params } } - ); + const options = appendCustomParams( + requestOptions, + ["originItemId", "destinationItemId", "relationshipType"], + { params: { ...requestOptions.params } } + ); - return request(url, options); + return request(url, options); + }); } /** @@ -79,17 +81,18 @@ export function removeItemRelationship( export function removeItemResource( requestOptions: IItemResourceOptions ): Promise<{ success: boolean }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/removeResources`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/removeResources`; - // mix in user supplied params - requestOptions.params = { - ...requestOptions.params, - resource: requestOptions.resource - }; - return request(url, requestOptions); + // mix in user supplied params + requestOptions.params = { + ...requestOptions.params, + resource: requestOptions.resource + }; + return request(url, requestOptions); + }); } /** @@ -121,11 +124,12 @@ export function removeFolder( title: string; }; }> { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl( - requestOptions - )}/content/users/${encodeURIComponent(owner)}/${ - requestOptions.folderId - }/delete`; - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl( + requestOptions + )}/content/users/${encodeURIComponent(owner)}/${ + requestOptions.folderId + }/delete`; + return request(url, requestOptions); + }); } diff --git a/packages/arcgis-rest-portal/src/items/update.ts b/packages/arcgis-rest-portal/src/items/update.ts index 8fdb7c9721..bff3599d03 100644 --- a/packages/arcgis-rest-portal/src/items/update.ts +++ b/packages/arcgis-rest-portal/src/items/update.ts @@ -8,7 +8,9 @@ import { getPortalUrl } from "../util/get-portal-url"; import { ICreateUpdateItemOptions, IMoveItemResponse, + IItemInfoOptions, IItemResourceOptions, + IItemInfoResponse, IItemResourceResponse, IUpdateItemResponse, serializeItem, @@ -53,18 +55,54 @@ export interface IMoveItemOptions extends ICreateUpdateItemOptions { export function updateItem( requestOptions: IUpdateItemOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.item.id - }/update`; - - // serialize the item into something Portal will accept - requestOptions.params = { - ...requestOptions.params, - ...serializeItem(requestOptions.item) - }; - - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.item.id + }/update`; + + // serialize the item into something Portal will accept + requestOptions.params = { + ...requestOptions.params, + ...serializeItem(requestOptions.item) + }; + + return request(url, requestOptions); + }); +} + +/** + * ```js + * import { updateItemInfo } from "@esri/arcgis-rest-portal"; + * // + * updateItemInfo({ + * id: '3ef', + * file: file, + * authentication + * }) + * .then(response) + * ``` + * Update an info file associated with an item. See the [REST Documentation](https://developers.arcgis.com/rest/users-groups-and-items/update-info.htm) for more information. + * + * @param requestOptions - Options for the request + * @returns A Promise that updates an item info file. + */ +export function updateItemInfo( + requestOptions: IItemInfoOptions +): Promise { + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl( + requestOptions as IRequestOptions + )}/content/users/${owner}/items/${requestOptions.id}/updateinfo`; + + // mix in user supplied params + requestOptions.params = { + folderName: requestOptions.folderName, + file: requestOptions.file, + ...requestOptions.params + }; + + return request(url, requestOptions); + }); } /** @@ -87,26 +125,27 @@ export function updateItem( export function updateItemResource( requestOptions: IItemResourceOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl( - requestOptions as IRequestOptions - )}/content/users/${owner}/items/${requestOptions.id}/updateResources`; - - // mix in user supplied params - requestOptions.params = { - file: requestOptions.resource, - fileName: requestOptions.name, - text: requestOptions.content, - ...requestOptions.params - }; - - // only override the access specified previously if 'private' is passed explicitly - if (typeof requestOptions.private !== "undefined") { - requestOptions.params.access = requestOptions.private - ? "private" - : "inherit"; - } - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl( + requestOptions as IRequestOptions + )}/content/users/${owner}/items/${requestOptions.id}/updateResources`; + + // mix in user supplied params + requestOptions.params = { + file: requestOptions.resource, + fileName: requestOptions.name, + text: requestOptions.content, + ...requestOptions.params + }; + + // only override the access specified previously if 'private' is passed explicitly + if (typeof requestOptions.private !== "undefined") { + requestOptions.params.access = requestOptions.private + ? "private" + : "inherit"; + } + return request(url, requestOptions); + }); } /** @@ -127,19 +166,20 @@ export function updateItemResource( export function moveItem( requestOptions: IMoveItemOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.itemId - }/move`; - - let folderId = requestOptions.folderId; - if (!folderId) { - folderId = "/"; - } - requestOptions.params = { - folder: folderId, - ...requestOptions.params - }; - - return request(url, requestOptions); + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.itemId + }/move`; + + let folderId = requestOptions.folderId; + if (!folderId) { + folderId = "/"; + } + requestOptions.params = { + folder: folderId, + ...requestOptions.params + }; + + return request(url, requestOptions); + }); } diff --git a/packages/arcgis-rest-portal/src/items/upload.ts b/packages/arcgis-rest-portal/src/items/upload.ts index a1cc767f5d..bf986203ac 100644 --- a/packages/arcgis-rest-portal/src/items/upload.ts +++ b/packages/arcgis-rest-portal/src/items/upload.ts @@ -29,12 +29,13 @@ import { export function commitItemUpload( requestOptions?: IUserItemOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/commit`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/commit`; - return request(url, requestOptions); + return request(url, requestOptions); + }); } /** @@ -56,10 +57,11 @@ export function commitItemUpload( export function cancelItemUpload( requestOptions?: IUserItemOptions ): Promise { - const owner = determineOwner(requestOptions); - const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ - requestOptions.id - }/cancel`; + return determineOwner(requestOptions).then(owner => { + const url = `${getPortalUrl(requestOptions)}/content/users/${owner}/items/${ + requestOptions.id + }/cancel`; - return request(url, requestOptions); + return request(url, requestOptions); + }); } diff --git a/packages/arcgis-rest-portal/src/sharing/access.ts b/packages/arcgis-rest-portal/src/sharing/access.ts index fe29764e16..8e5b9c6ef5 100644 --- a/packages/arcgis-rest-portal/src/sharing/access.ts +++ b/packages/arcgis-rest-portal/src/sharing/access.ts @@ -49,9 +49,7 @@ export function setItemAccess( } else { // if neither, updating the sharing isnt possible throw Error( - `This item can not be shared by ${ - requestOptions.authentication.username - }. They are neither the item owner nor an organization admin.` + `This item can not be shared by ${requestOptions.authentication.username}. They are neither the item owner nor an organization admin.` ); } }); diff --git a/packages/arcgis-rest-portal/src/sharing/group-sharing.ts b/packages/arcgis-rest-portal/src/sharing/group-sharing.ts index 405babb7be..233efdaa42 100644 --- a/packages/arcgis-rest-portal/src/sharing/group-sharing.ts +++ b/packages/arcgis-rest-portal/src/sharing/group-sharing.ts @@ -2,7 +2,6 @@ * Apache-2.0 */ import { request } from "@esri/arcgis-rest-request"; -import { IItem } from "@esri/arcgis-rest-types"; import { getPortalUrl } from "../util/get-portal-url"; import { IGroupSharingOptions, @@ -10,6 +9,12 @@ import { isOrgAdmin, getUserMembership } from "./helpers"; +import { getUser } from "../users/get-user"; +import { addGroupUsers, IAddGroupUsersResult } from "../groups/add-users"; +import { updateUserMemberships } from "../groups/update-user-membership"; +import { searchItems } from "../items/search"; +import { ISearchOptions } from "../util/search"; +import { IUser } from "@esri/arcgis-rest-types"; interface IGroupSharingUnsharingOptions extends IGroupSharingOptions { action: "share" | "unshare"; @@ -75,7 +80,11 @@ function changeGroupSharing( const itemOwner = requestOptions.owner || username; const isSharedEditingGroup = requestOptions.confirmItemControl || false; - return isOrgAdmin(requestOptions).then(admin => { + return getUser({ + username, + authentication: requestOptions.authentication + }).then(currentUser => { + const isAdmin = currentUser.role === "org_admin"; const resultProp = requestOptions.action === "share" ? "notSharedWith" : "notUnsharedFrom"; // check if the item has already been shared with the group... @@ -96,59 +105,56 @@ function changeGroupSharing( // next check to ensure the user is a member of the group return getUserMembership(requestOptions) .then(membership => { - // if user is not a member of the group and not an admin - if (membership === "none" && !admin) { + // Stack all the exception conditions up top so we can + // strealine the promise chain + // if user is not a member of the group and not an orgAdmin + if (membership === "none" && !isAdmin) { // abort and reject promise throw Error( `This item can not be ${requestOptions.action}d by ${username} as they are not a member of the specified group ${requestOptions.groupId}.` ); - } else { - // ...they are some level of membership or org-admin - // if the current user does not own the item... - if (itemOwner !== username) { - // only item owners can share/unshare items w/ shared editing groups - if (isSharedEditingGroup) { - throw Error( - `This item can not be ${requestOptions.action}d to shared editing group ${requestOptions.groupId} by ${username} as they not the item owner.` - ); - } - // only item-owners, group-admin's, group-owners can unshare an item from a group - if ( - requestOptions.action === "unshare" && - membership !== "admin" && // not group admin - membership !== "owner" // not group owner - ) { - throw Error( - `This item can not be ${requestOptions.action}d from group ${requestOptions.groupId} by ${username} as they not the item owner, group admin or group owner.` - ); - } - } - - // at this point, the user *should* be able to take the action - - // only question is what url to use + } + // it's a sharedEditing Group and user is not owner, org orgAdmin + if (isSharedEditingGroup && itemOwner !== username && !isAdmin) { + // abort and reject promise + throw Error( + `This item can not be ${requestOptions.action}d to shared editing group ${requestOptions.groupId} by ${username} as they not the item owner.` + ); + } - // default to the non-owner url... - let url = `${getPortalUrl(requestOptions)}/content/items/${ - requestOptions.id - }/${requestOptions.action}`; + // only item-owners, group-admin's, group-owners can unshare an item from a group + if ( + requestOptions.action === "unshare" && + itemOwner !== username && // not item owner + membership !== "admin" && // not group admin + membership !== "owner" // not group owner + ) { + throw Error( + `This item can not be ${requestOptions.action}d from group ${requestOptions.groupId} by ${username} as they not the item owner, group admin or group owner.` + ); + } - // but if they are the owner, we use a different path... - if (itemOwner === username) { - url = `${getPortalUrl( - requestOptions - )}/content/users/${itemOwner}/items/${requestOptions.id}/${ - requestOptions.action - }`; + // if it's a sharedEditing Group, and the current user is not the owner, but an OrgAdmin + // then we can let call shareToGroupAsNonOwner which will add the owner to the group + // and then share the item to the group + if ( + requestOptions.action === "share" && + isSharedEditingGroup && + itemOwner !== username && + isAdmin + ) { + return shareToGroupAsNonOwner(currentUser, requestOptions); + } else { + // if the current user is a member of the target group + if (membership !== "none") { + // we let the sharing call go + return shareToGroup(requestOptions); + } else { + // otherwise - even if they are org_admin - we throw staying the current user must be a member of the group + throw Error( + `This item can not be ${requestOptions.action}d by ${username} as they are not a member of the specified group ${requestOptions.groupId}.` + ); } - - // now its finally time to do the sharing - requestOptions.params = { - groups: requestOptions.groupId, - confirmItemControl: requestOptions.confirmItemControl - }; - - return request(url, requestOptions); } }) .then(sharingResponse => { @@ -167,53 +173,161 @@ function changeGroupSharing( } /** + * Under very specific circumstances, and item may be shared + * to a group by a user other than the owner. + * Specifically: + * - current user must be org_admin + * - item owner must be in same org as current user + * - item owner must be able to be added to the group (less than 512 groups) + * @param currentUser Current user attempting to do the share + * @param requestOptions IGroupSharingUnshareingOptions + */ +function shareToGroupAsNonOwner( + currentUser: IUser, + requestOptions: IGroupSharingUnsharingOptions +): Promise { + const itemOwner = requestOptions.owner; + + return getUser({ + username: itemOwner, + authentication: requestOptions.authentication + }) + .then(ownerUser => { + // if they are in different orgs, eject + if (currentUser.orgId !== ownerUser.orgId) { + throw Error( + `User ${itemOwner} is not a member of the same org as ${currentUser.username}. Consequently they can not be added added to group ${requestOptions.groupId} nor can item ${requestOptions.id} be shared to the group.` + ); + } + + // see if the owner is a member of the group + const ownerGroups = ownerUser.groups || []; + const group = ownerGroups.find(g => { + return g.id === requestOptions.groupId; + }); + + // if owner is not a member, and has 512 groups + if (!group && ownerGroups.length > 511) { + throw Error( + `User ${itemOwner} already has 512 groups, and can not be added to group ${requestOptions.groupId}. Consequently item ${requestOptions.id} can not be shared to the group.` + ); + } + + // decide if we need to add them or upgrade them + if (group) { + // they are in the group... + // check member type + if (group.userMembership.memberType === "member") { + // promote them + return updateUserMemberships({ + id: requestOptions.groupId, + users: [itemOwner], + newMemberType: "admin", + authentication: requestOptions.authentication + }).then(response => { + // convert the result into the right type + const notAdded = response.results.reduce( + (acc: any[], entry: any) => { + if (!entry.success) { + acc.push(entry.username); + } + return acc; + }, + [] + ); + // and return it + return { + notAdded + } as IAddGroupUsersResult; + }); + } else { + // they are already an admin in the group + // return the same response the API would if we added them + return { notAdded: [] } as IAddGroupUsersResult; + } + } else { + // add user to group as an admin + return addGroupUsers({ + id: requestOptions.groupId, + admins: [itemOwner], + authentication: requestOptions.authentication + }); + } + }) + .then(membershipResponse => { + if (membershipResponse.notAdded.length) { + throw Error( + `Error adding user ${itemOwner} to group ${requestOptions.groupId}. Consequently item ${requestOptions.id} was not shared to the group.` + ); + } else { + // then make the sharing call + return shareToGroup(requestOptions); + } + }); +} + +function shareToGroup( + requestOptions: IGroupSharingUnsharingOptions +): Promise { + const username = requestOptions.authentication.username; + const itemOwner = requestOptions.owner || username; + // decide what url to use + // default to the non-owner url... + let url = `${getPortalUrl(requestOptions)}/content/items/${ + requestOptions.id + }/${requestOptions.action}`; + + // but if they are the owner, we use a different path... + if (itemOwner === username) { + url = `${getPortalUrl(requestOptions)}/content/users/${itemOwner}/items/${ + requestOptions.id + }/${requestOptions.action}`; + } + + // now its finally time to do the sharing + requestOptions.params = { + groups: requestOptions.groupId, + confirmItemControl: requestOptions.confirmItemControl + }; + + return request(url, requestOptions); +} + +/** + * ```js + * import { isItemSharedWithGroup } from "@esri/arcgis-rest-portal"; + * // + * isItemSharedWithGroup({ + * groupId: 'bc3, + * itemId: 'f56, + * authentication + * }) + * .then(isShared => {}) + * ``` * Find out whether or not an item is already shared with a group. * * @param requestOptions - Options for the request. NOTE: `rawResponse` is not supported by this operation. - * @returns A Promise that will resolve with the data from the response. + * @returns Promise that will resolve with true/false */ -function isItemSharedWithGroup( +export function isItemSharedWithGroup( requestOptions: IGroupSharingOptions ): Promise { - const query = { + const searchOpts = { q: `id: ${requestOptions.id} AND group: ${requestOptions.groupId}`, start: 1, num: 10, - sortField: "title" - }; - - // we need to append some params into requestOptions, so make a clone - // instead of mutating the params on the inbound requestOptions object - const options = { ...requestOptions, rawResponse: false }; - // instead of calling out to "@esri/arcgis-rest-items, make the request manually to forgoe another dependency - options.params = { - ...query, - ...requestOptions.params - }; + sortField: "title", + authentication: requestOptions.authentication, + httpMethod: "POST" + } as ISearchOptions; - const url = `${getPortalUrl(options)}/search`; - - // to do: just call searchItems now that its in the same package - return request(url, options).then(searchResponse => { - // if there are no search results at all, we know the item hasnt already been shared with the group - if (searchResponse.total === 0) { - return false; - } else { - let sharedItem: IItem; - // otherwise loop through and search for the id - searchResponse.results.some((item: IItem) => { - const matchedItem = item.id === requestOptions.id; - if (matchedItem) { - sharedItem = item; - } - return matchedItem; + return searchItems(searchOpts).then(searchResponse => { + let result = false; + if (searchResponse.total > 0) { + result = searchResponse.results.some((itm: any) => { + return itm.id === requestOptions.id; }); - - if (sharedItem) { - return true; - } else { - return false; - } + return result; } }); } diff --git a/packages/arcgis-rest-portal/src/users/invitation.ts b/packages/arcgis-rest-portal/src/users/invitation.ts index 7e8022a145..8c03cdd724 100644 --- a/packages/arcgis-rest-portal/src/users/invitation.ts +++ b/packages/arcgis-rest-portal/src/users/invitation.ts @@ -84,9 +84,7 @@ export function getUserInvitation( ): Promise { const username = encodeURIComponent(requestOptions.authentication.username); const portalUrl = getPortalUrl(requestOptions); - const url = `${portalUrl}/community/users/${username}/invitations/${ - requestOptions.invitationId - }`; + const url = `${portalUrl}/community/users/${username}/invitations/${requestOptions.invitationId}`; let options = { httpMethod: "GET" } as IGetUserInvitationOptions; options = { ...requestOptions, ...options }; @@ -120,9 +118,7 @@ export function acceptInvitation( }> { const username = encodeURIComponent(requestOptions.authentication.username); const portalUrl = getPortalUrl(requestOptions); - const url = `${portalUrl}/community/users/${username}/invitations/${ - requestOptions.invitationId - }/accept`; + const url = `${portalUrl}/community/users/${username}/invitations/${requestOptions.invitationId}/accept`; const options: IGetUserInvitationOptions = { ...requestOptions }; return request(url, options); @@ -153,9 +149,7 @@ export function declineInvitation( }> { const username = encodeURIComponent(requestOptions.authentication.username); const portalUrl = getPortalUrl(requestOptions); - const url = `${portalUrl}/community/users/${username}/invitations/${ - requestOptions.invitationId - }/decline`; + const url = `${portalUrl}/community/users/${username}/invitations/${requestOptions.invitationId}/decline`; const options: IGetUserInvitationOptions = { ...requestOptions }; return request(url, options); diff --git a/packages/arcgis-rest-portal/src/users/notification.ts b/packages/arcgis-rest-portal/src/users/notification.ts index 1e04fbca2e..0cd85fa40b 100644 --- a/packages/arcgis-rest-portal/src/users/notification.ts +++ b/packages/arcgis-rest-portal/src/users/notification.ts @@ -62,9 +62,7 @@ export function removeNotification( ): Promise<{ success: boolean; notificationId: string }> { const username = encodeURIComponent(requestOptions.authentication.username); const portalUrl = getPortalUrl(requestOptions); - const url = `${portalUrl}/community/users/${username}/notifications/${ - requestOptions.id - }/delete`; + const url = `${portalUrl}/community/users/${username}/notifications/${requestOptions.id}/delete`; return request(url, requestOptions); } diff --git a/packages/arcgis-rest-portal/src/util/generic-search.ts b/packages/arcgis-rest-portal/src/util/generic-search.ts index befd3513fe..56644f0443 100644 --- a/packages/arcgis-rest-portal/src/util/generic-search.ts +++ b/packages/arcgis-rest-portal/src/util/generic-search.ts @@ -10,11 +10,19 @@ import { IItem, IGroup, IUser } from "@esri/arcgis-rest-types"; import { SearchQueryBuilder } from "./SearchQueryBuilder"; import { getPortalUrl } from "../util/get-portal-url"; -import { ISearchOptions, ISearchResult } from "../util/search"; +import { + ISearchOptions, + ISearchGroupContentOptions, + ISearchResult +} from "../util/search"; export function genericSearch( - search: string | ISearchOptions | SearchQueryBuilder, - searchType: "item" | "group" | "user" + search: + | string + | ISearchOptions + | ISearchGroupContentOptions + | SearchQueryBuilder, + searchType: "item" | "group" | "groupContent" | "user" ): Promise> { let url: string; let options: IRequestOptions; @@ -35,7 +43,7 @@ export function genericSearch( ); } - let path = searchType === "item" ? "/search" : "/community/groups"; + let path; switch (searchType) { case "item": path = "/search"; @@ -43,6 +51,19 @@ export function genericSearch( case "group": path = "/community/groups"; break; + case "groupContent": + // Need to have groupId property to do group contents search, + // cso filter out all but ISearchGroupContentOptions + if ( + typeof search !== "string" && + !(search instanceof SearchQueryBuilder) && + search.groupId + ) { + path = `/content/groups/${search.groupId}/search`; + } else { + return Promise.reject(new Error("you must pass a `groupId` option to `searchGroupContent`")); + } + break; default: // "users" path = "/portals/self/users/search"; diff --git a/packages/arcgis-rest-portal/src/util/get-portal-settings.ts b/packages/arcgis-rest-portal/src/util/get-portal-settings.ts new file mode 100644 index 0000000000..9b948827d6 --- /dev/null +++ b/packages/arcgis-rest-portal/src/util/get-portal-settings.ts @@ -0,0 +1,45 @@ +/* Copyright (c) 2017-2019 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { IExtent } from "@esri/arcgis-rest-types"; +import { request, IRequestOptions } from "@esri/arcgis-rest-request"; + +import { getPortalUrl } from "./get-portal-url"; + +export interface IPortalSettings { + allowedRedirectUris: string[]; + defaultExtent: IExtent; + helperServices: { [key: string]: any }; + informationalBanner: { [key: string]: any }; + [key: string]: any; +} + +/** + * ```js + * import { getPortalSettings } from "@esri/arcgis-rest-portal"; + * // + * getPortalSettings() + * getPortalSettings("fe8") + * getPortalSettings(null, { portal: "https://custom.maps.arcgis.com/sharing/rest/" }) + * ``` + * Fetch the settings for the current portal by id. If no id is passed, portals/self/settings will be called + * @param id + * @param requestOptions + */ +export function getPortalSettings( + id?: string, + requestOptions?: IRequestOptions +): Promise { + // construct the search url + const idOrSelf = id ? id : "self"; + const url = `${getPortalUrl(requestOptions)}/portals/${idOrSelf}/settings`; + + // default to a GET request + const options: IRequestOptions = { + ...{ httpMethod: "GET" }, + ...requestOptions + }; + + // send the request + return request(url, options); +} diff --git a/packages/arcgis-rest-portal/src/util/search.ts b/packages/arcgis-rest-portal/src/util/search.ts index d15b8daf97..0382020432 100644 --- a/packages/arcgis-rest-portal/src/util/search.ts +++ b/packages/arcgis-rest-portal/src/util/search.ts @@ -12,6 +12,10 @@ export interface ISearchOptions extends IRequestOptions, IPagingParams { [key: string]: any; } +export interface ISearchGroupContentOptions extends ISearchOptions { + groupId: string; +} + /** * Results from an item or group search. */ diff --git a/packages/arcgis-rest-portal/test/groups/get.test.ts b/packages/arcgis-rest-portal/test/groups/get.test.ts index ba7b36e270..859d5cdeaa 100644 --- a/packages/arcgis-rest-portal/test/groups/get.test.ts +++ b/packages/arcgis-rest-portal/test/groups/get.test.ts @@ -3,6 +3,7 @@ import { getGroup, + getGroupCategorySchema, getGroupContent, getGroupUsers, searchGroupUsers @@ -10,6 +11,7 @@ import { import { GroupResponse, + GroupCategorySchemaResponse, GroupContentResponse, GroupUsersResponse, SearchGroupUsersResponse @@ -39,6 +41,25 @@ describe("groups", () => { }); }); + describe("getGroupCategorySchema", () => { + it("should return group's category schema", done => { + fetchMock.once("*", GroupCategorySchemaResponse); + getGroupCategorySchema("3ef") + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/community/groups/3ef/categorySchema?f=json" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); + describe("getGroupContent", () => { it("should return group content", done => { fetchMock.once("*", GroupContentResponse); diff --git a/packages/arcgis-rest-portal/test/groups/search.test.ts b/packages/arcgis-rest-portal/test/groups/search.test.ts index 34d36cda09..76596f36ed 100644 --- a/packages/arcgis-rest-portal/test/groups/search.test.ts +++ b/packages/arcgis-rest-portal/test/groups/search.test.ts @@ -1,9 +1,10 @@ /* Copyright (c) 2018 Environmental Systems Research Institute, Inc. * Apache-2.0 */ -import { searchGroups } from "../../src/groups/search"; +import { searchGroups, searchGroupContent } from "../../src/groups/search"; import { GroupSearchResponse } from "../mocks/groups/responses"; import { SearchQueryBuilder } from "../../src/util/SearchQueryBuilder"; +import { genericSearch } from "../../src/util/generic-search"; import * as fetchMock from "fetch-mock"; @@ -51,6 +52,46 @@ describe("groups", () => { fail(e); }); }); + + it("should search for group contents", done => { + fetchMock.once("*", GroupSearchResponse); + + searchGroupContent({ + groupId: "grp1234567890", + q: "water" + }) + .then(() => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://www.arcgis.com/sharing/rest/content/groups/grp1234567890/search?f=json&q=water" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should catch search for group contents without group id", done => { + genericSearch( + { + q: "water" + }, + "groupContent" + ).then( + () => fail(), + err => { + expect(err).toEqual( + new Error( + "you must pass a `groupId` option to `searchGroupContent`" + ) + ); + done(); + } + ); + }); }); it("should make a simple, single search request with a builder", done => { diff --git a/packages/arcgis-rest-portal/test/groups/update-user-membership.test.ts b/packages/arcgis-rest-portal/test/groups/update-user-membership.test.ts new file mode 100644 index 0000000000..1661155d34 --- /dev/null +++ b/packages/arcgis-rest-portal/test/groups/update-user-membership.test.ts @@ -0,0 +1,62 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import * as fetchMock from "fetch-mock"; + +import { MOCK_USER_SESSION } from "../mocks/sharing/sharing"; +import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; +import { updateUserMemberships } from "../../src/groups/update-user-membership"; + +describe("udpate-user-membership", () => { + beforeEach(done => { + fetchMock.post("https://myorg.maps.arcgis.com/sharing/rest/generateToken", { + token: "fake-token", + expires: TOMORROW.getTime(), + username: "jsmith" + }); + + // make sure session doesnt cache metadata + MOCK_USER_SESSION.refreshSession() + .then(() => done()) + .catch(); + }); + + afterEach(fetchMock.restore); + + it("converts member to admin", done => { + fetchMock.post( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/3ef/updateUsers", + { results: [{ username: "casey", success: true }] } + ); + return updateUserMemberships({ + authentication: MOCK_USER_SESSION, + id: "3ef", + users: ["larry", "curly", "moe"], + newMemberType: "admin" + }).then(() => { + const opts: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/3ef/updateUsers" + ); + expect(opts.body).toContain("admins=larry%2Ccurly%2Cmoe"); + done(); + }); + }); + it("converts admin to member", done => { + fetchMock.post( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/3ef/updateUsers", + { results: [{ username: "casey", success: true }] } + ); + return updateUserMemberships({ + authentication: MOCK_USER_SESSION, + id: "3ef", + users: ["larry", "curly", "moe"], + newMemberType: "member" + }).then(() => { + const opts: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/3ef/updateUsers" + ); + expect(opts.body).toContain("users=larry%2Ccurly%2Cmoe"); + done(); + }); + }); +}); diff --git a/packages/arcgis-rest-portal/test/items/add.test.ts b/packages/arcgis-rest-portal/test/items/add.test.ts index 52c3eed7df..d68d7a095f 100644 --- a/packages/arcgis-rest-portal/test/items/add.test.ts +++ b/packages/arcgis-rest-portal/test/items/add.test.ts @@ -224,6 +224,7 @@ describe("search", () => { // File() is only available in the browser resource: file, name: "thebigkahuna", + prefix: "myfiles", ...MOCK_USER_REQOPTS }) .then(() => { @@ -241,6 +242,7 @@ describe("search", () => { expect(params.get("file")).toEqual(file); expect(params.get("access")).toEqual("inherit"); expect(params.get("fileName")).toEqual("thebigkahuna"); + expect(params.get("resourcesPrefix")).toEqual("myfiles"); } done(); diff --git a/packages/arcgis-rest-portal/test/items/content.test.ts b/packages/arcgis-rest-portal/test/items/content.test.ts new file mode 100644 index 0000000000..dc55725b3d --- /dev/null +++ b/packages/arcgis-rest-portal/test/items/content.test.ts @@ -0,0 +1,156 @@ +import * as fetchMock from "fetch-mock"; + +import { + getUserContent, IUserContentResponse, IUserContentRequestOptions +} from "../../src/items/content"; + +import { UserSession } from "@esri/arcgis-rest-auth"; +import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; + +describe("getContent", () => { + afterEach(fetchMock.restore); + + describe("Authenticated methods", () => { + const authentication = new UserSession({ + clientId: "clientId", + redirectUri: "https://example-app.com/redirect-uri", + token: "fake-token", + tokenExpires: TOMORROW, + refreshToken: "refreshToken", + refreshTokenExpires: TOMORROW, + refreshTokenTTL: 1440, + username: "moses", + password: "123456", + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + + const mockResponse: IUserContentResponse = { + username: "geemike", + total: 20, + start: 1, + num: 2, + nextStart: 3, + items: [ + { + id: "1qwe", + owner: "geemike", + tags: [], + created: 0, + modified: 0, + numViews: 0, + size: 10, + title: "Test Title #1", + type: "CSV" + }, + { + id: "2asd", + owner: "geemike", + tags: [], + created: 0, + modified: 0, + numViews: 0, + size: 10, + title: "Test Title #2", + type: "CSV" + } + ], + folders: [ + { + username: 'geemike', + id: 'ba07', + title: 'testing', + created: 1576264694000 + } + ] + }; + + it("should get the user content defaulting the start and num parameters", done => { + fetchMock.once("*", mockResponse); + + const requestOptions: IUserContentRequestOptions = { + owner: 'geemike', + authentication + }; + + getUserContent(requestOptions).then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + `https://myorg.maps.arcgis.com/sharing/rest/content/users/${requestOptions.owner}?f=json&start=1&num=10&token=fake-token` + ); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should get the user content using the supplied start and num parameters", done => { + fetchMock.once("*", mockResponse); + + const requestOptions: IUserContentRequestOptions = { + owner: 'geemike', + start: 2, + num: 1, + authentication + }; + + getUserContent(requestOptions).then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + `https://myorg.maps.arcgis.com/sharing/rest/content/users/${requestOptions.owner}?f=json&start=2&num=1&token=fake-token` + ); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should get the user content using the authenticated username", done => { + fetchMock.once("*", mockResponse); + + const requestOptions: IUserContentRequestOptions = { + start: 2, + num: 1, + authentication + }; + + getUserContent(requestOptions).then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + `https://myorg.maps.arcgis.com/sharing/rest/content/users/${authentication.username}?f=json&start=2&num=1&token=fake-token` + ); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should get the user content using the supplied folderId", done => { + fetchMock.once("*", mockResponse); + + const requestOptions: IUserContentRequestOptions = { + folderId: "ba07", + start: 2, + num: 1, + authentication + }; + + getUserContent(requestOptions).then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + `https://myorg.maps.arcgis.com/sharing/rest/content/users/${authentication.username}/${requestOptions.folderId}?f=json&start=2&num=1&token=fake-token` + ); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/arcgis-rest-portal/test/items/export.test.ts b/packages/arcgis-rest-portal/test/items/export.test.ts new file mode 100644 index 0000000000..629a01435d --- /dev/null +++ b/packages/arcgis-rest-portal/test/items/export.test.ts @@ -0,0 +1,122 @@ +import * as fetchMock from "fetch-mock"; + +import { + exportItem, IExportItemResponse, IExportItemRequestOptions +} from "../../src/items/export"; + +import { ItemSuccessResponse } from "../mocks/items/item"; + +import { UserSession } from "@esri/arcgis-rest-auth"; +import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; +import { encodeParam } from "@esri/arcgis-rest-request"; + +describe("exportItem", () => { + afterEach(fetchMock.restore); + + describe("Authenticated methods", () => { + const authentication = new UserSession({ + clientId: "clientId", + redirectUri: "https://example-app.com/redirect-uri", + token: "fake-token", + tokenExpires: TOMORROW, + refreshToken: "refreshToken", + refreshTokenExpires: TOMORROW, + refreshTokenTTL: 1440, + username: "moses", + password: "123456", + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + + it("should export an item using the supplied owner", done => { + const mockResponse: IExportItemResponse = { + type: 'CSV', + size: 100, + jobId: 'n0n0', + exportItemId: 'm0m0', + serviceItemId: '5u4i', + exportFormat: 'CSV' + }; + + fetchMock.once("*", mockResponse); + + const exportOptions: IExportItemRequestOptions = { + id: '3af', + owner: 'geemike', + title: 'test title', + exportFormat: 'CSV', + exportParameters: { + layers: [ + { id: 0 }, + { id: 1, where: 'POP1999 > 100000' } + ], + targetSR: { + wkid: 102100 + } + }, + authentication + }; + exportItem(exportOptions).then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + `https://myorg.maps.arcgis.com/sharing/rest/content/users/${exportOptions.owner}/export` + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain("f=json"); + expect(options.body).toContain(encodeParam("token", "fake-token")); + expect(options.body).toContain("itemId=3af"); + expect(options.body).toContain( + encodeParam("exportFormat", "CSV") + ); + expect(options.body).toContain( + encodeParam("title", "test title") + ); + expect(options.body).toContain( + encodeParam("exportParameters", JSON.stringify(exportOptions.exportParameters)) + ) + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should export an item falling back to the authenticated owner", done => { + const mockResponse: IExportItemResponse = { + type: 'CSV', + size: 100, + jobId: 'n0n0', + exportItemId: 'm0m0', + serviceItemId: '5u4i', + exportFormat: 'CSV' + }; + + fetchMock.once("*", mockResponse); + + exportItem({ + id: 'g33M1k3', + exportFormat: 'CSV', + exportParameters: { + layers: [ + { id: 0 }, + { id: 1, where: 'POP1999 > 100000' } + ] + }, + authentication, + }).then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + `https://myorg.maps.arcgis.com/sharing/rest/content/users/${authentication.username}/export` + ); + expect(options.method).toBe("POST"); + expect(options.body).toContain("f=json"); + expect(options.body).toContain(encodeParam("token", "fake-token")); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/arcgis-rest-portal/test/items/helpers.test.ts b/packages/arcgis-rest-portal/test/items/helpers.test.ts new file mode 100644 index 0000000000..8cdb82e1dc --- /dev/null +++ b/packages/arcgis-rest-portal/test/items/helpers.test.ts @@ -0,0 +1,60 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { determineOwner } from "../../src/items/helpers"; +import { UserSession } from "@esri/arcgis-rest-auth/src"; + +describe("determineOwner()", () => { + it("should use owner if passed", done => { + determineOwner({ + owner: "Casey" + }) + .then(owner => { + expect(owner).toEqual("Casey"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should use item owner if owner is not passed", done => { + determineOwner({ + item: { + owner: "Casey" + } + }) + .then(owner => { + expect(owner).toEqual("Casey"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should lookup owner from authentication if no owner or item owner", done => { + determineOwner({ + authentication: new UserSession({ + token: "ABC", + username: "Casey" + }) + }) + .then(owner => { + expect(owner).toEqual("Casey"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should throw an error is the user cannot be determined", done => { + determineOwner({}).catch(e => { + expect(e.message).toEqual( + "Could not determine the owner of this item. Pass the `owner`, `item.owner`, or `authentication` option." + ); + done(); + }); + }); +}); diff --git a/packages/arcgis-rest-portal/test/items/reassign.test.ts b/packages/arcgis-rest-portal/test/items/reassign.test.ts index f71444eb0d..eb88c12a87 100644 --- a/packages/arcgis-rest-portal/test/items/reassign.test.ts +++ b/packages/arcgis-rest-portal/test/items/reassign.test.ts @@ -15,24 +15,18 @@ import { describe("reassignItem", () => { afterEach(fetchMock.restore); - const MOCK_USER_SESSION = new UserSession({ - clientId: "clientId", - redirectUri: "https://example-app.com/redirect-uri", - token: "fake-token", - tokenExpires: TOMORROW, - refreshToken: "refreshToken", - refreshTokenExpires: TOMORROW, - refreshTokenTTL: 1440, - username: "casey", - password: "123456", - portal: "https://myorg.maps.arcgis.com/sharing/rest" - }); - it("shoulds throw if not authd as org_admin", done => { + const MOCK_USER_SESSION = new UserSession({ + token: "fake-token", + tokenExpires: TOMORROW, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + "https://myorg.maps.arcgis.com/sharing/rest/community/self?f=json&token=fake-token", GroupMemberUserResponse ); + reassignItem({ id: "3ef", currentOwner: "alex", @@ -47,15 +41,22 @@ describe("reassignItem", () => { }); it("should send the folder if passed", done => { + const MOCK_USER_SESSION = new UserSession({ + token: "fake-token", + tokenExpires: TOMORROW, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + fetchMock .once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + "https://myorg.maps.arcgis.com/sharing/rest/community/self?f=json&token=fake-token", OrgAdminUserResponse ) .once( "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign", { success: true, itemId: "3ef" } ); + reassignItem({ id: "3ef", currentOwner: "alex", @@ -64,9 +65,9 @@ describe("reassignItem", () => { authentication: MOCK_USER_SESSION }) .then(resp => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); + // expect(fetchMock.done()).toBeTruthy( + // "All fetchMocks should have been called" + // ); expect(resp.success).toBe(true); const [url, options]: [string, RequestInit] = fetchMock.lastCall( "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign" @@ -85,15 +86,22 @@ describe("reassignItem", () => { }); it("should not send the folder if not passed", done => { + const MOCK_USER_SESSION = new UserSession({ + token: "fake-token", + tokenExpires: TOMORROW, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }); + fetchMock .once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + "https://myorg.maps.arcgis.com/sharing/rest/community/self?f=json&token=fake-token", OrgAdminUserResponse ) .once( "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign", { success: true, itemId: "3ef" } ); + reassignItem({ id: "3ef", currentOwner: "alex", @@ -101,9 +109,9 @@ describe("reassignItem", () => { authentication: MOCK_USER_SESSION }) .then(resp => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); + // expect(fetchMock.done()).toBeTruthy( + // "All fetchMocks should have been called" + // ); expect(resp.success).toBe(true); const [url, options]: [string, RequestInit] = fetchMock.lastCall( "https://myorg.maps.arcgis.com/sharing/rest/content/users/alex/items/3ef/reassign" diff --git a/packages/arcgis-rest-portal/test/items/update.test.ts b/packages/arcgis-rest-portal/test/items/update.test.ts index d57e532b37..b9d996f881 100644 --- a/packages/arcgis-rest-portal/test/items/update.test.ts +++ b/packages/arcgis-rest-portal/test/items/update.test.ts @@ -7,13 +7,17 @@ import { attachmentFile } from "../../../arcgis-rest-feature-layer/test/attachme import { updateItem, + updateItemInfo, updateItemResource, moveItem } from "../../src/items/update"; import { ItemSuccessResponse } from "../mocks/items/item"; -import { UpdateItemResourceResponse } from "../mocks/items/resources"; +import { + UpdateItemResourceResponse, + UpdateItemInfoResponse +} from "../mocks/items/resources"; import { UserSession } from "@esri/arcgis-rest-auth"; import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; @@ -209,6 +213,40 @@ describe("search", () => { }); }); + it("update an item info file", done => { + fetchMock.once("*", UpdateItemInfoResponse); + const fakeData = { + values: { + key: "someValue" + } + }; + updateItemInfo({ + id: "3ef", + folderName: "subfolder", + file: fakeData, + ...MOCK_USER_REQOPTS + }) + .then(() => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/items/3ef/updateinfo" + ); + expect(options.method).toBe("POST"); + + expect(options.body).toContain("f=json"); + expect(options.body).toContain("token=fake-token"); + expect(options.body).toContain( + encodeParam("file", JSON.stringify(fakeData)) + ); + + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("update an item resource", done => { fetchMock.once("*", UpdateItemResourceResponse); updateItemResource({ diff --git a/packages/arcgis-rest-portal/test/mocks/groups/responses.ts b/packages/arcgis-rest-portal/test/mocks/groups/responses.ts index 6b64f6b551..b2e2dfb06b 100644 --- a/packages/arcgis-rest-portal/test/mocks/groups/responses.ts +++ b/packages/arcgis-rest-portal/test/mocks/groups/responses.ts @@ -3,6 +3,7 @@ import { ISearchResult } from "../../../src/util/search"; import { + IGroupCategorySchema, IGroupContentResult, IGroupUsersResult, ISearchGroupUsersResult @@ -90,6 +91,41 @@ export const GroupResponse: IGroup = { notificationsEnabled: false }; +// JSON Response Example from https://developers.arcgis.com/rest/users-groups-and-items/group-category-schema.htm +export const GroupCategorySchemaResponse: IGroupCategorySchema = { + categorySchema: [ + { + title: "Categories", + categories: [ + { + title: "Basemaps", + categories: [ + { title: "Partner Basemap" }, + { + title: "Esri Basemaps", + categories: [ + { title: "Esri Raster Basemap" }, + { title: "Esri Vector Basemap" } + ] + } + ] + }, + { + title: "Imagery", + categories: [ + { title: "Multispectral Imagery" }, + { title: "Temporal Imagery" } + ] + } + ] + }, + { + title: "Region", + categories: [{ title: "US" }, { title: "World" }] + } + ] +}; + export const GroupContentResponse: IGroupContentResult = { total: 36, start: 1, diff --git a/packages/arcgis-rest-portal/test/mocks/items/resources.ts b/packages/arcgis-rest-portal/test/mocks/items/resources.ts index 4a0d870dee..9797ca5e58 100644 --- a/packages/arcgis-rest-portal/test/mocks/items/resources.ts +++ b/packages/arcgis-rest-portal/test/mocks/items/resources.ts @@ -23,6 +23,13 @@ export const RemoveItemResourceResponse: any = { success: true }; +export const UpdateItemInfoResponse: any = { + success: true, + itemId: "0c66beb52dff4994be67937cdadbdbf1", + owner: "jsmith", + folder: null +}; + export const UpdateItemResourceResponse: any = { success: true, itemId: "0c66beb52dff4994be67937cdadbdbf1", diff --git a/packages/arcgis-rest-portal/test/mocks/portal/settings-response.ts b/packages/arcgis-rest-portal/test/mocks/portal/settings-response.ts new file mode 100644 index 0000000000..7fbfd3e50d --- /dev/null +++ b/packages/arcgis-rest-portal/test/mocks/portal/settings-response.ts @@ -0,0 +1,56 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { IPortalSettings } from "../../../src/util/get-portal-settings"; + +export const PortalSettingsResponse: IPortalSettings = { + "allowedRedirectUris": [], + "authorizedCrossOriginDomains": [], + "canShareBingPublic": false, + "defaultExtent": { + "xmin": -15000000, + "ymin": 2700000, + "xmax": -6200000, + "ymax": 6500000, + "spatialReference": { + "wkid": 102100 + } + }, + "featuredGroups": [ + { + "title": "Esri Maps and Data", + "owner": "esri" + } + ], + "helperServices": { + "geocode": [ + { + "url": "http://geocodeqa.arcgis.com/arcgis/rest/services/World/GeocodeServer", + "northLat": "Ymax", + "southLat": "Ymin", + "eastLon": "Xmax", + "westLon": "Xmin", + "name": "ArcGIS World Geocoding Service", + "batch": true, + "placefinding": true, + "suggest": true + } + ], + "route": { + "url": "http://routeqa.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World", + "defaultTravelMode": "FEgifRtFndKNcJMJ" + }, + "closestFacility": { + "url": "http://routeqa.arcgis.com/arcgis/rest/services/World/ClosestFacility/NAServer/ClosestFacility_World", + "defaultTravelMode": "FEgifRtFndKNcJMJ" + } + }, + "homePageFeaturedContentCount": 12, + "informationalBanner": { + "text": "Wow Check Out This Info Banner!!!", + "bgColor": "#ff0000", + "fontColor": "#ffffff", + "enabled": true + }, + "showHomePageDescription": false + }; diff --git a/packages/arcgis-rest-portal/test/sharing/access.test.ts b/packages/arcgis-rest-portal/test/sharing/access.test.ts index 1496a32b46..3ae907df79 100644 --- a/packages/arcgis-rest-portal/test/sharing/access.test.ts +++ b/packages/arcgis-rest-portal/test/sharing/access.test.ts @@ -108,7 +108,7 @@ describe("setItemAccess()", () => { it("should share another persons item if an org admin makes the request", done => { fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + "https://myorg.maps.arcgis.com/sharing/rest/community/self?f=json&token=fake-token", OrgAdminUserResponse ); @@ -143,7 +143,7 @@ describe("setItemAccess()", () => { it("should throw if the person trying to share doesnt own the item and is not an admin", done => { fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + "https://myorg.maps.arcgis.com/sharing/rest/community/self?f=json&token=fake-token", AnonUserResponse ); diff --git a/packages/arcgis-rest-portal/test/sharing/group-sharing.test.ts b/packages/arcgis-rest-portal/test/sharing/group-sharing.test.ts index 47c0941526..6f7d094721 100644 --- a/packages/arcgis-rest-portal/test/sharing/group-sharing.test.ts +++ b/packages/arcgis-rest-portal/test/sharing/group-sharing.test.ts @@ -112,408 +112,804 @@ describe("shareItemWithGroup() ::", () => { }); afterEach(fetchMock.restore); + describe("share item as owner::", () => { + it("should share an item with a group by owner", done => { + // this is used when isOrgAdmin is called... + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupMemberUserResponse + ); + // this is called when we try to determine if the item is already in the group + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); - it("should share an item with a group by owner", done => { - // this is used when isOrgAdmin is called... - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupMemberUserResponse - ); - // this is called when we try to determine if the item is already in the group - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); + // called when we determine if the user is a member of the group + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupOwnerResponse - ); + // the actual sharing request + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share", + SharingResponse + ); - // the actual sharing request - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share", - SharingResponse - ); + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b" + }) + .then(response => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share" + ); + expect(url).toBe( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share" + ); + expect(options.method).toBe("POST"); + expect(response).toEqual(SharingResponse); + expect(options.body).toContain("f=json"); + expect(options.body).toContain("groups=t6b"); + done(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + fail(e); + }); + }); + it("should throw is owner is not member of the group", done => { + // this is used when isOrgAdmin is called... + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupNonMemberResponse + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b" - }) - .then(response => { + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b" + }).catch(e => { expect(fetchMock.done()).toBeTruthy( "All fetchMocks should have been called" ); - const [url, options]: [string, RequestInit] = fetchMock.lastCall( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share" - ); - expect(url).toBe( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share" + expect(e.message).toBe( + "This item can not be shared by jsmith as they are not a member of the specified group t6b." ); - expect(options.method).toBe("POST"); - expect(response).toEqual(SharingResponse); - expect(options.body).toContain("f=json"); - expect(options.body).toContain("groups=t6b"); done(); - }) - .catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - fail(e); }); - }); - - it("should fail to share an item with a group if the request is made by a non-org admin and non-group member", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupNonMemberUserResponse - ); - - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); - - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupNonMemberUserResponse - ); - - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b" - }).catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - expect(e.message).toBe( - "This item can not be shared by jsmith as they are not a member of the specified group t6b." - ); - done(); }); - }); - it("should share an item with a group by org administrator", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - OrgAdminUserResponse - ); + it("should fail to share an item with a group if the request is made by a non-org admin and non-group member", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupNonMemberUserResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - NoResultsSearchResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupOwnerResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupNonMemberUserResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", - SharingResponse - ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b", - owner: "casey" - }) - .then(response => { + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b" + }).catch(e => { expect(fetchMock.done()).toBeTruthy( "All fetchMocks should have been called" ); - const [url, options]: [string, RequestInit] = fetchMock.lastCall( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" - ); - expect(url).toBe( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + expect(e.message).toBe( + "This item can not be shared by jsmith as they are not a member of the specified group t6b." ); - expect(options.method).toBe("POST"); - expect(response).toEqual(SharingResponse); - expect(options.body).toContain("f=json"); - expect(options.body).toContain("groups=t6b"); done(); - }) - .catch(e => { - fail(e); }); - }); + }); - it("should fail share an item with a group by org administrator and pass through confirmItemControl", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - OrgAdminUserResponse - ); + it("should share an item with a group by org administrator", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - NoResultsSearchResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupOwnerResponse - ); + // called when we determine if the user is a member of the group + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b", - owner: "casey", - confirmItemControl: true - }).catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + SharingResponse ); - expect(e.message).toBe( - "This item can not be shared to shared editing group t6b by jsmith as they not the item owner." + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey" + }) + .then(response => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(url).toBe( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(options.method).toBe("POST"); + expect(response).toEqual(SharingResponse); + expect(options.body).toContain("f=json"); + expect(options.body).toContain("groups=t6b"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should fail unshare an item with a group by org administrator thats not a group member ", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse ); - done(); + + fetchMock.once("https://myorg.maps.arcgis.com/sharing/rest/search", { + total: 1, + results: [ + { + id: "n3v" + } + ] + }); + + // called when we determine if the user is a member of the group + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupNonMemberResponse + ); + unshareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "vader" + }) + .then(_ => { + fail(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + expect(e.message).toBe( + "This item can not be unshared from group t6b by jsmith as they not the item owner, group admin or group owner." + ); + done(); + }); }); - }); - it("should fail unshare an item with a group by org administrator thats not a group member ", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - OrgAdminUserResponse - ); + it("should share an item with a group by group owner/admin", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupAdminUserResponse + ); - fetchMock.once("https://myorg.maps.arcgis.com/sharing/rest/search", { - total: 1, - results: [ - { - id: "n3v" - } - ] + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); + + // called when we determine if the user is a member of the group + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ); + + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + SharingResponse + ); + + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "otherguy" + }) + .then(response => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(url).toBe( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(options.method).toBe("POST"); + expect(response).toEqual(SharingResponse); + expect(options.body).toContain("f=json"); + expect(options.body).toContain("groups=t6b"); + done(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + fail(e); + }); }); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupNonMemberResponse - ); - unshareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b", - owner: "vader" - }) - .then(_ => { - fail(); + it("should mock the response if an item was previously shared with a group", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupAdminUserResponse + ); + + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); + + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "a5b", + groupId: "t6b" }) - .catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - expect(e.message).toBe( - "This item can not be unshared from group t6b by jsmith as they not the item owner, group admin or group owner." - ); - done(); - }); - }); + .then(response => { + // no web request to share at all + expect(response).toEqual(CachedSharingResponse); + done(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + fail(e); + }); + }); - it("should share an item with a group by group owner/admin", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupAdminUserResponse - ); + it("should allow group owner/admin/member to share item they do not own", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupMemberUserResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupOwnerResponse - ); + // called when we determine if the user is a member of the group + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupMemberResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", - SharingResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + SharingResponse + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b", - owner: "otherguy" - }) - .then(response => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - const [url, options]: [string, RequestInit] = fetchMock.lastCall( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" - ); - expect(url).toBe( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" - ); - expect(options.method).toBe("POST"); - expect(response).toEqual(SharingResponse); - expect(options.body).toContain("f=json"); - expect(options.body).toContain("groups=t6b"); - done(); + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey" }) - .catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - fail(e); - }); - }); + .then(response => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + const [url, options]: [string, RequestInit] = fetchMock.lastCall( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(url).toBe( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(options.method).toBe("POST"); + expect(response).toEqual(SharingResponse); + expect(options.body).toContain("f=json"); + expect(options.body).toContain("groups=t6b"); + done(); + }) + .catch(e => { + fail(e); + }); + }); - it("should mock the response if an item was previously shared with a group", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupAdminUserResponse - ); + it("should throw if non-owner tries to share to shared editing group", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupMemberUserResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "a5b", - groupId: "t6b" - }) - .then(response => { - // no web request to share at all - expect(response).toEqual(CachedSharingResponse); - done(); - }) - .catch(e => { + // called when we determine if the user is a member of the group + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupMemberResponse + ); + + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }).catch(e => { expect(fetchMock.done()).toBeTruthy( "All fetchMocks should have been called" ); - fail(e); + expect(e.message).toContain( + "This item can not be shared to shared editing group t6b by jsmith as they not the item owner." + ); + done(); }); - }); + }); - it("should allow group owner/admin/member to share item they do not own", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupMemberUserResponse - ); + it("should throw if the response from the server is fishy", done => { + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + GroupMemberUserResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + SearchResponse + ); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupMemberResponse - ); + fetchMock.once( + "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share", + FailedSharingResponse + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", - SharingResponse - ); + fetchMock.get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupMemberResponse + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b", - owner: "casey" - }) - .then(response => { + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b" + }).catch(e => { expect(fetchMock.done()).toBeTruthy( "All fetchMocks should have been called" ); - const [url, options]: [string, RequestInit] = fetchMock.lastCall( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" - ); - expect(url).toBe( - "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" - ); - expect(options.method).toBe("POST"); - expect(response).toEqual(SharingResponse); - expect(options.body).toContain("f=json"); - expect(options.body).toContain("groups=t6b"); + expect(e.message).toBe("Item n3v could not be shared to group t6b."); done(); - }) - .catch(e => { - fail(e); }); + }); }); - it("should throw is non-owner tries to share to shared editing group", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupMemberUserResponse - ); - - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); + describe("share item as org admin on behalf of other user ::", () => { + it("should add user to group then share item", done => { + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + { + username: "casey", + orgId: "qWAReEOCnD7eTxOe", + groups: [] as any[] + } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/addUsers", + { notAdded: [] } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + { notSharedWith: [], itemId: "n3v" } + ); - // called when we determine if the user is a member of the group - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupMemberResponse - ); + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(result => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + // verify we added casey to t6b + const addUsersOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/addUsers" + ); + expect(addUsersOptions.body).toContain("admins=casey"); + // verify we shared the item + const shareOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(shareOptions.body).toContain("groups=t6b"); + expect(shareOptions.body).toContain("confirmItemControl=true"); + + done(); + }) + .catch(e => { + fail(); + }); + }); + it("should add user to group the returned user lacks groups array", done => { + // tbh, not 100% sure this can even happen, but... 100% coverage + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + { + username: "casey", + orgId: "qWAReEOCnD7eTxOe" + } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/addUsers", + { notAdded: [] } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + { notSharedWith: [], itemId: "n3v" } + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b", - owner: "casey", - confirmItemControl: true - }).catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - expect(e.message).toContain( - "This item can not be shared to shared editing group t6b by jsmith as they not the item owner." - ); - done(); + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(result => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + // verify we added casey to t6b + const addUsersOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/addUsers" + ); + expect(addUsersOptions.body).toContain("admins=casey"); + // verify we shared the item + const shareOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(shareOptions.body).toContain("groups=t6b"); + expect(shareOptions.body).toContain("confirmItemControl=true"); + + done(); + }) + .catch(e => { + fail(); + }); }); - }); + it("should upgrade user to admin then share item", done => { + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + { + username: "casey", + orgId: "qWAReEOCnD7eTxOe", + groups: [ + { + id: "t6b", + userMembership: { + memberType: "member" + } + } + ] as any[] + } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/updateUsers", + { results: [{ username: "casey", success: true }] } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + { notSharedWith: [], itemId: "n3v" } + ); - it("should throw if the response from the server is fishy", done => { - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", - GroupMemberUserResponse - ); + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(result => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + // verify we added casey to t6b + const addUsersOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/updateUsers" + ); + expect(addUsersOptions.body).toContain("admins=casey"); + // verify we shared the item + const shareOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(shareOptions.body).toContain("groups=t6b"); + expect(shareOptions.body).toContain("confirmItemControl=true"); + + done(); + }) + .catch(e => { + fail(); + }); + }); + it("should share item if user is already admin in group", done => { + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + { + username: "casey", + orgId: "qWAReEOCnD7eTxOe", + groups: [ + { + id: "t6b", + userMembership: { + memberType: "admin" + } + } + ] as any[] + } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share", + { notSharedWith: [], itemId: "n3v" } + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/search", - SearchResponse - ); + shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(result => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + // verify we shared the item + const shareOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/content/items/n3v/share" + ); + expect(shareOptions.body).toContain("groups=t6b"); + expect(shareOptions.body).toContain("confirmItemControl=true"); + + done(); + }) + .catch(e => { + fail(); + }); + }); + it("should throw if we can not upgrade user membership", done => { + fetchMock + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + { + username: "casey", + orgId: "qWAReEOCnD7eTxOe", + groups: [ + { + id: "t6b", + userMembership: { + memberType: "member" + } + } + ] as any[] + } + ) + .post( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/updateUsers", + { results: [{ username: "casey", success: false }] } + ); - fetchMock.once( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/jsmith/items/n3v/share", - FailedSharingResponse - ); + return shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(() => { + expect("").toBe("Should Throw, but it returned"); + fail(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + const addUsersOptions: RequestInit = fetchMock.lastOptions( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b/updateUsers" + ); + expect(addUsersOptions.body).toContain("admins=casey"); + expect(e.message).toBe( + "Error adding user casey to group t6b. Consequently item n3v was not shared to the group." + ); + done(); + }); + }); + it("should throw if owner is in other org", done => { + fetchMock + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + { + username: "casey", + orgId: "some-other-org", + groups: [] as any[] + } + ); - fetchMock.get( - "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", - GroupMemberResponse - ); + return shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(() => { + expect("").toBe("Should Throw, but it returned"); + fail(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + + expect(e.message).toBe( + "User casey is not a member of the same org as jsmith. Consequently they can not be added added to group t6b nor can item n3v be shared to the group." + ); + done(); + }); + }); + it("should throw if owner has > 511 groups", done => { + const caseyUser = { + username: "casey", + orgId: "qWAReEOCnD7eTxOe", + groups: new Array(512) + }; + caseyUser.groups.fill({ id: "not-real-group" }); + fetchMock + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/jsmith?f=json&token=fake-token", + OrgAdminUserResponse + ) + .once( + "https://myorg.maps.arcgis.com/sharing/rest/search", + NoResultsSearchResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/groups/t6b?f=json&token=fake-token", + GroupOwnerResponse + ) + .get( + "https://myorg.maps.arcgis.com/sharing/rest/community/users/casey?f=json&token=fake-token", + caseyUser + ); - shareItemWithGroup({ - authentication: MOCK_USER_SESSION, - id: "n3v", - groupId: "t6b" - }).catch(e => { - expect(fetchMock.done()).toBeTruthy( - "All fetchMocks should have been called" - ); - expect(e.message).toBe("Item n3v could not be shared to group t6b."); - done(); + return shareItemWithGroup({ + authentication: MOCK_USER_SESSION, + id: "n3v", + groupId: "t6b", + owner: "casey", + confirmItemControl: true + }) + .then(() => { + expect("").toBe("Should Throw, but it returned"); + fail(); + }) + .catch(e => { + expect(fetchMock.done()).toBeTruthy( + "All fetchMocks should have been called" + ); + + expect(e.message).toBe( + "User casey already has 512 groups, and can not be added to group t6b. Consequently item n3v can not be shared to the group." + ); + done(); + }); }); }); }); diff --git a/packages/arcgis-rest-portal/test/util/get-portal-settings.test.ts b/packages/arcgis-rest-portal/test/util/get-portal-settings.test.ts new file mode 100644 index 0000000000..1816ce4c8e --- /dev/null +++ b/packages/arcgis-rest-portal/test/util/get-portal-settings.test.ts @@ -0,0 +1,68 @@ +/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. + * Apache-2.0 */ + +import { getPortalSettings } from "../../src/util/get-portal-settings"; + +import { PortalResponse } from "./../mocks/portal/response"; + +import * as fetchMock from "fetch-mock"; + +describe("portal", () => { + let paramsSpy: jasmine.Spy; + + beforeEach(() => { + paramsSpy = spyOn(FormData.prototype, "append").and.callThrough(); + }); + + afterAll(() => { + paramsSpy.calls.reset(); + }); + + afterEach(fetchMock.restore); + + describe("getPortalSettings", () => { + // setup an authmgr to use in all these tests + const MOCK_AUTH = { + getToken() { + return Promise.resolve("fake-token"); + }, + portal: "https://myorg.maps.arcgis.com/sharing/rest" + }; + const MOCK_REQOPTS = { + authentication: MOCK_AUTH + }; + + it("should get the portal settings by id", done => { + fetchMock.once("*", PortalResponse); + getPortalSettings("5BZFaKe", MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/portals/5BZFaKe/settings?f=json&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should get the portal self settings if no id", done => { + fetchMock.once("*", PortalResponse); + getPortalSettings(null, MOCK_REQOPTS) + .then(response => { + expect(fetchMock.called()).toEqual(true); + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual( + "https://myorg.maps.arcgis.com/sharing/rest/portals/self/settings?f=json&token=fake-token" + ); + expect(options.method).toBe("GET"); + done(); + }) + .catch(e => { + fail(e); + }); + }); + }); +}); diff --git a/packages/arcgis-rest-request/package.json b/packages/arcgis-rest-request/package.json index 718bb23291..84ffeffee3 100644 --- a/packages/arcgis-rest-request/package.json +++ b/packages/arcgis-rest-request/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-request", - "version": "2.8.2", + "version": "2.14.1", "description": "Common methods and utilities for @esri/arcgis-rest-js packages.", "main": "dist/node/index.js", "unpkg": "dist/umd/request.umd.js", diff --git a/packages/arcgis-rest-request/src/request.ts b/packages/arcgis-rest-request/src/request.ts index 247a94996a..39f8ec0146 100644 --- a/packages/arcgis-rest-request/src/request.ts +++ b/packages/arcgis-rest-request/src/request.ts @@ -281,7 +281,20 @@ export function request( params.token = token; } + // Custom headers to add to request. IRequestOptions.headers with merge over requestHeaders. + const requestHeaders: { + [key: string]: any; + } = {}; + if (fetchOptions.method === "GET") { + // Prevents token from being passed in query params when hideToken option is used. + /* istanbul ignore if - window is always defined in a browser. Test case is covered by Jasmine in node test */ + if (params.token && options.hideToken && + // Sharing API does not support preflight check required by modern browsers https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request + typeof window === 'undefined') { + requestHeaders["X-Esri-Authorization"] = `Bearer ${params.token}` + delete params.token; + } // encode the parameters into the query string const queryParams = encodeQueryString(params); // dont append a '?' unless parameters are actually present @@ -289,12 +302,22 @@ export function request( queryParams === "" ? url : url + "?" + encodeQueryString(params); if ( - options.maxUrlLength && - urlWithQueryString.length > options.maxUrlLength + // This would exceed the maximum length for URLs specified by the consumer and requires POST + (options.maxUrlLength && + urlWithQueryString.length > options.maxUrlLength) || + // Or if the customer requires the token to be hidden and it has not already been hidden in the header (for browsers) + (params.token && options.hideToken) ) { // the consumer specified a maximum length for URLs // and this would exceed it, so use post instead fetchOptions.method = "POST"; + + // If the token was already added as a Auth header, add the token back to body with other params instead of header + if (token.length && options.hideToken) { + params.token = token; + // Remove existing header that was added before url query length was checked + delete requestHeaders["X-Esri-Authorization"]; + } } else { // just use GET url = urlWithQueryString; @@ -312,6 +335,7 @@ export function request( // Mixin headers from request options fetchOptions.headers = { + ...requestHeaders, ...options.headers }; diff --git a/packages/arcgis-rest-request/src/utils/IRequestOptions.ts b/packages/arcgis-rest-request/src/utils/IRequestOptions.ts index 3404cd049c..b1d8e96d2f 100644 --- a/packages/arcgis-rest-request/src/utils/IRequestOptions.ts +++ b/packages/arcgis-rest-request/src/utils/IRequestOptions.ts @@ -22,6 +22,12 @@ export interface IRequestOptions { * The instance of `IAuthenticationManager` to use to authenticate this request. */ authentication?: IAuthenticationManager; + /** + * Prevents the token from being passed in a URL Query param that is saved in browser history. + * Instead, the token will be passed in POST request body or through X-Esri-Authorization header. + * NOTE: This will force POST requests in browsers since auth header is not yet supported by preflight OPTIONS check with CORS. + */ + hideToken?: boolean; /** * Base url for the portal you want to make the request to. Defaults to 'https://www.arcgis.com/sharing/rest'. */ diff --git a/packages/arcgis-rest-request/test/request.test.ts b/packages/arcgis-rest-request/test/request.test.ts index 2b19d809e2..c193abf44d 100644 --- a/packages/arcgis-rest-request/test/request.test.ts +++ b/packages/arcgis-rest-request/test/request.test.ts @@ -171,6 +171,81 @@ describe("request()", () => { }); }); + it("should hide token in POST body in browser environments otherwise it should hide token in `X-ESRI_AUTHORIZATION` header in Node", done => { + fetchMock.once("*", SharingRestInfo); + + const MOCK_AUTH = { + portal: "https://www.arcgis.com/sharing/rest", + getToken() { + return Promise.resolve("token"); + } + }; + + request("https://www.arcgis.com/sharing/rest/info", { + authentication: MOCK_AUTH, + httpMethod: 'GET', + hideToken: true + }) + .then(response => { + // Test Node path with Jasmine in Node + if (typeof window === 'undefined') { + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual("https://www.arcgis.com/sharing/rest/info?f=json"); + expect(options.method).toBe("GET"); + expect(response).toEqual(SharingRestInfo); + expect((options.headers as any)["X-Esri-Authorization"]).toBe("Bearer token"); + } else { + // Test browser path when run in browser with Karma + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual("https://www.arcgis.com/sharing/rest/info"); + expect(options.method).toBe("POST"); + expect(options.body).toContain("f=json"); + expect(options.body).toContain("token=token"); + expect((options.headers as any)["X-Esri-Authorization"]).toBe(undefined); + expect(response).toEqual(SharingRestInfo) + } + done(); + }) + .catch(e => { + fail(e); + }); + }); + + it("should switch from GET to POST when url is longer than specified and replace token in header with token in POST body", done => { + fetchMock.once("*", SharingRestInfo); + const restInfoUrl = "https://www.arcgis.com/sharing/rest/info"; + + const MOCK_AUTH = { + portal: "https://www.arcgis.com/sharing/rest", + getToken() { + return Promise.resolve("token"); + } + }; + + request(restInfoUrl, { + authentication: MOCK_AUTH, + httpMethod: "GET", + hideToken: true, + // typically consumers would base maxUrlLength on browser/server limits + // but for testing, we use an artificially low limit + // like this one that assumes no parameters will be added + maxUrlLength: restInfoUrl.length + }) + .then(response => { + const [url, options]: [string, RequestInit] = fetchMock.lastCall("*"); + expect(url).toEqual("https://www.arcgis.com/sharing/rest/info"); + expect(options.method).toBe("POST"); + expect(options.body).toContain("f=json"); + expect(options.body).toContain("token=token"); + expect((options.headers as any)["X-Esri-Authorization"]).toBe(undefined); + expect(response).toEqual(SharingRestInfo); + done(); + }) + .catch(e => { + fail(e); + }); + }); + it("should re-throw HTTP errors (404, 500, etc)", done => { fetchMock.once("*", 404); diff --git a/packages/arcgis-rest-routing/package.json b/packages/arcgis-rest-routing/package.json index a0d234c0c3..dd6ace2d3a 100644 --- a/packages/arcgis-rest-routing/package.json +++ b/packages/arcgis-rest-routing/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-routing", - "version": "2.8.2", + "version": "2.14.1", "description": "Routing helpers for @esri/arcgis-rest-js", "main": "dist/node/index.js", "unpkg": "dist/umd/routing.umd.js", @@ -13,12 +13,12 @@ "dist/**" ], "dependencies": { - "@esri/arcgis-rest-types": "^2.8.2", + "@esri/arcgis-rest-types": "^2.14.1", "tslib": "^1.9.3" }, "devDependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "peerDependencies": { "@esri/arcgis-rest-auth": "^2.0.0", diff --git a/packages/arcgis-rest-service-admin/package.json b/packages/arcgis-rest-service-admin/package.json index f6dce95f0b..8cc8c662d4 100644 --- a/packages/arcgis-rest-service-admin/package.json +++ b/packages/arcgis-rest-service-admin/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-service-admin", - "version": "2.8.2", + "version": "2.14.1", "description": "Service administration helpers for @esri/arcgis-rest-js", "main": "dist/node/index.js", "unpkg": "dist/umd/service-admin.umd.js", @@ -13,13 +13,13 @@ "dist/**" ], "dependencies": { - "@esri/arcgis-rest-types": "^2.8.2", + "@esri/arcgis-rest-types": "^2.14.1", "tslib": "^1.9.3" }, "devDependencies": { - "@esri/arcgis-rest-auth": "^2.8.2", - "@esri/arcgis-rest-portal": "^2.8.2", - "@esri/arcgis-rest-request": "^2.8.2" + "@esri/arcgis-rest-auth": "^2.14.1", + "@esri/arcgis-rest-portal": "^2.14.1", + "@esri/arcgis-rest-request": "^2.14.1" }, "peerDependencies": { "@esri/arcgis-rest-auth": "^2.0.0", diff --git a/packages/arcgis-rest-service-admin/src/create.ts b/packages/arcgis-rest-service-admin/src/create.ts index a372d3b753..d397d225e3 100644 --- a/packages/arcgis-rest-service-admin/src/create.ts +++ b/packages/arcgis-rest-service-admin/src/create.ts @@ -168,46 +168,22 @@ export interface ICreateServiceResult { export function createFeatureService( requestOptions: ICreateServiceOptions ): Promise { - const owner = determineOwner(requestOptions); - const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`; - const url = `${baseUrl}/createService`; - const options: ICreateServiceOptions = { - ...requestOptions, - rawResponse: false - }; - - // Create the service - options.params = { - createParameters: options.item, - outputType: "featureService", - ...options.params - }; + return determineOwner(requestOptions).then(owner => { + const options: ICreateServiceOptions = { + ...requestOptions, + rawResponse: false + }; + const baseUrl = `${getPortalUrl(requestOptions)}/content/users/${owner}`; + const folder = (!options.folderId || options.folderId === "/") ? "" : "/" + options.folderId; + const url = `${baseUrl}${folder}/createService`; + + // Create the service + options.params = { + createParameters: options.item, + outputType: "featureService", + ...options.params + }; - if (!options.folderId || options.folderId === "/") { - // If the service is destined for the root folder, just send the request return request(url, options); - } else { - // If the service is destined for a subfolder, move it (via another call) - return request(url, options).then(createResponse => { - if (createResponse.success) { - return moveItem({ - itemId: createResponse.itemId, - folderId: options.folderId, - authentication: options.authentication - }).then(moveResponse => { - if (moveResponse.success) { - return createResponse; - } else { - throw Error( - `A problem was encountered when trying to move the service to a different folder.` - ); - } - }); - } else { - throw Error( - `A problem was encountered when trying to create the service.` - ); - } - }); - } + }); } diff --git a/packages/arcgis-rest-service-admin/test/create.test.ts b/packages/arcgis-rest-service-admin/test/create.test.ts index 6b3b97a20d..c8746d9a8a 100644 --- a/packages/arcgis-rest-service-admin/test/create.test.ts +++ b/packages/arcgis-rest-service-admin/test/create.test.ts @@ -6,7 +6,6 @@ import * as fetchMock from "fetch-mock"; import { createFeatureService } from "../src/create"; import { FeatureServiceResponse } from "./mocks/service"; -import { MoveToFolderResponse } from "./mocks/move"; import { UserSession } from "@esri/arcgis-rest-auth"; import { TOMORROW } from "@esri/arcgis-rest-auth/test/utils"; @@ -75,7 +74,6 @@ describe("create feature service", () => { .then( response => { expect(fetchMock.called("end:createService")).toEqual(true); - expect(fetchMock.called("end:move")).toEqual(false); // Check create service call const [urlCreate, optionsCreate]: [ @@ -124,7 +122,6 @@ describe("create feature service", () => { .then( response => { expect(fetchMock.called("end:createService")).toEqual(true); - expect(fetchMock.called("end:move")).toEqual(false); // Check create service call const [urlCreate, optionsCreate]: [ @@ -173,7 +170,6 @@ describe("create feature service", () => { .then( response => { expect(fetchMock.called("end:createService")).toEqual(true); - expect(fetchMock.called("end:move")).toEqual(false); // Check create service call const [urlCreate, optionsCreate]: [ @@ -212,8 +208,7 @@ describe("create feature service", () => { it("should create a feature service in a particular folder", done => { fetchMock - .mock("end:createService", FeatureServiceResponse, {}) - .mock("end:move", MoveToFolderResponse, {}); + .mock("end:createService", FeatureServiceResponse, {}); const folderId = "83216cba44bf4357bf06687ec88a847b"; createFeatureService({ @@ -224,7 +219,6 @@ describe("create feature service", () => { .then( response => { expect(fetchMock.called("end:createService")).toEqual(true); - expect(fetchMock.called("end:move")).toEqual(true); // Check create service call const [urlCreate, optionsCreate]: [ @@ -232,7 +226,7 @@ describe("create feature service", () => { RequestInit ] = fetchMock.lastCall("end:createService"); expect(urlCreate).toEqual( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/createService" + "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/83216cba44bf4357bf06687ec88a847b/createService" ); expect(optionsCreate.method).toBe("POST"); expect(optionsCreate.body).toContain("f=json"); @@ -247,23 +241,6 @@ describe("create feature service", () => { encodeParam("token", "fake-token") ); - // Because the service is created in the root folder, the API follows it with a move call - const [urlMove, optionsMove]: [ - string, - RequestInit - ] = fetchMock.lastCall("end:move"); - expect(urlMove).toEqual( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/items/" + - response.serviceItemId + - "/move" - ); - expect(optionsMove.method).toBe("POST"); - expect(optionsMove.body).toContain("folder=" + folderId); - expect(optionsMove.body).toContain("f=json"); - expect(optionsMove.body).toContain( - encodeParam("token", "fake-token") - ); - // Check response expect(response).toEqual(FeatureServiceResponse); @@ -288,10 +265,8 @@ describe("create feature service", () => { folderId, ...MOCK_USER_REQOPTS }) - .then(() => fail()) - .catch(e => { + .then(e => { expect(fetchMock.called("end:createService")).toEqual(true); - expect(fetchMock.called("end:move")).toEqual(false); // Check create service call const [urlCreate, optionsCreate]: [ @@ -299,7 +274,7 @@ describe("create feature service", () => { RequestInit ] = fetchMock.lastCall("end:createService"); expect(urlCreate).toEqual( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/createService" + "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/83216cba44bf4357bf06687ec88a847b/createService" ); expect(optionsCreate.method).toBe("POST"); expect(optionsCreate.body).toContain("f=json"); @@ -310,68 +285,10 @@ describe("create feature service", () => { expect(optionsCreate.body).toContain( encodeParam("token", "fake-token") ); - expect(e.message).toEqual( - `A problem was encountered when trying to create the service.` - ); + expect(e.success).toBeFalsy(); done(); - }); - }); - - it("should fail to create a feature service destined for a particular folder with success=false (for the move)", done => { - fetchMock.mock("end:createService", FeatureServiceResponse, {}); - fetchMock.mock("end:move", { success: false }); - const folderId = "83216cba44bf4357bf06687ec88a847b"; - - createFeatureService({ - item: serviceDescription, - folderId, - ...MOCK_USER_REQOPTS - }) - .then(() => fail()) - .catch(e => { - expect(fetchMock.called("end:createService")).toEqual(true); - expect(fetchMock.called("end:move")).toEqual(true); - - // Check create service call - const [urlCreate, optionsCreate]: [ - string, - RequestInit - ] = fetchMock.lastCall("end:createService"); - expect(urlCreate).toEqual( - "https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/createService" - ); - expect(optionsCreate.method).toBe("POST"); - expect(optionsCreate.body).toContain("f=json"); - expect(optionsCreate.body).toContain( - encodeParam("createParameters", JSON.stringify(serviceDescription)) - ); - expect(optionsCreate.body).toContain("outputType=featureService"); - expect(optionsCreate.body).toContain( - encodeParam("token", "fake-token") - ); - - // Because the service is created in the root folder, the API follows it with a move call - const [urlMove, optionsMove]: [ - string, - RequestInit - ] = fetchMock.lastCall("end:move"); - expect(urlMove).toEqual( - `https://myorg.maps.arcgis.com/sharing/rest/content/users/casey/items/${ - FeatureServiceResponse.serviceItemId - }/move` - ); - expect(optionsMove.method).toBe("POST"); - expect(optionsMove.body).toContain("folder=" + folderId); - expect(optionsMove.body).toContain("f=json"); - expect(optionsMove.body).toContain( - encodeParam("token", "fake-token") - ); - - expect(e.message).toEqual( - `A problem was encountered when trying to move the service to a different folder.` - ); - done(); - }); + }) + .catch(() => fail()); }); }); }); diff --git a/packages/arcgis-rest-service-admin/test/mocks/move.ts b/packages/arcgis-rest-service-admin/test/mocks/move.ts deleted file mode 100644 index 6262ef4029..0000000000 --- a/packages/arcgis-rest-service-admin/test/mocks/move.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* Copyright (c) 2018 Environmental Systems Research Institute, Inc. - * Apache-2.0 */ - -import { IMoveItemResponse } from "@esri/arcgis-rest-portal"; - -export const MoveToFolderResponse: IMoveItemResponse = { - folder: "83216cba44bf4357bf06687ec88a847b", - itemId: "1b1a3c914ef94c49ae55ce223cac5754", - owner: "casey", - success: true -}; diff --git a/packages/arcgis-rest-types/package.json b/packages/arcgis-rest-types/package.json index ccdc1374b0..4be1026e67 100755 --- a/packages/arcgis-rest-types/package.json +++ b/packages/arcgis-rest-types/package.json @@ -1,6 +1,6 @@ { "name": "@esri/arcgis-rest-types", - "version": "2.8.2", + "version": "2.14.1", "description": "Shared TypeScript types for @esri/arcgis-rest-js", "types": "dist/types/index.d.ts", "author": "", diff --git a/packages/arcgis-rest-types/src/item.ts b/packages/arcgis-rest-types/src/item.ts index 32682d4247..0cf651e13d 100644 --- a/packages/arcgis-rest-types/src/item.ts +++ b/packages/arcgis-rest-types/src/item.ts @@ -63,3 +63,19 @@ export interface IItem extends IItemAdd { size: number; protected?: boolean; // not present in search results } + +/** + * Used for organizing content. See [Create Folder](https://developers.arcgis.com/rest/users-groups-and-items/create-folder.htm) for more details. + * + * `IFolder` can also be imported from the following packages: + * + * ```js + * import { IFolder } from "@esri/arcgis-rest-portal"; + * ``` + */ +export interface IFolder { + username: string; + id: string; + title: string; + created?: number; +} diff --git a/packages/arcgis-rest-types/src/statisticDefinition.ts b/packages/arcgis-rest-types/src/statisticDefinition.ts index e58034d4f0..6a06507d2d 100644 --- a/packages/arcgis-rest-types/src/statisticDefinition.ts +++ b/packages/arcgis-rest-types/src/statisticDefinition.ts @@ -1,29 +1,6 @@ /* Copyright (c) 2017-2018 Environmental Systems Research Institute, Inc. * Apache-2.0 */ -/** - * Enum of all different statistics operations - */ -export const enum StatisticType { - Count = "count", - Sum = "sum", - Minimum = "min", - Maximum = "max", - Average = "avg", - StandardDeviation = "stddev", - Variance = "var", - ContinuousPercentile = "percentile_cont", - DiscretePercentile = "percentile_disc" -} - -/** - * Enum of sorting orders - */ -export const enum SortingOrder { - Ascending = "asc", - Descending = "desc" -} - export interface IStatisticDefinition { /** * Statistical operation to perform (count, sum, min, max, avg, stddev, var, percentile_cont, percentile_disc). @@ -37,14 +14,13 @@ export interface IStatisticDefinition { | "stddev" | "var" | "percentile_cont" - | "percentile_disc" - | StatisticType; + | "percentile_disc"; /** - * Parameter to be used along with statisticType. Currently, only applicable for percentile_cont (continuous percentile) and percentile_disc (discrete percentile). + * Parameters to be used along with statisticType. Currently, only applicable for percentile_cont (continuous percentile) and percentile_disc (discrete percentile). */ - statisticParameter?: { + statisticParameters?: { value: number; - orderBy?: "asc" | "desc" | SortingOrder; + orderBy?: "asc" | "desc"; }; /** * Field on which to perform the statistical operation. diff --git a/packages/arcgis-rest-types/src/symbol.ts b/packages/arcgis-rest-types/src/symbol.ts index fb510150d2..7e71823722 100644 --- a/packages/arcgis-rest-types/src/symbol.ts +++ b/packages/arcgis-rest-types/src/symbol.ts @@ -6,43 +6,15 @@ */ export type Color = [number, number, number, number]; -/** - * - */ -export const enum FontStyle { - Italic = "italic", - Normal = "normal", - Oblique = "oblique" -} - -/** - * - */ -export const enum FontWeight { - Bold = "bold", - Bolder = "bolder", - Lighter = "lighter", - Normal = "normal" -} - -/** - * - */ -export const enum FontDecoration { - LineThrough = "line-through", - Underline = "underline", - None = "none" -} - /** * */ export interface IFont { family?: string; // ""; size?: number; // ; - style?: "italic" | "normal" | "oblique" | FontStyle; - weight?: "bold" | "bolder" | "lighter" | "normal" | FontWeight; - decoration?: "line-through" | "underline" | "none" | FontDecoration; + style?: "italic" | "normal" | "oblique"; + weight?: "bold" | "bolder" | "lighter" | "normal"; + decoration?: "line-through" | "underline" | "none"; } /** @@ -62,27 +34,16 @@ export interface IPictureSourced { /** * */ -export const enum SymbolType { - SLS = "esriSLS", - SMS = "esriSMS", - SFS = "esriSFS", - PMS = "esriPMS", - PFS = "esriPFS", - TS = "esriTS" +export interface ISymbol { + type: SymbolType; + style?: string; } /** * */ export interface ISymbol { - type: - | "esriSLS" - | "esriSMS" - | "esriSFS" - | "esriPMS" - | "esriPFS" - | "esriTS" - | SymbolType; + type: SymbolType; style?: string; } @@ -99,7 +60,7 @@ export interface IMarkerSymbol extends ISymbol { * */ export interface IPictureFillSymbol extends ISymbol, IPictureSourced { - type: "esriPFS" | SymbolType.PFS; + type: "esriPFS"; outline?: ISimpleLineSymbol; // if outline has been specified xscale?: number; yscale?: number; @@ -109,62 +70,61 @@ export interface IPictureFillSymbol extends ISymbol, IPictureSourced { * */ export interface IPictureMarkerSymbol extends IMarkerSymbol, IPictureSourced { - type: "esriPMS" | SymbolType.PMS; + type: "esriPMS"; } /** * */ -export const enum SimpleMarkerSymbolStyle { - Circle = "esriSMSCircle", - Cross = "esriSMSCross", - Diamond = "esriSMSDiamond", - Square = "esriSMSSquare", - X = "esriSMSX", - Triangle = "esriSMSTriangle" -} +export type SimpleMarkerSymbolStyle = + | "esriSMSCircle" + | "esriSMSCross" + | "esriSMSDiamond" + | "esriSMSSquare" + | "esriSMSX" + | "esriSMSTriangle"; /** * */ -export const enum SimpleLineSymbolStyle { - Dash = "esriSLSDash", - DashDot = "esriSLSDashDot", - DashDotDot = "esriSLSDashDotDot", - Dot = "esriSLSDot", - Null = "esriSLSNull", - Solid = "esriSLSSolid" -} +export type SimpleLineSymbolStyle = + | "esriSLSDash" + | "esriSLSDashDot" + | "esriSLSDashDotDot" + | "esriSLSDot" + | "esriSLSNull" + | "esriSLSSolid"; /** * */ -export const enum SimpleFillSymbolStyle { - BackwardDiagonal = "esriSFSBackwardDiagonal", - Cross = "esriSFSCross", - DiagonalCross = "esriSFSDiagonalCross", - ForwardDiagonal = "esriSFSForwardDiagonal", - Horizontal = "esriSFSHorizontal", - Null = "esriSFSNull", - Solid = "esriSFSSolid", - Vertical = "esriSFSVertical" -} +export type SimpleFillSymbolStyle = + | "esriSFSBackwardDiagonal" + | "esriSFSCross" + | "esriSFSDiagonalCross" + | "esriSFSForwardDiagonal" + | "esriSFSHorizontal" + | "esriSFSNull" + | "esriSFSSolid" + | "esriSFSVertical"; + +/** + * + */ +export type SymbolType = + | "esriSLS" + | "esriSMS" + | "esriSFS" + | "esriPMS" + | "esriPFS" + | "esriTS"; /** * */ export interface ISimpleFillSymbol extends ISymbol { - type: "esriSFS" | SymbolType.SFS; - style?: - | "esriSFSBackwardDiagonal" - | "esriSFSCross" - | "esriSFSDiagonalCross" - | "esriSFSForwardDiagonal" - | "esriSFSHorizontal" - | "esriSFSNull" - | "esriSFSSolid" - | "esriSFSVertical" - | SimpleFillSymbolStyle; + type: "esriSFS"; + style?: SimpleFillSymbolStyle; color?: Color; outline?: ISimpleLineSymbol; // if outline has been specified } @@ -173,15 +133,8 @@ export interface ISimpleFillSymbol extends ISymbol { * */ export interface ISimpleLineSymbol extends ISymbol { - type: "esriSLS" | SymbolType.SLS; - style?: - | "esriSLSDash" - | "esriSLSDashDot" - | "esriSLSDashDotDot" - | "esriSLSDot" - | "esriSLSNull" - | "esriSLSSolid" - | SimpleLineSymbolStyle; + type: "esriSLS"; + style?: SimpleLineSymbolStyle; color?: Color; width?: number; } @@ -190,60 +143,26 @@ export interface ISimpleLineSymbol extends ISymbol { * */ export interface ISimpleMarkerSymbol extends IMarkerSymbol { - type: "esriSMS" | SymbolType.SMS; - style?: - | "esriSMSCircle" - | "esriSMSCross" - | "esriSMSDiamond" - | "esriSMSSquare" - | "esriSMSX" - | "esriSMSTriangle" - | SimpleMarkerSymbolStyle; + type: "esriSMS"; + style?: SimpleMarkerSymbolStyle; color?: Color; size?: number; outline?: ISimpleLineSymbol; } -/** - * - */ -export const enum VerticalAlignment { - Baseline = "baseline", - Top = "top", - Middle = "middle", - Bottom = "bottom" -} - -export const enum HorizontalAlignment { - Left = "left", - Right = "right", - Center = "center", - Justify = "justify" -} - /** * */ export interface ITextSymbol extends IMarkerSymbol { - type: "esriTS" | SymbolType.TS; + type: "esriTS"; color?: Color; backgroundColor?: Color; borderLineSize?: number; // ; borderLineColor?: Color; haloSize?: number; // ; haloColor?: Color; - verticalAlignment?: - | "baseline" - | "top" - | "middle" - | "bottom" - | VerticalAlignment; - horizontalAlignment?: - | "left" - | "right" - | "center" - | "justify" - | HorizontalAlignment; + verticalAlignment?: "baseline" | "top" | "middle" | "bottom"; + horizontalAlignment?: "left" | "right" | "center" | "justify"; rightToLeft?: boolean; kerning?: boolean; font?: IFont; diff --git a/packages/arcgis-rest-types/src/webmap.ts b/packages/arcgis-rest-types/src/webmap.ts index f64f50f48e..e5c71d9680 100644 --- a/packages/arcgis-rest-types/src/webmap.ts +++ b/packages/arcgis-rest-types/src/webmap.ts @@ -22,21 +22,20 @@ import { /** * Field type. */ -export const enum FieldType { - Blob = "esriFieldTypeBlob", - Date = "esriFieldTypeDate", - Double = "esriFieldTypeDouble", - Geometry = "esriFieldTypeGeometry", - GlobalID = "esriFieldTypeGlobalID", - GUID = "esriFieldTypeGUID", - Integer = "esriFieldTypeInteger", - OID = "esriFieldTypeOID", - Raster = "esriFieldTypeRaster", - Single = "esriFieldTypeSingle", - SmallInteger = "esriFieldTypeSmallInteger", - String = "esriFieldTypeString", - XML = "esriFieldTypeXML" -} +export type FieldType = + | "esriFieldTypeBlob" + | "esriFieldTypeDate" + | "esriFieldTypeDouble" + | "esriFieldTypeGeometry" + | "esriFieldTypeGlobalID" + | "esriFieldTypeGUID" + | "esriFieldTypeInteger" + | "esriFieldTypeOID" + | "esriFieldTypeRaster" + | "esriFieldTypeSingle" + | "esriFieldTypeSmallInteger" + | "esriFieldTypeString" + | "esriFieldTypeXML"; /** * Contains information about an attribute field. @@ -51,21 +50,7 @@ export interface IField { /** A string defining the field name. */ name: string; /** A string defining the field type. */ - type: - | "esriFieldTypeBlob" - | "esriFieldTypeDate" - | "esriFieldTypeDouble" - | "esriFieldTypeGeometry" - | "esriFieldTypeGlobalID" - | "esriFieldTypeGUID" - | "esriFieldTypeInteger" - | "esriFieldTypeOID" - | "esriFieldTypeRaster" - | "esriFieldTypeSingle" - | "esriFieldTypeSmallInteger" - | "esriFieldTypeString" - | "esriFieldTypeXML" - | FieldType; + type: FieldType; /** A string defining the field alias. */ alias?: string; /** The domain objects if applicable. */ @@ -95,29 +80,21 @@ export interface IPagingParams { start?: number; num?: number; } - /** - * Date fields types to specify how the date should appear in popup windows + * Paging properties for paged responses. + * + * `IPagedResponse` can also be imported from the following packages: + * + * ```js + * import { IPagedResponse } from "@esri/arcgis-rest-portal"; + * ``` */ -export const enum DateFormat { - ShortDate = "shortDate", - ShortDateLE = "shortDateLE", - LongMonthDayYear = "longMonthDayYear", - DayShortMonthYear = "dayShortMonthYear", - LongDate = "longDate", - ShortDateShortTime = "shortDateShortTime", - ShortDateLEShortTime = "shortDateLEShortTime", - ShortDateShortTime24 = "shortDateShortTime24", - ShortDateLEShortTime24 = "shortDateLEShortTime24", - ShortDateLongTime = "shortDateLongTime", - ShortDateLELongTime = "shortDateLELongTime", - ShortDateLongTime24 = "shortDateLongTime24", - ShortDateLELongTime24 = "shortDateLELongTime24", - LongMonthYear = "longMonthYear", - ShortMonthYear = "shortMonthYear", - Year = "year" +export interface IPagedResponse extends IPagingParams { + /** total number of object across all pages */ + total: number; + /** next entry index or -1 for the last page */ + nextStart: number; } - /** * The format object can be used with numerical or date fields to provide more detail about how values should be displayed in popup windows. */ @@ -139,8 +116,7 @@ export interface IFieldFormat { | "shortDateLELongTime24" | "longMonthYear" | "shortMonthYear" - | "year" - | DateFormat; + | "year"; /** * A Boolean used with numerical fields. A value of true allows the number to have a digit (or thousands) separator when the value appears in popup windows.