@esri/solution-deployer 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.
- package/dist/cjs/deploySolutionFromTemplate.d.ts +48 -48
- package/dist/cjs/deploySolutionFromTemplate.js +331 -331
- package/dist/cjs/deploySolutionFromTemplate.js.map +1 -1
- package/dist/cjs/deploySolutionItems.d.ts +224 -224
- package/dist/cjs/deploySolutionItems.js +853 -849
- package/dist/cjs/deploySolutionItems.js.map +1 -1
- package/dist/cjs/deployer.d.ts +34 -34
- package/dist/cjs/deployer.js +101 -101
- package/dist/cjs/deployerUtils.d.ts +47 -47
- package/dist/cjs/deployerUtils.js +123 -123
- package/dist/cjs/helpers/post-process.d.ts +29 -29
- package/dist/cjs/helpers/post-process.js +61 -61
- package/dist/cjs/helpers/share-templates-to-groups.d.ts +24 -24
- package/dist/cjs/helpers/share-templates-to-groups.js +64 -64
- package/dist/cjs/helpers/sortTemplates.d.ts +23 -23
- package/dist/cjs/helpers/sortTemplates.js +14 -14
- package/dist/cjs/index.d.ts +24 -24
- package/dist/cjs/index.js +27 -27
- package/dist/cjs/module-map.d.ts +23 -23
- package/dist/cjs/module-map.js +195 -195
- package/dist/esm/deploySolutionFromTemplate.d.ts +48 -48
- package/dist/esm/deploySolutionFromTemplate.js +317 -317
- package/dist/esm/deploySolutionFromTemplate.js.map +1 -1
- package/dist/esm/deploySolutionItems.d.ts +224 -224
- package/dist/esm/deploySolutionItems.js +830 -826
- package/dist/esm/deploySolutionItems.js.map +1 -1
- package/dist/esm/deployer.d.ts +34 -34
- package/dist/esm/deployer.js +96 -96
- package/dist/esm/deployerUtils.d.ts +47 -47
- package/dist/esm/deployerUtils.js +115 -115
- package/dist/esm/helpers/post-process.d.ts +29 -29
- package/dist/esm/helpers/post-process.js +57 -57
- package/dist/esm/helpers/share-templates-to-groups.d.ts +24 -24
- package/dist/esm/helpers/share-templates-to-groups.js +60 -60
- package/dist/esm/helpers/sortTemplates.d.ts +23 -23
- package/dist/esm/helpers/sortTemplates.js +10 -10
- package/dist/esm/index.d.ts +24 -24
- package/dist/esm/index.js +24 -24
- package/dist/esm/module-map.d.ts +23 -23
- package/dist/esm/module-map.js +191 -191
- package/package.json +12 -12
|
@@ -1,332 +1,332 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/** @license
|
|
3
|
-
* Copyright 2018 Esri
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports._getNewItemId = exports._purgeTemplateProperties = exports._updateGroupReferences = exports._getPortalBaseUrl = exports._checkedReplaceAll = exports._updateProp = exports._replaceParamVariables = exports._applySourceToDeployOptions = exports._addSourceId = exports.deploySolutionFromTemplate = void 0;
|
|
19
|
-
const tslib_1 = require("tslib");
|
|
20
|
-
const common = tslib_1.__importStar(require("@esri/solution-common"));
|
|
21
|
-
const deployItems = tslib_1.__importStar(require("./deploySolutionItems"));
|
|
22
|
-
const hub_common_1 = require("@esri/hub-common");
|
|
23
|
-
const portal = tslib_1.__importStar(require("@esri/arcgis-rest-portal"));
|
|
24
|
-
const post_process_1 = require("./helpers/post-process");
|
|
25
|
-
const sortTemplates_1 = require("./helpers/sortTemplates");
|
|
26
|
-
const solution_common_1 = require("@esri/solution-common");
|
|
27
|
-
// NOTE: Moved to separate file to allow stubbing in main deploySolution tests
|
|
28
|
-
function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBase, solutionTemplateData, authentication, options) {
|
|
29
|
-
options.storageVersion = common.extractSolutionVersion(solutionTemplateData);
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
// It is possible to provide a separate authentication for the source
|
|
32
|
-
const storageAuthentication = options.storageAuthentication
|
|
33
|
-
? options.storageAuthentication
|
|
34
|
-
: authentication;
|
|
35
|
-
// Replacement dictionary and high-level deployment ids for cleanup
|
|
36
|
-
// TODO: Extract all templateDictionary prep into a separate function
|
|
37
|
-
const templateDictionary = options.templateDictionary ?? {};
|
|
38
|
-
let deployedFolderId;
|
|
39
|
-
let deployedSolutionId;
|
|
40
|
-
_applySourceToDeployOptions(options, solutionTemplateBase, templateDictionary, authentication);
|
|
41
|
-
if (options.additionalTypeKeywords) {
|
|
42
|
-
solutionTemplateBase.typeKeywords = [].concat(solutionTemplateBase.typeKeywords, options.additionalTypeKeywords);
|
|
43
|
-
}
|
|
44
|
-
// Get the thumbnail file
|
|
45
|
-
let thumbFilename = "thumbnail";
|
|
46
|
-
let thumbDef = Promise.resolve(null);
|
|
47
|
-
if (!options.thumbnail && options.thumbnailurl) {
|
|
48
|
-
// Figure out the thumbnail's filename
|
|
49
|
-
thumbFilename =
|
|
50
|
-
common.getFilenameFromUrl(options.thumbnailurl) || thumbFilename;
|
|
51
|
-
const thumbnailurl = common.appendQueryParam(options.thumbnailurl, "w=400");
|
|
52
|
-
delete options.thumbnailurl;
|
|
53
|
-
// Fetch the thumbnail
|
|
54
|
-
thumbDef = common.getBlobAsFile(thumbnailurl, thumbFilename, storageAuthentication, [400]);
|
|
55
|
-
}
|
|
56
|
-
_replaceParamVariables(solutionTemplateData, templateDictionary);
|
|
57
|
-
// Get information about deployment environment
|
|
58
|
-
Promise.all([
|
|
59
|
-
common.getPortal("", authentication),
|
|
60
|
-
common.getUser(authentication),
|
|
61
|
-
common.getFoldersAndGroups(authentication),
|
|
62
|
-
thumbDef
|
|
63
|
-
])
|
|
64
|
-
.then(responses => {
|
|
65
|
-
const [portalResponse, userResponse, foldersAndGroupsResponse, thumbnailFile] = responses;
|
|
66
|
-
if (!options.thumbnail && thumbnailFile) {
|
|
67
|
-
options.thumbnail = thumbnailFile;
|
|
68
|
-
}
|
|
69
|
-
// update template items with source-itemId type keyword
|
|
70
|
-
solutionTemplateData.templates = _addSourceId(solutionTemplateData.templates);
|
|
71
|
-
templateDictionary.isPortal = portalResponse.isPortal;
|
|
72
|
-
templateDictionary.organization = Object.assign(templateDictionary.organization || {}, portalResponse);
|
|
73
|
-
// TODO: Add more computed properties here
|
|
74
|
-
// portal: portalResponse
|
|
75
|
-
// orgextent as bbox for assignment onto items
|
|
76
|
-
// more info in #266 https://github.com/Esri/solution.js/issues/266
|
|
77
|
-
templateDictionary.portalBaseUrl = _getPortalBaseUrl(portalResponse, authentication);
|
|
78
|
-
templateDictionary.user = userResponse;
|
|
79
|
-
templateDictionary.user.folders = foldersAndGroupsResponse.folders;
|
|
80
|
-
templateDictionary.user.groups = foldersAndGroupsResponse.groups.filter((group) => group.owner === templateDictionary.user.username);
|
|
81
|
-
// if we have tracking views and the user is not admin or the org doesn't support tracking an error is thrown
|
|
82
|
-
common.setLocationTrackingEnabled(portalResponse, userResponse, templateDictionary, solutionTemplateData.templates);
|
|
83
|
-
const trackingOwnerPromise = common.getTackingServiceOwner(templateDictionary, authentication);
|
|
84
|
-
// Create a folder to hold the deployed solution. We use the solution name, appending a sequential
|
|
85
|
-
// suffix if the folder exists, e.g.,
|
|
86
|
-
// * Manage Right of Way Activities
|
|
87
|
-
// * Manage Right of Way Activities 1
|
|
88
|
-
// * Manage Right of Way Activities 2
|
|
89
|
-
const folderPromise = common.createUniqueFolder(solutionTemplateBase.title, templateDictionary, authentication);
|
|
90
|
-
// Apply the portal extents to the solution
|
|
91
|
-
const portalExtent = portalResponse.defaultExtent;
|
|
92
|
-
const extentsPromise = common.convertExtentWithFallback(portalExtent, undefined, { wkid: 4326 }, portalResponse.helperServices.geometry.url, authentication);
|
|
93
|
-
// Await completion of async actions: folder creation & extents conversion
|
|
94
|
-
return Promise.all([folderPromise, extentsPromise, trackingOwnerPromise]);
|
|
95
|
-
})
|
|
96
|
-
.then(responses => {
|
|
97
|
-
const [folderResponse, wgs84Extent, trackingOwnerResponse] = responses;
|
|
98
|
-
deployedFolderId = folderResponse.folder.id;
|
|
99
|
-
templateDictionary.folderId = deployedFolderId;
|
|
100
|
-
templateDictionary.solutionItemExtent =
|
|
101
|
-
wgs84Extent.xmin +
|
|
102
|
-
"," +
|
|
103
|
-
wgs84Extent.ymin +
|
|
104
|
-
"," +
|
|
105
|
-
wgs84Extent.xmax +
|
|
106
|
-
"," +
|
|
107
|
-
wgs84Extent.ymax;
|
|
108
|
-
// Hub Solutions depend on organization defaultExtentBBox as a nested array not a string
|
|
109
|
-
templateDictionary.organization.defaultExtentBBox = [
|
|
110
|
-
[wgs84Extent.xmin, wgs84Extent.ymin],
|
|
111
|
-
[wgs84Extent.xmax, wgs84Extent.ymax]
|
|
112
|
-
];
|
|
113
|
-
// update templateDictionary to indicate if the user owns the tracking service
|
|
114
|
-
// this will affect how we handle group sharing
|
|
115
|
-
/* istanbul ignore else */
|
|
116
|
-
if (templateDictionary.locationTrackingEnabled) {
|
|
117
|
-
(0, solution_common_1.setCreateProp)(templateDictionary, "locationTracking.userIsOwner", trackingOwnerResponse);
|
|
118
|
-
}
|
|
119
|
-
// Create a deployed Solution item
|
|
120
|
-
const createSolutionItemBase = {
|
|
121
|
-
...common.sanitizeJSON(solutionTemplateBase),
|
|
122
|
-
type: "Solution",
|
|
123
|
-
typeKeywords: ["Solution"]
|
|
124
|
-
};
|
|
125
|
-
if (options.additionalTypeKeywords) {
|
|
126
|
-
createSolutionItemBase.typeKeywords = ["Solution"].concat(options.additionalTypeKeywords);
|
|
127
|
-
}
|
|
128
|
-
// Create deployed solution item
|
|
129
|
-
createSolutionItemBase.thumbnail = options.thumbnail;
|
|
130
|
-
return common.createItemWithData(createSolutionItemBase, {}, authentication, deployedFolderId);
|
|
131
|
-
})
|
|
132
|
-
.then(createSolutionResponse => {
|
|
133
|
-
deployedSolutionId = createSolutionResponse.id;
|
|
134
|
-
// Protect the solution item
|
|
135
|
-
const protectOptions = {
|
|
136
|
-
id: deployedSolutionId,
|
|
137
|
-
authentication
|
|
138
|
-
};
|
|
139
|
-
return portal.protectItem(protectOptions);
|
|
140
|
-
})
|
|
141
|
-
.then(() => {
|
|
142
|
-
// TODO: Attach the whole solution model so we can
|
|
143
|
-
// have stuff like `{{solution.item.title}}
|
|
144
|
-
templateDictionary.solutionItemId = deployedSolutionId;
|
|
145
|
-
solutionTemplateBase.id = deployedSolutionId;
|
|
146
|
-
solutionTemplateBase.tryitUrl = _checkedReplaceAll(solutionTemplateBase.tryitUrl, templateSolutionId, deployedSolutionId);
|
|
147
|
-
solutionTemplateBase.url = _checkedReplaceAll(solutionTemplateBase.url, templateSolutionId, deployedSolutionId);
|
|
148
|
-
// Handle the contained item templates
|
|
149
|
-
return deployItems.deploySolutionItems(storageAuthentication.portal, templateSolutionId, solutionTemplateData.templates, storageAuthentication, templateDictionary, deployedSolutionId, authentication, options);
|
|
150
|
-
})
|
|
151
|
-
.then((clonedSolutionsResponse) => {
|
|
152
|
-
solutionTemplateData.templates = solutionTemplateData.templates.map((itemTemplate) => {
|
|
153
|
-
// Update ids present in template dictionary
|
|
154
|
-
itemTemplate.itemId = common.getProp(templateDictionary, `${itemTemplate.itemId}.itemId`);
|
|
155
|
-
// Update the dependencies hash to point to the new item ids
|
|
156
|
-
itemTemplate.dependencies = itemTemplate.dependencies.map((id) => (0, hub_common_1.getWithDefault)(templateDictionary, `${id}.itemId`, id));
|
|
157
|
-
return itemTemplate;
|
|
158
|
-
});
|
|
159
|
-
// Sort the templates into build order, which is provided by clonedSolutionsResponse
|
|
160
|
-
(0, sortTemplates_1.sortTemplates)(solutionTemplateData.templates, clonedSolutionsResponse.map(response => response.id));
|
|
161
|
-
// Wrap up with post-processing, in which we deal with groups and cycle remnants
|
|
162
|
-
return (0, post_process_1.postProcess)(deployedSolutionId, solutionTemplateData.templates, clonedSolutionsResponse, authentication, templateDictionary);
|
|
163
|
-
})
|
|
164
|
-
.then(() => {
|
|
165
|
-
// Update solution item using internal representation & and the updated data JSON
|
|
166
|
-
solutionTemplateBase.typeKeywords = [].concat(solutionTemplateBase.typeKeywords, ["Deployed"]);
|
|
167
|
-
const iTemplateKeyword = solutionTemplateBase.typeKeywords.indexOf("Template");
|
|
168
|
-
/* istanbul ignore else */
|
|
169
|
-
if (iTemplateKeyword >= 0) {
|
|
170
|
-
solutionTemplateBase.typeKeywords.splice(iTemplateKeyword, 1);
|
|
171
|
-
}
|
|
172
|
-
solutionTemplateData.templates = solutionTemplateData.templates.map((itemTemplate) => _purgeTemplateProperties(itemTemplate));
|
|
173
|
-
solutionTemplateData.templates = _updateGroupReferences(solutionTemplateData.templates, templateDictionary);
|
|
174
|
-
// Update solution items data using template dictionary, and then update the
|
|
175
|
-
// itemId & dependencies in each item template
|
|
176
|
-
solutionTemplateBase.data = common.replaceInTemplate(solutionTemplateData, templateDictionary);
|
|
177
|
-
// Write any user defined params to the solution
|
|
178
|
-
/* istanbul ignore else */
|
|
179
|
-
if (templateDictionary.params) {
|
|
180
|
-
solutionTemplateBase.data.params = templateDictionary.params;
|
|
181
|
-
}
|
|
182
|
-
return common.updateItem(solutionTemplateBase, authentication, deployedFolderId);
|
|
183
|
-
})
|
|
184
|
-
.then(() => resolve(solutionTemplateBase.id), reject);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
exports.deploySolutionFromTemplate = deploySolutionFromTemplate;
|
|
188
|
-
/**
|
|
189
|
-
* Add source-id to items/groups typeKeywords
|
|
190
|
-
*
|
|
191
|
-
* @param template the array of solution data templates
|
|
192
|
-
* @private
|
|
193
|
-
*/
|
|
194
|
-
function _addSourceId(templates) {
|
|
195
|
-
return templates.map((template) => {
|
|
196
|
-
/* istanbul ignore else */
|
|
197
|
-
if (template.item) {
|
|
198
|
-
const typeKeywords = template.item.typeKeywords || [];
|
|
199
|
-
typeKeywords.push("source-" + template.itemId);
|
|
200
|
-
template.item.typeKeywords = typeKeywords;
|
|
201
|
-
}
|
|
202
|
-
return template;
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
exports._addSourceId = _addSourceId;
|
|
206
|
-
/**
|
|
207
|
-
* Update the deployOptions with the group properties
|
|
208
|
-
*
|
|
209
|
-
* @param deployOptions
|
|
210
|
-
* @param sourceInfo
|
|
211
|
-
* @param authentication
|
|
212
|
-
* @param isGroup Boolean to indicate if the files are associated with a group or item
|
|
213
|
-
* @private
|
|
214
|
-
*/
|
|
215
|
-
function _applySourceToDeployOptions(deployOptions, solutionTemplateBase, templateDictionary, authentication) {
|
|
216
|
-
// Deploy a solution from the template's contents,
|
|
217
|
-
// using the template's information as defaults for the deployed solution item
|
|
218
|
-
["title", "snippet", "description", "tags"].forEach(prop => {
|
|
219
|
-
deployOptions[prop] = deployOptions[prop] ?? solutionTemplateBase[prop];
|
|
220
|
-
if (deployOptions[prop]) {
|
|
221
|
-
solutionTemplateBase[prop] = deployOptions[prop];
|
|
222
|
-
// carry these options forward on the templateDict
|
|
223
|
-
templateDictionary[prop] = deployOptions[prop];
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
if (!deployOptions.thumbnailurl && solutionTemplateBase.thumbnail) {
|
|
227
|
-
// Get the full path to the thumbnail
|
|
228
|
-
deployOptions.thumbnailurl = common.generateSourceThumbnailUrl(authentication.portal, solutionTemplateBase.id, solutionTemplateBase.thumbnail);
|
|
229
|
-
delete solutionTemplateBase.thumbnail;
|
|
230
|
-
}
|
|
231
|
-
return deployOptions;
|
|
232
|
-
}
|
|
233
|
-
exports._applySourceToDeployOptions = _applySourceToDeployOptions;
|
|
234
|
-
|
|
235
|
-
function _replaceParamVariables(solutionTemplateData, templateDictionary) {
|
|
236
|
-
// a custom params object can be passed in with the options to deploy a solution
|
|
237
|
-
// in most cases we can defer to the item type handlers to use these values
|
|
238
|
-
// for variable replacement
|
|
239
|
-
// for spatial reference specifically we need to replace up front so the default extent
|
|
240
|
-
// logic can execute as expected
|
|
241
|
-
solutionTemplateData.templates = solutionTemplateData.templates.map((template) => {
|
|
242
|
-
// can't do this as it causes other values that don't exist in the dict yet to revert to defaults they may have defined
|
|
243
|
-
// return common.replaceInTemplate(template, templateDictionary);
|
|
244
|
-
/* istanbul ignore else */
|
|
245
|
-
if (template.type === "Feature Service") {
|
|
246
|
-
const paramsLookup = "params.";
|
|
247
|
-
const wkidItemPath = "item.spatialReference.wkid";
|
|
248
|
-
template = _updateProp(template, wkidItemPath, paramsLookup, templateDictionary);
|
|
249
|
-
const wkidServicePath = "properties.service.spatialReference.wkid";
|
|
250
|
-
template = _updateProp(template, wkidServicePath, paramsLookup, templateDictionary);
|
|
251
|
-
}
|
|
252
|
-
return template;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
exports._replaceParamVariables = _replaceParamVariables;
|
|
256
|
-
|
|
257
|
-
function _updateProp(template, path, lookup, templateDictionary) {
|
|
258
|
-
const wkid = common.getProp(template, path);
|
|
259
|
-
/* istanbul ignore else */
|
|
260
|
-
if (wkid && typeof wkid === "string" && wkid.indexOf(lookup) > -1) {
|
|
261
|
-
common.setProp(template, path, common.replaceInTemplate(wkid, templateDictionary));
|
|
262
|
-
}
|
|
263
|
-
return template;
|
|
264
|
-
}
|
|
265
|
-
exports._updateProp = _updateProp;
|
|
266
|
-
|
|
267
|
-
function _checkedReplaceAll(template, oldValue, newValue) {
|
|
268
|
-
let newTemplate;
|
|
269
|
-
if (template && template.indexOf(oldValue) > -1) {
|
|
270
|
-
const re = new RegExp(oldValue, "g");
|
|
271
|
-
newTemplate = template.replace(re, newValue);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
newTemplate = template;
|
|
275
|
-
}
|
|
276
|
-
return newTemplate;
|
|
277
|
-
}
|
|
278
|
-
exports._checkedReplaceAll = _checkedReplaceAll;
|
|
279
|
-
|
|
280
|
-
function _getPortalBaseUrl(portalResponse, authentication) {
|
|
281
|
-
// As of Spring 2020, only HTTPS (see
|
|
282
|
-
// https://www.esri.com/arcgis-blog/products/product/administration/2019-arcgis-transport-security-improvements/)
|
|
283
|
-
const scheme = "https"; // portalResponse.allSSL ? "https" : "http";
|
|
284
|
-
const urlKey = common.getProp(portalResponse, "urlKey");
|
|
285
|
-
const customBaseUrl = common.getProp(portalResponse, "customBaseUrl");
|
|
286
|
-
const enterpriseBaseUrl = common.getProp(portalResponse, "portalHostname");
|
|
287
|
-
return urlKey && customBaseUrl
|
|
288
|
-
? `${scheme}://${urlKey}.${customBaseUrl}`
|
|
289
|
-
: enterpriseBaseUrl
|
|
290
|
-
? `${scheme}://${enterpriseBaseUrl}`
|
|
291
|
-
: authentication.portal.replace("/sharing/rest", "");
|
|
292
|
-
}
|
|
293
|
-
exports._getPortalBaseUrl = _getPortalBaseUrl;
|
|
294
|
-
|
|
295
|
-
function _updateGroupReferences(itemTemplates, templateDictionary) {
|
|
296
|
-
const groupIds = itemTemplates.reduce((result, t) => {
|
|
297
|
-
if (t.type === "Group") {
|
|
298
|
-
result.push(t.itemId);
|
|
299
|
-
}
|
|
300
|
-
return result;
|
|
301
|
-
}, []);
|
|
302
|
-
Object.keys(templateDictionary).forEach(k => {
|
|
303
|
-
const newId = templateDictionary[k].itemId;
|
|
304
|
-
if (groupIds.indexOf(newId) > -1) {
|
|
305
|
-
itemTemplates.forEach(t => {
|
|
306
|
-
t.groups = t.groups.map((id) => (id === k ? newId : id));
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
return itemTemplates;
|
|
311
|
-
}
|
|
312
|
-
exports._updateGroupReferences = _updateGroupReferences;
|
|
313
|
-
|
|
314
|
-
function _purgeTemplateProperties(itemTemplate) {
|
|
315
|
-
const retainProps = ["itemId", "type", "dependencies", "groups"];
|
|
316
|
-
const deleteProps = Object.keys(itemTemplate).filter(k => retainProps.indexOf(k) < 0);
|
|
317
|
-
common.deleteProps(itemTemplate, deleteProps);
|
|
318
|
-
return itemTemplate;
|
|
319
|
-
}
|
|
320
|
-
exports._purgeTemplateProperties = _purgeTemplateProperties;
|
|
321
|
-
/**
|
|
322
|
-
* Returns a match of a supplied id with the suffix ".itemId" in the template dictionary.
|
|
323
|
-
*
|
|
324
|
-
* @param id Id to look for
|
|
325
|
-
* @param templateDictionary Hash mapping property names to replacement values
|
|
326
|
-
* @returns Match in template dictionary or original id
|
|
327
|
-
*/
|
|
328
|
-
function _getNewItemId(id, templateDictionary) {
|
|
329
|
-
return common.getProp(templateDictionary, id + ".itemId") ?? id;
|
|
330
|
-
}
|
|
331
|
-
exports._getNewItemId = _getNewItemId;
|
|
1
|
+
"use strict";
|
|
2
|
+
/** @license
|
|
3
|
+
* Copyright 2018 Esri
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports._getNewItemId = exports._purgeTemplateProperties = exports._updateGroupReferences = exports._getPortalBaseUrl = exports._checkedReplaceAll = exports._updateProp = exports._replaceParamVariables = exports._applySourceToDeployOptions = exports._addSourceId = exports.deploySolutionFromTemplate = void 0;
|
|
19
|
+
const tslib_1 = require("tslib");
|
|
20
|
+
const common = tslib_1.__importStar(require("@esri/solution-common"));
|
|
21
|
+
const deployItems = tslib_1.__importStar(require("./deploySolutionItems"));
|
|
22
|
+
const hub_common_1 = require("@esri/hub-common");
|
|
23
|
+
const portal = tslib_1.__importStar(require("@esri/arcgis-rest-portal"));
|
|
24
|
+
const post_process_1 = require("./helpers/post-process");
|
|
25
|
+
const sortTemplates_1 = require("./helpers/sortTemplates");
|
|
26
|
+
const solution_common_1 = require("@esri/solution-common");
|
|
27
|
+
// NOTE: Moved to separate file to allow stubbing in main deploySolution tests
|
|
28
|
+
function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBase, solutionTemplateData, authentication, options) {
|
|
29
|
+
options.storageVersion = common.extractSolutionVersion(solutionTemplateData);
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
// It is possible to provide a separate authentication for the source
|
|
32
|
+
const storageAuthentication = options.storageAuthentication
|
|
33
|
+
? options.storageAuthentication
|
|
34
|
+
: authentication;
|
|
35
|
+
// Replacement dictionary and high-level deployment ids for cleanup
|
|
36
|
+
// TODO: Extract all templateDictionary prep into a separate function
|
|
37
|
+
const templateDictionary = options.templateDictionary ?? {};
|
|
38
|
+
let deployedFolderId;
|
|
39
|
+
let deployedSolutionId;
|
|
40
|
+
_applySourceToDeployOptions(options, solutionTemplateBase, templateDictionary, authentication);
|
|
41
|
+
if (options.additionalTypeKeywords) {
|
|
42
|
+
solutionTemplateBase.typeKeywords = [].concat(solutionTemplateBase.typeKeywords, options.additionalTypeKeywords);
|
|
43
|
+
}
|
|
44
|
+
// Get the thumbnail file
|
|
45
|
+
let thumbFilename = "thumbnail";
|
|
46
|
+
let thumbDef = Promise.resolve(null);
|
|
47
|
+
if (!options.thumbnail && options.thumbnailurl) {
|
|
48
|
+
// Figure out the thumbnail's filename
|
|
49
|
+
thumbFilename =
|
|
50
|
+
common.getFilenameFromUrl(options.thumbnailurl) || thumbFilename;
|
|
51
|
+
const thumbnailurl = common.appendQueryParam(options.thumbnailurl, "w=400");
|
|
52
|
+
delete options.thumbnailurl;
|
|
53
|
+
// Fetch the thumbnail
|
|
54
|
+
thumbDef = common.getBlobAsFile(thumbnailurl, thumbFilename, storageAuthentication, [400]);
|
|
55
|
+
}
|
|
56
|
+
_replaceParamVariables(solutionTemplateData, templateDictionary);
|
|
57
|
+
// Get information about deployment environment
|
|
58
|
+
Promise.all([
|
|
59
|
+
common.getPortal("", authentication),
|
|
60
|
+
common.getUser(authentication),
|
|
61
|
+
common.getFoldersAndGroups(authentication),
|
|
62
|
+
thumbDef
|
|
63
|
+
])
|
|
64
|
+
.then(responses => {
|
|
65
|
+
const [portalResponse, userResponse, foldersAndGroupsResponse, thumbnailFile] = responses;
|
|
66
|
+
if (!options.thumbnail && thumbnailFile) {
|
|
67
|
+
options.thumbnail = thumbnailFile;
|
|
68
|
+
}
|
|
69
|
+
// update template items with source-itemId type keyword
|
|
70
|
+
solutionTemplateData.templates = _addSourceId(solutionTemplateData.templates);
|
|
71
|
+
templateDictionary.isPortal = portalResponse.isPortal;
|
|
72
|
+
templateDictionary.organization = Object.assign(templateDictionary.organization || {}, portalResponse);
|
|
73
|
+
// TODO: Add more computed properties here
|
|
74
|
+
// portal: portalResponse
|
|
75
|
+
// orgextent as bbox for assignment onto items
|
|
76
|
+
// more info in #266 https://github.com/Esri/solution.js/issues/266
|
|
77
|
+
templateDictionary.portalBaseUrl = _getPortalBaseUrl(portalResponse, authentication);
|
|
78
|
+
templateDictionary.user = userResponse;
|
|
79
|
+
templateDictionary.user.folders = foldersAndGroupsResponse.folders;
|
|
80
|
+
templateDictionary.user.groups = foldersAndGroupsResponse.groups.filter((group) => group.owner === templateDictionary.user.username);
|
|
81
|
+
// if we have tracking views and the user is not admin or the org doesn't support tracking an error is thrown
|
|
82
|
+
common.setLocationTrackingEnabled(portalResponse, userResponse, templateDictionary, solutionTemplateData.templates);
|
|
83
|
+
const trackingOwnerPromise = common.getTackingServiceOwner(templateDictionary, authentication);
|
|
84
|
+
// Create a folder to hold the deployed solution. We use the solution name, appending a sequential
|
|
85
|
+
// suffix if the folder exists, e.g.,
|
|
86
|
+
// * Manage Right of Way Activities
|
|
87
|
+
// * Manage Right of Way Activities 1
|
|
88
|
+
// * Manage Right of Way Activities 2
|
|
89
|
+
const folderPromise = common.createUniqueFolder(solutionTemplateBase.title, templateDictionary, authentication);
|
|
90
|
+
// Apply the portal extents to the solution
|
|
91
|
+
const portalExtent = portalResponse.defaultExtent;
|
|
92
|
+
const extentsPromise = common.convertExtentWithFallback(portalExtent, undefined, { wkid: 4326 }, portalResponse.helperServices.geometry.url, authentication);
|
|
93
|
+
// Await completion of async actions: folder creation & extents conversion
|
|
94
|
+
return Promise.all([folderPromise, extentsPromise, trackingOwnerPromise]);
|
|
95
|
+
})
|
|
96
|
+
.then(responses => {
|
|
97
|
+
const [folderResponse, wgs84Extent, trackingOwnerResponse] = responses;
|
|
98
|
+
deployedFolderId = folderResponse.folder.id;
|
|
99
|
+
templateDictionary.folderId = deployedFolderId;
|
|
100
|
+
templateDictionary.solutionItemExtent =
|
|
101
|
+
wgs84Extent.xmin +
|
|
102
|
+
"," +
|
|
103
|
+
wgs84Extent.ymin +
|
|
104
|
+
"," +
|
|
105
|
+
wgs84Extent.xmax +
|
|
106
|
+
"," +
|
|
107
|
+
wgs84Extent.ymax;
|
|
108
|
+
// Hub Solutions depend on organization defaultExtentBBox as a nested array not a string
|
|
109
|
+
templateDictionary.organization.defaultExtentBBox = [
|
|
110
|
+
[wgs84Extent.xmin, wgs84Extent.ymin],
|
|
111
|
+
[wgs84Extent.xmax, wgs84Extent.ymax]
|
|
112
|
+
];
|
|
113
|
+
// update templateDictionary to indicate if the user owns the tracking service
|
|
114
|
+
// this will affect how we handle group sharing
|
|
115
|
+
/* istanbul ignore else */
|
|
116
|
+
if (templateDictionary.locationTrackingEnabled) {
|
|
117
|
+
(0, solution_common_1.setCreateProp)(templateDictionary, "locationTracking.userIsOwner", trackingOwnerResponse);
|
|
118
|
+
}
|
|
119
|
+
// Create a deployed Solution item
|
|
120
|
+
const createSolutionItemBase = {
|
|
121
|
+
...common.sanitizeJSON(solutionTemplateBase),
|
|
122
|
+
type: "Solution",
|
|
123
|
+
typeKeywords: ["Solution"]
|
|
124
|
+
};
|
|
125
|
+
if (options.additionalTypeKeywords) {
|
|
126
|
+
createSolutionItemBase.typeKeywords = ["Solution"].concat(options.additionalTypeKeywords);
|
|
127
|
+
}
|
|
128
|
+
// Create deployed solution item
|
|
129
|
+
createSolutionItemBase.thumbnail = options.thumbnail;
|
|
130
|
+
return common.createItemWithData(createSolutionItemBase, {}, authentication, deployedFolderId);
|
|
131
|
+
})
|
|
132
|
+
.then(createSolutionResponse => {
|
|
133
|
+
deployedSolutionId = createSolutionResponse.id;
|
|
134
|
+
// Protect the solution item
|
|
135
|
+
const protectOptions = {
|
|
136
|
+
id: deployedSolutionId,
|
|
137
|
+
authentication
|
|
138
|
+
};
|
|
139
|
+
return portal.protectItem(protectOptions);
|
|
140
|
+
})
|
|
141
|
+
.then(() => {
|
|
142
|
+
// TODO: Attach the whole solution model so we can
|
|
143
|
+
// have stuff like `{{solution.item.title}}
|
|
144
|
+
templateDictionary.solutionItemId = deployedSolutionId;
|
|
145
|
+
solutionTemplateBase.id = deployedSolutionId;
|
|
146
|
+
solutionTemplateBase.tryitUrl = _checkedReplaceAll(solutionTemplateBase.tryitUrl, templateSolutionId, deployedSolutionId);
|
|
147
|
+
solutionTemplateBase.url = _checkedReplaceAll(solutionTemplateBase.url, templateSolutionId, deployedSolutionId);
|
|
148
|
+
// Handle the contained item templates
|
|
149
|
+
return deployItems.deploySolutionItems(storageAuthentication.portal, templateSolutionId, solutionTemplateData.templates, storageAuthentication, templateDictionary, deployedSolutionId, authentication, options);
|
|
150
|
+
})
|
|
151
|
+
.then((clonedSolutionsResponse) => {
|
|
152
|
+
solutionTemplateData.templates = solutionTemplateData.templates.map((itemTemplate) => {
|
|
153
|
+
// Update ids present in template dictionary
|
|
154
|
+
itemTemplate.itemId = common.getProp(templateDictionary, `${itemTemplate.itemId}.itemId`);
|
|
155
|
+
// Update the dependencies hash to point to the new item ids
|
|
156
|
+
itemTemplate.dependencies = itemTemplate.dependencies.map((id) => (0, hub_common_1.getWithDefault)(templateDictionary, `${id}.itemId`, id));
|
|
157
|
+
return itemTemplate;
|
|
158
|
+
});
|
|
159
|
+
// Sort the templates into build order, which is provided by clonedSolutionsResponse
|
|
160
|
+
(0, sortTemplates_1.sortTemplates)(solutionTemplateData.templates, clonedSolutionsResponse.map(response => response.id));
|
|
161
|
+
// Wrap up with post-processing, in which we deal with groups and cycle remnants
|
|
162
|
+
return (0, post_process_1.postProcess)(deployedSolutionId, solutionTemplateData.templates, clonedSolutionsResponse, authentication, templateDictionary);
|
|
163
|
+
})
|
|
164
|
+
.then(() => {
|
|
165
|
+
// Update solution item using internal representation & and the updated data JSON
|
|
166
|
+
solutionTemplateBase.typeKeywords = [].concat(solutionTemplateBase.typeKeywords, ["Deployed"]);
|
|
167
|
+
const iTemplateKeyword = solutionTemplateBase.typeKeywords.indexOf("Template");
|
|
168
|
+
/* istanbul ignore else */
|
|
169
|
+
if (iTemplateKeyword >= 0) {
|
|
170
|
+
solutionTemplateBase.typeKeywords.splice(iTemplateKeyword, 1);
|
|
171
|
+
}
|
|
172
|
+
solutionTemplateData.templates = solutionTemplateData.templates.map((itemTemplate) => _purgeTemplateProperties(itemTemplate));
|
|
173
|
+
solutionTemplateData.templates = _updateGroupReferences(solutionTemplateData.templates, templateDictionary);
|
|
174
|
+
// Update solution items data using template dictionary, and then update the
|
|
175
|
+
// itemId & dependencies in each item template
|
|
176
|
+
solutionTemplateBase.data = common.replaceInTemplate(solutionTemplateData, templateDictionary);
|
|
177
|
+
// Write any user defined params to the solution
|
|
178
|
+
/* istanbul ignore else */
|
|
179
|
+
if (templateDictionary.params) {
|
|
180
|
+
solutionTemplateBase.data.params = templateDictionary.params;
|
|
181
|
+
}
|
|
182
|
+
return common.updateItem(solutionTemplateBase, authentication, deployedFolderId);
|
|
183
|
+
})
|
|
184
|
+
.then(() => resolve(solutionTemplateBase.id), reject);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
exports.deploySolutionFromTemplate = deploySolutionFromTemplate;
|
|
188
|
+
/**
|
|
189
|
+
* Add source-id to items/groups typeKeywords
|
|
190
|
+
*
|
|
191
|
+
* @param template the array of solution data templates
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
function _addSourceId(templates) {
|
|
195
|
+
return templates.map((template) => {
|
|
196
|
+
/* istanbul ignore else */
|
|
197
|
+
if (template.item) {
|
|
198
|
+
const typeKeywords = template.item.typeKeywords || [];
|
|
199
|
+
typeKeywords.push("source-" + template.itemId);
|
|
200
|
+
template.item.typeKeywords = typeKeywords;
|
|
201
|
+
}
|
|
202
|
+
return template;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
exports._addSourceId = _addSourceId;
|
|
206
|
+
/**
|
|
207
|
+
* Update the deployOptions with the group properties
|
|
208
|
+
*
|
|
209
|
+
* @param deployOptions
|
|
210
|
+
* @param sourceInfo
|
|
211
|
+
* @param authentication
|
|
212
|
+
* @param isGroup Boolean to indicate if the files are associated with a group or item
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
function _applySourceToDeployOptions(deployOptions, solutionTemplateBase, templateDictionary, authentication) {
|
|
216
|
+
// Deploy a solution from the template's contents,
|
|
217
|
+
// using the template's information as defaults for the deployed solution item
|
|
218
|
+
["title", "snippet", "description", "tags"].forEach(prop => {
|
|
219
|
+
deployOptions[prop] = deployOptions[prop] ?? solutionTemplateBase[prop];
|
|
220
|
+
if (deployOptions[prop]) {
|
|
221
|
+
solutionTemplateBase[prop] = deployOptions[prop];
|
|
222
|
+
// carry these options forward on the templateDict
|
|
223
|
+
templateDictionary[prop] = deployOptions[prop];
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
if (!deployOptions.thumbnailurl && solutionTemplateBase.thumbnail) {
|
|
227
|
+
// Get the full path to the thumbnail
|
|
228
|
+
deployOptions.thumbnailurl = common.generateSourceThumbnailUrl(authentication.portal, solutionTemplateBase.id, solutionTemplateBase.thumbnail);
|
|
229
|
+
delete solutionTemplateBase.thumbnail;
|
|
230
|
+
}
|
|
231
|
+
return deployOptions;
|
|
232
|
+
}
|
|
233
|
+
exports._applySourceToDeployOptions = _applySourceToDeployOptions;
|
|
234
|
+
//TODO: function doc
|
|
235
|
+
function _replaceParamVariables(solutionTemplateData, templateDictionary) {
|
|
236
|
+
// a custom params object can be passed in with the options to deploy a solution
|
|
237
|
+
// in most cases we can defer to the item type handlers to use these values
|
|
238
|
+
// for variable replacement
|
|
239
|
+
// for spatial reference specifically we need to replace up front so the default extent
|
|
240
|
+
// logic can execute as expected
|
|
241
|
+
solutionTemplateData.templates = solutionTemplateData.templates.map((template) => {
|
|
242
|
+
// can't do this as it causes other values that don't exist in the dict yet to revert to defaults they may have defined
|
|
243
|
+
// return common.replaceInTemplate(template, templateDictionary);
|
|
244
|
+
/* istanbul ignore else */
|
|
245
|
+
if (template.type === "Feature Service") {
|
|
246
|
+
const paramsLookup = "params.";
|
|
247
|
+
const wkidItemPath = "item.spatialReference.wkid";
|
|
248
|
+
template = _updateProp(template, wkidItemPath, paramsLookup, templateDictionary);
|
|
249
|
+
const wkidServicePath = "properties.service.spatialReference.wkid";
|
|
250
|
+
template = _updateProp(template, wkidServicePath, paramsLookup, templateDictionary);
|
|
251
|
+
}
|
|
252
|
+
return template;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
exports._replaceParamVariables = _replaceParamVariables;
|
|
256
|
+
//TODO: function doc
|
|
257
|
+
function _updateProp(template, path, lookup, templateDictionary) {
|
|
258
|
+
const wkid = common.getProp(template, path);
|
|
259
|
+
/* istanbul ignore else */
|
|
260
|
+
if (wkid && typeof wkid === "string" && wkid.indexOf(lookup) > -1) {
|
|
261
|
+
common.setProp(template, path, common.replaceInTemplate(wkid, templateDictionary));
|
|
262
|
+
}
|
|
263
|
+
return template;
|
|
264
|
+
}
|
|
265
|
+
exports._updateProp = _updateProp;
|
|
266
|
+
//TODO: function doc
|
|
267
|
+
function _checkedReplaceAll(template, oldValue, newValue) {
|
|
268
|
+
let newTemplate;
|
|
269
|
+
if (template && template.indexOf(oldValue) > -1) {
|
|
270
|
+
const re = new RegExp(oldValue, "g");
|
|
271
|
+
newTemplate = template.replace(re, newValue);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
newTemplate = template;
|
|
275
|
+
}
|
|
276
|
+
return newTemplate;
|
|
277
|
+
}
|
|
278
|
+
exports._checkedReplaceAll = _checkedReplaceAll;
|
|
279
|
+
//TODO: function doc
|
|
280
|
+
function _getPortalBaseUrl(portalResponse, authentication) {
|
|
281
|
+
// As of Spring 2020, only HTTPS (see
|
|
282
|
+
// https://www.esri.com/arcgis-blog/products/product/administration/2019-arcgis-transport-security-improvements/)
|
|
283
|
+
const scheme = "https"; // portalResponse.allSSL ? "https" : "http";
|
|
284
|
+
const urlKey = common.getProp(portalResponse, "urlKey");
|
|
285
|
+
const customBaseUrl = common.getProp(portalResponse, "customBaseUrl");
|
|
286
|
+
const enterpriseBaseUrl = common.getProp(portalResponse, "portalHostname");
|
|
287
|
+
return urlKey && customBaseUrl
|
|
288
|
+
? `${scheme}://${urlKey}.${customBaseUrl}`
|
|
289
|
+
: enterpriseBaseUrl
|
|
290
|
+
? `${scheme}://${enterpriseBaseUrl}`
|
|
291
|
+
: authentication.portal.replace("/sharing/rest", "");
|
|
292
|
+
}
|
|
293
|
+
exports._getPortalBaseUrl = _getPortalBaseUrl;
|
|
294
|
+
//TODO: function doc
|
|
295
|
+
function _updateGroupReferences(itemTemplates, templateDictionary) {
|
|
296
|
+
const groupIds = itemTemplates.reduce((result, t) => {
|
|
297
|
+
if (t.type === "Group") {
|
|
298
|
+
result.push(t.itemId);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}, []);
|
|
302
|
+
Object.keys(templateDictionary).forEach(k => {
|
|
303
|
+
const newId = templateDictionary[k].itemId;
|
|
304
|
+
if (groupIds.indexOf(newId) > -1) {
|
|
305
|
+
itemTemplates.forEach(t => {
|
|
306
|
+
t.groups = t.groups.map((id) => (id === k ? newId : id));
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
return itemTemplates;
|
|
311
|
+
}
|
|
312
|
+
exports._updateGroupReferences = _updateGroupReferences;
|
|
313
|
+
//TODO: function doc
|
|
314
|
+
function _purgeTemplateProperties(itemTemplate) {
|
|
315
|
+
const retainProps = ["itemId", "type", "dependencies", "groups"];
|
|
316
|
+
const deleteProps = Object.keys(itemTemplate).filter(k => retainProps.indexOf(k) < 0);
|
|
317
|
+
common.deleteProps(itemTemplate, deleteProps);
|
|
318
|
+
return itemTemplate;
|
|
319
|
+
}
|
|
320
|
+
exports._purgeTemplateProperties = _purgeTemplateProperties;
|
|
321
|
+
/**
|
|
322
|
+
* Returns a match of a supplied id with the suffix ".itemId" in the template dictionary.
|
|
323
|
+
*
|
|
324
|
+
* @param id Id to look for
|
|
325
|
+
* @param templateDictionary Hash mapping property names to replacement values
|
|
326
|
+
* @returns Match in template dictionary or original id
|
|
327
|
+
*/
|
|
328
|
+
function _getNewItemId(id, templateDictionary) {
|
|
329
|
+
return common.getProp(templateDictionary, id + ".itemId") ?? id;
|
|
330
|
+
}
|
|
331
|
+
exports._getNewItemId = _getNewItemId;
|
|
332
332
|
//# sourceMappingURL=deploySolutionFromTemplate.js.map
|