UNPKG

sfdx-hardis

Version:

Swiss-army-knife Toolbox for Salesforce. Allows you to define a complete CD/CD Pipeline. Orchestrate base commands and assist users with interactive wizards

209 lines (202 loc) • 11.7 kB
/* jscpd:ignore-start */ import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; import c from 'chalk'; import { generateReports, isCI, uxLog, uxLogTable } from '../../../../common/utils/index.js'; import { promptProfiles } from '../../../../common/utils/orgUtils.js'; //import { executeApex } from "../../../../common/utils/deployUtils.js"; import { prompts } from '../../../../common/utils/prompts.js'; import { soqlQuery, bulkQuery, bulkUpdate } from '../../../../common/utils/apiUtils.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('sfdx-hardis', 'org'); export default class OrgUnfreezeUser extends SfCommand { static title = 'Unfreeze user logins'; static description = ` ## Command Behavior **Unfreezes Salesforce user logins, restoring access for selected users.** This command allows administrators to unfreeze Salesforce user logins, reactivating their access to the Salesforce org. This is the counterpart to the \`freeze\` command and is used to restore access after a temporary suspension. Key functionalities: - **User Selection:** You can select users to unfreeze based on their assigned profiles. - \`--includeprofiles\`: Unfreeze users belonging to a comma-separated list of specified profiles. - \`--excludeprofiles\`: Unfreeze users belonging to all profiles *except* those specified in a comma-separated list. - If no profile flags are provided, an interactive menu will allow you to select profiles. - **Interactive Confirmation:** In non-CI environments, it prompts for confirmation before unfreezing the selected users. - **Bulk Unfreezing:** Efficiently unfreezes multiple user logins using Salesforce's Bulk API. - **Reporting:** Generates CSV and XLSX reports of the users that are about to be unfrozen. <details markdown="1"> <summary>Technical explanations</summary> The command's technical implementation involves: - **SOQL Queries (Bulk API):** It executes SOQL queries against the \`User\` and \`Profile\` objects to identify active users based on the provided profile filters. It then queries the \`UserLogin\` object to find frozen login sessions for these users. - **Interactive Prompts:** Uses the \`prompts\` library to guide the user through profile selection and to confirm the unfreezing operation. - **Bulk Update:** It constructs an array of \`UserLogin\` records with their \`Id\` and \`IsFrozen\` set to \`false\`, then uses \`bulkUpdate\` to perform the mass update operation on the Salesforce org. - **Reporting:** It uses \`generateReports\` to create CSV and XLSX files containing details of the users to be unfrozen. - **Logging:** Provides clear messages about the number of users found and the success of the unfreezing process. </details> `; static examples = [ `$ sf hardis:org:user:unfreeze`, `$ sf hardis:org:user:unfreeze --target-org my-user@myorg.com`, `$ sf hardis:org:user:unfreeze --includeprofiles 'Standard'`, `$ sf hardis:org:user:unfreeze --excludeprofiles 'System Administrator,Some Other Profile'`, ]; // public static args = [{name: 'file'}]; static flags = { // flag with a value (-n, --name=VALUE) name: Flags.string({ char: 'n', description: messages.getMessage('nameFilter'), }), includeprofiles: Flags.string({ char: 'p', description: 'List of profiles that you want to unfreeze, separated by commas', }), excludeprofiles: Flags.string({ char: 'e', description: 'List of profiles that you want to NOT unfreeze, separated by commas', }), maxuserdisplay: Flags.integer({ char: 'm', default: 100, description: 'Maximum users to display in logs', }), debug: Flags.boolean({ char: 'd', default: false, description: messages.getMessage('debugMode'), }), websocket: Flags.string({ description: messages.getMessage('websocket'), }), skipauth: Flags.boolean({ description: 'Skip authentication check when a default username is required', }), 'target-org': requiredOrgFlagWithDeprecations, }; // Set this to true if your command requires a project workspace; 'requiresProject' is false by default static requiresProject = false; maxUsersDisplay = 100; debugMode = false; /* jscpd:ignore-end */ async run() { const { flags } = await this.parse(OrgUnfreezeUser); const includeProfileNames = flags.includeprofiles ? flags.includeprofiles.split(',') : []; const excludeProfileNames = flags.excludeprofiles ? flags.excludeprofiles.split(',') : []; this.maxUsersDisplay = flags.maxuserdisplay || 100; this.debugMode = flags.debug || false; const conn = flags['target-org'].getConnection(); // Select profiles that we want users to be unfrozen let profileIds = []; let profileNames = []; if (includeProfileNames.length === 0 && excludeProfileNames.length === 0) { // Manual user selection const profilesRes = await promptProfiles(conn, { multiselect: true, message: 'Please select profiles that you do you want to unfreeze users that are assigned to them ?', returnField: 'record', }); profileIds = profilesRes.map((profile) => profile.Id); profileNames = profilesRes.map((profile) => { return [profile.Id, profile.Name]; }); } else if (includeProfileNames.length > 0) { // Use includeprofiles argument const profilesConstraintIn = includeProfileNames.map((profileName) => `'${profileName}'`).join(','); const profilesQuery = `SELECT Id,Name FROM Profile WHERE Name IN (${profilesConstraintIn})`; const profilesQueryRes = await soqlQuery(profilesQuery, conn); if (this.debugMode) { uxLog("log", this, c.grey(`Query result:\n${JSON.stringify(profilesQueryRes, null, 2)}`)); } profileIds = profilesQueryRes.records.map((profile) => profile.Id); profileNames = profilesQueryRes.records.map((profile) => { return [profile.Id, profile.Name]; }); } else if (excludeProfileNames.length > 0) { // Use excludeprofiles argument const profilesConstraintIn = excludeProfileNames.map((profileName) => `'${profileName}'`).join(','); const profilesQuery = `SELECT Id,Name FROM Profile WHERE Name NOT IN (${profilesConstraintIn})`; const profilesQueryRes = await soqlQuery(profilesQuery, conn); if (this.debugMode) { uxLog("log", this, c.grey(`Query result:\n${JSON.stringify(profilesQueryRes, null, 2)}`)); } profileIds = profilesQueryRes.records.map((profile) => profile.Id); profileNames = profilesQueryRes.records.map((profile) => { return [profile.Id, profile.Name]; }); } // List profiles that must be unfrozen const profileIdsStr = profileIds.map((profileId) => `'${profileId}'`).join(','); // Query users that we want to unfreeze uxLog("action", this, c.cyan(`Querying User records matching ${c.bold(profileIds.length)} profiles...`)); const userQuery = `SELECT Id,Name,Username,ProfileId FROM User WHERE ProfileId IN (${profileIdsStr}) and IsActive=true`; const userQueryRes = await bulkQuery(userQuery, conn); const usersToUnfreeze = userQueryRes.records; const userIdsStr = usersToUnfreeze.map((user) => `'${user.Id}'`).join(','); // Check empty result if (usersToUnfreeze.length === 0) { const outputString = `No matching user records found with defined profile constraints`; uxLog("warning", this, c.yellow(outputString)); return { outputString }; } // Query related UserLogin records uxLog("action", this, c.cyan(`Querying UserLogin records matching ${c.bold(usersToUnfreeze.length)} users...`)); const userLoginQuery = `SELECT Id,UserId,IsFrozen FROM UserLogin WHERE UserId IN (${userIdsStr}) and IsFrozen=true`; const userLoginQueryRes = await bulkQuery(userLoginQuery, conn); const userLoginsToUnfreeze = userLoginQueryRes.records; // Display list of users to unfreeze const usersToUnfreezeDisplay = userLoginsToUnfreeze.map((userLogin) => { const matchingUser = usersToUnfreeze.filter((user) => user.Id === userLogin.UserId)[0]; return { Username: matchingUser.Username, Name: matchingUser.Name, Profile: profileNames.filter((profile) => profile[0] === matchingUser.ProfileId)[1], }; }); uxLog("action", this, c.cyan(`List of users to unfreeze (${userLoginsToUnfreeze.length}):`)); uxLogTable(this, this.debugMode ? usersToUnfreezeDisplay : usersToUnfreezeDisplay.slice(0, this.maxUsersDisplay)); if (!this.debugMode && usersToUnfreezeDisplay.length > this.maxUsersDisplay) { uxLog("warning", this, c.yellow(c.italic(`(list truncated to the first ${this.maxUsersDisplay} users)`))); } // Generate csv + xls of users about to be unfrozen await generateReports(usersToUnfreezeDisplay, ['Username', 'Name', 'Profile'], this, { logFileName: 'users-to-unfreeze', logLabel: 'Extract of users to unfreeze', }); // Request configuration from user if (!isCI) { const confirmunfreeze = await prompts({ type: 'confirm', name: 'value', initial: true, message: c.cyanBright(`Are you sure you want to unfreeze these ${c.bold(userLoginsToUnfreeze.length)} users in org ${c.green(flags['target-org'].getUsername())} ?`), description: 'Confirm unfreezing selected users, which will reactivate their accounts in the Salesforce org', }); if (confirmunfreeze.value !== true) { const outputString = 'Script cancelled by user.'; uxLog("warning", this, c.yellow(outputString)); return { outputString }; } } // Process UserLogin freezing const userLoginsFrozen = userLoginsToUnfreeze.map((userLogin) => { return { Id: userLogin.Id, IsFrozen: false }; }); const bulkUpdateRes = await bulkUpdate('UserLogin', 'update', userLoginsFrozen, conn); const unfreezeSuccessNb = bulkUpdateRes.successfulResults.length; const unfreezeErrorsNb = bulkUpdateRes.failedResults.length; if (unfreezeErrorsNb > 0) { uxLog("warning", this, c.yellow(`Warning: ${c.red(c.bold(unfreezeErrorsNb))} users has not been unfrozen (bulk API errors)`)); } // Build results summary uxLog("success", this, c.green(`${c.bold(unfreezeSuccessNb)} users has been be unfrozen.`)); // Return an object to be displayed with --json return { orgId: flags['target-org'].getOrgId(), unfreezeSuccess: unfreezeSuccessNb, unfreezeErrors: unfreezeErrorsNb, outputString: `${unfreezeSuccessNb} users has been be unfrozen`, }; } } //# sourceMappingURL=unfreeze.js.map