@esri/solution-common 4.1.2 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/dist/cjs/completeItem.d.ts +29 -29
  2. package/dist/cjs/completeItem.js +81 -81
  3. package/dist/cjs/create-hub-request-options.d.ts +29 -29
  4. package/dist/cjs/create-hub-request-options.js +63 -63
  5. package/dist/cjs/deleteHelpers/deleteEmptyGroups.d.ts +24 -24
  6. package/dist/cjs/deleteHelpers/deleteEmptyGroups.js +41 -41
  7. package/dist/cjs/deleteHelpers/deleteGroupIfEmpty.d.ts +27 -27
  8. package/dist/cjs/deleteHelpers/deleteGroupIfEmpty.js +96 -96
  9. package/dist/cjs/deleteHelpers/deleteSolutionContents.d.ts +38 -38
  10. package/dist/cjs/deleteHelpers/deleteSolutionContents.js +129 -129
  11. package/dist/cjs/deleteHelpers/deleteSolutionFolder.d.ts +29 -29
  12. package/dist/cjs/deleteHelpers/deleteSolutionFolder.js +78 -78
  13. package/dist/cjs/deleteHelpers/deleteSolutionItem.d.ts +30 -30
  14. package/dist/cjs/deleteHelpers/deleteSolutionItem.js +53 -53
  15. package/dist/cjs/deleteHelpers/index.d.ts +22 -22
  16. package/dist/cjs/deleteHelpers/index.js +25 -25
  17. package/dist/cjs/deleteHelpers/reconstructBuildOrderIds.d.ts +27 -27
  18. package/dist/cjs/deleteHelpers/reconstructBuildOrderIds.js +33 -33
  19. package/dist/cjs/deleteHelpers/removeItems.d.ts +34 -34
  20. package/dist/cjs/deleteHelpers/removeItems.js +111 -111
  21. package/dist/cjs/deleteHelpers/reportProgress.d.ts +27 -27
  22. package/dist/cjs/deleteHelpers/reportProgress.js +45 -45
  23. package/dist/cjs/deleteSolution.d.ts +55 -55
  24. package/dist/cjs/deleteSolution.js +106 -106
  25. package/dist/cjs/dependencies.d.ts +26 -26
  26. package/dist/cjs/dependencies.js +170 -170
  27. package/dist/cjs/featureServiceHelpers.d.ts +791 -791
  28. package/dist/cjs/featureServiceHelpers.js +2420 -2420
  29. package/dist/cjs/generalHelpers.d.ts +392 -385
  30. package/dist/cjs/generalHelpers.js +857 -854
  31. package/dist/cjs/generalHelpers.js.map +1 -1
  32. package/dist/cjs/get-subscription-info.d.ts +27 -27
  33. package/dist/cjs/get-subscription-info.js +38 -38
  34. package/dist/cjs/getDeletableSolutionInfo.d.ts +29 -29
  35. package/dist/cjs/getDeletableSolutionInfo.js +52 -52
  36. package/dist/cjs/getItemTypeAbbrev.d.ts +19 -19
  37. package/dist/cjs/getItemTypeAbbrev.js +184 -184
  38. package/dist/cjs/getSolutionSummary.d.ts +27 -27
  39. package/dist/cjs/getSolutionSummary.js +100 -100
  40. package/dist/cjs/index.d.ts +43 -44
  41. package/dist/cjs/index.js +46 -47
  42. package/dist/cjs/index.js.map +1 -1
  43. package/dist/cjs/interfaces.d.ts +1334 -1334
  44. package/dist/cjs/interfaces.js +74 -74
  45. package/dist/cjs/interfaces.js.map +1 -1
  46. package/dist/cjs/libConnectors.d.ts +73 -73
  47. package/dist/cjs/libConnectors.js +114 -114
  48. package/dist/cjs/migrations/apply-schema.d.ts +24 -24
  49. package/dist/cjs/migrations/apply-schema.js +35 -35
  50. package/dist/cjs/migrations/is-legacy-solution.d.ts +24 -24
  51. package/dist/cjs/migrations/is-legacy-solution.js +39 -39
  52. package/dist/cjs/migrations/upgrade-three-dot-one.d.ts +27 -27
  53. package/dist/cjs/migrations/upgrade-three-dot-one.js +48 -48
  54. package/dist/cjs/migrations/upgrade-three-dot-zero.d.ts +27 -27
  55. package/dist/cjs/migrations/upgrade-three-dot-zero.js +42 -42
  56. package/dist/cjs/migrations/upgrade-two-dot-five.d.ts +24 -24
  57. package/dist/cjs/migrations/upgrade-two-dot-five.js +72 -72
  58. package/dist/cjs/migrations/upgrade-two-dot-four.d.ts +24 -24
  59. package/dist/cjs/migrations/upgrade-two-dot-four.js +71 -71
  60. package/dist/cjs/migrations/upgrade-two-dot-one.d.ts +7 -7
  61. package/dist/cjs/migrations/upgrade-two-dot-one.js +38 -38
  62. package/dist/cjs/migrations/upgrade-two-dot-seven.d.ts +23 -23
  63. package/dist/cjs/migrations/upgrade-two-dot-seven.js +57 -57
  64. package/dist/cjs/migrations/upgrade-two-dot-six.d.ts +27 -27
  65. package/dist/cjs/migrations/upgrade-two-dot-six.js +60 -60
  66. package/dist/cjs/migrations/upgrade-two-dot-three.d.ts +23 -23
  67. package/dist/cjs/migrations/upgrade-two-dot-three.js +54 -54
  68. package/dist/cjs/migrations/upgrade-two-dot-two.d.ts +23 -23
  69. package/dist/cjs/migrations/upgrade-two-dot-two.js +57 -57
  70. package/dist/cjs/migrations/upgrade-two-dot-zero.d.ts +44 -44
  71. package/dist/cjs/migrations/upgrade-two-dot-zero.js +94 -94
  72. package/dist/cjs/migrator.d.ts +25 -25
  73. package/dist/cjs/migrator.js +76 -76
  74. package/dist/cjs/resourceHelpers.d.ts +191 -191
  75. package/dist/cjs/resourceHelpers.js +383 -390
  76. package/dist/cjs/resourceHelpers.js.map +1 -1
  77. package/dist/cjs/resources/add-resource-from-blob.d.ts +26 -26
  78. package/dist/cjs/resources/add-resource-from-blob.js +51 -51
  79. package/dist/cjs/resources/addMetadataFromBlob.d.ts +25 -25
  80. package/dist/cjs/resources/addMetadataFromBlob.js +42 -42
  81. package/dist/cjs/resources/convert-item-resource-to-storage-resource.d.ts +32 -32
  82. package/dist/cjs/resources/convert-item-resource-to-storage-resource.js +69 -69
  83. package/dist/cjs/resources/convert-storage-resource-to-item-resource.d.ts +29 -29
  84. package/dist/cjs/resources/convert-storage-resource-to-item-resource.js +69 -69
  85. package/dist/cjs/resources/copyAssociatedFiles.d.ts +67 -67
  86. package/dist/cjs/resources/copyAssociatedFiles.js +301 -301
  87. package/dist/cjs/resources/copyDataIntoItem.d.ts +33 -33
  88. package/dist/cjs/resources/copyDataIntoItem.js +61 -62
  89. package/dist/cjs/resources/copyDataIntoItem.js.map +1 -1
  90. package/dist/cjs/resources/copyMetadataIntoItem.d.ts +26 -26
  91. package/dist/cjs/resources/copyMetadataIntoItem.js +45 -45
  92. package/dist/cjs/resources/copyResourceIntoZip.d.ts +33 -33
  93. package/dist/cjs/resources/copyResourceIntoZip.js +77 -77
  94. package/dist/cjs/resources/copyZipIntoItem.d.ts +25 -25
  95. package/dist/cjs/resources/copyZipIntoItem.js +53 -53
  96. package/dist/cjs/resources/createCopyResults.d.ts +25 -25
  97. package/dist/cjs/resources/createCopyResults.js +35 -35
  98. package/dist/cjs/resources/get-blob.d.ts +26 -26
  99. package/dist/cjs/resources/get-blob.js +26 -26
  100. package/dist/cjs/resources/getItemResourcesFilesFromPaths.d.ts +24 -24
  101. package/dist/cjs/resources/getItemResourcesFilesFromPaths.js +48 -48
  102. package/dist/cjs/resources/getItemResourcesPaths.d.ts +26 -26
  103. package/dist/cjs/resources/getItemResourcesPaths.js +72 -72
  104. package/dist/cjs/resources/index.d.ts +29 -29
  105. package/dist/cjs/resources/index.js +32 -32
  106. package/dist/cjs/resources/solution-resource.d.ts +35 -35
  107. package/dist/cjs/resources/solution-resource.js +30 -30
  108. package/dist/cjs/resources/solution-resource.js.map +1 -1
  109. package/dist/cjs/resources/transform-resource-paths-to-solution-resources.d.ts +56 -56
  110. package/dist/cjs/resources/transform-resource-paths-to-solution-resources.js +145 -145
  111. package/dist/cjs/restHelpers.d.ts +586 -585
  112. package/dist/cjs/restHelpers.js +1889 -1883
  113. package/dist/cjs/restHelpers.js.map +1 -1
  114. package/dist/cjs/restHelpersGet.d.ts +288 -288
  115. package/dist/cjs/restHelpersGet.js +803 -803
  116. package/dist/cjs/sharing/index.d.ts +16 -16
  117. package/dist/cjs/sharing/index.js +19 -19
  118. package/dist/cjs/sharing/share-item-to-groups.d.ts +26 -26
  119. package/dist/cjs/sharing/share-item-to-groups.js +43 -43
  120. package/dist/cjs/templatization.d.ts +139 -139
  121. package/dist/cjs/templatization.js +313 -313
  122. package/dist/cjs/trackingHelpers.d.ts +116 -116
  123. package/dist/cjs/trackingHelpers.js +216 -216
  124. package/dist/cjs/velocityHelpers.d.ts +57 -57
  125. package/dist/cjs/velocityHelpers.js +134 -134
  126. package/dist/cjs/workforceHelpers.d.ts +115 -115
  127. package/dist/cjs/workforceHelpers.js +746 -746
  128. package/dist/cjs/workforceHelpers.js.map +1 -1
  129. package/dist/esm/completeItem.d.ts +29 -29
  130. package/dist/esm/completeItem.js +76 -76
  131. package/dist/esm/create-hub-request-options.d.ts +29 -29
  132. package/dist/esm/create-hub-request-options.js +59 -59
  133. package/dist/esm/deleteHelpers/deleteEmptyGroups.d.ts +24 -24
  134. package/dist/esm/deleteHelpers/deleteEmptyGroups.js +37 -37
  135. package/dist/esm/deleteHelpers/deleteGroupIfEmpty.d.ts +27 -27
  136. package/dist/esm/deleteHelpers/deleteGroupIfEmpty.js +91 -91
  137. package/dist/esm/deleteHelpers/deleteSolutionContents.d.ts +38 -38
  138. package/dist/esm/deleteHelpers/deleteSolutionContents.js +124 -124
  139. package/dist/esm/deleteHelpers/deleteSolutionFolder.d.ts +29 -29
  140. package/dist/esm/deleteHelpers/deleteSolutionFolder.js +73 -73
  141. package/dist/esm/deleteHelpers/deleteSolutionItem.d.ts +30 -30
  142. package/dist/esm/deleteHelpers/deleteSolutionItem.js +48 -48
  143. package/dist/esm/deleteHelpers/index.d.ts +22 -22
  144. package/dist/esm/deleteHelpers/index.js +22 -22
  145. package/dist/esm/deleteHelpers/reconstructBuildOrderIds.d.ts +27 -27
  146. package/dist/esm/deleteHelpers/reconstructBuildOrderIds.js +28 -28
  147. package/dist/esm/deleteHelpers/removeItems.d.ts +34 -34
  148. package/dist/esm/deleteHelpers/removeItems.js +106 -106
  149. package/dist/esm/deleteHelpers/reportProgress.d.ts +27 -27
  150. package/dist/esm/deleteHelpers/reportProgress.js +41 -41
  151. package/dist/esm/deleteSolution.d.ts +55 -55
  152. package/dist/esm/deleteSolution.js +100 -100
  153. package/dist/esm/dependencies.d.ts +26 -26
  154. package/dist/esm/dependencies.js +166 -166
  155. package/dist/esm/featureServiceHelpers.d.ts +791 -791
  156. package/dist/esm/featureServiceHelpers.js +2336 -2336
  157. package/dist/esm/generalHelpers.d.ts +392 -385
  158. package/dist/esm/generalHelpers.js +810 -808
  159. package/dist/esm/generalHelpers.js.map +1 -1
  160. package/dist/esm/get-subscription-info.d.ts +27 -27
  161. package/dist/esm/get-subscription-info.js +34 -34
  162. package/dist/esm/getDeletableSolutionInfo.d.ts +29 -29
  163. package/dist/esm/getDeletableSolutionInfo.js +47 -47
  164. package/dist/esm/getItemTypeAbbrev.d.ts +19 -19
  165. package/dist/esm/getItemTypeAbbrev.js +180 -180
  166. package/dist/esm/getSolutionSummary.d.ts +27 -27
  167. package/dist/esm/getSolutionSummary.js +95 -95
  168. package/dist/esm/index.d.ts +43 -44
  169. package/dist/esm/index.js +43 -44
  170. package/dist/esm/index.js.map +1 -1
  171. package/dist/esm/interfaces.d.ts +1334 -1334
  172. package/dist/esm/interfaces.js +70 -70
  173. package/dist/esm/libConnectors.d.ts +73 -73
  174. package/dist/esm/libConnectors.js +104 -104
  175. package/dist/esm/migrations/apply-schema.d.ts +24 -24
  176. package/dist/esm/migrations/apply-schema.js +31 -31
  177. package/dist/esm/migrations/is-legacy-solution.d.ts +24 -24
  178. package/dist/esm/migrations/is-legacy-solution.js +35 -35
  179. package/dist/esm/migrations/upgrade-three-dot-one.d.ts +27 -27
  180. package/dist/esm/migrations/upgrade-three-dot-one.js +44 -44
  181. package/dist/esm/migrations/upgrade-three-dot-zero.d.ts +27 -27
  182. package/dist/esm/migrations/upgrade-three-dot-zero.js +38 -38
  183. package/dist/esm/migrations/upgrade-two-dot-five.d.ts +24 -24
  184. package/dist/esm/migrations/upgrade-two-dot-five.js +68 -68
  185. package/dist/esm/migrations/upgrade-two-dot-four.d.ts +24 -24
  186. package/dist/esm/migrations/upgrade-two-dot-four.js +67 -67
  187. package/dist/esm/migrations/upgrade-two-dot-one.d.ts +7 -7
  188. package/dist/esm/migrations/upgrade-two-dot-one.js +34 -34
  189. package/dist/esm/migrations/upgrade-two-dot-seven.d.ts +23 -23
  190. package/dist/esm/migrations/upgrade-two-dot-seven.js +53 -53
  191. package/dist/esm/migrations/upgrade-two-dot-six.d.ts +27 -27
  192. package/dist/esm/migrations/upgrade-two-dot-six.js +56 -56
  193. package/dist/esm/migrations/upgrade-two-dot-three.d.ts +23 -23
  194. package/dist/esm/migrations/upgrade-two-dot-three.js +50 -50
  195. package/dist/esm/migrations/upgrade-two-dot-two.d.ts +23 -23
  196. package/dist/esm/migrations/upgrade-two-dot-two.js +53 -53
  197. package/dist/esm/migrations/upgrade-two-dot-zero.d.ts +44 -44
  198. package/dist/esm/migrations/upgrade-two-dot-zero.js +87 -87
  199. package/dist/esm/migrator.d.ts +25 -25
  200. package/dist/esm/migrator.js +72 -72
  201. package/dist/esm/resourceHelpers.d.ts +191 -191
  202. package/dist/esm/resourceHelpers.js +364 -371
  203. package/dist/esm/resourceHelpers.js.map +1 -1
  204. package/dist/esm/resources/add-resource-from-blob.d.ts +26 -26
  205. package/dist/esm/resources/add-resource-from-blob.js +47 -47
  206. package/dist/esm/resources/addMetadataFromBlob.d.ts +25 -25
  207. package/dist/esm/resources/addMetadataFromBlob.js +38 -38
  208. package/dist/esm/resources/convert-item-resource-to-storage-resource.d.ts +32 -32
  209. package/dist/esm/resources/convert-item-resource-to-storage-resource.js +65 -65
  210. package/dist/esm/resources/convert-storage-resource-to-item-resource.d.ts +29 -29
  211. package/dist/esm/resources/convert-storage-resource-to-item-resource.js +65 -65
  212. package/dist/esm/resources/copyAssociatedFiles.d.ts +67 -67
  213. package/dist/esm/resources/copyAssociatedFiles.js +293 -293
  214. package/dist/esm/resources/copyDataIntoItem.d.ts +33 -33
  215. package/dist/esm/resources/copyDataIntoItem.js +56 -57
  216. package/dist/esm/resources/copyDataIntoItem.js.map +1 -1
  217. package/dist/esm/resources/copyMetadataIntoItem.d.ts +26 -26
  218. package/dist/esm/resources/copyMetadataIntoItem.js +41 -41
  219. package/dist/esm/resources/copyResourceIntoZip.d.ts +33 -33
  220. package/dist/esm/resources/copyResourceIntoZip.js +72 -72
  221. package/dist/esm/resources/copyZipIntoItem.d.ts +25 -25
  222. package/dist/esm/resources/copyZipIntoItem.js +49 -49
  223. package/dist/esm/resources/createCopyResults.d.ts +25 -25
  224. package/dist/esm/resources/createCopyResults.js +31 -31
  225. package/dist/esm/resources/get-blob.d.ts +26 -26
  226. package/dist/esm/resources/get-blob.js +22 -22
  227. package/dist/esm/resources/getItemResourcesFilesFromPaths.d.ts +24 -24
  228. package/dist/esm/resources/getItemResourcesFilesFromPaths.js +44 -44
  229. package/dist/esm/resources/getItemResourcesPaths.d.ts +26 -26
  230. package/dist/esm/resources/getItemResourcesPaths.js +68 -68
  231. package/dist/esm/resources/index.d.ts +29 -29
  232. package/dist/esm/resources/index.js +29 -29
  233. package/dist/esm/resources/solution-resource.d.ts +35 -35
  234. package/dist/esm/resources/solution-resource.js +27 -27
  235. package/dist/esm/resources/transform-resource-paths-to-solution-resources.d.ts +56 -56
  236. package/dist/esm/resources/transform-resource-paths-to-solution-resources.js +137 -137
  237. package/dist/esm/restHelpers.d.ts +586 -585
  238. package/dist/esm/restHelpers.js +1828 -1823
  239. package/dist/esm/restHelpers.js.map +1 -1
  240. package/dist/esm/restHelpersGet.d.ts +288 -288
  241. package/dist/esm/restHelpersGet.js +763 -763
  242. package/dist/esm/sharing/index.d.ts +16 -16
  243. package/dist/esm/sharing/index.js +16 -16
  244. package/dist/esm/sharing/share-item-to-groups.d.ts +26 -26
  245. package/dist/esm/sharing/share-item-to-groups.js +39 -39
  246. package/dist/esm/templatization.d.ts +139 -139
  247. package/dist/esm/templatization.js +293 -293
  248. package/dist/esm/trackingHelpers.d.ts +116 -116
  249. package/dist/esm/trackingHelpers.js +204 -204
  250. package/dist/esm/velocityHelpers.d.ts +57 -57
  251. package/dist/esm/velocityHelpers.js +128 -128
  252. package/dist/esm/workforceHelpers.d.ts +115 -115
  253. package/dist/esm/workforceHelpers.js +717 -717
  254. package/dist/esm/workforceHelpers.js.map +1 -1
  255. package/package.json +3 -3
  256. package/dist/cjs/polyfills.d.ts +0 -40
  257. package/dist/cjs/polyfills.js +0 -98
  258. package/dist/cjs/polyfills.js.map +0 -1
  259. package/dist/esm/polyfills.d.ts +0 -40
  260. package/dist/esm/polyfills.js +0 -93
  261. package/dist/esm/polyfills.js.map +0 -1
