@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,424 +1,427 @@
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
- * Manages creation of the template of a Solution item via the REST API.
18
- *
19
- * @module createItemTemplate
20
- */
21
- import { SolutionTemplateFormatVersion, EItemProgressStatus, blobToJson, cleanLayerBasedItemId, createPlaceholderTemplate, fail, findTemplateInList, getGroupBase, getItemBase, getItemResourcesFilesFromPaths, getItemResourcesPaths, hasDatasource, jsonToFile, replaceTemplate, sanitizeJSON } from "@esri/solution-common";
22
- import { getProp } from "@esri/hub-common";
23
- import { moduleMap, UNSUPPORTED } from "./module-map";
24
- // ------------------------------------------------------------------------------------------------------------------ //
25
- /**
26
- * Creates template for an AGO item and its dependencies
27
- *
28
- * @param solutionItemId The solution to contain the item
29
- * @param itemId AGO id string
30
- * @param templateDictionary Hash of facts
31
- * @param srcAuthentication Credentials for requests to source items
32
- * @param destAuthentication Authentication for requesting information from AGO about items to be included in solution item
33
- * @param existingTemplates A collection of AGO item templates that can be referenced by newly-created templates
34
- * @returns A promise which resolves with an array of resources for the item and its dependencies
35
- * @private
36
- */
37
- export function createItemTemplate(solutionItemId, itemId, templateDictionary, srcAuthentication, destAuthentication, existingTemplates, itemProgressCallback) {
38
- return new Promise(resolve => {
39
- // Check if item and its dependents are already in list or are queued
40
- if (findTemplateInList(existingTemplates, itemId)) {
41
- resolve([]);
42
- }
43
- else {
44
- // Add the id as a placeholder to show that it is being fetched
45
- existingTemplates.push(createPlaceholderTemplate(itemId));
46
- itemProgressCallback(itemId, EItemProgressStatus.Started, 0);
47
- // Fetch the item
48
- getItemBase(itemId, srcAuthentication)
49
- .catch(() => {
50
- // If item query fails, try fetching item as a group
51
- // Change its placeholder from an empty type to the Group type so that we can later distinguish
52
- // between items and groups (the base info for a group doesn't include a type property)
53
- replaceTemplate(existingTemplates, itemId, createPlaceholderTemplate(itemId, "Group"));
54
- return getGroupBase(itemId, srcAuthentication);
55
- })
56
- .then(itemInfo => {
57
- itemInfo = sanitizeJSON(itemInfo);
58
- // Save the URL as a symbol
59
- if (itemInfo.url) {
60
- templateDictionary[itemInfo.url] = "{{" + itemInfo.id + ".url}}";
61
- itemInfo.origUrl = itemInfo.url;
62
- }
63
- const idTest = /^source-[0-9A-F]{32}/i;
64
- // Remove any source-itemId type keywords
65
- /* istanbul ignore else */
66
- if (Array.isArray(itemInfo.typeKeywords)) {
67
- itemInfo.typeKeywords = itemInfo.typeKeywords.filter(v => idTest.test(v) ? false : true);
68
- }
69
- // Remove any source-itemId tags
70
- /* istanbul ignore else */
71
- if (Array.isArray(itemInfo.tags)) {
72
- itemInfo.tags = itemInfo.tags.filter(v => idTest.test(v) ? false : true);
73
- }
74
- const placeholder = findTemplateInList(existingTemplates, itemId);
75
- let itemType = placeholder.type;
76
- if (!itemType) {
77
- // Groups have this defined when their placeholder is created
78
- itemType = itemInfo.type;
79
- placeholder.type = itemType;
80
- }
81
- if (!itemInfo.type) {
82
- itemInfo.type = itemType; // Groups don't have this property, so we'll patch it in
83
- }
84
- placeholder.item = {
85
- ...itemInfo
86
- };
87
- // Interrupt process if progress callback returns `false`
88
- if (!itemProgressCallback(itemId, EItemProgressStatus.Created, 1)) {
89
- itemProgressCallback(itemId, EItemProgressStatus.Cancelled, 1);
90
- resolve(fail("Cancelled"));
91
- return;
92
- }
93
- const itemHandler = moduleMap[itemType];
94
- if (!itemHandler || itemHandler === UNSUPPORTED) {
95
- if (itemHandler === UNSUPPORTED) {
96
- itemProgressCallback(itemId, EItemProgressStatus.Ignored, 1);
97
- resolve([]);
98
- }
99
- else {
100
- itemProgressCallback(itemId, EItemProgressStatus.Failed, 1);
101
- placeholder.properties["failed"] = true;
102
- replaceTemplate(existingTemplates, itemId, placeholder);
103
- resolve(fail("The type of AGO item " +
104
- itemId +
105
- " ('" +
106
- itemType +
107
- "') is not supported at this time"));
108
- }
109
- }
110
- else {
111
- // Handle original Story Maps with next-gen Story Maps
112
- /* istanbul ignore else */
113
- /* Not yet supported
114
- if (storyMap.isAStoryMap(itemType, itemInfo.url)) {
115
- itemHandler = storyMap;
116
- } */
117
- // Delegate the creation of the item to the handler
118
- itemHandler
119
- .convertItemToTemplate(solutionItemId, itemInfo, destAuthentication, srcAuthentication, templateDictionary)
120
- .then(itemTemplate => {
121
- let resourcePrepPromise = Promise.resolve([]);
122
- // If the item type is Quick Capture, then we already have the resource files and just need to
123
- // convert them into ISourceFile objects
124
- if (itemTemplate.type === "QuickCapture Project") {
125
- const resourceItemFiles = itemTemplate.resources.map((file) => {
126
- const fileParts = file.name.split("/");
127
- return {
128
- itemId: itemTemplate.itemId,
129
- file,
130
- folder: fileParts.length === 1 ? "" : fileParts[0],
131
- filename: fileParts.length === 1 ? fileParts[0] : fileParts[1],
132
- };
133
- });
134
- resourcePrepPromise = Promise.resolve(resourceItemFiles);
135
- }
136
- else {
137
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
138
- resourcePrepPromise = getItemResourcesPaths(itemTemplate, solutionItemId, srcAuthentication, SolutionTemplateFormatVersion).then((resourceItemFilePaths) => {
139
- itemTemplate.item.thumbnail = null; // not needed in this property; handled as a resource
140
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
141
- return getItemResourcesFilesFromPaths(resourceItemFilePaths, srcAuthentication);
142
- });
143
- }
144
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
145
- resourcePrepPromise.then(async (resourceItemFiles) => {
146
- // Perform any custom processing needed on resource files
147
- await _templatizeResources(itemTemplate, resourceItemFiles, srcAuthentication);
148
- // update the template's resources
149
- itemTemplate.resources = resourceItemFiles.map((file) => file.folder + "/" + file.filename);
150
- // Set the value keyed by the id to the created template, replacing the placeholder template
151
- replaceTemplate(existingTemplates, itemTemplate.itemId, itemTemplate);
152
- // Trace item dependencies
153
- if (itemTemplate.dependencies.length === 0) {
154
- itemProgressCallback(itemId, EItemProgressStatus.Finished, 1);
155
- resolve(resourceItemFiles);
156
- }
157
- else {
158
- // Get its dependencies, asking each to get its dependents via
159
- // recursive calls to this function
160
- const dependentDfds = [];
161
- itemTemplate.dependencies.forEach(dependentId => {
162
- if (!findTemplateInList(existingTemplates, dependentId)) {
163
- dependentDfds.push(createItemTemplate(solutionItemId, dependentId, templateDictionary, srcAuthentication, destAuthentication, existingTemplates, itemProgressCallback));
164
- }
165
- });
166
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
167
- Promise.all(dependentDfds).then((dependentResourceItemFiles) => {
168
- // Templatization of item and its dependencies done
169
- itemProgressCallback(itemId, EItemProgressStatus.Finished, 1);
170
- resourceItemFiles = dependentResourceItemFiles.reduce((accumulator, currentValue) => accumulator.concat(currentValue), resourceItemFiles);
171
- resolve(resourceItemFiles);
172
- });
173
- }
174
- ;
175
- });
176
- }, error => {
177
- placeholder.properties["error"] = JSON.stringify(error);
178
- replaceTemplate(existingTemplates, itemId, placeholder);
179
- itemProgressCallback(itemId, EItemProgressStatus.Failed, 1);
180
- resolve([]);
181
- });
182
- }
183
- },
184
- // Id not found or item is not accessible
185
- () => {
186
- // mock hasInvalidDesignations so this will be processed at the end
187
- // as we do with living atlas layers
188
- const t = findTemplateInList(existingTemplates, itemId);
189
- t.properties.hasInvalidDesignations = true;
190
- // Skip items that we cannot fetch per issue #859
191
- // Use finished rather than ignored
192
- // ignored will cause the template to be removed before we can check for hasInvalidDesignations
193
- itemProgressCallback(itemId, EItemProgressStatus.Finished, 0);
194
- resolve([]);
195
- });
196
- }
197
- });
198
- }
199
- /**
200
- * Templatizes field references within specific template types.
201
- * Currently only handles web applications
202
- *
203
- * @param templates List of solution templates
204
- * @returns A list of templates that have templatized field references
205
- */
206
- export function postProcessFieldReferences(templates) {
207
- const datasourceInfos = _getDatasourceInfos(templates);
208
- const templateTypeHash = _getTemplateTypeHash(templates);
209
- return templates.map(template => {
210
- /* istanbul ignore else */
211
- if (template.type === "Web Mapping Application" ||
212
- template.type === "Dashboard" ||
213
- template.type === "Web Map") {
214
- const webMapFSDependencies = _getWebMapFSDependencies(template, templateTypeHash);
215
- const itemHandler = moduleMap[template.item.type];
216
- /* istanbul ignore else */
217
- if (itemHandler) {
218
- const dependencies = webMapFSDependencies.concat(template.dependencies);
219
- let dependentDatasources = datasourceInfos.filter(ds => {
220
- if (dependencies.indexOf(ds.itemId) > -1) {
221
- return ds;
222
- }
223
- });
224
- dependentDatasources = _addMapLayerIds(dependentDatasources, templateTypeHash);
225
- if (dependentDatasources.length > 0) {
226
- template = itemHandler.postProcessFieldReferences(template, dependentDatasources, template.item.type);
227
- }
228
- }
229
- }
230
- return template;
231
- });
232
- }
233
- // ------------------------------------------------------------------------------------------------------------------ //
234
- /**
235
- * Get common properties that will support the templatization of field references
236
- *
237
- * @param templates List of solution templates
238
- * @returns A list of IDataSourceInfo objects with key properties
239
- * @private
240
- */
241
- export function _getDatasourceInfos(templates) {
242
- const datasourceInfos = [];
243
- templates.forEach(t => {
244
- if (t.type === "Feature Service") {
245
- const layers = getProp(t, "properties.layers") || [];
246
- const tables = getProp(t, "properties.tables") || [];
247
- const layersAndTables = layers.concat(tables);
248
- layersAndTables.forEach(obj => {
249
- /* istanbul ignore else */
250
- if (!hasDatasource(datasourceInfos, t.itemId, obj.id)) {
251
- datasourceInfos.push({
252
- itemId: t.itemId,
253
- layerId: obj.id,
254
- fields: obj.fields,
255
- basePath: t.itemId + ".layer" + obj.id + ".fields",
256
- url: getProp(t, "item.url"),
257
- ids: [],
258
- relationships: obj.relationships || [],
259
- adminLayerInfo: obj.adminLayerInfo || {}
260
- });
261
- }
262
- });
263
- }
264
- });
265
- return datasourceInfos;
266
- }
267
- /**
268
- * Creates a simple lookup object to quickly understand an items type and dependencies
269
- * and associated web map layer ids based on itemId
270
- *
271
- * @param templates List of solution templates
272
- * @returns The lookup object with type, dependencies, and webmap layer info
273
- * @private
274
- */
275
- export function _getTemplateTypeHash(templates) {
276
- const templateTypeHash = {};
277
- templates.forEach(template => {
278
- templateTypeHash[template.itemId] = {
279
- type: template.type,
280
- dependencies: template.dependencies
281
- };
282
- if (template.type === "Web Map") {
283
- _updateWebMapHashInfo(template, templateTypeHash[template.itemId]);
284
- }
285
- });
286
- return templateTypeHash;
287
- }
288
- /**
289
- * Updates the lookup object with webmap layer info
290
- * so we can know the id used within a map for a given feature service
291
- *
292
- * @param template A webmap solution template
293
- * @returns The lookup object with webmap layer info added
294
- * @private
295
- */
296
- export function _updateWebMapHashInfo(template, hashItem) {
297
- const operationalLayers = getProp(template, "data.operationalLayers") || [];
298
- const tables = getProp(template, "data.tables") || [];
299
- const layersAndTables = operationalLayers.concat(tables);
300
- if (layersAndTables && layersAndTables.length > 0) {
301
- hashItem.layersAndTables = [];
302
- layersAndTables.forEach(layer => {
303
- const obj = {};
304
- let itemId;
305
- /* istanbul ignore else */
306
- if (layer.itemId) {
307
- itemId = layer.itemId;
308
- }
309
- /* istanbul ignore else */
310
- if (itemId) {
311
- obj[cleanLayerBasedItemId(itemId)] = {
312
- id: layer.id,
313
- url: layer.url
314
- };
315
- hashItem.layersAndTables.push(obj);
316
- }
317
- });
318
- }
319
- }
320
- /**
321
- * Updates a templatized datasource URL with a layer id.
322
- *
323
- * @param dataSourceUrl Templatized datasource URL
324
- * @param layerId Layer id
325
- * @returns string Amended datasource URL
326
- * @private
327
- */
328
- export function _addLayerIdToDatasourceUrl(datasourceUrl, layerId) {
329
- return datasourceUrl && !isNaN(layerId)
330
- ? datasourceUrl.replace(/[.]/, ".layer" + layerId + ".")
331
- : "";
332
- }
333
- /**
334
- * Updates the datasource info objects by passing the webmap layer IDs from the lookup hash
335
- * to the underlying feature service datasource infos
336
- *
337
- * @param datasourceInfos A webmap solution template
338
- * @param templateTypeHash A simple lookup object populated with key item info
339
- * @returns The updated datasource infos
340
- * @private
341
- */
342
- export function _addMapLayerIds(datasourceInfos, templateTypeHash) {
343
- const webMapIds = Object.keys(templateTypeHash).filter(k => {
344
- if (templateTypeHash[k].type === "Web Map") {
345
- return templateTypeHash[k];
346
- }
347
- });
348
- return datasourceInfos.map(ds => {
349
- webMapIds.forEach(webMapId => {
350
- templateTypeHash[webMapId].layersAndTables.forEach((opLayer) => {
351
- const opLayerInfo = opLayer[ds.itemId];
352
- const url = _addLayerIdToDatasourceUrl(ds.url, ds.layerId);
353
- if (opLayerInfo &&
354
- url === opLayerInfo.url &&
355
- ds.ids.indexOf(opLayerInfo.id) < 0) {
356
- ds.ids.push(opLayerInfo.id);
357
- }
358
- });
359
- });
360
- return ds;
361
- });
362
- }
363
- /**
364
- * Get feature service item IDs from applications webmaps
365
- * As they are not explict dependencies of the application but are needed for field references
366
- *
367
- * @param template A webmap solution template
368
- * @param templateTypeHash A simple lookup object populated with key item info
369
- * @returns A list of feature service item IDs
370
- * @private
371
- */
372
- export function _getWebMapFSDependencies(template, templateTypeHash) {
373
- const webMapFSDependencies = [];
374
- template.dependencies.forEach(dep => {
375
- const depObj = templateTypeHash[dep];
376
- if (depObj.type === "Web Map") {
377
- depObj.dependencies.forEach((depObjDependency) => {
378
- /* istanbul ignore else */
379
- if (templateTypeHash[depObjDependency].type === "Feature Service") {
380
- webMapFSDependencies.push(depObjDependency);
381
- }
382
- });
383
- }
384
- });
385
- return webMapFSDependencies;
386
- }
387
- /**
388
- * Perform templatizations needed in an item's resources
389
- *
390
- * @param itemTemplate Item being templatized
391
- * @param resourceItemFiles Resources for the item; these resources are modified as needed
392
- * by the templatization
393
- * @param srcAuthentication Credentials for requests to source items
394
- *
395
- * @returns A promise that resolves when all templatization has completed
396
- */
397
- export function _templatizeResources(itemTemplate, resourceItemFiles, srcAuthentication) {
398
- const synchronizePromises = [];
399
- if (itemTemplate.type === "Vector Tile Service") {
400
- // Get the root.json files
401
- const rootJsonResources = resourceItemFiles.filter(file => file.filename === "root.json");
402
- const resourcePath = srcAuthentication.portal + "/content/items/" + itemTemplate.itemId;
403
- const templatizedResourcePath = "{{" + itemTemplate.itemId + ".url}}";
404
- const replacer = new RegExp(resourcePath, "g");
405
- // Templatize the paths in the files that reference the source item id
406
- rootJsonResources.forEach(rootFileResource => {
407
- synchronizePromises.push(new Promise(resolve => {
408
- // Read the file
409
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
410
- blobToJson(rootFileResource.file)
411
- .then(fileJson => {
412
- // Templatize by turning JSON into string, replacing paths with template, and re-JSONing
413
- const updatedFileJson = JSON.parse(JSON.stringify(fileJson)
414
- .replace(replacer, templatizedResourcePath));
415
- // Write the changes back into the file
416
- rootFileResource.file = jsonToFile(updatedFileJson, rootFileResource.filename);
417
- resolve(null);
418
- });
419
- }));
420
- });
421
- }
422
- return Promise.all(synchronizePromises);
423
- }
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
+ * Manages creation of the template of a Solution item via the REST API.
18
+ *
19
+ * @module createItemTemplate
20
+ */
21
+ import { SolutionTemplateFormatVersion, EItemProgressStatus, blobToJson, cleanLayerBasedItemId, createPlaceholderTemplate, fail, findTemplateInList, generateSourceThumbnailPath, getGroupBase, getItemBase, getItemResourcesFilesFromPaths, getItemResourcesPaths, hasDatasource, jsonToFile, replaceTemplate, sanitizeJSON } from "@esri/solution-common";
22
+ import { getProp } from "@esri/hub-common";
23
+ import { moduleMap, UNSUPPORTED } from "./module-map";
24
+ // ------------------------------------------------------------------------------------------------------------------ //
25
+ /**
26
+ * Creates template for an AGO item and its dependencies
27
+ *
28
+ * @param solutionItemId The solution to contain the item
29
+ * @param itemId AGO id string
30
+ * @param templateDictionary Hash of facts
31
+ * @param srcAuthentication Credentials for requests to source items
32
+ * @param destAuthentication Authentication for requesting information from AGO about items to be included in solution item
33
+ * @param existingTemplates A collection of AGO item templates that can be referenced by newly-created templates
34
+ * @returns A promise which resolves with an array of resources for the item and its dependencies
35
+ * @private
36
+ */
37
+ export function createItemTemplate(solutionItemId, itemId, templateDictionary, srcAuthentication, destAuthentication, existingTemplates, itemProgressCallback) {
38
+ return new Promise(resolve => {
39
+ // Check if item and its dependents are already in list or are queued
40
+ if (findTemplateInList(existingTemplates, itemId)) {
41
+ resolve([]);
42
+ }
43
+ else {
44
+ // Add the id as a placeholder to show that it is being fetched
45
+ existingTemplates.push(createPlaceholderTemplate(itemId));
46
+ itemProgressCallback(itemId, EItemProgressStatus.Started, 0);
47
+ // Fetch the item
48
+ getItemBase(itemId, srcAuthentication)
49
+ .catch(() => {
50
+ // If item query fails, try fetching item as a group
51
+ // Change its placeholder from an empty type to the Group type so that we can later distinguish
52
+ // between items and groups (the base info for a group doesn't include a type property)
53
+ replaceTemplate(existingTemplates, itemId, createPlaceholderTemplate(itemId, "Group"));
54
+ return getGroupBase(itemId, srcAuthentication);
55
+ })
56
+ .then(itemInfo => {
57
+ itemInfo = sanitizeJSON(itemInfo);
58
+ // Save the URL as a symbol
59
+ if (itemInfo.url) {
60
+ templateDictionary[itemInfo.url] = "{{" + itemInfo.id + ".url}}";
61
+ itemInfo.origUrl = itemInfo.url;
62
+ }
63
+ const idTest = /^source-[0-9A-F]{32}/i;
64
+ // Remove any source-itemId type keywords
65
+ /* istanbul ignore else */
66
+ if (Array.isArray(itemInfo.typeKeywords)) {
67
+ itemInfo.typeKeywords = itemInfo.typeKeywords.filter(v => idTest.test(v) ? false : true);
68
+ }
69
+ // Remove any source-itemId tags
70
+ /* istanbul ignore else */
71
+ if (Array.isArray(itemInfo.tags)) {
72
+ itemInfo.tags = itemInfo.tags.filter(v => idTest.test(v) ? false : true);
73
+ }
74
+ const placeholder = findTemplateInList(existingTemplates, itemId);
75
+ let itemType = placeholder.type;
76
+ if (!itemType) {
77
+ // Groups have this defined when their placeholder is created
78
+ itemType = itemInfo.type;
79
+ placeholder.type = itemType;
80
+ }
81
+ if (!itemInfo.type) {
82
+ itemInfo.type = itemType; // Groups don't have this property, so we'll patch it in
83
+ }
84
+ placeholder.item = {
85
+ ...itemInfo
86
+ };
87
+ // Interrupt process if progress callback returns `false`
88
+ if (!itemProgressCallback(itemId, EItemProgressStatus.Created, 1)) {
89
+ itemProgressCallback(itemId, EItemProgressStatus.Cancelled, 1);
90
+ resolve(fail("Cancelled"));
91
+ return;
92
+ }
93
+ const itemHandler = moduleMap[itemType];
94
+ if (!itemHandler || itemHandler === UNSUPPORTED) {
95
+ if (itemHandler === UNSUPPORTED) {
96
+ itemProgressCallback(itemId, EItemProgressStatus.Ignored, 1);
97
+ resolve([]);
98
+ }
99
+ else {
100
+ itemProgressCallback(itemId, EItemProgressStatus.Failed, 1);
101
+ placeholder.properties["failed"] = true;
102
+ replaceTemplate(existingTemplates, itemId, placeholder);
103
+ resolve(fail("The type of AGO item " +
104
+ itemId +
105
+ " ('" +
106
+ itemType +
107
+ "') is not supported at this time"));
108
+ }
109
+ }
110
+ else {
111
+ // Handle original Story Maps with next-gen Story Maps
112
+ /* istanbul ignore else */
113
+ /* Not yet supported
114
+ if (storyMap.isAStoryMap(itemType, itemInfo.url)) {
115
+ itemHandler = storyMap;
116
+ } */
117
+ // Delegate the creation of the item to the handler
118
+ itemHandler
119
+ .convertItemToTemplate(solutionItemId, itemInfo, destAuthentication, srcAuthentication, templateDictionary)
120
+ .then(itemTemplate => {
121
+ let resourcePrepPromise = Promise.resolve([]);
122
+ // If the item type is Quick Capture, then we already have the resource files and just need to
123
+ // convert them into ISourceFile objects
124
+ if (itemTemplate.type === "QuickCapture Project") {
125
+ // Fetch thumbnail
126
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
127
+ resourcePrepPromise = getItemResourcesFilesFromPaths([generateSourceThumbnailPath(srcAuthentication.portal, itemTemplate.itemId, itemTemplate.item.thumbnail)], srcAuthentication).then((thumbnailFile) => {
128
+ itemTemplate.item.thumbnail = null; // not needed in this property; handled as a resource
129
+ return itemTemplate.resources.map((file) => {
130
+ return {
131
+ itemId: itemTemplate.itemId,
132
+ file,
133
+ folder: itemTemplate.itemId,
134
+ filename: file.name
135
+ };
136
+ }).concat(thumbnailFile);
137
+ });
138
+ }
139
+ else {
140
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
141
+ resourcePrepPromise = getItemResourcesPaths(itemTemplate, solutionItemId, srcAuthentication, SolutionTemplateFormatVersion).then((resourceItemFilePaths) => {
142
+ itemTemplate.item.thumbnail = null; // not needed in this property; handled as a resource
143
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
144
+ return getItemResourcesFilesFromPaths(resourceItemFilePaths, srcAuthentication);
145
+ });
146
+ }
147
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
148
+ resourcePrepPromise.then(async (resourceItemFiles) => {
149
+ // Perform any custom processing needed on resource files
150
+ await _templatizeResources(itemTemplate, resourceItemFiles, srcAuthentication);
151
+ // update the template's resources
152
+ itemTemplate.resources = resourceItemFiles.map((file) => file.folder + "/" + file.filename);
153
+ // Set the value keyed by the id to the created template, replacing the placeholder template
154
+ replaceTemplate(existingTemplates, itemTemplate.itemId, itemTemplate);
155
+ // Trace item dependencies
156
+ if (itemTemplate.dependencies.length === 0) {
157
+ itemProgressCallback(itemId, EItemProgressStatus.Finished, 1);
158
+ resolve(resourceItemFiles);
159
+ }
160
+ else {
161
+ // Get its dependencies, asking each to get its dependents via
162
+ // recursive calls to this function
163
+ const dependentDfds = [];
164
+ itemTemplate.dependencies.forEach(dependentId => {
165
+ if (!findTemplateInList(existingTemplates, dependentId)) {
166
+ dependentDfds.push(createItemTemplate(solutionItemId, dependentId, templateDictionary, srcAuthentication, destAuthentication, existingTemplates, itemProgressCallback));
167
+ }
168
+ });
169
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
170
+ Promise.all(dependentDfds).then((dependentResourceItemFiles) => {
171
+ // Templatization of item and its dependencies done
172
+ itemProgressCallback(itemId, EItemProgressStatus.Finished, 1);
173
+ resourceItemFiles = dependentResourceItemFiles.reduce((accumulator, currentValue) => accumulator.concat(currentValue), resourceItemFiles);
174
+ resolve(resourceItemFiles);
175
+ });
176
+ }
177
+ ;
178
+ });
179
+ }, error => {
180
+ placeholder.properties["error"] = JSON.stringify(error);
181
+ replaceTemplate(existingTemplates, itemId, placeholder);
182
+ itemProgressCallback(itemId, EItemProgressStatus.Failed, 1);
183
+ resolve([]);
184
+ });
185
+ }
186
+ },
187
+ // Id not found or item is not accessible
188
+ () => {
189
+ // mock hasInvalidDesignations so this will be processed at the end
190
+ // as we do with living atlas layers
191
+ const t = findTemplateInList(existingTemplates, itemId);
192
+ t.properties.hasInvalidDesignations = true;
193
+ // Skip items that we cannot fetch per issue #859
194
+ // Use finished rather than ignored
195
+ // ignored will cause the template to be removed before we can check for hasInvalidDesignations
196
+ itemProgressCallback(itemId, EItemProgressStatus.Finished, 0);
197
+ resolve([]);
198
+ });
199
+ }
200
+ });
201
+ }
202
+ /**
203
+ * Templatizes field references within specific template types.
204
+ * Currently only handles web applications
205
+ *
206
+ * @param templates List of solution templates
207
+ * @returns A list of templates that have templatized field references
208
+ */
209
+ export function postProcessFieldReferences(templates) {
210
+ const datasourceInfos = _getDatasourceInfos(templates);
211
+ const templateTypeHash = _getTemplateTypeHash(templates);
212
+ return templates.map(template => {
213
+ /* istanbul ignore else */
214
+ if (template.type === "Web Mapping Application" ||
215
+ template.type === "Dashboard" ||
216
+ template.type === "Web Map") {
217
+ const webMapFSDependencies = _getWebMapFSDependencies(template, templateTypeHash);
218
+ const itemHandler = moduleMap[template.item.type];
219
+ /* istanbul ignore else */
220
+ if (itemHandler) {
221
+ const dependencies = webMapFSDependencies.concat(template.dependencies);
222
+ let dependentDatasources = datasourceInfos.filter(ds => {
223
+ if (dependencies.indexOf(ds.itemId) > -1) {
224
+ return ds;
225
+ }
226
+ });
227
+ dependentDatasources = _addMapLayerIds(dependentDatasources, templateTypeHash);
228
+ if (dependentDatasources.length > 0) {
229
+ template = itemHandler.postProcessFieldReferences(template, dependentDatasources, template.item.type);
230
+ }
231
+ }
232
+ }
233
+ return template;
234
+ });
235
+ }
236
+ // ------------------------------------------------------------------------------------------------------------------ //
237
+ /**
238
+ * Get common properties that will support the templatization of field references
239
+ *
240
+ * @param templates List of solution templates
241
+ * @returns A list of IDataSourceInfo objects with key properties
242
+ * @private
243
+ */
244
+ export function _getDatasourceInfos(templates) {
245
+ const datasourceInfos = [];
246
+ templates.forEach(t => {
247
+ if (t.type === "Feature Service") {
248
+ const layers = getProp(t, "properties.layers") || [];
249
+ const tables = getProp(t, "properties.tables") || [];
250
+ const layersAndTables = layers.concat(tables);
251
+ layersAndTables.forEach(obj => {
252
+ /* istanbul ignore else */
253
+ if (!hasDatasource(datasourceInfos, t.itemId, obj.id)) {
254
+ datasourceInfos.push({
255
+ itemId: t.itemId,
256
+ layerId: obj.id,
257
+ fields: obj.fields,
258
+ basePath: t.itemId + ".layer" + obj.id + ".fields",
259
+ url: getProp(t, "item.url"),
260
+ ids: [],
261
+ relationships: obj.relationships || [],
262
+ adminLayerInfo: obj.adminLayerInfo || {}
263
+ });
264
+ }
265
+ });
266
+ }
267
+ });
268
+ return datasourceInfos;
269
+ }
270
+ /**
271
+ * Creates a simple lookup object to quickly understand an items type and dependencies
272
+ * and associated web map layer ids based on itemId
273
+ *
274
+ * @param templates List of solution templates
275
+ * @returns The lookup object with type, dependencies, and webmap layer info
276
+ * @private
277
+ */
278
+ export function _getTemplateTypeHash(templates) {
279
+ const templateTypeHash = {};
280
+ templates.forEach(template => {
281
+ templateTypeHash[template.itemId] = {
282
+ type: template.type,
283
+ dependencies: template.dependencies
284
+ };
285
+ if (template.type === "Web Map") {
286
+ _updateWebMapHashInfo(template, templateTypeHash[template.itemId]);
287
+ }
288
+ });
289
+ return templateTypeHash;
290
+ }
291
+ /**
292
+ * Updates the lookup object with webmap layer info
293
+ * so we can know the id used within a map for a given feature service
294
+ *
295
+ * @param template A webmap solution template
296
+ * @returns The lookup object with webmap layer info added
297
+ * @private
298
+ */
299
+ export function _updateWebMapHashInfo(template, hashItem) {
300
+ const operationalLayers = getProp(template, "data.operationalLayers") || [];
301
+ const tables = getProp(template, "data.tables") || [];
302
+ const layersAndTables = operationalLayers.concat(tables);
303
+ if (layersAndTables && layersAndTables.length > 0) {
304
+ hashItem.layersAndTables = [];
305
+ layersAndTables.forEach(layer => {
306
+ const obj = {};
307
+ let itemId;
308
+ /* istanbul ignore else */
309
+ if (layer.itemId) {
310
+ itemId = layer.itemId;
311
+ }
312
+ /* istanbul ignore else */
313
+ if (itemId) {
314
+ obj[cleanLayerBasedItemId(itemId)] = {
315
+ id: layer.id,
316
+ url: layer.url
317
+ };
318
+ hashItem.layersAndTables.push(obj);
319
+ }
320
+ });
321
+ }
322
+ }
323
+ /**
324
+ * Updates a templatized datasource URL with a layer id.
325
+ *
326
+ * @param dataSourceUrl Templatized datasource URL
327
+ * @param layerId Layer id
328
+ * @returns string Amended datasource URL
329
+ * @private
330
+ */
331
+ export function _addLayerIdToDatasourceUrl(datasourceUrl, layerId) {
332
+ return datasourceUrl && !isNaN(layerId)
333
+ ? datasourceUrl.replace(/[.]/, ".layer" + layerId + ".")
334
+ : "";
335
+ }
336
+ /**
337
+ * Updates the datasource info objects by passing the webmap layer IDs from the lookup hash
338
+ * to the underlying feature service datasource infos
339
+ *
340
+ * @param datasourceInfos A webmap solution template
341
+ * @param templateTypeHash A simple lookup object populated with key item info
342
+ * @returns The updated datasource infos
343
+ * @private
344
+ */
345
+ export function _addMapLayerIds(datasourceInfos, templateTypeHash) {
346
+ const webMapIds = Object.keys(templateTypeHash).filter(k => {
347
+ if (templateTypeHash[k].type === "Web Map") {
348
+ return templateTypeHash[k];
349
+ }
350
+ });
351
+ return datasourceInfos.map(ds => {
352
+ webMapIds.forEach(webMapId => {
353
+ templateTypeHash[webMapId].layersAndTables.forEach((opLayer) => {
354
+ const opLayerInfo = opLayer[ds.itemId];
355
+ const url = _addLayerIdToDatasourceUrl(ds.url, ds.layerId);
356
+ if (opLayerInfo &&
357
+ url === opLayerInfo.url &&
358
+ ds.ids.indexOf(opLayerInfo.id) < 0) {
359
+ ds.ids.push(opLayerInfo.id);
360
+ }
361
+ });
362
+ });
363
+ return ds;
364
+ });
365
+ }
366
+ /**
367
+ * Get feature service item IDs from applications webmaps
368
+ * As they are not explict dependencies of the application but are needed for field references
369
+ *
370
+ * @param template A webmap solution template
371
+ * @param templateTypeHash A simple lookup object populated with key item info
372
+ * @returns A list of feature service item IDs
373
+ * @private
374
+ */
375
+ export function _getWebMapFSDependencies(template, templateTypeHash) {
376
+ const webMapFSDependencies = [];
377
+ template.dependencies.forEach(dep => {
378
+ const depObj = templateTypeHash[dep];
379
+ if (depObj.type === "Web Map") {
380
+ depObj.dependencies.forEach((depObjDependency) => {
381
+ /* istanbul ignore else */
382
+ if (templateTypeHash[depObjDependency].type === "Feature Service") {
383
+ webMapFSDependencies.push(depObjDependency);
384
+ }
385
+ });
386
+ }
387
+ });
388
+ return webMapFSDependencies;
389
+ }
390
+ /**
391
+ * Perform templatizations needed in an item's resources
392
+ *
393
+ * @param itemTemplate Item being templatized
394
+ * @param resourceItemFiles Resources for the item; these resources are modified as needed
395
+ * by the templatization
396
+ * @param srcAuthentication Credentials for requests to source items
397
+ *
398
+ * @returns A promise that resolves when all templatization has completed
399
+ */
400
+ export function _templatizeResources(itemTemplate, resourceItemFiles, srcAuthentication) {
401
+ const synchronizePromises = [];
402
+ if (itemTemplate.type === "Vector Tile Service") {
403
+ // Get the root.json files
404
+ const rootJsonResources = resourceItemFiles.filter(file => file.filename === "root.json");
405
+ const resourcePath = srcAuthentication.portal + "/content/items/" + itemTemplate.itemId;
406
+ const templatizedResourcePath = "{{" + itemTemplate.itemId + ".url}}";
407
+ const replacer = new RegExp(resourcePath, "g");
408
+ // Templatize the paths in the files that reference the source item id
409
+ rootJsonResources.forEach(rootFileResource => {
410
+ synchronizePromises.push(new Promise(resolve => {
411
+ // Read the file
412
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
413
+ blobToJson(rootFileResource.file)
414
+ .then(fileJson => {
415
+ // Templatize by turning JSON into string, replacing paths with template, and re-JSONing
416
+ const updatedFileJson = JSON.parse(JSON.stringify(fileJson)
417
+ .replace(replacer, templatizedResourcePath));
418
+ // Write the changes back into the file
419
+ rootFileResource.file = jsonToFile(updatedFileJson, rootFileResource.filename);
420
+ resolve(null);
421
+ });
422
+ }));
423
+ });
424
+ }
425
+ return Promise.all(synchronizePromises);
426
+ }
424
427
  //# sourceMappingURL=createItemTemplate.js.map