Skip to content

Commit 42b714a

Browse files
author
Mike Tschudi
authored
Merge pull request #1514 from Esri/enh/1512-make-deployed-soln-item-optional
Added support for not creating solution item during deployment
2 parents 778318b + 296f707 commit 42b714a

File tree

14 files changed

+241
-103
lines changed

14 files changed

+241
-103
lines changed

demos/deploySolution/index.html

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,16 @@ <h3>Deploy one or more Solutions</h3>
8686
<br/><br/>
8787

8888
<div class="section-title">Options</div>
89-
<div class="labeledItem">
90-
<label for="useExistingChk">Use Existing Items:</label>
91-
<input type="checkbox" id="useExistingChk">
92-
</div>
93-
89+
<ul style="list-style:none">
90+
<li>
91+
<input type="checkbox" id="useExistingChk">
92+
<label for="useExistingChk">Use existing items</label>
93+
</li>
94+
<li>
95+
<input type="checkbox" id="dontCreateSolutionItem">
96+
<label for="dontCreateSolutionItem">Don't include Solution item in deployment</label>
97+
</li>
98+
</ul>
9499
<br /><br />
95100

96101
<div class="section-title">Custom Params</div>

demos/deploySolution/src/deploy-solution-main.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function deploySolutionsInFolder(
3232
destAuthentication: common.UserSession,
3333
progressCallback: common.ISolutionProgressCallback,
3434
enableItemReuse: boolean,
35+
dontCreateSolutionItem: boolean,
3536
customParams: any
3637
): Promise<string> {
3738
const query = new common.SearchQueryBuilder()
@@ -53,7 +54,8 @@ export function deploySolutionsInFolder(
5354
} as ISolutionInfoCard;
5455
}
5556
);
56-
return deployBatchOfSolutions(solutionsToDeploy, solutionsToDeploy.length, srcAuthentication, destAuthentication, progressCallback, enableItemReuse, customParams);
57+
return deployBatchOfSolutions(solutionsToDeploy, solutionsToDeploy.length,
58+
srcAuthentication, destAuthentication, progressCallback, enableItemReuse, dontCreateSolutionItem, customParams);
5759
} else {
5860
return Promise.resolve("<i>No solutions found in folder</i>");
5961
}
@@ -67,6 +69,7 @@ function deployBatchOfSolutions(
6769
destAuthentication: common.UserSession,
6870
progressCallback: common.ISolutionProgressCallback,
6971
enableItemReuse: boolean,
72+
dontCreateSolutionItem: boolean,
7073
customParams: any
7174
): Promise<string> {
7275
// Deploy the first item in the list
@@ -83,6 +86,7 @@ function deployBatchOfSolutions(
8386
destAuthentication,
8487
progressCallback,
8588
enableItemReuse,
89+
dontCreateSolutionItem,
8690
customParams
8791
);
8892
}
@@ -94,7 +98,7 @@ function deployBatchOfSolutions(
9498
let remainingDeployPromise = Promise.resolve("");
9599
if (solutionsToDeploy.length > 0) {
96100
remainingDeployPromise = deployBatchOfSolutions(solutionsToDeploy, totalNumberOfSolutions,
97-
srcAuthentication, destAuthentication, progressCallback, enableItemReuse, customParams);
101+
srcAuthentication, destAuthentication, progressCallback, enableItemReuse, dontCreateSolutionItem, customParams);
98102
}
99103
return remainingDeployPromise;
100104
})
@@ -110,6 +114,7 @@ export function deploySolution(
110114
destAuthentication: common.UserSession,
111115
progressCallback: common.ISolutionProgressCallback,
112116
enableItemReuse: boolean,
117+
dontCreateSolutionItem: boolean,
113118
customParams: any
114119
): Promise<string> {
115120
// Deploy a solution described by the supplied id
@@ -121,7 +126,8 @@ export function deploySolution(
121126
enableItemReuse,
122127
templateDictionary: isJsonStr(customParams) ? {
123128
params: JSON.parse(customParams)
124-
} : {}
129+
} : {},
130+
dontCreateSolutionItem
125131
};
126132
const itemUrlPrefix = destAuthentication.portal.replace("/sharing/rest", "");
127133

