Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle multiple sandbox processes in resumable state #944

Merged
merged 2 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions src/org/org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export enum SandboxEvents {
EVENT_RESULT = 'result',
EVENT_AUTH = 'auth',
EVENT_RESUME = 'resume',
EVENT_MULTIPLE_SBX_PROCESSES = 'multipleMatchingSbxProcesses',
}

export interface SandboxUserAuthResponse {
Expand Down Expand Up @@ -246,11 +247,11 @@ export class Org extends AsyncOptionalCreatable<Org.Options> {
}

/**
* resume a sandbox creation from a production org
* 'this' needs to be a production org with sandbox licenses available
* Resume a sandbox creation from a production org.
* `this` needs to be a production org with sandbox licenses available.
*
* @param resumeSandboxRequest SandboxRequest options to create the sandbox with
* @param options Wait: The amount of time to wait (default: 30 minutes) before timing out,
* @param options Wait: The amount of time to wait (default: 0 minutes) before timing out,
* Interval: The time interval (default: 30 seconds) between polling
*/
public async resumeSandbox(
Expand All @@ -264,10 +265,23 @@ export class Org extends AsyncOptionalCreatable<Org.Options> {
this.logger.debug(resumeSandboxRequest, 'ResumeSandbox called with ResumeSandboxRequest');
let sandboxCreationProgress: SandboxProcessObject;
// seed the sandboxCreationProgress via the resumeSandboxRequest options
if (resumeSandboxRequest.SandboxName) {
sandboxCreationProgress = await this.querySandboxProcessBySandboxName(resumeSandboxRequest.SandboxName);
} else if (resumeSandboxRequest.SandboxProcessObjId) {
if (resumeSandboxRequest.SandboxProcessObjId) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer the SandboxProcessId over name since it's unique.

sandboxCreationProgress = await this.querySandboxProcessById(resumeSandboxRequest.SandboxProcessObjId);
} else if (resumeSandboxRequest.SandboxName) {
try {
// There can be multiple sandbox processes returned when querying by name. Use the most recent
// process and fire a warning event with all processes.
sandboxCreationProgress = await this.querySandboxProcessBySandboxName(resumeSandboxRequest.SandboxName);
} catch (err) {
if (err instanceof SfError && err.name === 'SingleRecordQuery_MultipleRecords' && err.data) {
const sbxProcesses = err.data as SandboxProcessObject[];
// 0 index will always be the most recently created process since the query sorts on created date desc.
sandboxCreationProgress = sbxProcesses[0];
await Lifecycle.getInstance().emit(SandboxEvents.EVENT_MULTIPLE_SBX_PROCESSES, sbxProcesses);
} else {
throw err;
}
}
} else {
throw messages.createError('sandboxNotFound', [
resumeSandboxRequest.SandboxName ?? resumeSandboxRequest.SandboxProcessObjId,
Expand Down Expand Up @@ -1332,9 +1346,7 @@ export class Org extends AsyncOptionalCreatable<Org.Options> {
let waitingOnAuth = false;
const pollingClient = await PollingClient.create({
poll: async (): Promise<StatusResult> => {
const sandboxProcessObj = await this.querySandboxProcessBySandboxInfoId(
options.sandboxProcessObj.SandboxInfoId
);
const sandboxProcessObj = await this.querySandboxProcessById(options.sandboxProcessObj.Id);
// check to see if sandbox can authenticate via sandboxAuth endpoint
const sandboxInfo = await this.sandboxSignupComplete(sandboxProcessObj);
if (sandboxInfo) {
Expand Down Expand Up @@ -1377,10 +1389,19 @@ export class Org extends AsyncOptionalCreatable<Org.Options> {
* @private
*/
private async querySandboxProcess(where: string): Promise<SandboxProcessObject> {
const queryStr = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE ${where} AND Status != 'D'`;
return this.connection.singleRecordQuery(queryStr, {
tooling: true,
});
const soql = `SELECT Id, Status, SandboxName, SandboxInfoId, LicenseType, CreatedDate, CopyProgress, SandboxOrganization, SourceId, Description, EndDate FROM SandboxProcess WHERE ${where} ORDER BY CreatedDate DESC`;
const result = (await this.connection.tooling.query<SandboxProcessObject>(soql)).records.filter(
(item) => !item.Status.startsWith('Del')
);
if (result.length === 0) {
throw new SfError(`No record found for ${soql}`, SingleRecordQueryErrors.NoRecords);
}
if (result.length > 1) {
const err = new SfError('The query returned more than 1 record', SingleRecordQueryErrors.MultipleRecords);
err.data = result;
throw err;
}
return result[0];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly what Jochen's PR is, with some additions to the query to ORDER BY and augment the MultipleRecords error (data property) with the sandbox processes.

}
/**
* determines if the sandbox has successfully been created
Expand Down
Loading
Loading