Skip to content

Commit

Permalink
Added support for not creating solution item during deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeTschudi committed Nov 25, 2024
1 parent 778318b commit 296f707
Show file tree
Hide file tree
Showing 14 changed files with 241 additions and 103 deletions.
15 changes: 10 additions & 5 deletions demos/deploySolution/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,16 @@ <h3>Deploy one or more Solutions</h3>
<br/><br/>

<div class="section-title">Options</div>
<div class="labeledItem">
<label for="useExistingChk">Use Existing Items:</label>
<input type="checkbox" id="useExistingChk">
</div>

<ul style="list-style:none">
<li>
<input type="checkbox" id="useExistingChk">
<label for="useExistingChk">Use existing items</label>
</li>
<li>
<input type="checkbox" id="dontCreateSolutionItem">
<label for="dontCreateSolutionItem">Don't include Solution item in deployment</label>
</li>
</ul>
<br /><br />

<div class="section-title">Custom Params</div>
Expand Down
24 changes: 18 additions & 6 deletions demos/deploySolution/src/deploy-solution-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function deploySolutionsInFolder(
destAuthentication: common.UserSession,
progressCallback: common.ISolutionProgressCallback,
enableItemReuse: boolean,
dontCreateSolutionItem: boolean,
customParams: any
): Promise<string> {
const query = new common.SearchQueryBuilder()
Expand All @@ -53,7 +54,8 @@ export function deploySolutionsInFolder(
} as ISolutionInfoCard;
}
);
return deployBatchOfSolutions(solutionsToDeploy, solutionsToDeploy.length, srcAuthentication, destAuthentication, progressCallback, enableItemReuse, customParams);
return deployBatchOfSolutions(solutionsToDeploy, solutionsToDeploy.length,
srcAuthentication, destAuthentication, progressCallback, enableItemReuse, dontCreateSolutionItem, customParams);
} else {
return Promise.resolve("<i>No solutions found in folder</i>");
}
Expand All @@ -67,6 +69,7 @@ function deployBatchOfSolutions(
destAuthentication: common.UserSession,
progressCallback: common.ISolutionProgressCallback,
enableItemReuse: boolean,
dontCreateSolutionItem: boolean,
customParams: any
): Promise<string> {
// Deploy the first item in the list
Expand All @@ -83,6 +86,7 @@ function deployBatchOfSolutions(
destAuthentication,
progressCallback,
enableItemReuse,
dontCreateSolutionItem,
customParams
);
}
Expand All @@ -94,7 +98,7 @@ function deployBatchOfSolutions(
let remainingDeployPromise = Promise.resolve("");
if (solutionsToDeploy.length > 0) {
remainingDeployPromise = deployBatchOfSolutions(solutionsToDeploy, totalNumberOfSolutions,
srcAuthentication, destAuthentication, progressCallback, enableItemReuse, customParams);
srcAuthentication, destAuthentication, progressCallback, enableItemReuse, dontCreateSolutionItem, customParams);
}
return remainingDeployPromise;
})
Expand All @@ -110,6 +114,7 @@ export function deploySolution(
destAuthentication: common.UserSession,
progressCallback: common.ISolutionProgressCallback,
enableItemReuse: boolean,
dontCreateSolutionItem: boolean,
customParams: any
): Promise<string> {
// Deploy a solution described by the supplied id
Expand All @@ -121,7 +126,8 @@ export function deploySolution(
enableItemReuse,
templateDictionary: isJsonStr(customParams) ? {
params: JSON.parse(customParams)
} : {}
} : {},
dontCreateSolutionItem
};
const itemUrlPrefix = destAuthentication.portal.replace("/sharing/rest", "");

Expand All @@ -138,6 +144,7 @@ export function deployAndDisplaySolution(
destAuthentication: common.UserSession,
progressCallback: common.ISolutionProgressCallback,
enableItemReuse: boolean,
dontCreateSolutionItem: boolean,
customParams: any
): Promise<string> {
// Deploy a solution described by the supplied id
Expand All @@ -150,12 +157,17 @@ export function deployAndDisplaySolution(
enableItemReuse,
templateDictionary: isJsonStr(customParams) ? {
params: JSON.parse(customParams)
} : {}
} : {},
dontCreateSolutionItem
};

