@esri/solution-common 4.1.2 → 5.0.0

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