@@ -1,1824 +1,1829 @@
1
- /** @license
2
- * Copyright 2018 Esri
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- /**
17
- * Provides common functions involving the arcgis-rest-js library.
18
- *
19
- * @module restHelpers
20
- */
21
- import { removeLayerOptimization, setDefaultSpatialReference, validateSpatialReferenceAndExtent, processContingentValues } from "./featureServiceHelpers";
22
- import { appendQueryParam, blobToJson, blobToText, checkUrlPathTermination, deleteProp, deleteProps, fail, getProp, getUniqueTitle, setCreateProp } from "./generalHelpers";
23
- import { UserSession } from "./interfaces";
24
- import { createZip } from "./libConnectors";
25
- import { getItemBase, getItemDataAsJson } from "./restHelpersGet";
26
- import { addItemData as portalAddItemData, addItemRelationship, addItemResource, createFolder, createGroup, createItemInFolder, getItem, removeFolder as portalRemoveFolder, removeGroup as portalRemoveGroup, removeGroupUsers as portalRemoveGroupUsers, removeItem as portalRemoveItem, searchGroupContent, searchGroups as portalSearchGroups, searchItems as portalSearchItems, SearchQueryBuilder, setItemAccess, shareItemWithGroup, updateItem as portalUpdateItem, updateGroup as portalUpdateGroup } from "@esri/arcgis-rest-portal";
27
- import { request } from "@esri/arcgis-rest-request";
28
- import { addToServiceDefinition as svcAdminAddToServiceDefinition, createFeatureService as svcAdminCreateFeatureService } from "@esri/arcgis-rest-service-admin";
29
- import { getWorkforceDependencies, isWorkforceProject, getWorkforceServiceInfo } from "./workforceHelpers";
30
- import { hasUnresolvedVariables, replaceInTemplate } from "./templatization";
31
- import { isTrackingViewTemplate, setTrackingOptions } from "./trackingHelpers";
32
- // ------------------------------------------------------------------------------------------------------------------ //
33
- export { request as rest_request } from "@esri/arcgis-rest-request";
34
- // ------------------------------------------------------------------------------------------------------------------ //
35
- /**
36
- * Creates a UserSession via a function so that the global arcgisSolution variable can access authentication.
37
- *
38
- * @param options See https://esri.github.io/arcgis-rest-js/api/auth/IUserSessionOptions/
39
- * @returns UserSession
40
- */
41
- export function getUserSession(options = {}) {
42
- return new UserSession(options);
43
- }
44
- /**
45
- * Adds a forward relationship between two items.
46
- *
47
- * @param originItemId Origin of relationship
48
- * @param destinationItemId Destination of relationship
49
- * @param relationshipType Type of relationship
50
- * @param authentication Credentials for the request
51
- * @returns A Promise to add item resources.
52
- */
53
- export function addForwardItemRelationship(originItemId, destinationItemId, relationshipType, authentication) {
54
- return new Promise(resolve => {
55
- const requestOptions = {
56
- originItemId,
57
- destinationItemId,
58
- relationshipType,
59
- authentication
60
- };
61
- addItemRelationship(requestOptions).then(response => {
62
- resolve({
63
- success: response.success,
64
- itemId: originItemId
65
- });
66
- }, () => {
67
- resolve({
68
- success: false,
69
- itemId: originItemId
70
- });
71
- });
72
- });
73
- }
74
- /**
75
- * Adds forward relationships for an item.
76
- *
77
- * @param originItemId Origin of relationship
78
- * @param destinationRelationships Destinations
79
- * @param authentication Credentials for the request
80
- * @returns A Promise to add item resources.
81
- */
82
- export function addForwardItemRelationships(originItemId, destinationRelationships, authentication) {
83
- return new Promise(resolve => {
84
- // Set up relationships using updated relationship information
85
- const relationshipPromises = new Array();
86
- destinationRelationships.forEach(relationship => {
87
- relationship.relatedItemIds.forEach(relatedItemId => {
88
- relationshipPromises.push(addForwardItemRelationship(originItemId, relatedItemId, relationship.relationshipType, authentication));
89
- });
90
- });
91
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
92
- Promise.all(relationshipPromises).then((responses) => resolve(responses));
93
- });
94
- }
95
- /**
96
- * Adds a token to the query parameters of a URL.
97
- *
98
- * @param url URL to use as base
99
- * @param authentication Credentials to be used to generate token for URL
100
- * @returns A promise that will resolve with the supplied URL with `token=<token>` added to its query params
101
- * unless either the URL doesn't exist or the token can't be generated
102
- */
103
- export function addTokenToUrl(url, authentication) {
104
- return new Promise(resolve => {
105
- if (!url || !authentication) {
106
- resolve(url);
107
- }
108
- else {
109
- authentication.getToken(url).then(token => {
110
- /* istanbul ignore else */
111
- if (token) {
112
- url = appendQueryParam(url, "token=" + token);
113
- }
114
- resolve(url);
115
- }, () => resolve(url));
116
- }
117
- });
118
- }
119
- /**
120
- * Calls addToDefinition for the service.
121
- *
122
- * Added retry due to some solutions failing to deploy in specific orgs/hives due to timeouts.
123
- * On the first pass we will use the quicker sync request to add.
124
- * If it fails we will use an async request that will avoid the timeout errors.
125
- *
126
- * @param url URL to use as base
127
- * @param options the info to add to the services definition
128
- * @param skipRetry a boolean to control if retry logic will be used. Defaults to false.
129
- * @param useAsync a boolean to control if we will use an async request
130
- * @returns A promise that will resolve when the request has completed
131
- */
132
- export function addToServiceDefinition(url, options, skipRetry = false, useAsync = false) {
133
- /* istanbul ignore else */
134
- if (useAsync) {
135
- options.params = { ...options.params, async: true };
136
- }
137
- return new Promise((resolve, reject) => {
138
- svcAdminAddToServiceDefinition(url, options).then((result) => {
139
- checkRequestStatus(result, options.authentication).then(() => resolve(null), e => reject(fail(e)));
140
- }, e => {
141
- if (!skipRetry) {
142
- addToServiceDefinition(url, options, true, true).then(() => resolve(null), e => reject(e));
143
- }
144
- else {
145
- reject(fail(e));
146
- }
147
- });
148
- });
149
- }
150
- /**
151
- * When using an async request we need to poll the status url to know when the request has completed or failed.
152
- *
153
- * @param result the result returned from the addToDefinition request.
154
- * This will contain a status url or the standard sync result.
155
- * @param authentication Credentials to be used to generate token for URL
156
- * @returns A promise that will resolve when the request has completed
157
- */
158
- export function checkRequestStatus(result, authentication) {
159
- return new Promise((resolve, reject) => {
160
- if (result.statusURL) {
161
- const checkStatus = setInterval(() => {
162
- request(result.statusURL, { authentication }).then(r => {
163
- /* istanbul ignore else */
164
- if (r.status === "Completed") {
165
- clearInterval(checkStatus);
166
- resolve();
167
- }
168
- else if (r.status === "Failed") {
169
- clearInterval(checkStatus);
170
- reject(r);
171
- }
172
- }, e => {
173
- clearInterval(checkStatus);
174
- reject(e);
175
- });
176
- }, 2000);
177
- }
178
- else {
179
- resolve();
180
- }
181
- });
182
- }
183
- /**
184
- * Converts a general search into an ISearchOptions structure.
185
- *
186
- * @param search Search specified in one of three ways
187
- * @returns Recast search
188
- */
189
- export function convertToISearchOptions(search) {
190
- // Convert the search into an ISearchOptions
191
- let searchOptions = {
192
- q: "",
193
- start: 1,
194
- num: 100
195
- };
196
- if (typeof search === "string") {
197
- // Insert query into defaults
198
- searchOptions.q = search;
199
- }
200
- else if (search instanceof SearchQueryBuilder) {
201
- // Insert query into defaults
202
- searchOptions.q = search.toParam();
203
- }
204
- else { // search is ISearchOptions
205
- searchOptions = {
206
- ...searchOptions,
207
- ...search // request
208
- };
209
- }
210
- return searchOptions;
211
- }
212
- /**
213
- * Simple validate function to ensure all coordinates are numbers
214
- * In some cases orgs can have null or undefined coordinate values associated with the org extent
215
- *
216
- * @param extent the extent to validate
217
- * @returns the provided extent or a default global extent if some coordinates are not numbers
218
- * @private
219
- */
220
- export function _validateExtent(extent) {
221
- // in some cases orgs can have invalid extents defined
222
- // this is a simple validate function that will ensure coordiantes are numbers
223
- // using -179,-89,179,89 because the project call is returning "NaN" when using -180,-90,180,90
224
- const hasInvalid = typeof extent.xmin !== "number" ||
225
- typeof extent.xmax !== "number" ||
226
- typeof extent.ymax !== "number" ||
227
- typeof extent.ymin !== "number";
228
- if (hasInvalid) {
229
- extent.xmin = -179;
230
- extent.xmax = 179;
231
- extent.ymax = 89;
232
- extent.ymin = -89;
233
- extent.spatialReference = { wkid: 4326 };
234
- }
235
- return extent;
236
- }
237
- /**
238
- * If the request to convert the extent fails it has commonly been due to an invalid extent.
239
- * This function will first attempt to use the provided extent. If it fails it will default to
240
- * the source items extent and if that fails it will then use a default global extent.
241
- *
242
- * @param extent the extent to convert
243
- * @param fallbackExtent the extent to convert if the main extent does not project to the outSR
244
- * @param outSR the spatial reference to project to
245
- * @param geometryServiceUrl the service url for the geometry service to use
246
- * @param authentication the credentials for the requests
247
- * @returns the extent projected to the provided spatial reference
248
- * or the world extent projected to the provided spatial reference
249
- * @private
250
- */
251
- export function convertExtentWithFallback(extent, fallbackExtent, outSR, geometryServiceUrl, authentication) {
252
- return new Promise((resolve, reject) => {
253
- const defaultExtent = {
254
- xmin: -179,
255
- xmax: 179,
256
- ymin: -89,
257
- ymax: 89,
258
- spatialReference: { wkid: 4326 }
259
- };
260
- convertExtent(_validateExtent(extent), outSR, geometryServiceUrl, authentication).then(extentResponse => {
261
- // in some cases project will complete successfully but return "NaN" values
262
- // check for this and call convert again if it does
263
- const extentResponseString = JSON.stringify(extentResponse);
264
- const validatedExtent = JSON.stringify(_validateExtent(extentResponse));
265
- if (extentResponseString === validatedExtent) {
266
- resolve(extentResponse);
267
- }
268
- else {
269
- convertExtent(fallbackExtent || defaultExtent, outSR, geometryServiceUrl, authentication).then(resolve, e => reject(fail(e)));
270
- }
271
- },
272
- // if convert fails try again with default global extent
273
- () => {
274
- convertExtent(defaultExtent, outSR, geometryServiceUrl, authentication).then(resolve, e => reject(fail(e)));
275
- });
276
- });
277
- }
278
- /**
279
- * Converts an extent to a specified spatial reference.
280
- *
281
- * @param extent Extent object to check and (possibly) to project
282
- * @param outSR Desired spatial reference
283
- * @param geometryServiceUrl Path to geometry service providing `findTransformations` and `project` services
284
- * @param authentication Credentials for the request
285
- * @returns Original extent if it's already using outSR or the extents projected into the outSR
286
- */
287
- export function convertExtent(extent, outSR, geometryServiceUrl, authentication) {
288
- const _requestOptions = { authentication };
289
- return new Promise((resolve, reject) => {
290
- if (extent.spatialReference.wkid === outSR?.wkid || !outSR) {
291
- resolve(extent);
292
- }
293
- else {
294
- _requestOptions.params = {
295
- f: "json",
296
- inSR: extent.spatialReference.wkid,
297
- outSR: outSR.wkid,
298
- extentOfInterest: JSON.stringify(extent)
299
- };
300
- request(checkUrlPathTermination(geometryServiceUrl) + "findTransformations", _requestOptions).then(response => {
301
- const transformations = response && response.transformations
302
- ? response.transformations
303
- : undefined;
304
- let transformation;
305
- if (transformations && transformations.length > 0) {
306
- // if a forward single transformation is found use that...otherwise check for and use composite
307
- transformation = transformations[0].wkid
308
- ? transformations[0].wkid
309
- : transformations[0].geoTransforms
310
- ? transformations[0]
311
- : undefined;
312
- }
313
- _requestOptions.params = {
314
- f: "json",
315
- outSR: outSR.wkid,
316
- inSR: extent.spatialReference.wkid,
317
- geometries: {
318
- geometryType: "esriGeometryPoint",
319
- geometries: [
320
- { x: extent.xmin, y: extent.ymin },
321
- { x: extent.xmax, y: extent.ymax }
322
- ]
323
- },
324
- transformation: transformation
325
- };
326
- request(checkUrlPathTermination(geometryServiceUrl) + "project", _requestOptions).then(projectResponse => {
327
- const projectGeom = projectResponse.geometries.length === 2
328
- ? projectResponse.geometries
329
- : undefined;
330
- if (projectGeom) {
331
- resolve({
332
- xmin: projectGeom[0].x,
333
- ymin: projectGeom[0].y,
334
- xmax: projectGeom[1].x,
335
- ymax: projectGeom[1].y,
336
- spatialReference: outSR
337
- });
338
- }
339
- else {
340
- resolve(undefined);
341
- }
342
- }, e => reject(fail(e)));
343
- }, e => reject(fail(e)));
344
- }
345
- });
346
- }
347
- /**
348
- * Publishes a feature service as an AGOL item; it does not include its layers and tables
349
- *
350
- * @param newItemTemplate Template of item to be created
351
- * @param authentication Credentials for the request
352
- * @param templateDictionary Hash of facts: org URL, adlib replacements, user; .user.folders property contains a list
353
- * @returns A promise that will resolve with an object reporting success and the Solution id
354
- */
355
- export function createFeatureService(newItemTemplate, authentication, templateDictionary) {
356
- return new Promise((resolve, reject) => {
357
- // Create item
358
- _getCreateServiceOptions(newItemTemplate, authentication, templateDictionary).then(createOptions => {
359
- svcAdminCreateFeatureService(createOptions).then(createResponse => {
360
- // Federated servers may have inconsistent casing, so lowerCase it
361
- createResponse.encodedServiceURL = _lowercaseDomain(createResponse.encodedServiceURL);
362
- createResponse.serviceurl = _lowercaseDomain(createResponse.serviceurl);
363
- resolve(createResponse);
364
- }, e => reject(fail(e)));
365
- }, e => reject(fail(e)));
366
- });
367
- }
368
- /**
369
- * Publishes an item and its data, metadata, and resources as an AGOL item.
370
- *
371
- * @param itemInfo Item's `item` section
372
- * @param folderId Id of folder to receive item; null indicates that the item goes into the root
373
- * folder; ignored for Group item type
374
- * @param destinationAuthentication Credentials for for requests to where the item is to be created
375
- * @param itemThumbnailUrl URL to image to use for item thumbnail
376
- * @param itemThumbnailAuthentication Credentials for requests to the thumbnail source
377
- * @param dataFile Item's `data` section
378
- * @param metadataFile Item's metadata file
379
- * @param resourcesFiles Item's resources
380
- * @param access Access to set for item: "public", "org", "private"
381
- * @returns A promise that will resolve with an object reporting success or failure and the Solution id
382
- */
383
- export function createFullItem(itemInfo, folderId, destinationAuthentication, itemThumbnailUrl, itemThumbnailAuthentication, dataFile, metadataFile, resourcesFiles, access = "private") {
384
- return new Promise((resolve, reject) => {
385
- // Create item
386
- const createOptions = {
387
- item: {
388
- ...itemInfo
389
- },
390
- folderId,
391
- authentication: destinationAuthentication
392
- };
393
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
394
- addTokenToUrl(itemThumbnailUrl, itemThumbnailAuthentication).then(updatedThumbnailUrl => {
395
- /* istanbul ignore else */
396
- if (updatedThumbnailUrl) {
397
- createOptions.item.thumbnailUrl = appendQueryParam(updatedThumbnailUrl, "w=400");
398
- }
399
- createItemInFolder(createOptions).then(createResponse => {
400
- if (createResponse.success) {
401
- let accessDef;
402
- // Set access if it is not AGOL default
403
- // Set the access manually since the access value in createItem appears to be ignored
404
- // Need to run serially; will not work reliably if done in parallel with adding the data section
405
- if (access !== "private") {
406
- const accessOptions = {
407
- id: createResponse.id,
408
- access: access === "public" ? "public" : "org",
409
- authentication: destinationAuthentication
410
- };
411
- accessDef = setItemAccess(accessOptions);
412
- }
413
- else {
414
- accessDef = Promise.resolve({
415
- itemId: createResponse.id
416
- });
417
- }
418
- // Now add attached items
419
- accessDef.then(() => {
420
- const updateDefs = [];
421
- // Add the data section
422
- if (dataFile) {
423
- updateDefs.push(_addItemDataFile(createResponse.id, dataFile, destinationAuthentication));
424
- }
425
- // Add the resources via a zip because AGO sometimes loses resources if many are added at the
426
- // same time to the same item
427
- if (Array.isArray(resourcesFiles) &&
428
- resourcesFiles.length > 0) {
429
- updateDefs.push(new Promise((rsrcResolve, rsrcReject) => {
430
- createZip("resources.zip", resourcesFiles).then((zipfile) => {
431
- const addResourceOptions = {
432
- id: createResponse.id,
433
- resource: zipfile,
434
- authentication: destinationAuthentication,
435
- params: {
436
- archive: true
437
- }
438
- };
439
- addItemResource(addResourceOptions).then(rsrcResolve, rsrcReject);
440
- }, rsrcReject);
441
- }));
442
- }
443
- // Add the metadata section
444
- if (metadataFile) {
445
- updateDefs.push(_addItemMetadataFile(createResponse.id, metadataFile, destinationAuthentication));
446
- }
447
- // Wait until all adds are done
448
- Promise.all(updateDefs).then(() => resolve(createResponse), e => reject(fail(e)));
449
- }, e => reject(fail(e)));
450
- }
451
- else {
452
- reject(fail());
453
- }
454
- }, e => reject(fail(e)));
455
- });
456
- });
457
- }
458
- /**
459
- * Publishes an item and its data as an AGOL item.
460
- *
461
- * @param itemInfo Item's `item` section
462
- * @param dataInfo Item's `data` section
463
- * @param authentication Credentials for the request
464
- * @param folderId Id of folder to receive item; null indicates that the item goes into the root
465
- * folder; ignored for Group item type
466
- * @param access Access to set for item: "public", "org", "private"
467
- * @returns A promise that will resolve with an object reporting success and the Solution id
468
- */
469
- export function createItemWithData(itemInfo, dataInfo, authentication, folderId, access = "private") {
470
- return new Promise((resolve, reject) => {
471
- // Create item
472
- const createOptions = {
473
- item: {
474
- title: "_",
475
- ...itemInfo,
476
- data: dataInfo
477
- },
478
- folderId,
479
- authentication: authentication
480
- };
481
- if (itemInfo.thumbnail) {
482
- createOptions.params = {
483
- // Pass thumbnail file in via params because item property is serialized, which discards a blob
484
- thumbnail: itemInfo.thumbnail
485
- };
486
- delete createOptions.item.thumbnail;
487
- }
488
- createItemInFolder(createOptions).then(createResponse => {
489
- if (createResponse.success) {
490
- if (access !== "private") {
491
- // Set access if it is not AGOL default
492
- // Set the access manually since the access value in createItem appears to be ignored
493
- const accessOptions = {
494
- id: createResponse.id,
495
- access: access === "public" ? "public" : "org",
496
- authentication: authentication
497
- };
498
- setItemAccess(accessOptions).then(() => {
499
- resolve({
500
- folder: createResponse.folder,
501
- id: createResponse.id,
502
- success: true
503
- });
504
- }, e => reject(fail(e)));
505
- }
506
- else {
507
- resolve({
508
- folder: createResponse.folder,
509
- id: createResponse.id,
510
- success: true
511
- });
512
- }
513
- }
514
- else {
515
- reject(fail());
516
- }
517
- }, e => reject(fail(e)));
518
- });
519
- }
520
- /**
521
- * Creates a folder using a numeric suffix to ensure uniqueness if necessary.
522
- *
523
- * @param title Folder title, used as-is if possible and with suffix otherwise
524
- * @param templateDictionary Hash of facts: org URL, adlib replacements, user; .user.folders property contains a list
525
- * of known folder names; function updates list with existing names not yet known, and creates .user.folders if it
526
- * doesn't exist in the dictionary
527
- * @param authentication Credentials for creating folder
528
- * @returns Id of created folder
529
- */
530
- export function createUniqueFolder(title, templateDictionary, authentication) {
531
- return new Promise((resolve, reject) => {
532
- // Get a title that is not already in use
533
- const folderTitle = getUniqueTitle(title, templateDictionary, "user.folders");
534
- const folderCreationParam = {
535
- title: folderTitle,
536
- authentication: authentication
537
- };
538
- createFolder(folderCreationParam).then(ok => resolve(ok), err => {
539
- // If the name already exists, we'll try again
540
- const errorDetails = getProp(err, "response.error.details");
541
- if (Array.isArray(errorDetails) && errorDetails.length > 0) {
542
- const nameNotAvailMsg = "Folder title '" + folderTitle + "' not available.";
543
- if (errorDetails.indexOf(nameNotAvailMsg) >= 0) {
544
- // Create the user.folders property if it doesn't exist
545
- /* istanbul ignore else */
546
- if (!getProp(templateDictionary, "user.folders")) {
547
- setCreateProp(templateDictionary, "user.folders", []);
548
- }
549
- templateDictionary.user.folders.push({
550
- title: folderTitle
551
- });
552
- createUniqueFolder(title, templateDictionary, authentication).then(resolve, reject);
553
- }
554
- else {
555
- reject(err);
556
- }
557
- }
558
- else {
559
- // Otherwise, error out
560
- reject(err);
561
- }
562
- });
563
- });
564
- }
565
- /**
566
- * Creates a group using numeric suffix to ensure uniqueness.
567
- *
568
- * @param title Group title, used as-is if possible and with suffix otherwise
569
- * @param templateDictionary Hash of facts: org URL, adlib replacements, user
570
- * @param authentication Credentials for creating group
571
- * @param owner Optional arg for the Tracking owner
572
- * If the tracking owner is not the one deploying the solution
573
- * tracker groups will be created under the deployment user but
574
- * will be reassigned to the tracking owner.
575
- * @returns Information about created group
576
- */
577
- export function createUniqueGroup(title, groupItem, templateDictionary, authentication, owner) {
578
- return new Promise((resolve, reject) => {
579
- let groupsPromise;
580
- // when working with tracker we need to consider the groups of the deploying user as well as the groups
581
- // of the tracking user...must be unique for both
582
- if (owner && owner !== authentication.username) {
583
- groupsPromise = searchAllGroups(`(owner:${owner}) orgid:${templateDictionary.organization.id}`, authentication);
584
- }
585
- else {
586
- groupsPromise = Promise.resolve([]);
587
- }
588
- // first get the tracker owner groups
589
- groupsPromise.then(groups => {
590
- templateDictionary["allGroups"] =
591
- groups.concat(getProp(templateDictionary, "user.groups"));
592
- // Get a title that is not already in use
593
- groupItem.title = getUniqueTitle(title, templateDictionary, "allGroups");
594
- const groupCreationParam = {
595
- group: groupItem,
596
- authentication: authentication
597
- };
598
- createGroup(groupCreationParam).then(resolve, err => {
599
- // If the name already exists, we'll try again
600
- const errorDetails = getProp(err, "response.error.details");
601
- if (Array.isArray(errorDetails) && errorDetails.length > 0) {
602
- const nameNotAvailMsg = "You already have a group named '" +
603
- groupItem.title +
604
- "'. Try a different name.";
605
- if (errorDetails.indexOf(nameNotAvailMsg) >= 0) {
606
- templateDictionary.user.groups.push({
607
- title: groupItem.title
608
- });
609
- createUniqueGroup(title, groupItem, templateDictionary, authentication).then(resolve, reject);
610
- }
611
- else {
612
- reject(err);
613
- }
614
- }
615
- else {
616
- // Otherwise, error out
617
- reject(err);
618
- }
619
- });
620
- }, e => reject(e));
621
- });
622
- }
623
- /**
624
- * Gets the ids of the dependencies of an AGOL feature service item.
625
- * Dependencies will only exist when the service is a view.
626
- *
627
- * @param itemTemplate Template of item to be created
628
- * @param authentication Credentials for the request
629
- * @returns A promise that will resolve a list of dependencies
630
- */
631
- export function extractDependencies(itemTemplate, authentication) {
632
- const dependencies = [];
633
- return new Promise((resolve, reject) => {
634
- // Get service dependencies when the item is a view
635
- // This step is skipped for tracker views as they will already have a source service in the org
636
- if (itemTemplate.properties.service.isView && itemTemplate.item.url && !isTrackingViewTemplate(itemTemplate)) {
637
- request(checkUrlPathTermination(itemTemplate.item.url) + "sources?f=json", {
638
- authentication: authentication
639
- }).then(response => {
640
- /* istanbul ignore else */
641
- if (response && response.services) {
642
- response.services.forEach((layer) => {
643
- dependencies.push({
644
- id: layer.serviceItemId,
645
- name: layer.name
646
- });
647
- });
648
- }
649
- resolve(dependencies);
650
- }, e => reject(fail(e)));
651
- }
652
- else if (isWorkforceProject(itemTemplate)) {
653
- resolve(getWorkforceDependencies(itemTemplate, dependencies));
654
- }
655
- else {
656
- resolve(dependencies);
657
- }
658
- });
659
- }
660
- /**
661
- * Get json info for the services layers
662
- *
663
- * @param serviceUrl the url for the service
664
- * @param layerList list of base layer info
665
- * @param authentication Credentials for the request
666
- * @returns A promise that will resolve a list of dependencies
667
- */
668
- export function getLayers(serviceUrl, layerList, authentication) {
669
- return new Promise((resolve, reject) => {
670
- if (layerList.length === 0) {
671
- resolve([]);
672
- }
673
- // get the admin URL
674
- serviceUrl = serviceUrl.replace("/rest/services", "/rest/admin/services");
675
- const requestsDfd = [];
676
- layerList.forEach(layer => {
677
- const requestOptions = {
678
- authentication: authentication
679
- };
680
- requestsDfd.push(request(checkUrlPathTermination(serviceUrl) + layer["id"] + "?f=json", requestOptions));
681
- });
682
- // Wait until all layers are heard from
683
- Promise.all(requestsDfd).then(layers => resolve(layers), e => reject(fail(e)));
684
- });
685
- }
686
- /**
687
- * Add additional options to a layers definition.
688
- *
689
- * @param args The IPostProcessArgs for the request(s)
690
- * @param isPortal boolean to indicate if we are deploying to portal
691
- *
692
- * @returns An array of update instructions
693
- * @private
694
- */
695
- export function getLayerUpdates(args, isPortal) {
696
- const adminUrl = args.itemTemplate.item.url.replace("rest/services", "rest/admin/services");
697
- const updates = [];
698
- const refresh = _getUpdate(adminUrl, null, null, args, "refresh");
699
- updates.push(refresh);
700
- Object.keys(args.objects).forEach(id => {
701
- const obj = Object.assign({}, args.objects[id]);
702
- // These properties cannot be set in the update definition when working with portal
703
- if (isPortal) {
704
- deleteProps(obj, ["type", "id", "relationships", "sourceServiceFields"]);
705
- }
706
- // handle definition deletes
707
- // removes previous editFieldsInfo fields if their names were changed
708
- if (obj.hasOwnProperty("deleteFields")) {
709
- updates.push(_getUpdate(adminUrl, id, obj, args, "delete"));
710
- deleteProp(obj, "deleteFields");
711
- updates.push(_getUpdate(adminUrl, null, null, args, "refresh"));
712
- }
713
- });
714
- // issue: #706
715
- // Add source service relationships
716
- // views will now always add all layers in a single call and will inherit the relationships from the source service
717
- if (!args.itemTemplate.properties.service.isView) {
718
- const relUpdates = _getRelationshipUpdates({
719
- message: "updated layer relationships",
720
- objects: args.objects,
721
- itemTemplate: args.itemTemplate,
722
- authentication: args.authentication
723
- });
724
- // issue: #724
725
- // In portal the order the relationships are added needs to follow the layer order
726
- // otherwise the relationship IDs will be reset
727
- relUpdates.layers = _sortRelationships(args.itemTemplate.properties.layers, args.itemTemplate.properties.tables, relUpdates);
728
- /* istanbul ignore else */
729
- if (relUpdates.layers.length > 0) {
730
- updates.push(_getUpdate(adminUrl, null, relUpdates, args, "add"));
731
- updates.push(refresh);
732
- }
733
- // handle contingent values
734
- const contingentValuesUpdates = _getContingentValuesUpdates({
735
- message: "add layer contingent values",
736
- objects: args.objects,
737
- itemTemplate: args.itemTemplate,
738
- authentication: args.authentication
739
- });
740
- /* istanbul ignore else */
741
- if (contingentValuesUpdates.length > 0) {
742
- contingentValuesUpdates.forEach(conUpdate => {
743
- updates.push(_getUpdate(adminUrl + conUpdate.id, null, conUpdate.contingentValues, args, "add"));
744
- });
745
- }
746
- }
747
- return updates.length === 1 ? [] : updates;
748
- }
749
- /**
750
- * Sorts relationships based on order of supporting layers and tables in the service definition
751
- *
752
- * @param layers the layers from the service
753
- * @param tables the tables from the service
754
- * @param relUpdates the relationships to add for the service
755
- *
756
- * @returns An array with relationships that have been sorted
757
- * @private
758
- */
759
- export function _sortRelationships(layers, tables, relUpdates) {
760
- const ids = [].concat(layers.map((l) => l.id), tables.map((t) => t.id));
761
- // In portal the order the relationships are added needs to follow the layer order
762
- // otherwise the relationship IDs will be reset
763
- const _relUpdateLayers = [];
764
- ids.forEach(id => {
765
- relUpdates.layers.some((relUpdate) => {
766
- if (id === relUpdate.id) {
767
- _relUpdateLayers.push(relUpdate);
768
- return true;
769
- }
770
- else {
771
- return false;
772
- }
773
- });
774
- });
775
- return _relUpdateLayers;
776
- }
777
- /**
778
- * Add additional options to a layers definition
779
- *
780
- * Added retry due to some solutions failing to deploy in specific orgs/hives
781
- *
782
- *
783
- * @param Update will contain either add, update, or delete from service definition call
784
- * @param skipRetry defaults to false. when true the retry logic will be ignored
785
- * @returns A promise that will resolve when service definition call has completed
786
- * @private
787
- */
788
- /* istanbul ignore else */
789
- export function getRequest(update, skipRetry = false, useAsync = false) {
790
- return new Promise((resolve, reject) => {
791
- const options = {
792
- params: update.params,
793
- authentication: update.args.authentication
794
- };
795
- /* istanbul ignore else */
796
- if ((useAsync && update.url.indexOf("addToDefinition") > -1) ||
797
- update.url.indexOf("updateDefinition") > -1 ||
798
- update.url.indexOf("deleteFromDefinition") > -1) {
799
- options.params = { ...options.params, async: true };
800
- }
801
- request(update.url, options).then(result => {
802
- checkRequestStatus(result, options.authentication).then(() => resolve(null), e => reject(fail(e)));
803
- }, (e) => {
804
- if (!skipRetry) {
805
- getRequest(update, true, true).then(() => resolve(), e => reject(e));
806
- }
807
- else {
808
- reject(e);
809
- }
810
- });
811
- });
812
- }
813
- /**
814
- * Fills in missing data, including full layer and table definitions, in a feature services' definition.
815
- *
816
- * @param itemTemplate Feature service item, data, dependencies definition to be modified
817
- * @param authentication Credentials for the request to AGOL
818
- * @returns A promise that will resolve when fullItem has been updated
819
- * @private
820
- */
821
- export function getServiceLayersAndTables(itemTemplate, authentication) {
822
- return new Promise((resolve, reject) => {
823
- // To have enough information for reconstructing the service, we'll supplement
824
- // the item and data sections with sections for the service, full layers, and
825
- // full tables
826
- // Extra steps must be taken for workforce version 2
827
- const isWorkforceService = isWorkforceProject(itemTemplate);
828
- // Get the service description
829
- if (itemTemplate.item.url) {
830
- getFeatureServiceProperties(itemTemplate.item.url, authentication, isWorkforceService).then(properties => {
831
- itemTemplate.properties = properties;
832
- resolve(itemTemplate);
833
- }, e => reject(fail(e)));
834
- }
835
- else {
836
- resolve(itemTemplate);
837
- }
838
- });
839
- }
840
- /**
841
- * Get service properties for the given url and update key props
842
- *
843
- * @param serviceUrl the feature service url
844
- * @param authentication Credentials for the request to AGOL
845
- * @param workforceService boolean to indicate if extra workforce service steps should be handled
846
- * @returns A promise that will resolve with the service properties
847
- * @private
848
- */
849
- export function getFeatureServiceProperties(serviceUrl, authentication, workforceService = false) {
850
- return new Promise((resolve, reject) => {
851
- const properties = {
852
- service: {},
853
- layers: [],
854
- tables: []
855
- };
856
- // get the admin URL
857
- serviceUrl = serviceUrl.replace("/rest/services", "/rest/admin/services");
858
- // Get the service description
859
- request(serviceUrl + "?f=json", {
860
- authentication: authentication
861
- }).then(serviceData => {
862
- properties.service = _parseAdminServiceData(serviceData);
863
- // Copy cacheMaxAge to top level so that AGO sees it when deploying the service
864
- // serviceData may have set it if there isn't an adminServiceInfo
865
- /* istanbul ignore else */
866
- if (serviceData.adminServiceInfo?.cacheMaxAge) {
867
- properties.service.cacheMaxAge =
868
- serviceData.adminServiceInfo.cacheMaxAge;
869
- }
870
- // Move the layers and tables out of the service's data section
871
- /* istanbul ignore else */
872
- if (serviceData.layers) {
873
- properties.layers = serviceData.layers;
874
- // Fill in properties that the service layer doesn't provide
875
- // and remove properties that should not exist in the template
876
- properties.layers.forEach(layer => {
877
- layer.serviceItemId = properties.service.serviceItemId;
878
- layer.extent = null;
879
- removeLayerOptimization(layer);
880
- });
881
- }
882
- delete serviceData.layers;
883
- /* istanbul ignore else */
884
- if (serviceData.tables) {
885
- properties.tables = serviceData.tables;
886
- // Fill in properties that the service layer doesn't provide
887
- properties.tables.forEach(table => {
888
- table.serviceItemId = properties.service.serviceItemId;
889
- table.extent = null;
890
- });
891
- }
892
- delete serviceData.tables;
893
- // Ensure solution items have unique indexes on relationship key fields
894
- _updateIndexesForRelationshipKeyFields(properties);
895
- processContingentValues(properties, serviceUrl, authentication).then(() => {
896
- if (workforceService) {
897
- getWorkforceServiceInfo(properties, serviceUrl, authentication).then(resolve, reject);
898
- }
899
- else {
900
- resolve(properties);
901
- }
902
- }, (e) => reject(fail(e)));
903
- }, (e) => reject(fail(e)));
904
- });
905
- }
906
- /**
907
- * Parses the layers array and will filter subsets of Layers and Tables
908
- * Layers and Tables are both returned in the layers array when we access a feature service from the admin api.
909
- *
910
- * @param adminData The data of the feature service
911
- * @returns A mutated version of the provided adminData
912
- * @private
913
- */
914
- export function _parseAdminServiceData(adminData) {
915
- const layers = adminData.layers || [];
916
- const tables = adminData.tables || [];
917
- setCreateProp(adminData, "layers", layers.filter(l => l.type === "Feature Layer"));
918
- // TODO understand if the concat is necessary.
919
- // Not sure if the admin api will ever actually return a tables collection here.
920
- setCreateProp(adminData, "tables", tables.concat(layers.filter(l => l.type === "Table")));
921
- return adminData;
922
- }
923
- /**
924
- * livingatlas designation test.
925
- * These layers should not be templatized or depolyed
926
- *
927
- * @param groupDesignations the items group designations to evaluate
928
- * @returns A boolean indicating if the invalid designation is found in the item info
929
- */
930
- export function hasInvalidGroupDesignations(groupDesignations) {
931
- const invalidGroupDesignations = ["livingatlas"];
932
- return groupDesignations
933
- ? invalidGroupDesignations.indexOf(groupDesignations) > -1
934
- : false;
935
- }
936
- /**
937
- * Removes a folder from AGO.
938
- *
939
- * @param folderId Id of a folder to delete
940
- * @param authentication Credentials for the request to AGO
941
- * @returns A promise that will resolve with the result of the request
942
- */
943
- export function removeFolder(folderId, authentication) {
944
- return new Promise((resolve, reject) => {
945
- const requestOptions = {
946
- folderId: folderId,
947
- authentication: authentication
948
- };
949
- portalRemoveFolder(requestOptions).then(result => (result.success ? resolve(result) : reject(result)), reject);
950
- });
951
- }
952
- /**
953
- * Removes a group from AGO.
954
- *
955
- * @param groupId Id of a group to delete
956
- * @param authentication Credentials for the request to AGO
957
- * @returns A promise that will resolve with the result of the request
958
- */
959
- export function removeGroup(groupId, authentication) {
960
- return new Promise((resolve, reject) => {
961
- const requestOptions = {
962
- id: groupId,
963
- authentication: authentication
964
- };
965
- portalRemoveGroup(requestOptions).then(result => (result.success ? resolve(result) : reject(result)), reject);
966
- });
967
- }
968
- /**
969
- * Removes an item from AGO.
970
- *
971
- * @param itemId Id of an item to delete
972
- * @param authentication Credentials for the request to AGO
973
- * @returns A promise that will resolve with the result of the request
974
- */
975
- export function removeItem(itemId, authentication) {
976
- return new Promise((resolve, reject) => {
977
- const requestOptions = {
978
- id: itemId,
979
- authentication: authentication
980
- };
981
- portalRemoveItem(requestOptions).then(result => (result.success ? resolve(result) : reject(result)), reject);
982
- });
983
- }
984
- /**
985
- * Removes an item or group from AGO.
986
- *
987
- * @param itemId Id of an item or group to delete
988
- * @param authentication Credentials for the request to AGO
989
- * @returns A promise that will resolve with the result of the request
990
- */
991
- export function removeItemOrGroup(itemId, authentication) {
992
- return new Promise((resolve, reject) => {
993
- removeItem(itemId, authentication).then(resolve, error => {
994
- removeGroup(itemId, authentication).then(resolve, () => reject(error));
995
- });
996
- });
997
- }
998
- /**
999
- * Searches for items matching a query and that the caller has access to.
1000
- *
1001
- * @param search Search string (e.g., "q=redlands+map") or a more detailed structure that can include authentication
1002
- * @returns Promise resolving with search results
1003
- * @see https://developers.arcgis.com/rest/users-groups-and-items/search.htm
1004
- */
1005
- export function searchItems(search) {
1006
- return portalSearchItems(search);
1007
- }
1008
- /**
1009
- * Searches for items matching a query and that the caller has access to, continuing recursively until done.
1010
- *
1011
- * @param search Search string (e.g., "q=redlands+map") or a more detailed structure that can include authentication
1012
- * @param accumulatedResponse Response built from previous requests
1013
- * @returns Promise resolving with search results
1014
- * @see https://developers.arcgis.com/rest/users-groups-and-items/search.htm
1015
- */
1016
- export function searchAllItems(search, accumulatedResponse) {
1017
- // Convert the search into an ISearchOptions
1018
- const searchOptions = convertToISearchOptions(search);
1019
- // Provide a base into which results can be concatenated
1020
- const completeResponse = accumulatedResponse ? accumulatedResponse : {
1021
- query: searchOptions.q,
1022
- start: 1,
1023
- num: 100,
1024
- nextStart: -1,
1025
- total: 0,
1026
- results: []
1027
- };
1028
- return new Promise((resolve, reject) => {
1029
- searchItems(search).then(response => {
1030
- completeResponse.results = completeResponse.results.concat(response.results);
1031
- completeResponse.num = completeResponse.total = completeResponse.results.length;
1032
- if (response.nextStart > 0) {
1033
- // Insert nextStart into next query
1034
- searchOptions.start = response.nextStart;
1035
- resolve(searchAllItems(searchOptions, completeResponse));
1036
- }
1037
- else {
1038
- resolve(completeResponse);
1039
- }
1040
- }, e => reject(e));
1041
- });
1042
- }
1043
- /**
1044
- * Searches for groups matching criteria.
1045
- *
1046
- * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1047
- * @param authentication Credentials for the request to AGO
1048
- * @param additionalSearchOptions Adjustments to search, such as tranche size
1049
- * @returns A promise that will resolve with a structure with a tranche of results and
1050
- * describing how many items are available
1051
- * @see https://developers.arcgis.com/rest/users-groups-and-items/group-search.htm
1052
- * @see https://developers.arcgis.com/rest/users-groups-and-items/search-reference.htm
1053
- */
1054
- export function searchGroups(searchString, authentication, additionalSearchOptions) {
1055
- const searchOptions = {
1056
- q: searchString,
1057
- params: {
1058
- ...additionalSearchOptions
1059
- },
1060
- authentication: authentication
1061
- };
1062
- return portalSearchGroups(searchOptions);
1063
- }
1064
- /**
1065
- * Searches for groups matching criteria recurusively.
1066
- *
1067
- * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1068
- * @param authentication Credentials for the request to AGO
1069
- * @param groups List of groups that have been found from previous requests
1070
- * @param inPagingParams The paging params for the recurisve searching
1071
- *
1072
- * @returns A promise that will resolve with all groups that meet the search criteria
1073
- */
1074
- export function searchAllGroups(searchString, authentication, groups, inPagingParams) {
1075
- const pagingParams = inPagingParams ? inPagingParams : {
1076
- start: 1,
1077
- num: 24
1078
- };
1079
- const additionalSearchOptions = {
1080
- sortField: "title",
1081
- sortOrder: "asc",
1082
- ...pagingParams
1083
- };
1084
- // Provide a base onto which results can be concatenated
1085
- let finalResults = groups ? groups : [];
1086
- return new Promise((resolve, reject) => {
1087
- searchGroups(searchString, authentication, additionalSearchOptions).then(response => {
1088
- finalResults = finalResults.concat(response.results);
1089
- if (response.nextStart > 0) {
1090
- pagingParams.start = response.nextStart;
1091
- resolve(searchAllGroups(searchString, authentication, finalResults, pagingParams));
1092
- }
1093
- else {
1094
- resolve(finalResults);
1095
- }
1096
- }, e => reject(e));
1097
- });
1098
- }
1099
- /**
1100
- * Searches for group contents matching criteria recursively.
1101
- *
1102
- * @param groupId Group whose contents are to be searched
1103
- * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1104
- * @param authentication Credentials for the request to AGO
1105
- * @param additionalSearchOptions Adjustments to search, such as tranche size and categories of interest; categories
1106
- * are supplied as an array: each array element consists of one or more categories to be ORed; array elements are ANDed
1107
- * @param portalUrl Rest Url of the portal to perform the search
1108
- * @param accumulatedResponse Response built from previous requests
1109
- * @returns A promise that will resolve with a structure with a tranche of results and
1110
- * describing how many items are available
1111
- * @see https://developers.arcgis.com/rest/users-groups-and-items/group-content-search.htm
1112
- * @see https://developers.arcgis.com/rest/users-groups-and-items/search-reference.htm
1113
- */
1114
- export function searchGroupAllContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl, accumulatedResponse) {
1115
- additionalSearchOptions = additionalSearchOptions ? additionalSearchOptions : {};
1116
- // Provide a base into which results can be concatenated
1117
- const completeResponse = accumulatedResponse ? accumulatedResponse : {
1118
- query: searchString,
1119
- start: 1,
1120
- num: 100,
1121
- nextStart: -1,
1122
- total: 0,
1123
- results: []
1124
- };
1125
- return new Promise((resolve, reject) => {
1126
- searchGroupContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl).then(response => {
1127
- completeResponse.results = completeResponse.results.concat(response.results);
1128
- completeResponse.num = completeResponse.total = completeResponse.results.length;
1129
- if (response.nextStart > 0) {
1130
- additionalSearchOptions.start = response.nextStart;
1131
- resolve(searchGroupAllContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl, completeResponse));
1132
- }
1133
- else {
1134
- resolve(completeResponse);
1135
- }
1136
- }, e => reject(e));
1137
- });
1138
- }
1139
- /**
1140
- * Searches for group contents matching criteria.
1141
- *
1142
- * @param groupId Group whose contents are to be searched
1143
- * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1144
- * @param authentication Credentials for the request to AGO
1145
- * @param additionalSearchOptions Adjustments to search, such as tranche size and categories of interest; categories
1146
- * are supplied as an array: each array element consists of one or more categories to be ORed; array elements are ANDed
1147
- * @param portalUrl Rest Url of the portal to perform the search
1148
- * @returns A promise that will resolve with a structure with a tranche of results and
1149
- * describing how many items are available
1150
- * @see https://developers.arcgis.com/rest/users-groups-and-items/group-content-search.htm
1151
- * @see https://developers.arcgis.com/rest/users-groups-and-items/search-reference.htm
1152
- */
1153
- export function searchGroupContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl) {
1154
- const searchOptions = {
1155
- groupId,
1156
- q: searchString,
1157
- params: Object.assign({
1158
- num: 100
1159
- }, additionalSearchOptions),
1160
- authentication: authentication,
1161
- portal: portalUrl
1162
- };
1163
- // If search options include `categories`, switch to new arcgis-rest-js format
1164
- /* istanbul ignore else */
1165
- if (Array.isArray(searchOptions.params.categories)) {
1166
- searchOptions.params.categories = searchOptions.params.categories.map(andGroup => andGroup.split(","));
1167
- }
1168
- return searchGroupContent(searchOptions);
1169
- }
1170
- /**
1171
- * Reassign ownership of a group
1172
- *
1173
- * @param groupId Group to remove users from
1174
- * @param userName The new owner for the group
1175
- * @param authentication Credentials for the request to
1176
- *
1177
- * @returns A promise that will resolve after the group ownership has been assigned
1178
- *
1179
- */
1180
- export function reassignGroup(groupId, userName, authentication) {
1181
- const requestOptions = {
1182
- authentication: authentication,
1183
- params: {
1184
- targetUsername: userName
1185
- }
1186
- };
1187
- return request(`${authentication.portal}/community/groups/${groupId}/reassign`, requestOptions);
1188
- }
1189
- /**
1190
- * Remove users from a group
1191
- *
1192
- * @param groupId Group to remove users from
1193
- * @param users List of users to remove from the group
1194
- * @param authentication Credentials for the request to
1195
- *
1196
- * @returns A promise that will resolve after the users have been removed
1197
- *
1198
- */
1199
- export function removeUsers(groupId, users, authentication) {
1200
- return portalRemoveGroupUsers({
1201
- id: groupId,
1202
- users,
1203
- authentication
1204
- });
1205
- }
1206
- /**
1207
- * Shares an item to the defined group
1208
- *
1209
- * @param groupId Group to share with
1210
- * @param id the item id to share with the group
1211
- * @param destinationAuthentication Credentials for the request to AGO
1212
- * @param owner owner of the group when sharing tracking items (can be different from the deploying user)
1213
- *
1214
- * @returns A promise that will resolve after the item has been shared
1215
- *
1216
- */
1217
- export function shareItem(groupId, id, destinationAuthentication, owner) {
1218
- return new Promise((resolve, reject) => {
1219
- const shareOptions = {
1220
- groupId,
1221
- id,
1222
- authentication: destinationAuthentication
1223
- };
1224
- /* istanbul ignore else */
1225
- if (owner) {
1226
- shareOptions.owner = owner;
1227
- }
1228
- shareItemWithGroup(shareOptions).then(() => resolve(null), (e) => reject(fail(e)));
1229
- });
1230
- }
1231
- /**
1232
- * Updates an item.
1233
- *
1234
- * @param itemInfo The base info of an item; note that this content will be serialized, which doesn't work
1235
- * for binary content
1236
- * @param authentication Credentials for request
1237
- * @param folderId Item's folder
1238
- * @param additionalParams Updates that are put under the `params` property, which is not serialized
1239
- * @return
1240
- */
1241
- export function updateItem(itemInfo, authentication, folderId, additionalParams) {
1242
- return new Promise((resolve, reject) => {
1243
- const updateOptions = {
1244
- item: itemInfo,
1245
- folderId: folderId,
1246
- authentication: authentication,
1247
- params: {
1248
- ...(additionalParams ?? {})
1249
- }
1250
- };
1251
- portalUpdateItem(updateOptions).then(response => (response.success ? resolve(response) : reject(response)), err => reject(err));
1252
- });
1253
- }
1254
- /**
1255
- * Updates a group.
1256
- *
1257
- * @param groupInfo The base info of a group; note that this content will be serialized, which doesn't work
1258
- * for binary content
1259
- * @param authentication Credentials for request
1260
- * @param additionalParams Updates that are put under the `params` property, which is not serialized
1261
- * @returns A Promise that will resolve with the success/failure status of the request
1262
- */
1263
- export function updateGroup(groupInfo, authentication, additionalParams) {
1264
- return new Promise((resolve, reject) => {
1265
- const updateOptions = {
1266
- group: groupInfo,
1267
- authentication,
1268
- params: {
1269
- ...(additionalParams ?? {})
1270
- }
1271
- };
1272
- portalUpdateGroup(updateOptions).then(response => (response.success ? resolve(response) : reject(response)), err => reject(err));
1273
- });
1274
- }
1275
- /**
1276
- * Updates an item.
1277
- *
1278
- * @param itemInfo The base info of an item
1279
- * @param data The items data section
1280
- * @param authentication Credentials for requests
1281
- * @param thumbnail optional thumbnail to update
1282
- * @param access "public" or "org"
1283
- * @return
1284
- */
1285
- export function updateItemExtended(itemInfo, data, authentication, thumbnail, access, templateDictionary) {
1286
- return new Promise((resolve, reject) => {
1287
- const updateOptions = {
1288
- item: itemInfo,
1289
- params: {
1290
- text: data || {} // AGO ignores update if `data` is empty
1291
- },
1292
- authentication: authentication
1293
- };
1294
- if (thumbnail) {
1295
- updateOptions.params.thumbnail = thumbnail;
1296
- }
1297
- if (isTrackingViewTemplate(undefined, itemInfo) && templateDictionary) {
1298
- updateOptions.owner = templateDictionary.locationTracking.owner;
1299
- }
1300
- portalUpdateItem(updateOptions).then(result => {
1301
- if (access && access !== "private") {
1302
- // Set access if it is not AGOL default
1303
- // Set the access manually since the access value in createItem appears to be ignored
1304
- const accessOptions = {
1305
- id: itemInfo.id,
1306
- access: access === "public" ? "public" : "org",
1307
- authentication: authentication
1308
- };
1309
- setItemAccess(accessOptions).then(() => resolve(result), e => reject(fail(e)));
1310
- }
1311
- else {
1312
- resolve(result);
1313
- }
1314
- }, e => reject(fail(e)));
1315
- });
1316
- }
1317
- /**
1318
- * Update an item's base and data using a dictionary.
1319
- *
1320
- * @param {string} itemId The item ID
1321
- * @param {any} templateDictionary The template dictionary
1322
- * @param {UserSession} authentication The destination session info
1323
- * @returns Promise resolving to successfulness of update
1324
- */
1325
- export function updateItemTemplateFromDictionary(itemId, templateDictionary, authentication) {
1326
- return new Promise((resolve, reject) => {
1327
- // Fetch the items as stored in AGO
1328
- Promise.all([
1329
- getItemBase(itemId, authentication),
1330
- getItemDataAsJson(itemId, authentication)
1331
- ])
1332
- .then(([item, data]) => {
1333
- // Do they have any variables?
1334
- if (hasUnresolvedVariables(item) || hasUnresolvedVariables(data)) {
1335
- // Update if so
1336
- const { item: updatedItem, data: updatedData } = replaceInTemplate({ item, data }, templateDictionary);
1337
- _reportVariablesInItem(itemId, item.type, updatedItem, updatedData);
1338
- return updateItemExtended(updatedItem, updatedData, authentication);
1339
- }
1340
- else {
1341
- // Shortcut out if not
1342
- return Promise.resolve({
1343
- success: true,
1344
- id: itemId
1345
- });
1346
- }
1347
- })
1348
- .then(result => resolve(result))
1349
- .catch(error => reject(error));
1350
- });
1351
- }
1352
- /**
1353
- * Updates the URL of an item.
1354
- *
1355
- * @param id AGOL id of item to update
1356
- * @param url URL to assign to item's base section
1357
- * @param authentication Credentials for the request
1358
- * @returns A promise that will resolve with the item id when the item has been updated or an AGO-style JSON failure
1359
- * response
1360
- */
1361
- export function updateItemURL(id, url, authentication) {
1362
- const numAttempts = 3;
1363
- return _updateItemURL(id, url, authentication, numAttempts);
1364
- }
1365
- // ------------------------------------------------------------------------------------------------------------------ //
1366
- /**
1367
- * Adds a data section to an item.
1368
- *
1369
- * @param itemId Id of item to receive data file
1370
- * @param dataFile Data to be added
1371
- * @param authentication Credentials for the request
1372
- * @returns Promise reporting success or failure
1373
- * @private
1374
- */
1375
- export function _addItemDataFile(itemId, dataFile, authentication) {
1376
- return new Promise((resolve, reject) => {
1377
- const addItemData = (data) => {
1378
- const addDataOptions = {
1379
- id: itemId,
1380
- data: data,
1381
- authentication: authentication
1382
- };
1383
- portalAddItemData(addDataOptions).then(resolve, reject);
1384
- };
1385
- // Item data has to be submitted as text or JSON for those file types
1386
- if (dataFile.type.startsWith("text/plain")) {
1387
- blobToText(dataFile).then(addItemData, reject);
1388
- }
1389
- else if (dataFile.type === "application/json") {
1390
- blobToJson(dataFile).then(addItemData, reject);
1391
- }
1392
- else {
1393
- addItemData(dataFile);
1394
- }
1395
- });
1396
- }
1397
- /**
1398
- * Adds a metadata file to an item.
1399
- *
1400
- * @param itemId Id of item to receive data file
1401
- * @param metadataFile Metadata to be added
1402
- * @param authentication Credentials for the request
1403
- * @returns Promise reporting success or failure
1404
- * @private
1405
- */
1406
- export function _addItemMetadataFile(itemId, metadataFile, authentication) {
1407
- return new Promise((resolve, reject) => {
1408
- const addMetadataOptions = {
1409
- item: {
1410
- id: itemId
1411
- },
1412
- params: {
1413
- // Pass metadata in via params because item property is serialized, which discards a blob
1414
- metadata: metadataFile
1415
- },
1416
- authentication: authentication
1417
- };
1418
- portalUpdateItem(addMetadataOptions).then(resolve, reject);
1419
- });
1420
- }
1421
- /**
1422
- * Accumulates the number of relationships in a collection of layers.
1423
- *
1424
- * @param List of layers to examine
1425
- * @returns The number of relationships
1426
- * @private
1427
- */
1428
- export function _countRelationships(layers) {
1429
- const reducer = (accumulator, currentLayer) => accumulator +
1430
- (currentLayer.relationships ? currentLayer.relationships.length : 0);
1431
- return layers.reduce(reducer, 0);
1432
- }
1433
- /**
1434
- * Gets the full definitions of the layers affiliated with a hosted service.
1435
- *
1436
- * @param serviceUrl URL to hosted service
1437
- * @param layerList List of layers at that service...must contain id
1438
- * @param authentication Credentials for the request
1439
- * @returns A promise that will resolve with a list of the layers from the admin api
1440
- * @private
1441
- */
1442
- export function _getCreateServiceOptions(newItemTemplate, authentication, templateDictionary) {
1443
- return new Promise((resolve, reject) => {
1444
- const serviceInfo = newItemTemplate.properties;
1445
- const folderId = templateDictionary.folderId;
1446
- const isPortal = templateDictionary.isPortal;
1447
- const itemId = newItemTemplate.itemId;
1448
- validateSpatialReferenceAndExtent(serviceInfo, newItemTemplate, templateDictionary);
1449
- const fallbackExtent = _getFallbackExtent(serviceInfo, templateDictionary);
1450
- const params = {};
1451
- const itemInfo = {
1452
- title: newItemTemplate.item.title,
1453
- name: newItemTemplate.item.name
1454
- };
1455
- const _item = {
1456
- ...itemInfo,
1457
- preserveLayerIds: true
1458
- };
1459
- const createOptions = {
1460
- item: _item,
1461
- folderId,
1462
- params,
1463
- authentication: authentication
1464
- };
1465
- createOptions.item = !isTrackingViewTemplate(newItemTemplate) ?
1466
- _setItemProperties(createOptions.item, newItemTemplate, serviceInfo, params, isPortal) :
1467
- setTrackingOptions(newItemTemplate, createOptions, templateDictionary);
1468
- // project the portals extent to match that of the service
1469
- convertExtentWithFallback(templateDictionary.organization.defaultExtent, fallbackExtent, serviceInfo.service.spatialReference, templateDictionary.organization.helperServices.geometry.url, authentication).then(extent => {
1470
- templateDictionary[itemId].solutionExtent = extent;
1471
- setDefaultSpatialReference(templateDictionary, itemId, extent.spatialReference);
1472
- createOptions.item = replaceInTemplate(createOptions.item, templateDictionary);
1473
- createOptions.params = replaceInTemplate(createOptions.params, templateDictionary);
1474
- if (newItemTemplate.item.thumbnail) {
1475
- // Pass thumbnail file in via params because item property is serialized, which discards a blob
1476
- createOptions.params.thumbnail = newItemTemplate.item.thumbnail;
1477
- }
1478
- resolve(createOptions);
1479
- }, e => reject(fail(e)));
1480
- });
1481
- }
1482
- /**
1483
- * When the services spatial reference does not match that of it's default extent
1484
- * use the out SRs default extent if it exists in the templateDictionary
1485
- * this should be set when adding a custom out wkid to the params before calling deploy
1486
- * this will help avoid situations where the orgs default extent and default world extent
1487
- * will not project successfully to the out SR
1488
- *
1489
- * @param serviceInfo the object that contains the spatial reference to evaluate
1490
- * @param templateDictionary the template dictionary
1491
- * @returns the extent to use as the fallback
1492
- * @private
1493
- */
1494
- export function _getFallbackExtent(serviceInfo, templateDictionary) {
1495
- const serviceSR = serviceInfo.service.spatialReference;
1496
- const serviceInfoWkid = getProp(serviceInfo, "defaultExtent.spatialReference.wkid");
1497
- const customDefaultExtent = getProp(templateDictionary, "params.defaultExtent");
1498
- return serviceInfoWkid && serviceInfoWkid === serviceSR.wkid
1499
- ? serviceInfo.defaultExtent
1500
- : customDefaultExtent
1501
- ? customDefaultExtent
1502
- : serviceInfo.defaultExtent;
1503
- }
1504
- /**
1505
- * Add relationships to all layers in one call to retain fully functioning composite relationships
1506
- *
1507
- * @param args The IPostProcessArgs for the request(s)
1508
- * @returns Any relationships that should be updated for the service
1509
- * @private
1510
- */
1511
- export function _getRelationshipUpdates(args) {
1512
- const rels = {
1513
- layers: []
1514
- };
1515
- Object.keys(args.objects).forEach((k) => {
1516
- const obj = args.objects[k];
1517
- /* istanbul ignore else */
1518
- if (obj.relationships && obj.relationships.length > 0) {
1519
- rels.layers.push({
1520
- id: obj.id,
1521
- relationships: obj.relationships
1522
- });
1523
- }
1524
- deleteProp(obj, "relationships");
1525
- });
1526
- return rels;
1527
- }
1528
- /**
1529
- * Get the stored contingent values and structure them to be added to the services layers.
1530
- *
1531
- * @param args The IPostProcessArgs for the request(s)
1532
- * @returns Any contingent values that should be added to the service.
1533
- * @private
1534
- */
1535
- export function _getContingentValuesUpdates(args) {
1536
- const contingentValues = [];
1537
- Object.keys(args.objects).forEach((k) => {
1538
- const obj = args.objects[k];
1539
- /* istanbul ignore else */
1540
- if (obj.contingentValues) {
1541
- contingentValues.push({
1542
- id: obj.id,
1543
- contingentValues: obj.contingentValues
1544
- });
1545
- }
1546
- deleteProp(obj, "contingentValues");
1547
- });
1548
- return contingentValues;
1549
- }
1550
- /**
1551
- * Get refresh, add, update, or delete definition info
1552
- *
1553
- * @param url the base admin url for the service
1554
- * @param id the id of the layer
1555
- * @param obj parameters for the request
1556
- * @param args various arguments to help support the request
1557
- * @param type type of update the request will handle
1558
- * @returns IUpdate that has the request url and arguments
1559
- * @private
1560
- */
1561
- export function _getUpdate(url, id, obj, args, type) {
1562
- const ops = {
1563
- delete: {
1564
- url: checkUrlPathTermination(url) + id + "/deleteFromDefinition",
1565
- params: {
1566
- deleteFromDefinition: {
1567
- fields: obj && obj.hasOwnProperty("deleteFields") ? obj.deleteFields : []
1568
- }
1569
- }
1570
- },
1571
- update: {
1572
- url: checkUrlPathTermination(url) +
1573
- (id ? `${id}/updateDefinition` : "updateDefinition"),
1574
- params: {
1575
- updateDefinition: obj
1576
- }
1577
- },
1578
- add: {
1579
- url: checkUrlPathTermination(url) + "addToDefinition",
1580
- params: {
1581
- addToDefinition: obj
1582
- }
1583
- },
1584
- refresh: {
1585
- url: checkUrlPathTermination(url) + "refresh",
1586
- params: {
1587
- f: "json"
1588
- }
1589
- }
1590
- };
1591
- return {
1592
- url: ops[type].url,
1593
- params: ops[type].params,
1594
- args: args
1595
- };
1596
- }
1597
- /**
1598
- * Changes just the domain part of a URL to lowercase.
1599
- *
1600
- * @param url URL to modify
1601
- * @return Adjusted URL
1602
- * @see From `getServerRootUrl` in arcgis-rest-js' ArcGISIdentityManager.ts
1603
- * @private
1604
- */
1605
- export function _lowercaseDomain(url) {
1606
- if (!url) {
1607
- return url;
1608
- }
1609
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1610
- const [_, protocol, domainAndPath] = url.match(/(https?:\/\/)(.+)/);
1611
- const [domain, ...path] = domainAndPath.split("/");
1612
- // Only the domain is lowercased because in some cases an org id might be
1613
- // in the path which cannot be lowercased.
1614
- return `${protocol}${domain.toLowerCase()}/${path.join("/")}`;
1615
- }
1616
- /**
1617
- * Checks the two main parts of an item for unresolved variables and reports any found.
1618
- *
1619
- * @param base Item's base section
1620
- * @param data Item's data section
1621
- * @private
1622
- */
1623
- export function _reportVariablesInItem(itemId, itemType, base, data) {
1624
- const getUnresolved = (v) => {
1625
- return JSON.stringify(v).match(/{{.+?}}/gim);
1626
- };
1627
- // Provide feedback about any remaining unresolved variables
1628
- /* istanbul ignore else */
1629
- if (base && hasUnresolvedVariables(base)) {
1630
- console.log(itemId +
1631
- " (" +
1632
- itemType +
1633
- ") contains variables in base: " +
1634
- JSON.stringify(getUnresolved(base)));
1635
- }
1636
- /* istanbul ignore else */
1637
- if (data && hasUnresolvedVariables(data)) {
1638
- console.log(itemId +
1639
- " (" +
1640
- itemType +
1641
- ") contains variables in data: " +
1642
- JSON.stringify(getUnresolved(data)));
1643
- }
1644
- }
1645
- /**
1646
- * Updates a feature service item.
1647
- *
1648
- * @param item Item to update
1649
- * @param itemTemplate item template for the new item
1650
- * @param serviceInfo Service information
1651
- * @param params arcgis-rest-js params to update
1652
- * @param isPortal Is the service hosted in a portal?
1653
- * @returns Updated item
1654
- * @private
1655
- */
1656
- export function _setItemProperties(item, itemTemplate, serviceInfo, params, isPortal) {
1657
- // Set the capabilities
1658
- const portalCapabilities = [
1659
- "Create",
1660
- "Query",
1661
- "Editing",
1662
- "Update",
1663
- "Delete",
1664
- "Uploads",
1665
- "Sync",
1666
- "Extract"
1667
- ];
1668
- const capabilities = getProp(serviceInfo, "service.capabilities") || (isPortal ? "" : []);
1669
- item.capabilities = isPortal
1670
- ? capabilities
1671
- .split(",")
1672
- .filter((c) => portalCapabilities.indexOf(c) > -1)
1673
- .join(",")
1674
- : capabilities;
1675
- if (serviceInfo.service.capabilities) {
1676
- serviceInfo.service.capabilities = item.capabilities;
1677
- }
1678
- // Handle index update for any pre-published solution items that
1679
- // have non-unique indexes on relationship key fields
1680
- _updateIndexesForRelationshipKeyFields(serviceInfo);
1681
- // set create options item properties
1682
- const keyProperties = [
1683
- "isView",
1684
- "sourceSchemaChangesAllowed",
1685
- "isUpdatableView",
1686
- "capabilities",
1687
- "isMultiServicesView"
1688
- ];
1689
- const deleteKeys = ["layers", "tables"];
1690
- /* istanbul ignore else */
1691
- if (isPortal) {
1692
- // removed for issue #423 causing FS to fail to create
1693
- deleteKeys.push("adminServiceInfo");
1694
- }
1695
- const itemKeys = Object.keys(item);
1696
- const serviceKeys = Object.keys(serviceInfo.service);
1697
- serviceKeys.forEach(k => {
1698
- /* istanbul ignore else */
1699
- if (itemKeys.indexOf(k) === -1 && deleteKeys.indexOf(k) < 0) {
1700
- item[k] = serviceInfo.service[k];
1701
- // These need to be included via params otherwise...
1702
- // addToDef calls fail when adding adminLayerInfo
1703
- /* istanbul ignore else */
1704
- if (serviceInfo.service.isView && keyProperties.indexOf(k) > -1) {
1705
- params[k] = serviceInfo.service[k];
1706
- }
1707
- }
1708
- });
1709
- // Enable editor tracking on layer with related tables is not supported.
1710
- /* istanbul ignore else */
1711
- if (item.isMultiServicesView &&
1712
- getProp(item, "editorTrackingInfo.enableEditorTracking")) {
1713
- item.editorTrackingInfo.enableEditorTracking = false;
1714
- params["editorTrackingInfo"] = item.editorTrackingInfo;
1715
- }
1716
- /* istanbul ignore else */
1717
- if (isPortal) {
1718
- // portal will fail when initialExtent is defined but null
1719
- // removed for issue #449 causing FS to fail to create on portal
1720
- /* istanbul ignore else */
1721
- if (Object.keys(item).indexOf("initialExtent") > -1 &&
1722
- !item.initialExtent) {
1723
- deleteProp(item, "initialExtent");
1724
- }
1725
- }
1726
- return item;
1727
- }
1728
- /**
1729
- * Set isUnique as true for indexes that reference origin relationship keyFields.
1730
- *
1731
- * @param serviceInfo Service information
1732
- * @private
1733
- */
1734
- export function _updateIndexesForRelationshipKeyFields(serviceInfo) {
1735
- const layersAndTables = (serviceInfo.layers || []).concat(serviceInfo.tables || []);
1736
- layersAndTables.forEach(item => {
1737
- const relationships = item.relationships;
1738
- const indexes = item.indexes;
1739
- /* istanbul ignore else */
1740
- if (relationships &&
1741
- relationships.length > 0 &&
1742
- indexes &&
1743
- indexes.length > 0) {
1744
- const keyFields = relationships.reduce((acc, v) => {
1745
- /* istanbul ignore else */
1746
- if (v.role === "esriRelRoleOrigin" &&
1747
- v.keyField &&
1748
- acc.indexOf(v.keyField) < 0) {
1749
- acc.push(v.keyField);
1750
- }
1751
- return acc;
1752
- }, []);
1753
- indexes.map(i => {
1754
- /* istanbul ignore else */
1755
- if (keyFields.some(k => {
1756
- const regEx = new RegExp(`\\b${k}\\b`);
1757
- return regEx.test(i.fields);
1758
- })) {
1759
- i.isUnique = true;
1760
- }
1761
- return i;
1762
- });
1763
- }
1764
- });
1765
- }
1766
- /**
1767
- * Updates the URL of an item.
1768
- *
1769
- * @param id AGOL id of item to update
1770
- * @param url URL to assign to item's base section
1771
- * @param authentication Credentials for the request
1772
- * @param numAttempts Number of times to try to set the URL if AGO says that it updated the URL, but really didn't
1773
- * @returns A promise that will resolve with the item id when the item has been updated or an AGO-style JSON failure
1774
- * response
1775
- * @private
1776
- */
1777
- export function _updateItemURL(id, url, authentication, numAttempts = 1) {
1778
- // Introduce a lag because AGO update appears to choke with rapid subsequent calls
1779
- const msLag = 1000;
1780
- return new Promise((resolve, reject) => {
1781
- // Update the item's URL
1782
- const options = { item: { id, url }, authentication: authentication };
1783
- portalUpdateItem(options).then(result => {
1784
- if (!result.success) {
1785
- reject(fail(result));
1786
- }
1787
- else {
1788
- // Get the item to see if the URL really changed; we'll delay a bit before testing because AGO
1789
- // has a timing problem with URL updates
1790
- setTimeout(() => {
1791
- getItem(id, { authentication: authentication }).then(item => {
1792
- const iBrace = item.url.indexOf("{");
1793
- if (iBrace > -1) {
1794
- console.warn(id + " has template variable: " + item.url.substr(iBrace));
1795
- }
1796
- if (url === item.url) {
1797
- resolve(id);
1798
- }
1799
- else {
1800
- // If it fails, try again if we have sufficient attempts remaining
1801
- const errorMsg = "URL not updated for " +
1802
- item.type +
1803
- " " +
1804
- item.id +
1805
- ": " +
1806
- item.url +
1807
- " (" +
1808
- numAttempts +
1809
- ")";
1810
- if (--numAttempts > 0) {
1811
- _updateItemURL(id, url, authentication, numAttempts).then(resolve, reject);
1812
- }
1813
- else {
1814
- console.error(id + ": " + errorMsg + "; FAILED");
1815
- reject(errorMsg);
1816
- }
1817
- }
1818
- }, e => reject(fail(e)));
1819
- }, msLag);
1820
- }
1821
- }, e => reject(fail(e)));
1822
- });
1823
- }
1
+ /** @license
2
+ * Copyright 2018 Esri
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * Provides common functions involving the arcgis-rest-js library.
18
+ *
19
+ * @module restHelpers
20
+ */
21
+ import { removeLayerOptimization, setDefaultSpatialReference, validateSpatialReferenceAndExtent, processContingentValues } from "./featureServiceHelpers";
22
+ import { appendQueryParam, blobToJson, blobToText, checkUrlPathTermination, deleteProp, deleteProps, fail, getProp, getUniqueTitle, setCreateProp } from "./generalHelpers";
23
+ import { UserSession } from "./interfaces";
24
+ import { createZip } from "./libConnectors";
25
+ import { getItemBase, getItemDataAsJson } from "./restHelpersGet";
26
+ import { addItemData as portalAddItemData, addItemRelationship, addItemResource, createFolder, createGroup, createItemInFolder, getItem, removeFolder as portalRemoveFolder, removeGroup as portalRemoveGroup, removeGroupUsers as portalRemoveGroupUsers, removeItem as portalRemoveItem, searchGroupContent, searchGroups as portalSearchGroups, searchItems as portalSearchItems, SearchQueryBuilder, setItemAccess, shareItemWithGroup, updateItem as portalUpdateItem, updateGroup as portalUpdateGroup } from "@esri/arcgis-rest-portal";
27
+ import { request } from "@esri/arcgis-rest-request";
28
+ import { addToServiceDefinition as svcAdminAddToServiceDefinition, createFeatureService as svcAdminCreateFeatureService } from "@esri/arcgis-rest-service-admin";
29
+ import { getWorkforceDependencies, isWorkforceProject, getWorkforceServiceInfo } from "./workforceHelpers";
30
+ import { hasUnresolvedVariables, replaceInTemplate } from "./templatization";
31
+ import { isTrackingViewTemplate, setTrackingOptions } from "./trackingHelpers";
32
+ // ------------------------------------------------------------------------------------------------------------------ //
33
+ export { request as rest_request } from "@esri/arcgis-rest-request";
34
+ // ------------------------------------------------------------------------------------------------------------------ //
35
+ export function addItemData(id, data, authentication) {
36
+ const addDataOptions = {
37
+ id,
38
+ data,
39
+ authentication
40
+ };
41
+ return portalAddItemData(addDataOptions);
42
+ }
43
+ ;
44
+ /**
45
+ * Creates a UserSession via a function so that the global arcgisSolution variable can access authentication.
46
+ *
47
+ * @param options See https://esri.github.io/arcgis-rest-js/api/auth/IUserSessionOptions/
48
+ * @returns UserSession
49
+ */
50
+ export function getUserSession(options = {}) {
51
+ return new UserSession(options);
52
+ }
53
+ /**
54
+ * Adds a forward relationship between two items.
55
+ *
56
+ * @param originItemId Origin of relationship
57
+ * @param destinationItemId Destination of relationship
58
+ * @param relationshipType Type of relationship
59
+ * @param authentication Credentials for the request
60
+ * @returns A Promise to add item resources.
61
+ */
62
+ export function addForwardItemRelationship(originItemId, destinationItemId, relationshipType, authentication) {
63
+ return new Promise(resolve => {
64
+ const requestOptions = {
65
+ originItemId,
66
+ destinationItemId,
67
+ relationshipType,
68
+ authentication
69
+ };
70
+ addItemRelationship(requestOptions).then(response => {
71
+ resolve({
72
+ success: response.success,
73
+ itemId: originItemId
74
+ });
75
+ }, () => {
76
+ resolve({
77
+ success: false,
78
+ itemId: originItemId
79
+ });
80
+ });
81
+ });
82
+ }
83
+ /**
84
+ * Adds forward relationships for an item.
85
+ *
86
+ * @param originItemId Origin of relationship
87
+ * @param destinationRelationships Destinations
88
+ * @param authentication Credentials for the request
89
+ * @returns A Promise to add item resources.
90
+ */
91
+ export function addForwardItemRelationships(originItemId, destinationRelationships, authentication) {
92
+ return new Promise(resolve => {
93
+ // Set up relationships using updated relationship information
94
+ const relationshipPromises = new Array();
95
+ destinationRelationships.forEach(relationship => {
96
+ relationship.relatedItemIds.forEach(relatedItemId => {
97
+ relationshipPromises.push(addForwardItemRelationship(originItemId, relatedItemId, relationship.relationshipType, authentication));
98
+ });
99
+ });
100
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
101
+ Promise.all(relationshipPromises).then((responses) => resolve(responses));
102
+ });
103
+ }
104
+ /**
105
+ * Adds a token to the query parameters of a URL.
106
+ *
107
+ * @param url URL to use as base
108
+ * @param authentication Credentials to be used to generate token for URL
109
+ * @returns A promise that will resolve with the supplied URL with `token=&lt;token&gt;` added to its query params
110
+ * unless either the URL doesn't exist or the token can't be generated
111
+ */
112
+ export function addTokenToUrl(url, authentication) {
113
+ return new Promise(resolve => {
114
+ if (!url || !authentication) {
115
+ resolve(url);
116
+ }
117
+ else {
118
+ authentication.getToken(url).then(token => {
119
+ /* istanbul ignore else */
120
+ if (token) {
121
+ url = appendQueryParam(url, "token=" + token);
122
+ }
123
+ resolve(url);
124
+ }, () => resolve(url));
125
+ }
126
+ });
127
+ }
128
+ /**
129
+ * Calls addToDefinition for the service.
130
+ *
131
+ * Added retry due to some solutions failing to deploy in specific orgs/hives due to timeouts.
132
+ * On the first pass we will use the quicker sync request to add.
133
+ * If it fails we will use an async request that will avoid the timeout errors.
134
+ *
135
+ * @param url URL to use as base
136
+ * @param options the info to add to the services definition
137
+ * @param skipRetry a boolean to control if retry logic will be used. Defaults to false.
138
+ * @param useAsync a boolean to control if we will use an async request
139
+ * @returns A promise that will resolve when the request has completed
140
+ */
141
+ export function addToServiceDefinition(url, options, skipRetry = false, useAsync = false) {
142
+ /* istanbul ignore else */
143
+ if (useAsync) {
144
+ options.params = { ...options.params, async: true };
145
+ }
146
+ return new Promise((resolve, reject) => {
147
+ svcAdminAddToServiceDefinition(url, options).then((result) => {
148
+ checkRequestStatus(result, options.authentication).then(() => resolve(null), e => reject(fail(e)));
149
+ }, e => {
150
+ if (!skipRetry) {
151
+ addToServiceDefinition(url, options, true, true).then(() => resolve(null), e => reject(e));
152
+ }
153
+ else {
154
+ reject(fail(e));
155
+ }
156
+ });
157
+ });
158
+ }
159
+ /**
160
+ * When using an async request we need to poll the status url to know when the request has completed or failed.
161
+ *
162
+ * @param result the result returned from the addToDefinition request.
163
+ * This will contain a status url or the standard sync result.
164
+ * @param authentication Credentials to be used to generate token for URL
165
+ * @returns A promise that will resolve when the request has completed
166
+ */
167
+ export function checkRequestStatus(result, authentication) {
168
+ return new Promise((resolve, reject) => {
169
+ const url = result.statusURL || result.statusUrl;
170
+ if (url) {
171
+ const checkStatus = setInterval(() => {
172
+ request(url, { authentication }).then(r => {
173
+ /* istanbul ignore else */
174
+ if (["completed", "success"].indexOf(r.status.toLowerCase()) > -1) {
175
+ clearInterval(checkStatus);
176
+ resolve();
177
+ }
178
+ else if (r.status.toLowerCase() === "failed") {
179
+ clearInterval(checkStatus);
180
+ reject(r);
181
+ }
182
+ }, e => {
183
+ clearInterval(checkStatus);
184
+ reject(e);
185
+ });
186
+ }, 2000);
187
+ }
188
+ else {
189
+ resolve();
190
+ }
191
+ });
192
+ }
193
+ /**
194
+ * Converts a general search into an ISearchOptions structure.
195
+ *
196
+ * @param search Search specified in one of three ways
197
+ * @returns Recast search
198
+ */
199
+ export function convertToISearchOptions(search) {
200
+ // Convert the search into an ISearchOptions
201
+ let searchOptions = {
202
+ q: "",
203
+ start: 1,
204
+ num: 100
205
+ };
206
+ if (typeof search === "string") {
207
+ // Insert query into defaults
208
+ searchOptions.q = search;
209
+ }
210
+ else if (search instanceof SearchQueryBuilder) {
211
+ // Insert query into defaults
212
+ searchOptions.q = search.toParam();
213
+ }
214
+ else { // search is ISearchOptions
215
+ searchOptions = {
216
+ ...searchOptions,
217
+ ...search // request
218
+ };
219
+ }
220
+ return searchOptions;
221
+ }
222
+ /**
223
+ * Simple validate function to ensure all coordinates are numbers
224
+ * In some cases orgs can have null or undefined coordinate values associated with the org extent
225
+ *
226
+ * @param extent the extent to validate
227
+ * @returns the provided extent or a default global extent if some coordinates are not numbers
228
+ * @private
229
+ */
230
+ export function _validateExtent(extent) {
231
+ // in some cases orgs can have invalid extents defined
232
+ // this is a simple validate function that will ensure coordiantes are numbers
233
+ // using -179,-89,179,89 because the project call is returning "NaN" when using -180,-90,180,90
234
+ const hasInvalid = typeof extent.xmin !== "number" ||
235
+ typeof extent.xmax !== "number" ||
236
+ typeof extent.ymax !== "number" ||
237
+ typeof extent.ymin !== "number";
238
+ if (hasInvalid) {
239
+ extent.xmin = -179;
240
+ extent.xmax = 179;
241
+ extent.ymax = 89;
242
+ extent.ymin = -89;
243
+ extent.spatialReference = { wkid: 4326 };
244
+ }
245
+ return extent;
246
+ }
247
+ /**
248
+ * If the request to convert the extent fails it has commonly been due to an invalid extent.
249
+ * This function will first attempt to use the provided extent. If it fails it will default to
250
+ * the source items extent and if that fails it will then use a default global extent.
251
+ *
252
+ * @param extent the extent to convert
253
+ * @param fallbackExtent the extent to convert if the main extent does not project to the outSR
254
+ * @param outSR the spatial reference to project to
255
+ * @param geometryServiceUrl the service url for the geometry service to use
256
+ * @param authentication the credentials for the requests
257
+ * @returns the extent projected to the provided spatial reference
258
+ * or the world extent projected to the provided spatial reference
259
+ * @private
260
+ */
261
+ export function convertExtentWithFallback(extent, fallbackExtent, outSR, geometryServiceUrl, authentication) {
262
+ return new Promise((resolve, reject) => {
263
+ const defaultExtent = {
264
+ xmin: -179,
265
+ xmax: 179,
266
+ ymin: -89,
267
+ ymax: 89,
268
+ spatialReference: { wkid: 4326 }
269
+ };
270
+ convertExtent(_validateExtent(extent), outSR, geometryServiceUrl, authentication).then(extentResponse => {
271
+ // in some cases project will complete successfully but return "NaN" values
272
+ // check for this and call convert again if it does
273
+ const extentResponseString = JSON.stringify(extentResponse);
274
+ const validatedExtent = JSON.stringify(_validateExtent(extentResponse));
275
+ if (extentResponseString === validatedExtent) {
276
+ resolve(extentResponse);
277
+ }
278
+ else {
279
+ convertExtent(fallbackExtent || defaultExtent, outSR, geometryServiceUrl, authentication).then(resolve, e => reject(fail(e)));
280
+ }
281
+ },
282
+ // if convert fails try again with default global extent
283
+ () => {
284
+ convertExtent(defaultExtent, outSR, geometryServiceUrl, authentication).then(resolve, e => reject(fail(e)));
285
+ });
286
+ });
287
+ }
288
+ /**
289
+ * Converts an extent to a specified spatial reference.
290
+ *
291
+ * @param extent Extent object to check and (possibly) to project
292
+ * @param outSR Desired spatial reference
293
+ * @param geometryServiceUrl Path to geometry service providing `findTransformations` and `project` services
294
+ * @param authentication Credentials for the request
295
+ * @returns Original extent if it's already using outSR or the extents projected into the outSR
296
+ */
297
+ export function convertExtent(extent, outSR, geometryServiceUrl, authentication) {
298
+ const _requestOptions = { authentication };
299
+ return new Promise((resolve, reject) => {
300
+ if (extent.spatialReference.wkid === outSR?.wkid || !outSR) {
301
+ resolve(extent);
302
+ }
303
+ else {
304
+ _requestOptions.params = {
305
+ f: "json",
306
+ inSR: extent.spatialReference.wkid,
307
+ outSR: outSR.wkid,
308
+ extentOfInterest: JSON.stringify(extent)
309
+ };
310
+ request(checkUrlPathTermination(geometryServiceUrl) + "findTransformations", _requestOptions).then(response => {
311
+ const transformations = response && response.transformations
312
+ ? response.transformations
313
+ : undefined;
314
+ let transformation;
315
+ if (transformations && transformations.length > 0) {
316
+ // if a forward single transformation is found use that...otherwise check for and use composite
317
+ transformation = transformations[0].wkid
318
+ ? transformations[0].wkid
319
+ : transformations[0].geoTransforms
320
+ ? transformations[0]
321
+ : undefined;
322
+ }
323
+ _requestOptions.params = {
324
+ f: "json",
325
+ outSR: outSR.wkid,
326
+ inSR: extent.spatialReference.wkid,
327
+ geometries: {
328
+ geometryType: "esriGeometryPoint",
329
+ geometries: [
330
+ { x: extent.xmin, y: extent.ymin },
331
+ { x: extent.xmax, y: extent.ymax }
332
+ ]
333
+ },
334
+ transformation: transformation
335
+ };
336
+ request(checkUrlPathTermination(geometryServiceUrl) + "project", _requestOptions).then(projectResponse => {
337
+ const projectGeom = projectResponse.geometries.length === 2
338
+ ? projectResponse.geometries
339
+ : undefined;
340
+ if (projectGeom) {
341
+ resolve({
342
+ xmin: projectGeom[0].x,
343
+ ymin: projectGeom[0].y,
344
+ xmax: projectGeom[1].x,
345
+ ymax: projectGeom[1].y,
346
+ spatialReference: outSR
347
+ });
348
+ }
349
+ else {
350
+ resolve(undefined);
351
+ }
352
+ }, e => reject(fail(e)));
353
+ }, e => reject(fail(e)));
354
+ }
355
+ });
356
+ }
357
+ /**
358
+ * Publishes a feature service as an AGOL item; it does not include its layers and tables
359
+ *
360
+ * @param newItemTemplate Template of item to be created
361
+ * @param authentication Credentials for the request
362
+ * @param templateDictionary Hash of facts: org URL, adlib replacements, user; .user.folders property contains a list
363
+ * @returns A promise that will resolve with an object reporting success and the Solution id
364
+ */
365
+ export function createFeatureService(newItemTemplate, authentication, templateDictionary) {
366
+ return new Promise((resolve, reject) => {
367
+ // Create item
368
+ _getCreateServiceOptions(newItemTemplate, authentication, templateDictionary).then(createOptions => {
369
+ svcAdminCreateFeatureService(createOptions).then(createResponse => {
370
+ // Federated servers may have inconsistent casing, so lowerCase it
371
+ createResponse.encodedServiceURL = _lowercaseDomain(createResponse.encodedServiceURL);
372
+ createResponse.serviceurl = _lowercaseDomain(createResponse.serviceurl);
373
+ resolve(createResponse);
374
+ }, e => reject(fail(e)));
375
+ }, e => reject(fail(e)));
376
+ });
377
+ }
378
+ /**
379
+ * Publishes an item and its data, metadata, and resources as an AGOL item.
380
+ *
381
+ * @param itemInfo Item's `item` section
382
+ * @param folderId Id of folder to receive item; null indicates that the item goes into the root
383
+ * folder; ignored for Group item type
384
+ * @param destinationAuthentication Credentials for for requests to where the item is to be created
385
+ * @param itemThumbnailUrl URL to image to use for item thumbnail
386
+ * @param itemThumbnailAuthentication Credentials for requests to the thumbnail source
387
+ * @param dataFile Item's `data` section
388
+ * @param metadataFile Item's metadata file
389
+ * @param resourcesFiles Item's resources
390
+ * @param access Access to set for item: "public", "org", "private"
391
+ * @returns A promise that will resolve with an object reporting success or failure and the Solution id
392
+ */
393
+ export function createFullItem(itemInfo, folderId, destinationAuthentication, itemThumbnailUrl, itemThumbnailAuthentication, dataFile, metadataFile, resourcesFiles, access = "private") {
394
+ return new Promise((resolve, reject) => {
395
+ // Create item
396
+ const createOptions = {
397
+ item: {
398
+ ...itemInfo
399
+ },
400
+ folderId,
401
+ authentication: destinationAuthentication
402
+ };
403
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
404
+ addTokenToUrl(itemThumbnailUrl, itemThumbnailAuthentication).then(updatedThumbnailUrl => {
405
+ /* istanbul ignore else */
406
+ if (updatedThumbnailUrl) {
407
+ createOptions.item.thumbnailUrl = appendQueryParam(updatedThumbnailUrl, "w=400");
408
+ }
409
+ createItemInFolder(createOptions).then(createResponse => {
410
+ if (createResponse.success) {
411
+ let accessDef;
412
+ // Set access if it is not AGOL default
413
+ // Set the access manually since the access value in createItem appears to be ignored
414
+ // Need to run serially; will not work reliably if done in parallel with adding the data section
415
+ if (access !== "private") {
416
+ const accessOptions = {
417
+ id: createResponse.id,
418
+ access: access === "public" ? "public" : "org",
419
+ authentication: destinationAuthentication
420
+ };
421
+ accessDef = setItemAccess(accessOptions);
422
+ }
423
+ else {
424
+ accessDef = Promise.resolve({
425
+ itemId: createResponse.id
426
+ });
427
+ }
428
+ // Now add attached items
429
+ accessDef.then(() => {
430
+ const updateDefs = [];
431
+ // Add the data section
432
+ if (dataFile) {
433
+ updateDefs.push(_addItemDataFile(createResponse.id, dataFile, destinationAuthentication));
434
+ }
435
+ // Add the resources via a zip because AGO sometimes loses resources if many are added at the
436
+ // same time to the same item
437
+ if (Array.isArray(resourcesFiles) &&
438
+ resourcesFiles.length > 0) {
439
+ updateDefs.push(new Promise((rsrcResolve, rsrcReject) => {
440
+ createZip("resources.zip", resourcesFiles).then((zipfile) => {
441
+ const addResourceOptions = {
442
+ id: createResponse.id,
443
+ resource: zipfile,
444
+ authentication: destinationAuthentication,
445
+ params: {
446
+ archive: true
447
+ }
448
+ };
449
+ addItemResource(addResourceOptions).then(rsrcResolve, rsrcReject);
450
+ }, rsrcReject);
451
+ }));
452
+ }
453
+ // Add the metadata section
454
+ if (metadataFile) {
455
+ updateDefs.push(_addItemMetadataFile(createResponse.id, metadataFile, destinationAuthentication));
456
+ }
457
+ // Wait until all adds are done
458
+ Promise.all(updateDefs).then(() => resolve(createResponse), e => reject(fail(e)));
459
+ }, e => reject(fail(e)));
460
+ }
461
+ else {
462
+ reject(fail());
463
+ }
464
+ }, e => reject(fail(e)));
465
+ });
466
+ });
467
+ }
468
+ /**
469
+ * Publishes an item and its data as an AGOL item.
470
+ *
471
+ * @param itemInfo Item's `item` section
472
+ * @param dataInfo Item's `data` section
473
+ * @param authentication Credentials for the request
474
+ * @param folderId Id of folder to receive item; null indicates that the item goes into the root
475
+ * folder; ignored for Group item type
476
+ * @param access Access to set for item: "public", "org", "private"
477
+ * @returns A promise that will resolve with an object reporting success and the Solution id
478
+ */
479
+ export function createItemWithData(itemInfo, dataInfo, authentication, folderId, access = "private") {
480
+ return new Promise((resolve, reject) => {
481
+ // Create item
482
+ const createOptions = {
483
+ item: {
484
+ title: "_",
485
+ ...itemInfo,
486
+ data: dataInfo
487
+ },
488
+ folderId,
489
+ authentication: authentication
490
+ };
491
+ if (itemInfo.thumbnail) {
492
+ createOptions.params = {
493
+ // Pass thumbnail file in via params because item property is serialized, which discards a blob
494
+ thumbnail: itemInfo.thumbnail
495
+ };
496
+ delete createOptions.item.thumbnail;
497
+ }
498
+ createItemInFolder(createOptions).then(createResponse => {
499
+ if (createResponse.success) {
500
+ if (access !== "private") {
501
+ // Set access if it is not AGOL default
502
+ // Set the access manually since the access value in createItem appears to be ignored
503
+ const accessOptions = {
504
+ id: createResponse.id,
505
+ access: access === "public" ? "public" : "org",
506
+ authentication: authentication
507
+ };
508
+ setItemAccess(accessOptions).then(() => {
509
+ resolve({
510
+ folder: createResponse.folder,
511
+ id: createResponse.id,
512
+ success: true
513
+ });
514
+ }, e => reject(fail(e)));
515
+ }
516
+ else {
517
+ resolve({
518
+ folder: createResponse.folder,
519
+ id: createResponse.id,
520
+ success: true
521
+ });
522
+ }
523
+ }
524
+ else {
525
+ reject(fail());
526
+ }
527
+ }, e => reject(fail(e)));
528
+ });
529
+ }
530
+ /**
531
+ * Creates a folder using a numeric suffix to ensure uniqueness if necessary.
532
+ *
533
+ * @param title Folder title, used as-is if possible and with suffix otherwise
534
+ * @param templateDictionary Hash of facts: org URL, adlib replacements, user; .user.folders property contains a list
535
+ * of known folder names; function updates list with existing names not yet known, and creates .user.folders if it
536
+ * doesn't exist in the dictionary
537
+ * @param authentication Credentials for creating folder
538
+ * @returns Id of created folder
539
+ */
540
+ export function createUniqueFolder(title, templateDictionary, authentication) {
541
+ return new Promise((resolve, reject) => {
542
+ // Get a title that is not already in use
543
+ const folderTitle = getUniqueTitle(title, templateDictionary, "user.folders");
544
+ const folderCreationParam = {
545
+ title: folderTitle,
546
+ authentication: authentication
547
+ };
548
+ createFolder(folderCreationParam).then(ok => resolve(ok), err => {
549
+ // If the name already exists, we'll try again
550
+ const errorDetails = getProp(err, "response.error.details");
551
+ if (Array.isArray(errorDetails) && errorDetails.length > 0) {
552
+ const nameNotAvailMsg = "Folder title '" + folderTitle + "' not available.";
553
+ if (errorDetails.indexOf(nameNotAvailMsg) >= 0) {
554
+ // Create the user.folders property if it doesn't exist
555
+ /* istanbul ignore else */
556
+ if (!getProp(templateDictionary, "user.folders")) {
557
+ setCreateProp(templateDictionary, "user.folders", []);
558
+ }
559
+ templateDictionary.user.folders.push({
560
+ title: folderTitle
561
+ });
562
+ createUniqueFolder(title, templateDictionary, authentication).then(resolve, reject);
563
+ }
564
+ else {
565
+ reject(err);
566
+ }
567
+ }
568
+ else {
569
+ // Otherwise, error out
570
+ reject(err);
571
+ }
572
+ });
573
+ });
574
+ }
575
+ /**
576
+ * Creates a group using numeric suffix to ensure uniqueness.
577
+ *
578
+ * @param title Group title, used as-is if possible and with suffix otherwise
579
+ * @param templateDictionary Hash of facts: org URL, adlib replacements, user
580
+ * @param authentication Credentials for creating group
581
+ * @param owner Optional arg for the Tracking owner
582
+ * If the tracking owner is not the one deploying the solution
583
+ * tracker groups will be created under the deployment user but
584
+ * will be reassigned to the tracking owner.
585
+ * @returns Information about created group
586
+ */
587
+ export function createUniqueGroup(title, groupItem, templateDictionary, authentication, owner) {
588
+ return new Promise((resolve, reject) => {
589
+ let groupsPromise;
590
+ // when working with tracker we need to consider the groups of the deploying user as well as the groups
591
+ // of the tracking user...must be unique for both
592
+ if (owner && owner !== authentication.username) {
593
+ groupsPromise = searchAllGroups(`(owner:${owner}) orgid:${templateDictionary.organization.id}`, authentication);
594
+ }
595
+ else {
596
+ groupsPromise = Promise.resolve([]);
597
+ }
598
+ // first get the tracker owner groups
599
+ groupsPromise.then(groups => {
600
+ templateDictionary["allGroups"] =
601
+ groups.concat(getProp(templateDictionary, "user.groups"));
602
+ // Get a title that is not already in use
603
+ groupItem.title = getUniqueTitle(title, templateDictionary, "allGroups");
604
+ const groupCreationParam = {
605
+ group: groupItem,
606
+ authentication: authentication
607
+ };
608
+ createGroup(groupCreationParam).then(resolve, err => {
609
+ // If the name already exists, we'll try again
610
+ const errorDetails = getProp(err, "response.error.details");
611
+ if (Array.isArray(errorDetails) && errorDetails.length > 0) {
612
+ const nameNotAvailMsg = "You already have a group named '" +
613
+ groupItem.title +
614
+ "'. Try a different name.";
615
+ if (errorDetails.indexOf(nameNotAvailMsg) >= 0) {
616
+ templateDictionary.user.groups.push({
617
+ title: groupItem.title
618
+ });
619
+ createUniqueGroup(title, groupItem, templateDictionary, authentication).then(resolve, reject);
620
+ }
621
+ else {
622
+ reject(err);
623
+ }
624
+ }
625
+ else {
626
+ // Otherwise, error out
627
+ reject(err);
628
+ }
629
+ });
630
+ }, e => reject(e));
631
+ });
632
+ }
633
+ /**
634
+ * Gets the ids of the dependencies of an AGOL feature service item.
635
+ * Dependencies will only exist when the service is a view.
636
+ *
637
+ * @param itemTemplate Template of item to be created
638
+ * @param authentication Credentials for the request
639
+ * @returns A promise that will resolve a list of dependencies
640
+ */
641
+ export function extractDependencies(itemTemplate, authentication) {
642
+ const dependencies = [];
643
+ return new Promise((resolve, reject) => {
644
+ // Get service dependencies when the item is a view
645
+ // This step is skipped for tracker views as they will already have a source service in the org
646
+ if (itemTemplate.properties.service.isView && itemTemplate.item.url && !isTrackingViewTemplate(itemTemplate)) {
647
+ request(checkUrlPathTermination(itemTemplate.item.url) + "sources?f=json", {
648
+ authentication: authentication
649
+ }).then(response => {
650
+ /* istanbul ignore else */
651
+ if (response && response.services) {
652
+ response.services.forEach((layer) => {
653
+ dependencies.push({
654
+ id: layer.serviceItemId,
655
+ name: layer.name
656
+ });
657
+ });
658
+ }
659
+ resolve(dependencies);
660
+ }, e => reject(fail(e)));
661
+ }
662
+ else if (isWorkforceProject(itemTemplate)) {
663
+ resolve(getWorkforceDependencies(itemTemplate, dependencies));
664
+ }
665
+ else {
666
+ resolve(dependencies);
667
+ }
668
+ });
669
+ }
670
+ /**
671
+ * Get json info for the services layers
672
+ *
673
+ * @param serviceUrl the url for the service
674
+ * @param layerList list of base layer info
675
+ * @param authentication Credentials for the request
676
+ * @returns A promise that will resolve a list of dependencies
677
+ */
678
+ export function getLayers(serviceUrl, layerList, authentication) {
679
+ return new Promise((resolve, reject) => {
680
+ if (layerList.length === 0) {
681
+ resolve([]);
682
+ }
683
+ // get the admin URL
684
+ serviceUrl = serviceUrl.replace("/rest/services", "/rest/admin/services");
685
+ const requestsDfd = [];
686
+ layerList.forEach(layer => {
687
+ const requestOptions = {
688
+ authentication: authentication
689
+ };
690
+ requestsDfd.push(request(checkUrlPathTermination(serviceUrl) + layer["id"] + "?f=json", requestOptions));
691
+ });
692
+ // Wait until all layers are heard from
693
+ Promise.all(requestsDfd).then(layers => resolve(layers), e => reject(fail(e)));
694
+ });
695
+ }
696
+ /**
697
+ * Add additional options to a layers definition.
698
+ *
699
+ * @param args The IPostProcessArgs for the request(s)
700
+ * @param isPortal boolean to indicate if we are deploying to portal
701
+ *
702
+ * @returns An array of update instructions
703
+ * @private
704
+ */
705
+ export function getLayerUpdates(args, isPortal) {
706
+ const adminUrl = args.itemTemplate.item.url.replace("rest/services", "rest/admin/services");
707
+ const updates = [];
708
+ const refresh = _getUpdate(adminUrl, null, null, args, "refresh");
709
+ updates.push(refresh);
710
+ Object.keys(args.objects).forEach(id => {
711
+ const obj = Object.assign({}, args.objects[id]);
712
+ // These properties cannot be set in the update definition when working with portal
713
+ if (isPortal) {
714
+ deleteProps(obj, ["type", "id", "relationships", "sourceServiceFields"]);
715
+ }
716
+ // handle definition deletes
717
+ // removes previous editFieldsInfo fields if their names were changed
718
+ if (obj.hasOwnProperty("deleteFields")) {
719
+ updates.push(_getUpdate(adminUrl, id, obj, args, "delete"));
720
+ deleteProp(obj, "deleteFields");
721
+ updates.push(_getUpdate(adminUrl, null, null, args, "refresh"));
722
+ }
723
+ });
724
+ // issue: #706
725
+ // Add source service relationships
726
+ // views will now always add all layers in a single call and will inherit the relationships from the source service
727
+ if (!args.itemTemplate.properties.service.isView) {
728
+ const relUpdates = _getRelationshipUpdates({
729
+ message: "updated layer relationships",
730
+ objects: args.objects,
731
+ itemTemplate: args.itemTemplate,
732
+ authentication: args.authentication
733
+ });
734
+ // issue: #724
735
+ // In portal the order the relationships are added needs to follow the layer order
736
+ // otherwise the relationship IDs will be reset
737
+ relUpdates.layers = _sortRelationships(args.itemTemplate.properties.layers, args.itemTemplate.properties.tables, relUpdates);
738
+ /* istanbul ignore else */
739
+ if (relUpdates.layers.length > 0) {
740
+ updates.push(_getUpdate(adminUrl, null, relUpdates, args, "add"));
741
+ updates.push(refresh);
742
+ }
743
+ // handle contingent values
744
+ const contingentValuesUpdates = _getContingentValuesUpdates({
745
+ message: "add layer contingent values",
746
+ objects: args.objects,
747
+ itemTemplate: args.itemTemplate,
748
+ authentication: args.authentication
749
+ });
750
+ /* istanbul ignore else */
751
+ if (contingentValuesUpdates.length > 0) {
752
+ contingentValuesUpdates.forEach(conUpdate => {
753
+ updates.push(_getUpdate(adminUrl + conUpdate.id, null, conUpdate.contingentValues, args, "add"));
754
+ });
755
+ }
756
+ }
757
+ return updates.length === 1 ? [] : updates;
758
+ }
759
+ /**
760
+ * Sorts relationships based on order of supporting layers and tables in the service definition
761
+ *
762
+ * @param layers the layers from the service
763
+ * @param tables the tables from the service
764
+ * @param relUpdates the relationships to add for the service
765
+ *
766
+ * @returns An array with relationships that have been sorted
767
+ * @private
768
+ */
769
+ export function _sortRelationships(layers, tables, relUpdates) {
770
+ const ids = [].concat(layers.map((l) => l.id), tables.map((t) => t.id));
771
+ // In portal the order the relationships are added needs to follow the layer order
772
+ // otherwise the relationship IDs will be reset
773
+ const _relUpdateLayers = [];
774
+ ids.forEach(id => {
775
+ relUpdates.layers.some((relUpdate) => {
776
+ if (id === relUpdate.id) {
777
+ _relUpdateLayers.push(relUpdate);
778
+ return true;
779
+ }
780
+ else {
781
+ return false;
782
+ }
783
+ });
784
+ });
785
+ return _relUpdateLayers;
786
+ }
787
+ /**
788
+ * Add additional options to a layers definition
789
+ *
790
+ * Added retry due to some solutions failing to deploy in specific orgs/hives
791
+ *
792
+ *
793
+ * @param Update will contain either add, update, or delete from service definition call
794
+ * @param skipRetry defaults to false. when true the retry logic will be ignored
795
+ * @returns A promise that will resolve when service definition call has completed
796
+ * @private
797
+ */
798
+ /* istanbul ignore else */
799
+ export function getRequest(update, skipRetry = false, useAsync = false) {
800
+ return new Promise((resolve, reject) => {
801
+ const options = {
802
+ params: update.params,
803
+ authentication: update.args.authentication
804
+ };
805
+ /* istanbul ignore else */
806
+ if ((useAsync && update.url.indexOf("addToDefinition") > -1) ||
807
+ update.url.indexOf("updateDefinition") > -1 ||
808
+ update.url.indexOf("deleteFromDefinition") > -1) {
809
+ options.params = { ...options.params, async: true };
810
+ }
811
+ request(update.url, options).then(result => {
812
+ checkRequestStatus(result, options.authentication).then(() => resolve(null), e => reject(fail(e)));
813
+ }, (e) => {
814
+ if (!skipRetry) {
815
+ getRequest(update, true, true).then(() => resolve(), e => reject(e));
816
+ }
817
+ else {
818
+ reject(e);
819
+ }
820
+ });
821
+ });
822
+ }
823
+ /**
824
+ * Fills in missing data, including full layer and table definitions, in a feature services' definition.
825
+ *
826
+ * @param itemTemplate Feature service item, data, dependencies definition to be modified
827
+ * @param authentication Credentials for the request to AGOL
828
+ * @returns A promise that will resolve when fullItem has been updated
829
+ * @private
830
+ */
831
+ export function getServiceLayersAndTables(itemTemplate, authentication) {
832
+ return new Promise((resolve, reject) => {
833
+ // To have enough information for reconstructing the service, we'll supplement
834
+ // the item and data sections with sections for the service, full layers, and
835
+ // full tables
836
+ // Extra steps must be taken for workforce version 2
837
+ const isWorkforceService = isWorkforceProject(itemTemplate);
838
+ // Get the service description
839
+ if (itemTemplate.item.url) {
840
+ getFeatureServiceProperties(itemTemplate.item.url, authentication, isWorkforceService).then(properties => {
841
+ itemTemplate.properties = properties;
842
+ resolve(itemTemplate);
843
+ }, e => reject(fail(e)));
844
+ }
845
+ else {
846
+ resolve(itemTemplate);
847
+ }
848
+ });
849
+ }
850
+ /**
851
+ * Get service properties for the given url and update key props
852
+ *
853
+ * @param serviceUrl the feature service url
854
+ * @param authentication Credentials for the request to AGOL
855
+ * @param workforceService boolean to indicate if extra workforce service steps should be handled
856
+ * @returns A promise that will resolve with the service properties
857
+ * @private
858
+ */
859
+ export function getFeatureServiceProperties(serviceUrl, authentication, workforceService = false) {
860
+ return new Promise((resolve, reject) => {
861
+ const properties = {
862
+ service: {},
863
+ layers: [],
864
+ tables: []
865
+ };
866
+ // get the admin URL
867
+ serviceUrl = serviceUrl.replace("/rest/services", "/rest/admin/services");
868
+ // Get the service description
869
+ request(serviceUrl + "?f=json", {
870
+ authentication: authentication
871
+ }).then(serviceData => {
872
+ properties.service = _parseAdminServiceData(serviceData);
873
+ // Copy cacheMaxAge to top level so that AGO sees it when deploying the service
874
+ // serviceData may have set it if there isn't an adminServiceInfo
875
+ /* istanbul ignore else */
876
+ if (serviceData.adminServiceInfo?.cacheMaxAge) {
877
+ properties.service.cacheMaxAge =
878
+ serviceData.adminServiceInfo.cacheMaxAge;
879
+ }
880
+ // Move the layers and tables out of the service's data section
881
+ /* istanbul ignore else */
882
+ if (serviceData.layers) {
883
+ properties.layers = serviceData.layers;
884
+ // Fill in properties that the service layer doesn't provide
885
+ // and remove properties that should not exist in the template
886
+ properties.layers.forEach(layer => {
887
+ layer.serviceItemId = properties.service.serviceItemId;
888
+ layer.extent = null;
889
+ removeLayerOptimization(layer);
890
+ });
891
+ }
892
+ delete serviceData.layers;
893
+ /* istanbul ignore else */
894
+ if (serviceData.tables) {
895
+ properties.tables = serviceData.tables;
896
+ // Fill in properties that the service layer doesn't provide
897
+ properties.tables.forEach(table => {
898
+ table.serviceItemId = properties.service.serviceItemId;
899
+ table.extent = null;
900
+ });
901
+ }
902
+ delete serviceData.tables;
903
+ // Ensure solution items have unique indexes on relationship key fields
904
+ _updateIndexesForRelationshipKeyFields(properties);
905
+ processContingentValues(properties, serviceUrl, authentication).then(() => {
906
+ if (workforceService) {
907
+ getWorkforceServiceInfo(properties, serviceUrl, authentication).then(resolve, reject);
908
+ }
909
+ else {
910
+ resolve(properties);
911
+ }
912
+ }, (e) => reject(fail(e)));
913
+ }, (e) => reject(fail(e)));
914
+ });
915
+ }
916
+ /**
917
+ * Parses the layers array and will filter subsets of Layers and Tables
918
+ * Layers and Tables are both returned in the layers array when we access a feature service from the admin api.
919
+ *
920
+ * @param adminData The data of the feature service
921
+ * @returns A mutated version of the provided adminData
922
+ * @private
923
+ */
924
+ export function _parseAdminServiceData(adminData) {
925
+ const layers = adminData.layers || [];
926
+ const tables = adminData.tables || [];
927
+ setCreateProp(adminData, "layers", layers.filter(l => l.type === "Feature Layer"));
928
+ // TODO understand if the concat is necessary.
929
+ // Not sure if the admin api will ever actually return a tables collection here.
930
+ setCreateProp(adminData, "tables", tables.concat(layers.filter(l => l.type === "Table")));
931
+ return adminData;
932
+ }
933
+ /**
934
+ * livingatlas designation test.
935
+ * These layers should not be templatized or depolyed
936
+ *
937
+ * @param groupDesignations the items group designations to evaluate
938
+ * @returns A boolean indicating if the invalid designation is found in the item info
939
+ */
940
+ export function hasInvalidGroupDesignations(groupDesignations) {
941
+ const invalidGroupDesignations = ["livingatlas"];
942
+ return groupDesignations
943
+ ? invalidGroupDesignations.indexOf(groupDesignations) > -1
944
+ : false;
945
+ }
946
+ /**
947
+ * Removes a folder from AGO.
948
+ *
949
+ * @param folderId Id of a folder to delete
950
+ * @param authentication Credentials for the request to AGO
951
+ * @returns A promise that will resolve with the result of the request
952
+ */
953
+ export function removeFolder(folderId, authentication) {
954
+ return new Promise((resolve, reject) => {
955
+ const requestOptions = {
956
+ folderId: folderId,
957
+ authentication: authentication
958
+ };
959
+ portalRemoveFolder(requestOptions).then(result => (result.success ? resolve(result) : reject(result)), reject);
960
+ });
961
+ }
962
+ /**
963
+ * Removes a group from AGO.
964
+ *
965
+ * @param groupId Id of a group to delete
966
+ * @param authentication Credentials for the request to AGO
967
+ * @returns A promise that will resolve with the result of the request
968
+ */
969
+ export function removeGroup(groupId, authentication) {
970
+ return new Promise((resolve, reject) => {
971
+ const requestOptions = {
972
+ id: groupId,
973
+ authentication: authentication
974
+ };
975
+ portalRemoveGroup(requestOptions).then(result => (result.success ? resolve(result) : reject(result)), reject);
976
+ });
977
+ }
978
+ /**
979
+ * Removes an item from AGO.
980
+ *
981
+ * @param itemId Id of an item to delete
982
+ * @param authentication Credentials for the request to AGO
983
+ * @returns A promise that will resolve with the result of the request
984
+ */
985
+ export function removeItem(itemId, authentication) {
986
+ return new Promise((resolve, reject) => {
987
+ const requestOptions = {
988
+ id: itemId,
989
+ authentication: authentication
990
+ };
991
+ portalRemoveItem(requestOptions).then(result => (result.success ? resolve(result) : reject(result)), reject);
992
+ });
993
+ }
994
+ /**
995
+ * Removes an item or group from AGO.
996
+ *
997
+ * @param itemId Id of an item or group to delete
998
+ * @param authentication Credentials for the request to AGO
999
+ * @returns A promise that will resolve with the result of the request
1000
+ */
1001
+ export function removeItemOrGroup(itemId, authentication) {
1002
+ return new Promise((resolve, reject) => {
1003
+ removeItem(itemId, authentication).then(resolve, error => {
1004
+ removeGroup(itemId, authentication).then(resolve, () => reject(error));
1005
+ });
1006
+ });
1007
+ }
1008
+ /**
1009
+ * Searches for items matching a query and that the caller has access to.
1010
+ *
1011
+ * @param search Search string (e.g., "q=redlands+map") or a more detailed structure that can include authentication
1012
+ * @returns Promise resolving with search results
1013
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/search.htm
1014
+ */
1015
+ export function searchItems(search) {
1016
+ return portalSearchItems(search);
1017
+ }
1018
+ /**
1019
+ * Searches for items matching a query and that the caller has access to, continuing recursively until done.
1020
+ *
1021
+ * @param search Search string (e.g., "q=redlands+map") or a more detailed structure that can include authentication
1022
+ * @param accumulatedResponse Response built from previous requests
1023
+ * @returns Promise resolving with search results
1024
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/search.htm
1025
+ */
1026
+ export function searchAllItems(search, accumulatedResponse) {
1027
+ // Convert the search into an ISearchOptions
1028
+ const searchOptions = convertToISearchOptions(search);
1029
+ // Provide a base into which results can be concatenated
1030
+ const completeResponse = accumulatedResponse ? accumulatedResponse : {
1031
+ query: searchOptions.q,
1032
+ start: 1,
1033
+ num: 100,
1034
+ nextStart: -1,
1035
+ total: 0,
1036
+ results: []
1037
+ };
1038
+ return new Promise((resolve, reject) => {
1039
+ searchItems(search).then(response => {
1040
+ completeResponse.results = completeResponse.results.concat(response.results);
1041
+ completeResponse.num = completeResponse.total = completeResponse.results.length;
1042
+ if (response.nextStart > 0) {
1043
+ // Insert nextStart into next query
1044
+ searchOptions.start = response.nextStart;
1045
+ resolve(searchAllItems(searchOptions, completeResponse));
1046
+ }
1047
+ else {
1048
+ resolve(completeResponse);
1049
+ }
1050
+ }, e => reject(e));
1051
+ });
1052
+ }
1053
+ /**
1054
+ * Searches for groups matching criteria.
1055
+ *
1056
+ * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1057
+ * @param authentication Credentials for the request to AGO
1058
+ * @param additionalSearchOptions Adjustments to search, such as tranche size
1059
+ * @returns A promise that will resolve with a structure with a tranche of results and
1060
+ * describing how many items are available
1061
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/group-search.htm
1062
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/search-reference.htm
1063
+ */
1064
+ export function searchGroups(searchString, authentication, additionalSearchOptions) {
1065
+ const searchOptions = {
1066
+ q: searchString,
1067
+ params: {
1068
+ ...additionalSearchOptions
1069
+ },
1070
+ authentication: authentication
1071
+ };
1072
+ return portalSearchGroups(searchOptions);
1073
+ }
1074
+ /**
1075
+ * Searches for groups matching criteria recurusively.
1076
+ *
1077
+ * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1078
+ * @param authentication Credentials for the request to AGO
1079
+ * @param groups List of groups that have been found from previous requests
1080
+ * @param inPagingParams The paging params for the recurisve searching
1081
+ *
1082
+ * @returns A promise that will resolve with all groups that meet the search criteria
1083
+ */
1084
+ export function searchAllGroups(searchString, authentication, groups, inPagingParams) {
1085
+ const pagingParams = inPagingParams ? inPagingParams : {
1086
+ start: 1,
1087
+ num: 24
1088
+ };
1089
+ const additionalSearchOptions = {
1090
+ sortField: "title",
1091
+ sortOrder: "asc",
1092
+ ...pagingParams
1093
+ };
1094
+ // Provide a base onto which results can be concatenated
1095
+ let finalResults = groups ? groups : [];
1096
+ return new Promise((resolve, reject) => {
1097
+ searchGroups(searchString, authentication, additionalSearchOptions).then(response => {
1098
+ finalResults = finalResults.concat(response.results);
1099
+ if (response.nextStart > 0) {
1100
+ pagingParams.start = response.nextStart;
1101
+ resolve(searchAllGroups(searchString, authentication, finalResults, pagingParams));
1102
+ }
1103
+ else {
1104
+ resolve(finalResults);
1105
+ }
1106
+ }, e => reject(e));
1107
+ });
1108
+ }
1109
+ /**
1110
+ * Searches for group contents matching criteria recursively.
1111
+ *
1112
+ * @param groupId Group whose contents are to be searched
1113
+ * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1114
+ * @param authentication Credentials for the request to AGO
1115
+ * @param additionalSearchOptions Adjustments to search, such as tranche size and categories of interest; categories
1116
+ * are supplied as an array: each array element consists of one or more categories to be ORed; array elements are ANDed
1117
+ * @param portalUrl Rest Url of the portal to perform the search
1118
+ * @param accumulatedResponse Response built from previous requests
1119
+ * @returns A promise that will resolve with a structure with a tranche of results and
1120
+ * describing how many items are available
1121
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/group-content-search.htm
1122
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/search-reference.htm
1123
+ */
1124
+ export function searchGroupAllContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl, accumulatedResponse) {
1125
+ additionalSearchOptions = additionalSearchOptions ? additionalSearchOptions : {};
1126
+ // Provide a base into which results can be concatenated
1127
+ const completeResponse = accumulatedResponse ? accumulatedResponse : {
1128
+ query: searchString,
1129
+ start: 1,
1130
+ num: 100,
1131
+ nextStart: -1,
1132
+ total: 0,
1133
+ results: []
1134
+ };
1135
+ return new Promise((resolve, reject) => {
1136
+ searchGroupContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl).then(response => {
1137
+ completeResponse.results = completeResponse.results.concat(response.results);
1138
+ completeResponse.num = completeResponse.total = completeResponse.results.length;
1139
+ if (response.nextStart > 0) {
1140
+ additionalSearchOptions.start = response.nextStart;
1141
+ resolve(searchGroupAllContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl, completeResponse));
1142
+ }
1143
+ else {
1144
+ resolve(completeResponse);
1145
+ }
1146
+ }, e => reject(e));
1147
+ });
1148
+ }
1149
+ /**
1150
+ * Searches for group contents matching criteria.
1151
+ *
1152
+ * @param groupId Group whose contents are to be searched
1153
+ * @param searchString Text for which to search, e.g., 'redlands+map', 'type:"Web Map" -type:"Web Mapping Application"'
1154
+ * @param authentication Credentials for the request to AGO
1155
+ * @param additionalSearchOptions Adjustments to search, such as tranche size and categories of interest; categories
1156
+ * are supplied as an array: each array element consists of one or more categories to be ORed; array elements are ANDed
1157
+ * @param portalUrl Rest Url of the portal to perform the search
1158
+ * @returns A promise that will resolve with a structure with a tranche of results and
1159
+ * describing how many items are available
1160
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/group-content-search.htm
1161
+ * @see https://developers.arcgis.com/rest/users-groups-and-items/search-reference.htm
1162
+ */
1163
+ export function searchGroupContents(groupId, searchString, authentication, additionalSearchOptions, portalUrl) {
1164
+ const searchOptions = {
1165
+ groupId,
1166
+ q: searchString,
1167
+ params: Object.assign({
1168
+ num: 100
1169
+ }, additionalSearchOptions),
1170
+ authentication: authentication,
1171
+ portal: portalUrl
1172
+ };
1173
+ // If search options include `categories`, switch to new arcgis-rest-js format
1174
+ /* istanbul ignore else */
1175
+ if (Array.isArray(searchOptions.params.categories)) {
1176
+ searchOptions.params.categories = searchOptions.params.categories.map(andGroup => andGroup.split(","));
1177
+ }
1178
+ return searchGroupContent(searchOptions);
1179
+ }
1180
+ /**
1181
+ * Reassign ownership of a group
1182
+ *
1183
+ * @param groupId Group to remove users from
1184
+ * @param userName The new owner for the group
1185
+ * @param authentication Credentials for the request to
1186
+ *
1187
+ * @returns A promise that will resolve after the group ownership has been assigned
1188
+ *
1189
+ */
1190
+ export function reassignGroup(groupId, userName, authentication) {
1191
+ const requestOptions = {
1192
+ authentication: authentication,
1193
+ params: {
1194
+ targetUsername: userName
1195
+ }
1196
+ };
1197
+ return request(`${authentication.portal}/community/groups/${groupId}/reassign`, requestOptions);
1198
+ }
1199
+ /**
1200
+ * Remove users from a group
1201
+ *
1202
+ * @param groupId Group to remove users from
1203
+ * @param users List of users to remove from the group
1204
+ * @param authentication Credentials for the request to
1205
+ *
1206
+ * @returns A promise that will resolve after the users have been removed
1207
+ *
1208
+ */
1209
+ export function removeUsers(groupId, users, authentication) {
1210
+ return portalRemoveGroupUsers({
1211
+ id: groupId,
1212
+ users,
1213
+ authentication
1214
+ });
1215
+ }
1216
+ /**
1217
+ * Shares an item to the defined group
1218
+ *
1219
+ * @param groupId Group to share with
1220
+ * @param id the item id to share with the group
1221
+ * @param destinationAuthentication Credentials for the request to AGO
1222
+ * @param owner owner of the group when sharing tracking items (can be different from the deploying user)
1223
+ *
1224
+ * @returns A promise that will resolve after the item has been shared
1225
+ *
1226
+ */
1227
+ export function shareItem(groupId, id, destinationAuthentication, owner) {
1228
+ return new Promise((resolve, reject) => {
1229
+ const shareOptions = {
1230
+ groupId,
1231
+ id,
1232
+ authentication: destinationAuthentication
1233
+ };
1234
+ /* istanbul ignore else */
1235
+ if (owner) {
1236
+ shareOptions.owner = owner;
1237
+ }
1238
+ shareItemWithGroup(shareOptions).then(() => resolve(null), (e) => reject(fail(e)));
1239
+ });
1240
+ }
1241
+ /**
1242
+ * Updates an item.
1243
+ *
1244
+ * @param itemInfo The base info of an item; note that this content will be serialized, which doesn't work
1245
+ * for binary content
1246
+ * @param authentication Credentials for request
1247
+ * @param folderId Item's folder
1248
+ * @param additionalParams Updates that are put under the `params` property, which is not serialized
1249
+ * @return
1250
+ */
1251
+ export function updateItem(itemInfo, authentication, folderId, additionalParams) {
1252
+ return new Promise((resolve, reject) => {
1253
+ const updateOptions = {
1254
+ item: itemInfo,
1255
+ folderId: folderId,
1256
+ authentication: authentication,
1257
+ params: {
1258
+ ...(additionalParams ?? {})
1259
+ }
1260
+ };
1261
+ portalUpdateItem(updateOptions).then(response => (response.success ? resolve(response) : reject(response)), err => reject(err));
1262
+ });
1263
+ }
1264
+ /**
1265
+ * Updates a group.
1266
+ *
1267
+ * @param groupInfo The base info of a group; note that this content will be serialized, which doesn't work
1268
+ * for binary content
1269
+ * @param authentication Credentials for request
1270
+ * @param additionalParams Updates that are put under the `params` property, which is not serialized
1271
+ * @returns A Promise that will resolve with the success/failure status of the request
1272
+ */
1273
+ export function updateGroup(groupInfo, authentication, additionalParams) {
1274
+ return new Promise((resolve, reject) => {
1275
+ const updateOptions = {
1276
+ group: groupInfo,
1277
+ authentication,
1278
+ params: {
1279
+ ...(additionalParams ?? {})
1280
+ }
1281
+ };
1282
+ portalUpdateGroup(updateOptions).then(response => (response.success ? resolve(response) : reject(response)), err => reject(err));
1283
+ });
1284
+ }
1285
+ /**
1286
+ * Updates an item.
1287
+ *
1288
+ * @param itemInfo The base info of an item
1289
+ * @param data The items data section
1290
+ * @param authentication Credentials for requests
1291
+ * @param thumbnail optional thumbnail to update
1292
+ * @param access "public" or "org"
1293
+ * @return
1294
+ */
1295
+ export function updateItemExtended(itemInfo, data, authentication, thumbnail, access, templateDictionary) {
1296
+ return new Promise((resolve, reject) => {
1297
+ const updateOptions = {
1298
+ item: itemInfo,
1299
+ params: {
1300
+ text: data || {} // AGO ignores update if `data` is empty
1301
+ },
1302
+ authentication: authentication
1303
+ };
1304
+ if (thumbnail) {
1305
+ updateOptions.params.thumbnail = thumbnail;
1306
+ }
1307
+ if (isTrackingViewTemplate(undefined, itemInfo) && templateDictionary) {
1308
+ updateOptions.owner = templateDictionary.locationTracking.owner;
1309
+ }
1310
+ portalUpdateItem(updateOptions).then(result => {
1311
+ if (access && access !== "private") {
1312
+ // Set access if it is not AGOL default
1313
+ // Set the access manually since the access value in createItem appears to be ignored
1314
+ const accessOptions = {
1315
+ id: itemInfo.id,
1316
+ access: access === "public" ? "public" : "org",
1317
+ authentication: authentication
1318
+ };
1319
+ setItemAccess(accessOptions).then(() => resolve(result), e => reject(fail(e)));
1320
+ }
1321
+ else {
1322
+ resolve(result);
1323
+ }
1324
+ }, e => reject(fail(e)));
1325
+ });
1326
+ }
1327
+ /**
1328
+ * Update an item's base and data using a dictionary.
1329
+ *
1330
+ * @param {string} itemId The item ID
1331
+ * @param {any} templateDictionary The template dictionary
1332
+ * @param {UserSession} authentication The destination session info
1333
+ * @returns Promise resolving to successfulness of update
1334
+ */
1335
+ export function updateItemTemplateFromDictionary(itemId, templateDictionary, authentication) {
1336
+ return new Promise((resolve, reject) => {
1337
+ // Fetch the items as stored in AGO
1338
+ Promise.all([
1339
+ getItemBase(itemId, authentication),
1340
+ getItemDataAsJson(itemId, authentication)
1341
+ ])
1342
+ .then(([item, data]) => {
1343
+ // Do they have any variables?
1344
+ if (hasUnresolvedVariables(item) || hasUnresolvedVariables(data)) {
1345
+ // Update if so
1346
+ const { item: updatedItem, data: updatedData } = replaceInTemplate({ item, data }, templateDictionary);
1347
+ _reportVariablesInItem(itemId, item.type, updatedItem, updatedData);
1348
+ return updateItemExtended(updatedItem, updatedData, authentication);
1349
+ }
1350
+ else {
1351
+ // Shortcut out if not
1352
+ return Promise.resolve({
1353
+ success: true,
1354
+ id: itemId
1355
+ });
1356
+ }
1357
+ })
1358
+ .then(result => resolve(result))
1359
+ .catch(error => reject(error));
1360
+ });
1361
+ }
1362
+ /**
1363
+ * Updates the URL of an item.
1364
+ *
1365
+ * @param id AGOL id of item to update
1366
+ * @param url URL to assign to item's base section
1367
+ * @param authentication Credentials for the request
1368
+ * @returns A promise that will resolve with the item id when the item has been updated or an AGO-style JSON failure
1369
+ * response
1370
+ */
1371
+ export function updateItemURL(id, url, authentication) {
1372
+ const numAttempts = 3;
1373
+ return _updateItemURL(id, url, authentication, numAttempts);
1374
+ }
1375
+ // ------------------------------------------------------------------------------------------------------------------ //
1376
+ /**
1377
+ * Adds a data section to an item.
1378
+ *
1379
+ * @param itemId Id of item to receive data file
1380
+ * @param dataFile Data to be added
1381
+ * @param authentication Credentials for the request
1382
+ * @returns Promise reporting success or failure
1383
+ * @private
1384
+ */
1385
+ export function _addItemDataFile(itemId, dataFile, authentication) {
1386
+ return new Promise((resolve, reject) => {
1387
+ const _addItemData = (data) => {
1388
+ addItemData(itemId, data, authentication).then(resolve, reject);
1389
+ };
1390
+ // Item data has to be submitted as text or JSON for those file types
1391
+ if (dataFile.type.startsWith("text/plain")) {
1392
+ blobToText(dataFile).then(_addItemData, reject);
1393
+ }
1394
+ else if (dataFile.type === "application/json") {
1395
+ blobToJson(dataFile).then(_addItemData, reject);
1396
+ }
1397
+ else {
1398
+ _addItemData(dataFile);
1399
+ }
1400
+ });
1401
+ }
1402
+ /**
1403
+ * Adds a metadata file to an item.
1404
+ *
1405
+ * @param itemId Id of item to receive data file
1406
+ * @param metadataFile Metadata to be added
1407
+ * @param authentication Credentials for the request
1408
+ * @returns Promise reporting success or failure
1409
+ * @private
1410
+ */
1411
+ export function _addItemMetadataFile(itemId, metadataFile, authentication) {
1412
+ return new Promise((resolve, reject) => {
1413
+ const addMetadataOptions = {
1414
+ item: {
1415
+ id: itemId
1416
+ },
1417
+ params: {
1418
+ // Pass metadata in via params because item property is serialized, which discards a blob
1419
+ metadata: metadataFile
1420
+ },
1421
+ authentication: authentication
1422
+ };
1423
+ portalUpdateItem(addMetadataOptions).then(resolve, reject);
1424
+ });
1425
+ }
1426
+ /**
1427
+ * Accumulates the number of relationships in a collection of layers.
1428
+ *
1429
+ * @param List of layers to examine
1430
+ * @returns The number of relationships
1431
+ * @private
1432
+ */
1433
+ export function _countRelationships(layers) {
1434
+ const reducer = (accumulator, currentLayer) => accumulator +
1435
+ (currentLayer.relationships ? currentLayer.relationships.length : 0);
1436
+ return layers.reduce(reducer, 0);
1437
+ }
1438
+ /**
1439
+ * Gets the full definitions of the layers affiliated with a hosted service.
1440
+ *
1441
+ * @param serviceUrl URL to hosted service
1442
+ * @param layerList List of layers at that service...must contain id
1443
+ * @param authentication Credentials for the request
1444
+ * @returns A promise that will resolve with a list of the layers from the admin api
1445
+ * @private
1446
+ */
1447
+ export function _getCreateServiceOptions(newItemTemplate, authentication, templateDictionary) {
1448
+ return new Promise((resolve, reject) => {
1449
+ const serviceInfo = newItemTemplate.properties;
1450
+ const folderId = templateDictionary.folderId;
1451
+ const isPortal = templateDictionary.isPortal;
1452
+ const itemId = newItemTemplate.itemId;
1453
+ validateSpatialReferenceAndExtent(serviceInfo, newItemTemplate, templateDictionary);
1454
+ const fallbackExtent = _getFallbackExtent(serviceInfo, templateDictionary);
1455
+ const params = {};
1456
+ const itemInfo = {
1457
+ title: newItemTemplate.item.title,
1458
+ name: newItemTemplate.item.name
1459
+ };
1460
+ const _item = {
1461
+ ...itemInfo,
1462
+ preserveLayerIds: true
1463
+ };
1464
+ const createOptions = {
1465
+ item: _item,
1466
+ folderId,
1467
+ params,
1468
+ authentication: authentication
1469
+ };
1470
+ createOptions.item = !isTrackingViewTemplate(newItemTemplate) ?
1471
+ _setItemProperties(createOptions.item, newItemTemplate, serviceInfo, params, isPortal) :
1472
+ setTrackingOptions(newItemTemplate, createOptions, templateDictionary);
1473
+ // project the portals extent to match that of the service
1474
+ convertExtentWithFallback(templateDictionary.organization.defaultExtent, fallbackExtent, serviceInfo.service.spatialReference, templateDictionary.organization.helperServices.geometry.url, authentication).then(extent => {
1475
+ templateDictionary[itemId].solutionExtent = extent;
1476
+ setDefaultSpatialReference(templateDictionary, itemId, extent.spatialReference);
1477
+ createOptions.item = replaceInTemplate(createOptions.item, templateDictionary);
1478
+ createOptions.params = replaceInTemplate(createOptions.params, templateDictionary);
1479
+ if (newItemTemplate.item.thumbnail) {
1480
+ // Pass thumbnail file in via params because item property is serialized, which discards a blob
1481
+ createOptions.params.thumbnail = newItemTemplate.item.thumbnail;
1482
+ }
1483
+ resolve(createOptions);
1484
+ }, e => reject(fail(e)));
1485
+ });
1486
+ }
1487
+ /**
1488
+ * When the services spatial reference does not match that of it's default extent
1489
+ * use the out SRs default extent if it exists in the templateDictionary
1490
+ * this should be set when adding a custom out wkid to the params before calling deploy
1491
+ * this will help avoid situations where the orgs default extent and default world extent
1492
+ * will not project successfully to the out SR
1493
+ *
1494
+ * @param serviceInfo the object that contains the spatial reference to evaluate
1495
+ * @param templateDictionary the template dictionary
1496
+ * @returns the extent to use as the fallback
1497
+ * @private
1498
+ */
1499
+ export function _getFallbackExtent(serviceInfo, templateDictionary) {
1500
+ const serviceSR = serviceInfo.service.spatialReference;
1501
+ const serviceInfoWkid = getProp(serviceInfo, "defaultExtent.spatialReference.wkid");
1502
+ const customDefaultExtent = getProp(templateDictionary, "params.defaultExtent");
1503
+ return serviceInfoWkid && serviceInfoWkid === serviceSR.wkid
1504
+ ? serviceInfo.defaultExtent
1505
+ : customDefaultExtent
1506
+ ? customDefaultExtent
1507
+ : serviceInfo.defaultExtent;
1508
+ }
1509
+ /**
1510
+ * Add relationships to all layers in one call to retain fully functioning composite relationships
1511
+ *
1512
+ * @param args The IPostProcessArgs for the request(s)
1513
+ * @returns Any relationships that should be updated for the service
1514
+ * @private
1515
+ */
1516
+ export function _getRelationshipUpdates(args) {
1517
+ const rels = {
1518
+ layers: []
1519
+ };
1520
+ Object.keys(args.objects).forEach((k) => {
1521
+ const obj = args.objects[k];
1522
+ /* istanbul ignore else */
1523
+ if (obj.relationships && obj.relationships.length > 0) {
1524
+ rels.layers.push({
1525
+ id: obj.id,
1526
+ relationships: obj.relationships
1527
+ });
1528
+ }
1529
+ deleteProp(obj, "relationships");
1530
+ });
1531
+ return rels;
1532
+ }
1533
+ /**
1534
+ * Get the stored contingent values and structure them to be added to the services layers.
1535
+ *
1536
+ * @param args The IPostProcessArgs for the request(s)
1537
+ * @returns Any contingent values that should be added to the service.
1538
+ * @private
1539
+ */
1540
+ export function _getContingentValuesUpdates(args) {
1541
+ const contingentValues = [];
1542
+ Object.keys(args.objects).forEach((k) => {
1543
+ const obj = args.objects[k];
1544
+ /* istanbul ignore else */
1545
+ if (obj.contingentValues) {
1546
+ contingentValues.push({
1547
+ id: obj.id,
1548
+ contingentValues: obj.contingentValues
1549
+ });
1550
+ }
1551
+ deleteProp(obj, "contingentValues");
1552
+ });
1553
+ return contingentValues;
1554
+ }
1555
+ /**
1556
+ * Get refresh, add, update, or delete definition info
1557
+ *
1558
+ * @param url the base admin url for the service
1559
+ * @param id the id of the layer
1560
+ * @param obj parameters for the request
1561
+ * @param args various arguments to help support the request
1562
+ * @param type type of update the request will handle
1563
+ * @returns IUpdate that has the request url and arguments
1564
+ * @private
1565
+ */
1566
+ export function _getUpdate(url, id, obj, args, type) {
1567
+ const ops = {
1568
+ delete: {
1569
+ url: checkUrlPathTermination(url) + id + "/deleteFromDefinition",
1570
+ params: {
1571
+ deleteFromDefinition: {
1572
+ fields: obj && obj.hasOwnProperty("deleteFields") ? obj.deleteFields : []
1573
+ }
1574
+ }
1575
+ },
1576
+ update: {
1577
+ url: checkUrlPathTermination(url) +
1578
+ (id ? `${id}/updateDefinition` : "updateDefinition"),
1579
+ params: {
1580
+ updateDefinition: obj
1581
+ }
1582
+ },
1583
+ add: {
1584
+ url: checkUrlPathTermination(url) + "addToDefinition",
1585
+ params: {
1586
+ addToDefinition: obj
1587
+ }
1588
+ },
1589
+ refresh: {
1590
+ url: checkUrlPathTermination(url) + "refresh",
1591
+ params: {
1592
+ f: "json"
1593
+ }
1594
+ }
1595
+ };
1596
+ return {
1597
+ url: ops[type].url,
1598
+ params: ops[type].params,
1599
+ args: args
1600
+ };
1601
+ }
1602
+ /**
1603
+ * Changes just the domain part of a URL to lowercase.
1604
+ *
1605
+ * @param url URL to modify
1606
+ * @return Adjusted URL
1607
+ * @see From `getServerRootUrl` in arcgis-rest-js' ArcGISIdentityManager.ts
1608
+ * @private
1609
+ */
1610
+ export function _lowercaseDomain(url) {
1611
+ if (!url) {
1612
+ return url;
1613
+ }
1614
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1615
+ const [_, protocol, domainAndPath] = url.match(/(https?:\/\/)(.+)/);
1616
+ const [domain, ...path] = domainAndPath.split("/");
1617
+ // Only the domain is lowercased because in some cases an org id might be
1618
+ // in the path which cannot be lowercased.
1619
+ return `${protocol}${domain.toLowerCase()}/${path.join("/")}`;
1620
+ }
1621
+ /**
1622
+ * Checks the two main parts of an item for unresolved variables and reports any found.
1623
+ *
1624
+ * @param base Item's base section
1625
+ * @param data Item's data section
1626
+ * @private
1627
+ */
1628
+ export function _reportVariablesInItem(itemId, itemType, base, data) {
1629
+ const getUnresolved = (v) => {
1630
+ return JSON.stringify(v).match(/{{.+?}}/gim);
1631
+ };
1632
+ // Provide feedback about any remaining unresolved variables
1633
+ /* istanbul ignore else */
1634
+ if (base && hasUnresolvedVariables(base)) {
1635
+ console.log(itemId +
1636
+ " (" +
1637
+ itemType +
1638
+ ") contains variables in base: " +
1639
+ JSON.stringify(getUnresolved(base)));
1640
+ }
1641
+ /* istanbul ignore else */
1642
+ if (data && hasUnresolvedVariables(data)) {
1643
+ console.log(itemId +
1644
+ " (" +
1645
+ itemType +
1646
+ ") contains variables in data: " +
1647
+ JSON.stringify(getUnresolved(data)));
1648
+ }
1649
+ }
1650
+ /**
1651
+ * Updates a feature service item.
1652
+ *
1653
+ * @param item Item to update
1654
+ * @param itemTemplate item template for the new item
1655
+ * @param serviceInfo Service information
1656
+ * @param params arcgis-rest-js params to update
1657
+ * @param isPortal Is the service hosted in a portal?
1658
+ * @returns Updated item
1659
+ * @private
1660
+ */
1661
+ export function _setItemProperties(item, itemTemplate, serviceInfo, params, isPortal) {
1662
+ // Set the capabilities
1663
+ const portalCapabilities = [
1664
+ "Create",
1665
+ "Query",
1666
+ "Editing",
1667
+ "Update",
1668
+ "Delete",
1669
+ "Uploads",
1670
+ "Sync",
1671
+ "Extract"
1672
+ ];
1673
+ const capabilities = getProp(serviceInfo, "service.capabilities") || (isPortal ? "" : []);
1674
+ item.capabilities = isPortal
1675
+ ? capabilities
1676
+ .split(",")
1677
+ .filter((c) => portalCapabilities.indexOf(c) > -1)
1678
+ .join(",")
1679
+ : capabilities;
1680
+ if (serviceInfo.service.capabilities) {
1681
+ serviceInfo.service.capabilities = item.capabilities;
1682
+ }
1683
+ // Handle index update for any pre-published solution items that
1684
+ // have non-unique indexes on relationship key fields
1685
+ _updateIndexesForRelationshipKeyFields(serviceInfo);
1686
+ // set create options item properties
1687
+ const keyProperties = [
1688
+ "isView",
1689
+ "sourceSchemaChangesAllowed",
1690
+ "isUpdatableView",
1691
+ "capabilities",
1692
+ "isMultiServicesView"
1693
+ ];
1694
+ const deleteKeys = ["layers", "tables"];
1695
+ /* istanbul ignore else */
1696
+ if (isPortal) {
1697
+ // removed for issue #423 causing FS to fail to create
1698
+ deleteKeys.push("adminServiceInfo");
1699
+ }
1700
+ const itemKeys = Object.keys(item);
1701
+ const serviceKeys = Object.keys(serviceInfo.service);
1702
+ serviceKeys.forEach(k => {
1703
+ /* istanbul ignore else */
1704
+ if (itemKeys.indexOf(k) === -1 && deleteKeys.indexOf(k) < 0) {
1705
+ item[k] = serviceInfo.service[k];
1706
+ // These need to be included via params otherwise...
1707
+ // addToDef calls fail when adding adminLayerInfo
1708
+ /* istanbul ignore else */
1709
+ if (serviceInfo.service.isView && keyProperties.indexOf(k) > -1) {
1710
+ params[k] = serviceInfo.service[k];
1711
+ }
1712
+ }
1713
+ });
1714
+ // Enable editor tracking on layer with related tables is not supported.
1715
+ /* istanbul ignore else */
1716
+ if (item.isMultiServicesView &&
1717
+ getProp(item, "editorTrackingInfo.enableEditorTracking")) {
1718
+ item.editorTrackingInfo.enableEditorTracking = false;
1719
+ params["editorTrackingInfo"] = item.editorTrackingInfo;
1720
+ }
1721
+ /* istanbul ignore else */
1722
+ if (isPortal) {
1723
+ // portal will fail when initialExtent is defined but null
1724
+ // removed for issue #449 causing FS to fail to create on portal
1725
+ /* istanbul ignore else */
1726
+ if (Object.keys(item).indexOf("initialExtent") > -1 &&
1727
+ !item.initialExtent) {
1728
+ deleteProp(item, "initialExtent");
1729
+ }
1730
+ }
1731
+ return item;
1732
+ }
1733
+ /**
1734
+ * Set isUnique as true for indexes that reference origin relationship keyFields.
1735
+ *
1736
+ * @param serviceInfo Service information
1737
+ * @private
1738
+ */
1739
+ export function _updateIndexesForRelationshipKeyFields(serviceInfo) {
1740
+ const layersAndTables = (serviceInfo.layers || []).concat(serviceInfo.tables || []);
1741
+ layersAndTables.forEach(item => {
1742
+ const relationships = item.relationships;
1743
+ const indexes = item.indexes;
1744
+ /* istanbul ignore else */
1745
+ if (relationships &&
1746
+ relationships.length > 0 &&
1747
+ indexes &&
1748
+ indexes.length > 0) {
1749
+ const keyFields = relationships.reduce((acc, v) => {
1750
+ /* istanbul ignore else */
1751
+ if (v.role === "esriRelRoleOrigin" &&
1752
+ v.keyField &&
1753
+ acc.indexOf(v.keyField) < 0) {
1754
+ acc.push(v.keyField);
1755
+ }
1756
+ return acc;
1757
+ }, []);
1758
+ indexes.map(i => {
1759
+ /* istanbul ignore else */
1760
+ if (keyFields.some(k => {
1761
+ const regEx = new RegExp(`\\b${k}\\b`);
1762
+ return regEx.test(i.fields);
1763
+ })) {
1764
+ i.isUnique = true;
1765
+ }
1766
+ return i;
1767
+ });
1768
+ }
1769
+ });
1770
+ }
1771
+ /**
1772
+ * Updates the URL of an item.
1773
+ *
1774
+ * @param id AGOL id of item to update
1775
+ * @param url URL to assign to item's base section
1776
+ * @param authentication Credentials for the request
1777
+ * @param numAttempts Number of times to try to set the URL if AGO says that it updated the URL, but really didn't
1778
+ * @returns A promise that will resolve with the item id when the item has been updated or an AGO-style JSON failure
1779
+ * response
1780
+ * @private
1781
+ */
1782
+ export function _updateItemURL(id, url, authentication, numAttempts = 1) {
1783
+ // Introduce a lag because AGO update appears to choke with rapid subsequent calls
1784
+ const msLag = 1000;
1785
+ return new Promise((resolve, reject) => {
1786
+ // Update the item's URL
1787
+ const options = { item: { id, url }, authentication: authentication };
1788
+ portalUpdateItem(options).then(result => {
1789
+ if (!result.success) {
1790
+ reject(fail(result));
1791
+ }
1792
+ else {
1793
+ // Get the item to see if the URL really changed; we'll delay a bit before testing because AGO
1794
+ // has a timing problem with URL updates
1795
+ setTimeout(() => {
1796
+ getItem(id, { authentication: authentication }).then(item => {
1797
+ const iBrace = item.url.indexOf("{");
1798
+ if (iBrace > -1) {
1799
+ console.warn(id + " has template variable: " + item.url.substr(iBrace));
1800
+ }
1801
+ if (url === item.url) {
1802
+ resolve(id);
1803
+ }
1804
+ else {
1805
+ // If it fails, try again if we have sufficient attempts remaining
1806
+ const errorMsg = "URL not updated for " +
1807
+ item.type +
1808
+ " " +
1809
+ item.id +
1810
+ ": " +
1811
+ item.url +
1812
+ " (" +
1813
+ numAttempts +
1814
+ ")";
1815
+ if (--numAttempts > 0) {
1816
+ _updateItemURL(id, url, authentication, numAttempts).then(resolve, reject);
1817
+ }
1818
+ else {
1819
+ console.error(id + ": " + errorMsg + "; FAILED");
1820
+ reject(errorMsg);
1821
+ }
1822
+ }
1823
+ }, e => reject(fail(e)));
1824
+ }, msLag);
1825
+ }
1826
+ }, e => reject(fail(e)));
1827
+ });
1828
+ }
1824
1829
  //# sourceMappingURL=restHelpers.js.map