From 4ce808024d8ce91ddfedb22a8133b653da0b7893 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 23 Feb 2024 09:58:10 -0600 Subject: [PATCH] feat: fix Failures table and handle partial success avoiding throws --- .../user/permsetlicense/assign.ts | 77 +++++++++++-------- src/commands/org/assign/permsetlicense.ts | 13 ++-- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/baseCommands/user/permsetlicense/assign.ts b/src/baseCommands/user/permsetlicense/assign.ts index e5513a4c..92073ba3 100644 --- a/src/baseCommands/user/permsetlicense/assign.ts +++ b/src/baseCommands/user/permsetlicense/assign.ts @@ -42,31 +42,30 @@ export const assignPSL = async ({ const logger = await Logger.child('assignPSL'); logger.debug(`will assign perm set license "${pslName}" to users: ${usernamesOrAliases.join(', ')}`); - const pslId = await queryPsl(conn, pslName); - - return ( - await Promise.all( - usernamesOrAliases.map((usernameOrAlias) => - usernameToPSLAssignment({ - pslName, - usernameOrAlias, - pslId, - conn, - }) + const queryResult = await queryPsl({ conn, pslName, usernamesOrAliases }); + + return typeof queryResult === 'string' + ? aggregate( + await Promise.all( + usernamesOrAliases.map((usernameOrAlias) => + usernameToPSLAssignment({ + pslName, + usernameOrAlias, + pslId: queryResult, + conn, + }) + ) + ) ) - ) - ).reduce( - (acc, result) => - isSuccess(result) - ? { ...acc, successes: [...acc.successes, result] } - : { ...acc, failures: [...acc.failures, result] }, - { - successes: [], - failures: [], - } - ); + : queryResult; }; +/** reduce an array of PSLResult to a single one */ +export const aggregate = (results: PSLResult[]): PSLResult => ({ + successes: results.flatMap((r) => r.successes), + failures: results.flatMap((r) => r.failures), +}); + export const resultsToExitCode = (results: PSLResult): number => { if (results.failures.length && results.successes.length) { return 68; @@ -75,7 +74,7 @@ export const resultsToExitCode = (results: PSLResult): number => { } else if (results.successes.length) { return 0; } - throw new SfError('Invalid results'); + throw new SfError('Unexpected state: no successes and no failures. This should not happen.'); }; export const print = (results: PSLResult): void => { @@ -97,7 +96,7 @@ export const print = (results: PSLResult): void => { } ux.styledHeader('Failures'); - ux.table(results.failures, { name: { header: 'Username' } }, { message: { header: 'Error Message' } }); + ux.table(results.failures, { name: { header: 'Username' }, message: { header: 'Error Message' } }); } }; @@ -112,7 +111,7 @@ const usernameToPSLAssignment = async ({ usernameOrAlias: string; pslId: string; conn: Connection; -}): Promise => { +}): Promise => { // Convert any aliases to usernames const resolvedUsername = (await StateAggregator.getInstance()).aliases.resolveUsername(usernameOrAlias); @@ -125,31 +124,41 @@ const usernameToPSLAssignment = async ({ AssigneeId, PermissionSetLicenseId: pslId, }); - return { + return toResult({ name: resolvedUsername, value: pslName, - }; + }); } catch (e) { // idempotency. If user(s) already have PSL, the API will throw an error about duplicate value. // but we're going to call that a success if (e instanceof Error && e.message.startsWith('duplicate value found')) { await Lifecycle.getInstance().emitWarning(messages.getMessage('duplicateValue', [resolvedUsername, pslName])); - return { + return toResult({ name: resolvedUsername, value: pslName, - }; + }); } else { - return { + return toResult({ name: resolvedUsername, message: e instanceof Error ? e.message : 'error contained no message', - }; + }); } } }; +const toResult = (input: SuccessMsg | FailureMsg): PSLResult => + isSuccess(input) ? { successes: [input], failures: [] } : { successes: [], failures: [input] }; const isSuccess = (input: SuccessMsg | FailureMsg): input is SuccessMsg => (input as SuccessMsg).value !== undefined; -const queryPsl = async (conn: Connection, pslName: string): Promise => { +const queryPsl = async ({ + conn, + pslName, + usernamesOrAliases, +}: { + conn: Connection; + pslName: string; + usernamesOrAliases: string[]; +}): Promise => { try { return ( await conn.singleRecordQuery( @@ -157,6 +166,8 @@ const queryPsl = async (conn: Connection, pslName: string): Promise => { ) ).Id; } catch (e) { - throw new SfError('PermissionSetLicense not found'); + return aggregate( + usernamesOrAliases.map((name) => toResult({ name, message: `PermissionSetLicense not found: ${pslName}` })) + ); } }; diff --git a/src/commands/org/assign/permsetlicense.ts b/src/commands/org/assign/permsetlicense.ts index d4bea696..f8fc98b2 100644 --- a/src/commands/org/assign/permsetlicense.ts +++ b/src/commands/org/assign/permsetlicense.ts @@ -8,7 +8,13 @@ import { Messages } from '@salesforce/core'; import { arrayWithDeprecation, Flags, SfCommand } from '@salesforce/sf-plugins-core'; import { ensureArray } from '@salesforce/kit'; -import { assignPSL, print, PSLResult, resultsToExitCode } from '../../../baseCommands/user/permsetlicense/assign.js'; +import { + aggregate, + assignPSL, + print, + PSLResult, + resultsToExitCode, +} from '../../../baseCommands/user/permsetlicense/assign.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('@salesforce/plugin-user', 'permsetlicense.assign'); @@ -56,8 +62,3 @@ export class AssignPermSetLicenseCommand extends SfCommand { return result; } } - -const aggregate = (results: PSLResult[]): PSLResult => ({ - successes: results.flatMap((r) => r.successes), - failures: results.flatMap((r) => r.failures), -});