@@ -138,6 +144,7 @@ export function deployAndDisplaySolution(
138144
destAuthentication: common.UserSession,
139145
progressCallback: common.ISolutionProgressCallback,
140146
enableItemReuse: boolean,
147+
dontCreateSolutionItem: boolean,
141148
customParams: any
142149
): Promise<string> {
143150
// Deploy a solution described by the supplied id
@@ -150,12 +157,17 @@ export function deployAndDisplaySolution(
150157
enableItemReuse,
151158
templateDictionary: isJsonStr(customParams) ? {
152159
params: JSON.parse(customParams)
153-
} : {}
160+
} : {},
161+
dontCreateSolutionItem
154162
};
155163

156164
return deployer.deploySolution(templateSolutionId, destAuthentication, options)
157-
.then((deployedSolution: any) => {
158-
return getFormattedItemInfo.getFormattedItemInfo(deployedSolution, destAuthentication);
165+
.then((createdSolutionId: string) => {
166+
if (dontCreateSolutionItem) {
167+
return "Deployed solution";
168+
} else {
169+
return getFormattedItemInfo.getFormattedItemInfo(createdSolutionId, destAuthentication);
170+
}
159171
});
160172
}
161173

demos/deploySolution/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function deploySolution(
6464
srcCreds: common.UserSession,
6565
destCreds: common.UserSession,
6666
useExisting: boolean,
67+
dontCreateSolutionItem: boolean,
6768
customParams: any
6869
): void {
6970
const startTime = Date.now();
@@ -87,6 +88,7 @@ function deploySolution(
8788
destCreds,
8889
progressFcn,
8990
useExisting,
91+
dontCreateSolutionItem,
9092
customParams
9193
);
9294
} else if (folderId.length > 0) {
@@ -96,6 +98,7 @@ function deploySolution(
9698
destCreds,
9799
progressFcn,
98100
useExisting,
101+
dontCreateSolutionItem,
99102
customParams
100103
);
101104
}
@@ -129,6 +132,9 @@ function go(
129132
// Use Existing
130133
const useExisting = htmlUtil.getHTMLChecked("useExistingChk");
131134

135+
// Create Solution item in destination organization
136+
const dontCreateSolutionItem = htmlUtil.getHTMLChecked("dontCreateSolutionItem");
137+
132138
// Custom Params
133139
const customParams = htmlUtil.getHTMLValue("customParams");
134140

@@ -154,7 +160,7 @@ function go(
154160
portal: destPortal,
155161
clientId: htmlUtil.getHTMLValue("clientId")
156162
});
157-
deploySolution(solutionId, folderId, srcCreds, destCreds, useExisting, customParams);
163+
deploySolution(solutionId, folderId, srcCreds, destCreds, useExisting, dontCreateSolutionItem, customParams);
158164
} else {
159165
let redirect_uri = window.location.origin + window.location.pathname;
160166
const iLastSlash = redirect_uri.lastIndexOf("/");
@@ -169,7 +175,7 @@ function go(
169175
// Upon a successful login, update the session with the new session.
170176
session = newSession;
171177
updateSessionInfo(session);
172-
deploySolution(solutionId, folderId, srcCreds, session, useExisting, customParams);
178+
deploySolution(solutionId, folderId, srcCreds, session, useExisting, dontCreateSolutionItem, customParams);
173179
}).catch(function (error) {
174180
console.log(error);
175181
});

