@esri/solution-creator 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.
@@ -1,449 +1,449 @@
1
- /** @license
2
- * Copyright 2020 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
- import { SolutionTemplateFormatVersion, EItemProgressStatus, failWithIds, getIDs, getPortal, getTemplateById, globalStringReplace, isWorkforceProject, removeTemplate, replaceInTemplate, SItemProgressStatus, copyFilesToStorageItem, postProcessWorkforceTemplates, UNREACHABLE, updateItem } from "@esri/solution-common";
17
- import { getProp, getWithDefault } from "@esri/hub-common";
18
- import { createItemTemplate, postProcessFieldReferences } from "../createItemTemplate";
19
- import { getDataFilesFromTemplates, removeDataFilesFromTemplates } from "./template";
20
- /**
21
- * Adds a list of AGO item ids to a solution item.
22
- *
23
- * @param solutionItemId AGO id of solution to receive items
24
- * @param options Customizations for creating the solution
25
- * @param srcAuthentication Credentials for requests to source items
26
- * @param destAuthentication Credentials for the requests to destination solution
27
- * @returns A promise that resolves with the AGO id of the updated solution
28
- * @internal
29
- */
30
- export function addContentToSolution(solutionItemId, options, srcAuthentication, destAuthentication) {
31
- return new Promise((resolve, reject) => {
32
- if (!options.itemIds || options.itemIds.length === 0) {
33
- resolve(solutionItemId);
34
- return;
35
- }
36
- // Prepare feedback mechanism
37
- let totalEstimatedCost = 2 * options.itemIds.length + 1; // solution items, plus avoid divide by 0
38
- let percentDone = 16; // allow for previous creation work
39
- let progressPercentStep = (95 - percentDone) / totalEstimatedCost; // leave some % for caller for wrapup
40
- const failedItemIds = [];
41
- let totalExpended = 0;
42
- let statusOK = true;
43
- const itemProgressCallback = (itemId, status, costUsed) => {
44
- // ---------------------------------------------------------------------------------------------------------------
45
- if (options.itemIds.indexOf(itemId) < 0) {
46
- // New item--a dependency that wasn't in the supplied list of itemIds; add it to the list
47
- // and recalculate the progress percent step based on how much progress remains to be done
48
- options.itemIds.push(itemId);
49
- totalEstimatedCost += 2;
50
- progressPercentStep =
51
- (95 - percentDone) / (totalEstimatedCost - totalExpended);
52
- }
53
- totalExpended += costUsed;
54
- percentDone += progressPercentStep * costUsed;
55
- if (options.progressCallback) {
56
- options.progressCallback(Math.round(percentDone), options.jobId);
57
- }
58
- /* istanbul ignore if */
59
- if (options.consoleProgress) {
60
- console.log(Date.now(), itemId, options.jobId ?? "", SItemProgressStatus[status], percentDone.toFixed(0) + "%", costUsed);
61
- }
62
- if (status === EItemProgressStatus.Failed) {
63
- let error = "";
64
- solutionTemplates.some(t => {
65
- /* istanbul ignore else */
66
- if (t.itemId === itemId) {
67
- /* istanbul ignore else */
68
- if (getProp(t, "properties.error")) {
69
- error = t.properties.error;
70
- try {
71
- // parse for better console logging if we can
72
- error = JSON.parse(error);
73
- }
74
- catch (e) {
75
- /* istanbul ignore next */
76
- // do nothing and show the error as is
77
- }
78
- }
79
- return true;
80
- }
81
- });
82
- removeTemplate(solutionTemplates, itemId);
83
- if (failedItemIds.indexOf(itemId) < 0) {
84
- failedItemIds.push(itemId);
85
- }
86
- statusOK = false;
87
- }
88
- else if (status === EItemProgressStatus.Ignored) {
89
- removeTemplate(solutionTemplates, itemId);
90
- }
91
- return statusOK;
92
- // ---------------------------------------------------------------------------------------------------------------
93
- };
94
- // Replacement dictionary and created templates
95
- const templateDictionary = options.templateDictionary ?? {};
96
- let solutionTemplates = [];
97
- // Handle a list of one or more AGO ids by stepping through the list
98
- // and calling this function recursively
99
- const getItemsPromise = [];
100
- options.itemIds.forEach(itemId => {
101
- const createDef = createItemTemplate(solutionItemId, itemId, templateDictionary, srcAuthentication, destAuthentication, solutionTemplates, itemProgressCallback);
102
- getItemsPromise.push(createDef);
103
- });
104
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
105
- Promise.all(getItemsPromise).then((multipleResourceItemFiles) => {
106
- if (failedItemIds.length > 0) {
107
- reject(failWithIds(failedItemIds, "One or more items cannot be converted into templates"));
108
- }
109
- else {
110
- if (solutionTemplates.length > 0) {
111
- // Coalesce the resource file paths from the created templates
112
- let resourceItemFiles = multipleResourceItemFiles.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
113
- // Extract resource data files from templates
114
- resourceItemFiles = resourceItemFiles.concat(getDataFilesFromTemplates(solutionTemplates));
115
- // test for and update group dependencies and other post-processing
116
- solutionTemplates = _postProcessGroupDependencies(solutionTemplates);
117
- solutionTemplates = postProcessWorkforceTemplates(solutionTemplates);
118
- // Filter out any resources from items that have been removed from the templates, such as
119
- // Living Atlas layers
120
- solutionTemplates = _postProcessIgnoredItems(solutionTemplates, templateDictionary);
121
- const templateIds = solutionTemplates.map(template => template.itemId);
122
- // Coalesce the resource file paths from the created templates
123
- resourceItemFiles = resourceItemFiles.filter(file => templateIds.includes(file.itemId));
124
- // Send the accumulated resources to the solution item
125
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
126
- copyFilesToStorageItem(resourceItemFiles, solutionItemId, destAuthentication).then(() => {
127
- // Remove data files from templates--no longer needed
128
- removeDataFilesFromTemplates(solutionTemplates);
129
- _templatizeSolutionIds(solutionTemplates);
130
- _simplifyUrlsInItemDescriptions(solutionTemplates);
131
- solutionTemplates.forEach(template => {
132
- if (template.type !== "Vector Tile Service") {
133
- _replaceDictionaryItemsInObject(templateDictionary, template);
134
- }
135
- });
136
- _templatizeOrgUrl(solutionTemplates, destAuthentication).then(solutionTemplates2 => {
137
- // Update solution item with its data JSON
138
- const solutionData = {
139
- metadata: { version: SolutionTemplateFormatVersion },
140
- templates: options.templatizeFields
141
- ? postProcessFieldReferences(solutionTemplates2)
142
- : solutionTemplates2
143
- };
144
- const itemInfo = {
145
- id: solutionItemId,
146
- text: solutionData
147
- };
148
- updateItem(itemInfo, destAuthentication).then(() => {
149
- resolve(solutionItemId);
150
- }, reject);
151
- }, reject);
152
- });
153
- }
154
- else {
155
- resolve(solutionItemId);
156
- }
157
- }
158
- });
159
- });
160
- }
161
- // ------------------------------------------------------------------------------------------------------------------ //
162
- /**
163
- * Gets the dependencies of an item by merging its dependencies list with item references in template variables.
164
- *
165
- * @param template Template to examine
166
- * @returns List of dependency ids
167
- * @private
168
- */
169
- export function _getDependencies(template) {
170
- // Get all dependencies
171
- let deps = template.dependencies.concat(_getIdsOutOfTemplateVariables(_getTemplateVariables(JSON.stringify(template.data))));
172
- // Remove duplicates and self-references
173
- deps.sort();
174
- deps = deps.filter((elem, index, array) => {
175
- if (elem === template.itemId) {
176
- return false;
177
- }
178
- else if (index > 0) {
179
- return elem !== array[index - 1];
180
- }
181
- else {
182
- return true;
183
- }
184
- });
185
- return deps;
186
- }
187
- /**
188
- * Extracts AGO ids out of template variables.
189
- *
190
- * @param variables List of template variables to examine
191
- * @returns List of AGO ids referenced in `variables`
192
- * @private
193
- */
194
- export function _getIdsOutOfTemplateVariables(variables) {
195
- return variables
196
- .map(variable => {
197
- const idList = variable.match(/[0-9A-F]{32}/i); // is it a guid?
198
- if (idList) {
199
- return idList[0];
200
- }
201
- else {
202
- return null;
203
- }
204
- })
205
- .filter(variable => !!variable);
206
- }
207
- /**
208
- * Creates a list of item URLs.
209
- *
210
- * @param templates Templates to check for URLs
211
- * @returns List of URLs
212
- * @private
213
- */
214
- export function _getSolutionItemUrls(templates) {
215
- const solutionUrls = [];
216
- templates.forEach(template => {
217
- /* istanbul ignore else */
218
- if (template.item.origUrl) {
219
- solutionUrls.push([template.itemId, template.item.origUrl]);
220
- }
221
- });
222
- return solutionUrls;
223
- }
224
- /**
225
- * Extracts template variables out of a string.
226
- *
227
- * @param text String to examine
228
- * @returns List of template variables found in string
229
- * @private
230
- */
231
- export function _getTemplateVariables(text) {
232
- return (text.match(/{{[a-z0-9.]*}}/gi) || []) // find variable
233
- .map(variable => variable.substring(2, variable.length - 2)); // remove "{{" & "}}"
234
- }
235
- /**
236
- * Update the items dependencies and groups arrays
237
- *
238
- * @param templates The array of templates to evaluate
239
- * @returns Updated version of the templates
240
- * @private
241
- */
242
- export function _postProcessGroupDependencies(templates) {
243
- return templates.map((template) => {
244
- if (template.type === "Group") {
245
- const id = template.itemId;
246
- // remove group dependencies if we find a circular dependency with one of its items
247
- let removeDependencies = false;
248
- // before we remove update each dependants groups array
249
- template.dependencies.forEach(dependencyId => {
250
- const dependantTemplate = getTemplateById(templates, dependencyId);
251
- // Not all items shared to the group will exist in the templates array
252
- // i.e. Hub Initiative items or any other unsupported types
253
- if (dependantTemplate) {
254
- // check if the group is in the dependantTemplate's list of dependencies
255
- const gIndex = getWithDefault(dependantTemplate, "dependencies", []).indexOf(id);
256
- /* istanbul ignore else */
257
- if (gIndex > -1) {
258
- removeDependencies = true;
259
- }
260
- // if the dependant template does not have the group id
261
- // in it's groups array, add it
262
- const groups = getWithDefault(dependantTemplate, "groups", []);
263
- if (groups.indexOf(id) === -1) {
264
- groups.push(id);
265
- dependantTemplate.groups = groups;
266
- }
267
- }
268
- });
269
- if (removeDependencies) {
270
- template.dependencies = [];
271
- }
272
- }
273
- return template;
274
- });
275
- }
276
- /**
277
- * Check for feature service items that have been flagged for invalid designations.
278
- * Remove templates that have invalid designations from the solution item and other item dependencies.
279
- * Clean up any references to items with invalid designations in the other templates.
280
- *
281
- * @param templates The array of templates to evaluate
282
- * @param templateDictionary Hash of key details used for variable replacement
283
- * @returns Updated version of the templates
284
- * @private
285
- */
286
- export function _postProcessIgnoredItems(templates, templateDictionary) {
287
- // replace in template
288
- const updateDictionary = templates.reduce((result, template) => {
289
- const invalidDes = template.properties.hasInvalidDesignations;
290
- const unreachableVal = getProp(templateDictionary, `${UNREACHABLE}.${template.itemId}`);
291
- if (invalidDes && unreachableVal && Object.keys(template.data).length < 1) {
292
- template.data[template.itemId] = unreachableVal;
293
- }
294
- return invalidDes ? Object.assign(result, template.data) : result;
295
- }, {});
296
- Object.keys(updateDictionary).forEach(k => {
297
- removeTemplate(templates, k);
298
- templates = templates.map(t => {
299
- t.dependencies = t.dependencies.filter(id => id !== k);
300
- return replaceInTemplate(t, updateDictionary);
301
- });
302
- });
303
- return templates;
304
- }
305
- /**
306
- * Recursively runs through an object to find and replace any strings found in a dictionary.
307
- *
308
- * @param templateDictionary Hash of things to be replaced
309
- * @param obj Object to be examined
310
- * @private
311
- */
312
- export function _replaceDictionaryItemsInObject(hash, obj) {
313
- /* istanbul ignore else */
314
- if (obj) {
315
- Object.keys(obj).forEach(prop => {
316
- const propObj = obj[prop];
317
- if (propObj) {
318
- if (typeof propObj === "object") {
319
- _replaceDictionaryItemsInObject(hash, propObj);
320
- }
321
- else if (typeof propObj === "string") {
322
- obj[prop] = hash[propObj] || propObj;
323
- }
324
- }
325
- });
326
- }
327
- return obj;
328
- }
329
- /**
330
- * Recursively runs through an object to find and templatize any remaining references to solution's items.
331
- *
332
- * @param ids Ids to be replaced in strings found in object
333
- * @param obj Object to be examined
334
- * @private
335
- */
336
- export function _replaceRemainingIdsInObject(ids, obj) {
337
- /* istanbul ignore else */
338
- if (obj) {
339
- Object.keys(obj).forEach(prop => {
340
- const propObj = obj[prop];
341
- if (propObj) {
342
- if (typeof propObj === "object") {
343
- _replaceRemainingIdsInObject(ids, propObj);
344
- }
345
- else if (typeof propObj === "string") {
346
- obj[prop] = _replaceRemainingIdsInString(ids, propObj);
347
- }
348
- }
349
- });
350
- }
351
- return obj;
352
- }
353
- /**
354
- * Templatizes ids from a list in a string if they're not already templatized.
355
- *
356
- * @param ids Ids to be replaced in source string
357
- * @param str Source string to be examined
358
- * @returns A copy of the source string with any templatization changes
359
- * @private
360
- */
361
- export function _replaceRemainingIdsInString(ids, str) {
362
- let updatedStr = str;
363
- const untemplatizedIds = getIDs(str);
364
- if (untemplatizedIds.length > 0) {
365
- untemplatizedIds.forEach(id => {
366
- if (ids.includes(id)) {
367
- const re = new RegExp("({*)" + id, "gi");
368
- updatedStr = updatedStr.replace(re, match => match.indexOf("{{") < 0
369
- ? "{{" + id.replace("{", "") + ".itemId}}"
370
- : match);
371
- }
372
- });
373
- }
374
- return updatedStr;
375
- }
376
- /**
377
- * Finds and templatizes any URLs in solution items' descriptions.
378
- *
379
- * @param templates The array of templates to evaluate, modified in place
380
- * @private
381
- */
382
- export function _simplifyUrlsInItemDescriptions(templates) {
383
- // Get the urls in the solution along with their item ids & convert the id into the form
384
- // "{{fcb2bf2837a6404ebb418a1f805f976a.url}}"
385
- const solutionUrls = _getSolutionItemUrls(templates).map(idUrl => [
386
- "{{" + idUrl[0] + ".url}}",
387
- idUrl[1]
388
- ]);
389
- /* istanbul ignore else */
390
- if (solutionUrls.length > 0) {
391
- // Make the replacements
392
- templates.forEach(template => {
393
- solutionUrls.forEach(
394
- // TypeScript for es2015 doesn't have a definition for `replaceAll`
395
- idUrl => {
396
- /* istanbul ignore else */
397
- if (template.item.description) {
398
- template.item.description = template.item
399
- .description.replaceAll(idUrl[1], idUrl[0]);
400
- }
401
- });
402
- });
403
- }
404
- }
405
- /**
406
- * Templatizes occurrences of the URL to the user's organization in the `item` and `data` template sections.
407
- *
408
- * @param templates The array of templates to evaluate; templates is modified in place
409
- * @param destAuthentication Credentials for request organization info
410
- * @returns Promise resolving with `templates`
411
- * @private
412
- */
413
- export function _templatizeOrgUrl(templates, destAuthentication) {
414
- return new Promise((resolve, reject) => {
415
- // Get the org's URL
416
- getPortal(null, destAuthentication).then(org => {
417
- const orgUrl = "https://" + org.urlKey + "." + org.customBaseUrl;
418
- const templatizedOrgUrl = "{{portalBaseUrl}}";
419
- // Cycle through each of the items in the template and scan the `item` and `data` sections of each for replacements
420
- templates.forEach((template) => {
421
- globalStringReplace(template.item, new RegExp(orgUrl, "gi"), templatizedOrgUrl);
422
- globalStringReplace(template.data, new RegExp(orgUrl, "gi"), templatizedOrgUrl);
423
- });
424
- resolve(templates);
425
- }, reject);
426
- });
427
- }
428
- /**
429
- * Finds and templatizes any references to solution's items.
430
- *
431
- * @param templates The array of templates to evaluate, modified in place
432
- * @private
433
- */
434
- export function _templatizeSolutionIds(templates) {
435
- // Get the ids in the solution
436
- const solutionIds = templates.map((template) => template.itemId);
437
- // Cycle through each of the items in the template and
438
- // 1. templatize untemplatized ids in our solution in the `item` and `data` sections;
439
- // 2. update the `dependencies` section
440
- templates.forEach((template) => {
441
- _replaceRemainingIdsInObject(solutionIds, template.item);
442
- _replaceRemainingIdsInObject(solutionIds, template.data);
443
- /* istanbul ignore else */
444
- if (template.type !== "Group" && !isWorkforceProject(template)) {
445
- template.dependencies = _getDependencies(template);
446
- }
447
- });
448
- }
1
+ /** @license
2
+ * Copyright 2020 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
+ import { SolutionTemplateFormatVersion, EItemProgressStatus, failWithIds, getIDs, getPortal, getTemplateById, globalStringReplace, isWorkforceProject, removeTemplate, replaceInTemplate, SItemProgressStatus, copyFilesToStorageItem, postProcessWorkforceTemplates, UNREACHABLE, updateItem } from "@esri/solution-common";
17
+ import { getProp, getWithDefault } from "@esri/hub-common";
18
+ import { createItemTemplate, postProcessFieldReferences } from "../createItemTemplate";
19
+ import { getDataFilesFromTemplates, removeDataFilesFromTemplates } from "./template";
20
+ /**
21
+ * Adds a list of AGO item ids to a solution item.
22
+ *
23
+ * @param solutionItemId AGO id of solution to receive items
24
+ * @param options Customizations for creating the solution
25
+ * @param srcAuthentication Credentials for requests to source items
26
+ * @param destAuthentication Credentials for the requests to destination solution
27
+ * @returns A promise that resolves with the AGO id of the updated solution
28
+ * @internal
29
+ */
30
+ export function addContentToSolution(solutionItemId, options, srcAuthentication, destAuthentication) {
31
+ return new Promise((resolve, reject) => {
32
+ if (!options.itemIds || options.itemIds.length === 0) {
33
+ resolve(solutionItemId);
34
+ return;
35
+ }
36
+ // Prepare feedback mechanism
37
+ let totalEstimatedCost = 2 * options.itemIds.length + 1; // solution items, plus avoid divide by 0
38
+ let percentDone = 16; // allow for previous creation work
39
+ let progressPercentStep = (95 - percentDone) / totalEstimatedCost; // leave some % for caller for wrapup
40
+ const failedItemIds = [];
41
+ let totalExpended = 0;
42
+ let statusOK = true;
43
+ const itemProgressCallback = (itemId, status, costUsed) => {
44
+ // ---------------------------------------------------------------------------------------------------------------
45
+ if (options.itemIds.indexOf(itemId) < 0) {
46
+ // New item--a dependency that wasn't in the supplied list of itemIds; add it to the list
47
+ // and recalculate the progress percent step based on how much progress remains to be done
48
+ options.itemIds.push(itemId);
49
+ totalEstimatedCost += 2;
50
+ progressPercentStep =
51
+ (95 - percentDone) / (totalEstimatedCost - totalExpended);
52
+ }
53
+ totalExpended += costUsed;
54
+ percentDone += progressPercentStep * costUsed;
55
+ if (options.progressCallback) {
56
+ options.progressCallback(Math.round(percentDone), options.jobId);
57
+ }
58
+ /* istanbul ignore if */
59
+ if (options.consoleProgress) {
60
+ console.log(Date.now(), itemId, options.jobId ?? "", SItemProgressStatus[status], percentDone.toFixed(0) + "%", costUsed);
61
+ }
62
+ if (status === EItemProgressStatus.Failed) {
63
+ let error = "";
64
+ solutionTemplates.some(t => {
65
+ /* istanbul ignore else */
66
+ if (t.itemId === itemId) {
67
+ /* istanbul ignore else */
68
+ if (getProp(t, "properties.error")) {
69
+ error = t.properties.error;
70
+ try {
71
+ // parse for better console logging if we can
72
+ error = JSON.parse(error);
73
+ }
74
+ catch (e) {
75
+ /* istanbul ignore next */
76
+ // do nothing and show the error as is
77
+ }
78
+ }
79
+ return true;
80
+ }
81
+ });
82
+ removeTemplate(solutionTemplates, itemId);
83
+ if (failedItemIds.indexOf(itemId) < 0) {
84
+ failedItemIds.push(itemId);
85
+ }
86
+ statusOK = false;
87
+ }
88
+ else if (status === EItemProgressStatus.Ignored) {
89
+ removeTemplate(solutionTemplates, itemId);
90
+ }
91
+ return statusOK;
92
+ // ---------------------------------------------------------------------------------------------------------------
93
+ };
94
+ // Replacement dictionary and created templates
95
+ const templateDictionary = options.templateDictionary ?? {};
96
+ let solutionTemplates = [];
97
+ // Handle a list of one or more AGO ids by stepping through the list
98
+ // and calling this function recursively
99
+ const getItemsPromise = [];
100
+ options.itemIds.forEach(itemId => {
101
+ const createDef = createItemTemplate(solutionItemId, itemId, templateDictionary, srcAuthentication, destAuthentication, solutionTemplates, itemProgressCallback);
102
+ getItemsPromise.push(createDef);
103
+ });
104
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
105
+ Promise.all(getItemsPromise).then((multipleResourceItemFiles) => {
106
+ if (failedItemIds.length > 0) {
107
+ reject(failWithIds(failedItemIds, "One or more items cannot be converted into templates"));
108
+ }
109
+ else {
110
+ if (solutionTemplates.length > 0) {
111
+ // Coalesce the resource file paths from the created templates
112
+ let resourceItemFiles = multipleResourceItemFiles.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
113
+ // Extract resource data files from templates
114
+ resourceItemFiles = resourceItemFiles.concat(getDataFilesFromTemplates(solutionTemplates));
115
+ // test for and update group dependencies and other post-processing
116
+ solutionTemplates = _postProcessGroupDependencies(solutionTemplates);
117
+ solutionTemplates = postProcessWorkforceTemplates(solutionTemplates);
118
+ // Filter out any resources from items that have been removed from the templates, such as
119
+ // Living Atlas layers
120
+ solutionTemplates = _postProcessIgnoredItems(solutionTemplates, templateDictionary);
121
+ const templateIds = solutionTemplates.map(template => template.itemId);
122
+ // Coalesce the resource file paths from the created templates
123
+ resourceItemFiles = resourceItemFiles.filter(file => templateIds.includes(file.itemId));
124
+ // Send the accumulated resources to the solution item
125
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
126
+ copyFilesToStorageItem(resourceItemFiles, solutionItemId, destAuthentication).then(() => {
127
+ // Remove data files from templates--no longer needed
128
+ removeDataFilesFromTemplates(solutionTemplates);
129
+ _templatizeSolutionIds(solutionTemplates);
130
+ _simplifyUrlsInItemDescriptions(solutionTemplates);
131
+ solutionTemplates.forEach(template => {
132
+ if (template.type !== "Vector Tile Service") {
133
+ _replaceDictionaryItemsInObject(templateDictionary, template);
134
+ }
135
+ });
136
+ _templatizeOrgUrl(solutionTemplates, destAuthentication).then(solutionTemplates2 => {
137
+ // Update solution item with its data JSON
138
+ const solutionData = {
139
+ metadata: { version: SolutionTemplateFormatVersion },
140
+ templates: options.templatizeFields
141
+ ? postProcessFieldReferences(solutionTemplates2)
142
+ : solutionTemplates2
143
+ };
144
+ const itemInfo = {
145
+ id: solutionItemId,
146
+ text: solutionData
147
+ };
148
+ updateItem(itemInfo, destAuthentication).then(() => {
149
+ resolve(solutionItemId);
150
+ }, reject);
151
+ }, reject);
152
+ });
153
+ }
154
+ else {
155
+ resolve(solutionItemId);
156
+ }
157
+ }
158
+ });
159
+ });
160
+ }
161
+ // ------------------------------------------------------------------------------------------------------------------ //
162
+ /**
163
+ * Gets the dependencies of an item by merging its dependencies list with item references in template variables.
164
+ *
165
+ * @param template Template to examine
166
+ * @returns List of dependency ids
167
+ * @private
168
+ */
169
+ export function _getDependencies(template) {
170
+ // Get all dependencies
171
+ let deps = template.dependencies.concat(_getIdsOutOfTemplateVariables(_getTemplateVariables(JSON.stringify(template.data))));
172
+ // Remove duplicates and self-references
173
+ deps.sort();
174
+ deps = deps.filter((elem, index, array) => {
175
+ if (elem === template.itemId) {
176
+ return false;
177
+ }
178
+ else if (index > 0) {
179
+ return elem !== array[index - 1];
180
+ }
181
+ else {
182
+ return true;
183
+ }
184
+ });
185
+ return deps;
186
+ }
187
+ /**
188
+ * Extracts AGO ids out of template variables.
189
+ *
190
+ * @param variables List of template variables to examine
191
+ * @returns List of AGO ids referenced in `variables`
192
+ * @private
193
+ */
194
+ export function _getIdsOutOfTemplateVariables(variables) {
195
+ return variables
196
+ .map(variable => {
197
+ const idList = variable.match(/[0-9A-F]{32}/i); // is it a guid?
198
+ if (idList) {
199
+ return idList[0];
200
+ }
201
+ else {
202
+ return null;
203
+ }
204
+ })
205
+ .filter(variable => !!variable);
206
+ }
207
+ /**
208
+ * Creates a list of item URLs.
209
+ *
210
+ * @param templates Templates to check for URLs
211
+ * @returns List of URLs
212
+ * @private
213
+ */
214
+ export function _getSolutionItemUrls(templates) {
215
+ const solutionUrls = [];
216
+ templates.forEach(template => {
217
+ /* istanbul ignore else */
218
+ if (template.item.origUrl) {
219
+ solutionUrls.push([template.itemId, template.item.origUrl]);
220
+ }
221
+ });
222
+ return solutionUrls;
223
+ }
224
+ /**
225
+ * Extracts template variables out of a string.
226
+ *
227
+ * @param text String to examine
228
+ * @returns List of template variables found in string
229
+ * @private
230
+ */
231
+ export function _getTemplateVariables(text) {
232
+ return (text.match(/{{[a-z0-9.]*}}/gi) || []) // find variable
233
+ .map(variable => variable.substring(2, variable.length - 2)); // remove "{{" & "}}"
234
+ }
235
+ /**
236
+ * Update the items dependencies and groups arrays
237
+ *
238
+ * @param templates The array of templates to evaluate
239
+ * @returns Updated version of the templates
240
+ * @private
241
+ */
242
+ export function _postProcessGroupDependencies(templates) {
243
+ return templates.map((template) => {
244
+ if (template.type === "Group") {
245
+ const id = template.itemId;
246
+ // remove group dependencies if we find a circular dependency with one of its items
247
+ let removeDependencies = false;
248
+ // before we remove update each dependants groups array
249
+ template.dependencies.forEach(dependencyId => {
250
+ const dependantTemplate = getTemplateById(templates, dependencyId);
251
+ // Not all items shared to the group will exist in the templates array
252
+ // i.e. Hub Initiative items or any other unsupported types
253
+ if (dependantTemplate) {
254
+ // check if the group is in the dependantTemplate's list of dependencies
255
+ const gIndex = getWithDefault(dependantTemplate, "dependencies", []).indexOf(id);
256
+ /* istanbul ignore else */
257
+ if (gIndex > -1) {
258
+ removeDependencies = true;
259
+ }
260
+ // if the dependant template does not have the group id
261
+ // in it's groups array, add it
262
+ const groups = getWithDefault(dependantTemplate, "groups", []);
263
+ if (groups.indexOf(id) === -1) {
264
+ groups.push(id);
265
+ dependantTemplate.groups = groups;
266
+ }
267
+ }
268
+ });
269
+ if (removeDependencies) {
270
+ template.dependencies = [];
271
+ }
272
+ }
273
+ return template;
274
+ });
275
+ }
276
+ /**
277
+ * Check for feature service items that have been flagged for invalid designations.
278
+ * Remove templates that have invalid designations from the solution item and other item dependencies.
279
+ * Clean up any references to items with invalid designations in the other templates.
280
+ *
281
+ * @param templates The array of templates to evaluate
282
+ * @param templateDictionary Hash of key details used for variable replacement
283
+ * @returns Updated version of the templates
284
+ * @private
285
+ */
286
+ export function _postProcessIgnoredItems(templates, templateDictionary) {
287
+ // replace in template
288
+ const updateDictionary = templates.reduce((result, template) => {
289
+ const invalidDes = template.properties.hasInvalidDesignations;
290
+ const unreachableVal = getProp(templateDictionary, `${UNREACHABLE}.${template.itemId}`);
291
+ if (invalidDes && unreachableVal && Object.keys(template.data).length < 1) {
292
+ template.data[template.itemId] = unreachableVal;
293
+ }
294
+ return invalidDes ? Object.assign(result, template.data) : result;
295
+ }, {});
296
+ Object.keys(updateDictionary).forEach(k => {
297
+ removeTemplate(templates, k);
298
+ templates = templates.map(t => {
299
+ t.dependencies = t.dependencies.filter(id => id !== k);
300
+ return replaceInTemplate(t, updateDictionary);
301
+ });
302
+ });
303
+ return templates;
304
+ }
305
+ /**
306
+ * Recursively runs through an object to find and replace any strings found in a dictionary.
307
+ *
308
+ * @param templateDictionary Hash of things to be replaced
309
+ * @param obj Object to be examined
310
+ * @private
311
+ */
312
+ export function _replaceDictionaryItemsInObject(hash, obj) {
313
+ /* istanbul ignore else */
314
+ if (obj) {
315
+ Object.keys(obj).forEach(prop => {
316
+ const propObj = obj[prop];
317
+ if (propObj) {
318
+ if (typeof propObj === "object") {
319
+ _replaceDictionaryItemsInObject(hash, propObj);
320
+ }
321
+ else if (typeof propObj === "string") {
322
+ obj[prop] = hash[propObj] || propObj;
323
+ }
324
+ }
325
+ });
326
+ }
327
+ return obj;
328
+ }
329
+ /**
330
+ * Recursively runs through an object to find and templatize any remaining references to solution's items.
331
+ *
332
+ * @param ids Ids to be replaced in strings found in object
333
+ * @param obj Object to be examined
334
+ * @private
335
+ */
336
+ export function _replaceRemainingIdsInObject(ids, obj) {
337
+ /* istanbul ignore else */
338
+ if (obj) {
339
+ Object.keys(obj).forEach(prop => {
340
+ const propObj = obj[prop];
341
+ if (propObj) {
342
+ if (typeof propObj === "object") {
343
+ _replaceRemainingIdsInObject(ids, propObj);
344
+ }
345
+ else if (typeof propObj === "string") {
346
+ obj[prop] = _replaceRemainingIdsInString(ids, propObj);
347
+ }
348
+ }
349
+ });
350
+ }
351
+ return obj;
352
+ }
353
+ /**
354
+ * Templatizes ids from a list in a string if they're not already templatized.
355
+ *
356
+ * @param ids Ids to be replaced in source string
357
+ * @param str Source string to be examined
358
+ * @returns A copy of the source string with any templatization changes
359
+ * @private
360
+ */
361
+ export function _replaceRemainingIdsInString(ids, str) {
362
+ let updatedStr = str;
363
+ const untemplatizedIds = getIDs(str);
364
+ if (untemplatizedIds.length > 0) {
365
+ untemplatizedIds.forEach(id => {
366
+ if (ids.includes(id)) {
367
+ const re = new RegExp("({*)" + id, "gi");
368
+ updatedStr = updatedStr.replace(re, match => match.indexOf("{{") < 0
369
+ ? "{{" + id.replace("{", "") + ".itemId}}"
370
+ : match);
371
+ }
372
+ });
373
+ }
374
+ return updatedStr;
375
+ }
376
+ /**
377
+ * Finds and templatizes any URLs in solution items' descriptions.
378
+ *
379
+ * @param templates The array of templates to evaluate, modified in place
380
+ * @private
381
+ */
382
+ export function _simplifyUrlsInItemDescriptions(templates) {
383
+ // Get the urls in the solution along with their item ids & convert the id into the form
384
+ // "{{fcb2bf2837a6404ebb418a1f805f976a.url}}"
385
+ const solutionUrls = _getSolutionItemUrls(templates).map(idUrl => [
386
+ "{{" + idUrl[0] + ".url}}",
387
+ idUrl[1]
388
+ ]);
389
+ /* istanbul ignore else */
390
+ if (solutionUrls.length > 0) {
391
+ // Make the replacements
392
+ templates.forEach(template => {
393
+ solutionUrls.forEach(
394
+ // TypeScript for es2015 doesn't have a definition for `replaceAll`
395
+ idUrl => {
396
+ /* istanbul ignore else */
397
+ if (template.item.description) {
398
+ template.item.description = template.item
399
+ .description.replaceAll(idUrl[1], idUrl[0]);
400
+ }
401
+ });
402
+ });
403
+ }
404
+ }
405
+ /**
406
+ * Templatizes occurrences of the URL to the user's organization in the `item` and `data` template sections.
407
+ *
408
+ * @param templates The array of templates to evaluate; templates is modified in place
409
+ * @param destAuthentication Credentials for request organization info
410
+ * @returns Promise resolving with `templates`
411
+ * @private
412
+ */
413
+ export function _templatizeOrgUrl(templates, destAuthentication) {
414
+ return new Promise((resolve, reject) => {
415
+ // Get the org's URL
416
+ getPortal(null, destAuthentication).then(org => {
417
+ const orgUrl = "https://" + org.urlKey + "." + org.customBaseUrl;
418
+ const templatizedOrgUrl = "{{portalBaseUrl}}";
419
+ // Cycle through each of the items in the template and scan the `item` and `data` sections of each for replacements
420
+ templates.forEach((template) => {
421
+ globalStringReplace(template.item, new RegExp(orgUrl, "gi"), templatizedOrgUrl);
422
+ globalStringReplace(template.data, new RegExp(orgUrl, "gi"), templatizedOrgUrl);
423
+ });
424
+ resolve(templates);
425
+ }, reject);
426
+ });
427
+ }
428
+ /**
429
+ * Finds and templatizes any references to solution's items.
430
+ *
431
+ * @param templates The array of templates to evaluate, modified in place
432
+ * @private
433
+ */
434
+ export function _templatizeSolutionIds(templates) {
435
+ // Get the ids in the solution
436
+ const solutionIds = templates.map((template) => template.itemId);
437
+ // Cycle through each of the items in the template and
438
+ // 1. templatize untemplatized ids in our solution in the `item` and `data` sections;
439
+ // 2. update the `dependencies` section
440
+ templates.forEach((template) => {
441
+ _replaceRemainingIdsInObject(solutionIds, template.item);
442
+ _replaceRemainingIdsInObject(solutionIds, template.data);
443
+ /* istanbul ignore else */
444
+ if (template.type !== "Group" && !isWorkforceProject(template)) {
445
+ template.dependencies = _getDependencies(template);
446
+ }
447
+ });
448
+ }
449
449
  //# sourceMappingURL=add-content-to-solution.js.map