return deployer.deploySolution(templateSolutionId, destAuthentication, options)
.then((deployedSolution: any) => {
return getFormattedItemInfo.getFormattedItemInfo(deployedSolution, destAuthentication);
.then((createdSolutionId: string) => {
if (dontCreateSolutionItem) {
return "Deployed solution";
} else {
return getFormattedItemInfo.getFormattedItemInfo(createdSolutionId, destAuthentication);
}
});
}

Expand Down
10 changes: 8 additions & 2 deletions demos/deploySolution/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function deploySolution(
srcCreds: common.UserSession,
destCreds: common.UserSession,
useExisting: boolean,
dontCreateSolutionItem: boolean,
customParams: any
): void {
const startTime = Date.now();
Expand All @@ -87,6 +88,7 @@ function deploySolution(
destCreds,
progressFcn,
useExisting,
dontCreateSolutionItem,
customParams
);
} else if (folderId.length > 0) {
Expand All @@ -96,6 +98,7 @@ function deploySolution(
destCreds,
progressFcn,
useExisting,
dontCreateSolutionItem,
customParams
);
}
Expand Down Expand Up @@ -129,6 +132,9 @@ function go(
// Use Existing
const useExisting = htmlUtil.getHTMLChecked("useExistingChk");

// Create Solution item in destination organization
const dontCreateSolutionItem = htmlUtil.getHTMLChecked("dontCreateSolutionItem");

// Custom Params
const customParams = htmlUtil.getHTMLValue("customParams");

Expand All @@ -154,7 +160,7 @@ function go(
portal: destPortal,
clientId: htmlUtil.getHTMLValue("clientId")
});
deploySolution(solutionId, folderId, srcCreds, destCreds, useExisting, customParams);
deploySolution(solutionId, folderId, srcCreds, destCreds, useExisting, dontCreateSolutionItem, customParams);
} else {
let redirect_uri = window.location.origin + window.location.pathname;
const iLastSlash = redirect_uri.lastIndexOf("/");
Expand All @@ -169,7 +175,7 @@ function go(
// Upon a successful login, update the session with the new session.
session = newSession;
updateSessionInfo(session);
deploySolution(solutionId, folderId, srcCreds, session, useExisting, customParams);
deploySolution(solutionId, folderId, srcCreds, session, useExisting, dontCreateSolutionItem, customParams);
}).catch(function (error) {
console.log(error);
});
Expand Down
18 changes: 12 additions & 6 deletions packages/common/src/deleteHelpers/deleteSolutionContents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export function deleteSolutionContents(
if (solutionSummary.items.length > 0) {
// Save a copy of the Solution item ids for the deleteSolutionFolder call because removeItems
// destroys the solutionSummary.items list
solutionIds = solutionSummary.items.map((item) => item.id).concat([solutionItemId]);
if (solutionItemId) {
solutionIds = solutionSummary.items.map((item) => item.id).concat([solutionItemId]);
}

const hubSiteItemIds: string[] = solutionSummary.items
.filter((item: any) => item.type === "Hub Site Application")
Expand Down Expand Up @@ -113,12 +115,16 @@ export function deleteSolutionContents(
});
})
.then(() => {
// If there were no failed deletes, it's OK to delete Solution item
if (solutionFailureSummary.items.length === 0) {
return deleteSolutionItem.deleteSolutionItem(solutionItemId, authentication);
if (solutionItemId) {
// If there were no failed deletes, it's OK to delete Solution item
if (solutionFailureSummary.items.length === 0) {
return deleteSolutionItem.deleteSolutionItem(solutionItemId, authentication);
} else {
// Not all items were deleted, so don't delete solution
return Promise.resolve({ success: false, itemId: solutionItemId });
}
} else {
// Not all items were deleted, so don't delete solution
return Promise.resolve({ success: false, itemId: solutionItemId });
return Promise.resolve({ success: true, itemId: "" });
}
})
.then((solutionItemDeleteStatus: IStatusResponse) => {
Expand Down
9 changes: 5 additions & 4 deletions packages/common/src/featureServiceHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
deleteProp,
deleteProps,
fail,
generateGUID,
getProp,
setCreateProp,
setProp,
Expand Down Expand Up @@ -449,13 +450,13 @@ export function getLayerSettings(layerInfos: any, url: string, itemId: string, e
* Set the names and titles for all feature services.
*
* This function will ensure that we have unique feature service names.
* The feature service name will have the solution item id appended.
* The feature service name will have a generated GUID appended.
*
* @param templates A collection of AGO item templates.
* @param solutionItemId The item id for the deployed solution item.
* @returns An updated collection of AGO templates with unique feature service names.
*/
export function setNamesAndTitles(templates: IItemTemplate[], solutionItemId: string): IItemTemplate[] {
export function setNamesAndTitles(templates: IItemTemplate[]): IItemTemplate[] {
const guid: string = generateGUID();
const names: string[] = [];
return templates.map((t) => {
/* istanbul ignore else */
Expand All @@ -473,7 +474,7 @@ export function setNamesAndTitles(templates: IItemTemplate[], solutionItemId: st

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

// If the name + GUID already exists then append "_occurrenceCount"
t.item.name = names.indexOf(name) === -1 ? name : `${name}_${names.filter((n) => n === name).length}`;
Expand Down
31 changes: 31 additions & 0 deletions packages/common/src/generalHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,37 @@ export function generateEmptyCreationResponse(itemType: string, id = ""): ICreat
};
}

/**
* Generates a version 4 2-bit variant GUID.
*
* @returns A GUID
* @see {@link https://en.wikipedia.org/wiki/Universally_unique_identifier}, section "Version 4 (random)"
*/
export function generateGUID(): string {
/** @license
* Copyright 2013 Steve Fenton
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*/
try {
return crypto.randomUUID().replace(/-/g, "");
} catch {
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
// eslint-disable-next-line no-bitwise
const r = (Math.random() * 16) | 0;
// eslint-disable-next-line no-bitwise
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}

/**
* Returns a regular expression matching a global search for a 32-character AGO-style id.
*
Expand Down
5 changes: 5 additions & 0 deletions packages/common/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,11 @@ export interface IDeploySolutionOptions {
* Version of storage read from Solution item. DO NOT USE--it is overwritten by function deploySolutionFromTemplate
*/
storageVersion?: number;

/**
* Determines if the solution item should be created during deployment; default: false
*/
dontCreateSolutionItem?: boolean;
}

/**
Expand Down
10 changes: 10 additions & 0 deletions packages/common/test/arcgisRestJS.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,14 @@ describe("Module arcgisRestJS", () => {
await arcgisRestJS.unprotectGroup(requestOptions);
expect(unprotectGroupSpy.called);
});

it("tests binding function unprotectItem", async () => {
const requestOptions: arcgisRestJS.IUserItemOptions = {
id: "0",
authentication: MOCK_USER_SESSION,
};
const unprotectItemSpy = sinon.stub(arcgisRestPortal, "unprotectItem").resolves();
await arcgisRestJS.unprotectItem(requestOptions);
expect(unprotectItemSpy.called);
});
});
27 changes: 18 additions & 9 deletions packages/common/test/featureServiceHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import {
IPopupInfos,
} from "../src/featureServiceHelpers";

import * as generalHelpers from "../../common/src/generalHelpers";
import * as restHelpers from "../../common/src/restHelpers";

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

spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");

const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
expectedTemplate.item.type = "Feature Service";
expectedTemplate.item.name = `TheName_${itemId}`;
expectedTemplate.item.name = `TheName_212dbc19b03943008fdfaf8d6adca00e`;
expectedTemplate.item.title = "TheName";
const expected: IItemTemplate[] = [expectedTemplate];

const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
expect(actual).toEqual(expected);
});

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

spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");

const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
expectedTemplate.item.type = "Feature Service";
expectedTemplate.item.name = `TheName_${itemId}`;
expectedTemplate.item.name = `TheName_212dbc19b03943008fdfaf8d6adca00e`;
expectedTemplate.item.title = "TheName_99ac87b220fd45038fc92ba10843886d";
const expectedTemplate2: IItemTemplate = templates.getItemTemplateSkeleton();
expectedTemplate2.item.type = "Feature Service";
expectedTemplate2.item.name = `TheName_${itemId}_1`;
expectedTemplate2.item.name = `TheName_212dbc19b03943008fdfaf8d6adca00e_1`;
expectedTemplate2.item.title = "TheName_88ac87b220fd45038fc92ba10843886d";
const expected: IItemTemplate[] = [expectedTemplate, expectedTemplate2];

const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
expect(actual).toEqual(expected);
});

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

spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");

const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
expectedTemplate.item.type = "Feature Service";
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_${itemId}`;
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_212dbc19b03943008fdfaf8d6adca00e`;
expectedTemplate.item.title = "TheName";
const expected: IItemTemplate[] = [expectedTemplate];

const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
expect(actual).toEqual(expected);
});

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

spyOn(generalHelpers, "generateGUID").and.returnValue("212dbc19b03943008fdfaf8d6adca00e");

const expectedTemplate: IItemTemplate = templates.getItemTemplateSkeleton();
expectedTemplate.item.type = "Feature Service";
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_${itemId}`;
expectedTemplate.item.name = `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_212dbc19b03943008fdfaf8d6adca00e`;
expectedTemplate.item.title = "TheName";
const expected: IItemTemplate[] = [expectedTemplate];

const actual: IItemTemplate[] = setNamesAndTitles(_templates, itemId);
const actual: IItemTemplate[] = setNamesAndTitles(_templates);
expect(actual).toEqual(expected);
});
});
Expand Down
Loading

0 comments on commit 296f707

Please sign in to comment.