packages/common/src/deleteHelpers/deleteSolutionContents.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ export function deleteSolutionContents(
7979
if (solutionSummary.items.length > 0) {
8080
// Save a copy of the Solution item ids for the deleteSolutionFolder call because removeItems
8181
// destroys the solutionSummary.items list
82-
solutionIds = solutionSummary.items.map((item) => item.id).concat([solutionItemId]);
82+
if (solutionItemId) {
83+
solutionIds = solutionSummary.items.map((item) => item.id).concat([solutionItemId]);
84+
}
8385

8486
const hubSiteItemIds: string[] = solutionSummary.items
8587
.filter((item: any) => item.type === "Hub Site Application")
@@ -113,12 +115,16 @@ export function deleteSolutionContents(
113115
});
114116
})
115117
.then(() => {
116-
// If there were no failed deletes, it's OK to delete Solution item
117-
if (solutionFailureSummary.items.length === 0) {
118-
return deleteSolutionItem.deleteSolutionItem(solutionItemId, authentication);
118+
if (solutionItemId) {
119+
// If there were no failed deletes, it's OK to delete Solution item
120+
if (solutionFailureSummary.items.length === 0) {
121+
return deleteSolutionItem.deleteSolutionItem(solutionItemId, authentication);
122+
} else {
123+
// Not all items were deleted, so don't delete solution
124+
return Promise.resolve({ success: false, itemId: solutionItemId });
125+
}
119126
} else {
120-
// Not all items were deleted, so don't delete solution
121-
return Promise.resolve({ success: false, itemId: solutionItemId });
127+
return Promise.resolve({ success: true, itemId: "" });
122128
}
123129
})
124130
.then((solutionItemDeleteStatus: IStatusResponse) => {

packages/common/src/featureServiceHelpers.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
deleteProp,
4141
deleteProps,
4242
fail,
43+
generateGUID,
4344
getProp,
4445
setCreateProp,
4546
setProp,
@@ -449,13 +450,13 @@ export function getLayerSettings(layerInfos: any, url: string, itemId: string, e
449450
* Set the names and titles for all feature services.
450451
*
451452
* This function will ensure that we have unique feature service names.
452-
* The feature service name will have the solution item id appended.
453+
* The feature service name will have a generated GUID appended.
453454
*
454455
* @param templates A collection of AGO item templates.
455-
* @param solutionItemId The item id for the deployed solution item.
456456
* @returns An updated collection of AGO templates with unique feature service names.
457457
*/
458-
export function setNamesAndTitles(templates: IItemTemplate[], solutionItemId: string): IItemTemplate[] {
458+
export function setNamesAndTitles(templates: IItemTemplate[]): IItemTemplate[] {
459+
const guid: string = generateGUID();
459460
const names: string[] = [];
460461
return templates.map((t) => {
461462
/* istanbul ignore else */
@@ -473,7 +474,7 @@ export function setNamesAndTitles(templates: IItemTemplate[], solutionItemId: st
473474

474475
// The name length limit is 98
475476
// Limit the baseName to 50 characters before the _<guid>
476-
const name: string = baseName.substring(0, 50) + "_" + solutionItemId;
477+
const name: string = baseName.substring(0, 50) + "_" + guid;
477478

478479
// If the name + GUID already exists then append "_occurrenceCount"
479480
t.item.name = names.indexOf(name) === -1 ? name : `${name}_${names.filter((n) => n === name).length}`;

packages/common/src/generalHelpers.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,37 @@ export function generateEmptyCreationResponse(itemType: string, id = ""): ICreat
182182
};
183183
}
184184

185+
/**
186+
* Generates a version 4 2-bit variant GUID.
187+
*
188+
* @returns A GUID
189+
* @see {@link https://en.wikipedia.org/wiki/Universally_unique_identifier}, section "Version 4 (random)"
190+
*/
191+
export function generateGUID(): string {
192+
/** @license
193+
* Copyright 2013 Steve Fenton
194+
*
195+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
196+
* with the License. You may obtain a copy of the License at
197+
* http://www.apache.org/licenses/LICENSE-2.0
198+
*
199+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
200+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
201+
* for the specific language governing permissions and limitations under the License.
202+
*/
203+
try {
204+
return crypto.randomUUID().replace(/-/g, "");
205+
} catch {
206+
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
207+
// eslint-disable-next-line no-bitwise
208+
const r = (Math.random() * 16) | 0;
209+
// eslint-disable-next-line no-bitwise
210+
const v = c === "x" ? r : (r & 0x3) | 0x8;
211+
return v.toString(16);
212+
});
213+
}
214+
}
215+
185216
/**
186217
* Returns a regular expression matching a global search for a 32-character AGO-style id.
187218
*

packages/common/src/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,11 @@ export interface IDeploySolutionOptions {
647647
* Version of storage read from Solution item. DO NOT USE--it is overwritten by function deploySolutionFromTemplate
648648
*/
649649
storageVersion?: number;
650+
651+
/**
652+
* Determines if the solution item should be created during deployment; default: false
653+
*/
654+
dontCreateSolutionItem?: boolean;
650655
}
651656

652657
/**

packages/common/test/arcgisRestJS.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,14 @@ describe("Module arcgisRestJS", () => {
8787
await arcgisRestJS.unprotectGroup(requestOptions);
8888
expect(unprotectGroupSpy.called);
8989
});
90+
91+
it("tests binding function unprotectItem", async () => {
92+
const requestOptions: arcgisRestJS.IUserItemOptions = {
93+
id: "0",
94+
authentication: MOCK_USER_SESSION,
95+
};
96+
const unprotectItemSpy = sinon.stub(arcgisRestPortal, "unprotectItem").resolves();
97+
await arcgisRestJS.unprotectItem(requestOptions);
98+
expect(unprotectItemSpy.called);
99+
});
90100
});

packages/common/test/featureServiceHelpers.test.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import {
8383
IPopupInfos,
8484
} from "../src/featureServiceHelpers";
8585

86+
import * as generalHelpers from "../../common/src/generalHelpers";
8687
import * as restHelpers from "../../common/src/restHelpers";
8788

8889
import * as arcGISRestJS from "../src/arcgisRestJS";
@@ -1683,13 +1684,15 @@ describe("Module `featureServiceHelpers`: utility functions for feature-service
16831684
t.item.title = "TheName";
16841685
const _templates: IItemTemplate[] = [t];
16851686

1687+
spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");
1688+
16861689
const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
16871690
expectedTemplate.item.type = "Feature Service";
1688-
expectedTemplate.item.name = `TheName_${itemId}`;
1691+
expectedTemplate.item.name = `TheName_212dbc19b03943008fdfaf8d6adca00e`;
16891692
expectedTemplate.item.title = "TheName";
16901693
const expected: IItemTemplate[] = [expectedTemplate];
16911694

1692-
const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
1695+
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
16931696
expect(actual).toEqual(expected);
16941697
});
16951698

@@ -1704,17 +1707,19 @@ describe("Module `featureServiceHelpers`: utility functions for feature-service
17041707
t2.item.title = undefined;
17051708
const _templates: IItemTemplate[] = [t, t2];
17061709

1710+
spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");
1711+
17071712
const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
17081713
expectedTemplate.item.type = "Feature Service";
1709-
expectedTemplate.item.name = `TheName_${itemId}`;
1714+
expectedTemplate.item.name = `TheName_212dbc19b03943008fdfaf8d6adca00e`;
17101715
expectedTemplate.item.title = "TheName_99ac87b220fd45038fc92ba10843886d";
17111716
const expectedTemplate2: IItemTemplate = templates.getItemTemplateSkeleton();
17121717
expectedTemplate2.item.type = "Feature Service";
1713-
expectedTemplate2.item.name = `TheName_${itemId}_1`;
1718+
expectedTemplate2.item.name = `TheName_212dbc19b03943008fdfaf8d6adca00e_1`;
17141719
expectedTemplate2.item.title = "TheName_88ac87b220fd45038fc92ba10843886d";
17151720
const expected: IItemTemplate[] = [expectedTemplate, expectedTemplate2];
17161721

1717-
const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
1722+
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
17181723
expect(actual).toEqual(expected);
17191724
});
17201725

@@ -1725,13 +1730,15 @@ describe("Module `featureServiceHelpers`: utility functions for feature-service
17251730
t.item.title = "TheName";
17261731
const _templates: IItemTemplate[] = [t];
17271732

1733+
spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");
1734+
17281735
const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
17291736
expectedTemplate.item.type = "Feature Service";
1730-
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_${itemId}`;
1737+
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_212dbc19b03943008fdfaf8d6adca00e`;
17311738
expectedTemplate.item.title = "TheName";
17321739
const expected: IItemTemplate[] = [expectedTemplate];
17331740

1734-
const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
1741+
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
17351742
expect(actual).toEqual(expected);
17361743
});
17371744

@@ -1742,13 +1749,15 @@ describe("Module `featureServiceHelpers`: utility functions for feature-service
17421749
t.item.title = "TheName";
17431750
const _templates: IItemTemplate[] = [t];
17441751

1752+
spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");
1753+
17451754
const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
17461755
expectedTemplate.item.type = "Feature Service";
1747-
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_${itemId}`;
1756+
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_212dbc19b03943008fdfaf8d6adca00e`;
17481757
expectedTemplate.item.title = "TheName";
17491758
const expected: IItemTemplate[] = [expectedTemplate];
17501759

1751-
const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
1760+
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
17521761
expect(actual).toEqual(expected);
17531762
});
17541763
});

0 commit comments

Comments
 (0)