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

129 lines 5.94 kB
/* jscpd:ignore-start */ import { SfCommand, Flags, requiredOrgFlagWithDeprecations } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; import c from 'chalk'; import { sortCrossPlatform, uxLog } from '../../../../common/utils/index.js'; import { soqlQuery } from '../../../../common/utils/apiUtils.js'; import { generateCsvFile, generateReportPath } from '../../../../common/utils/filesUtils.js'; import { NotifProvider } from '../../../../common/notifProvider/index.js'; import { setConnectionVariables } from '../../../../common/utils/orgUtils.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); const messages = Messages.loadMessages('sfdx-hardis', 'org'); export default class DiagnoseUnusedUsers extends SfCommand { static title = 'List licenses subscribed and used in a Salesforce org'; static description = `Mostly used for monitoring (Grafana) but you can also use it manually :)`; static examples = ['$ sf hardis:org:diagnose:licenses']; //Comment default values to test the prompts static flags = { outputfile: Flags.string({ char: 'f', description: 'Force the path and name of output report file. Must end with .csv', }), usedonly: Flags.boolean({ char: 'u', default: false, description: 'Filter to have only used licenses', }), 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; usedOnly = false; debugMode = false; outputFile; outputFilesRes = {}; licenses = []; statusCode = 0; /* jscpd:ignore-end */ async run() { const { flags } = await this.parse(DiagnoseUnusedUsers); this.usedOnly = flags.usedonly || false; this.debugMode = flags.debug || false; this.outputFile = flags.outputfile || null; // Retrieve the list of users who haven't logged in for a while const conn = flags['target-org'].getConnection(); uxLog(this, c.cyan(`Extracting Licenses from ${conn.instanceUrl} ...` + this.usedOnly ? '(used only)' : '')); const licensesByKey = {}; const usedLicenses = []; // Query User Licenses const userLicenseQuery = `Select MasterLabel, Name, TotalLicenses, UsedLicenses ` + `FROM UserLicense ` + `WHERE Status='Active' AND TotalLicenses > 0 ` + `ORDER BY MasterLabel`; const userLicenseQueryRes = await soqlQuery(userLicenseQuery, conn); const userLicenses = userLicenseQueryRes.records.map((userLicense) => { const userLicenseInfo = Object.assign({}, userLicense); delete userLicenseInfo.Id; delete userLicenseInfo.attributes; userLicenseInfo.type = 'UserLicense'; licensesByKey[userLicenseInfo.MasterLabel] = userLicenseInfo.TotalLicenses; if (userLicenseInfo.UsedLicenses > 0) { usedLicenses.push(userLicenseInfo.MasterLabel); } return userLicenseInfo; }); this.licenses.push(...userLicenses); // Query Permission Set Licenses let pslQuery = `SELECT MasterLabel, PermissionSetLicenseKey, TotalLicenses, UsedLicenses ` + `FROM PermissionSetLicense ` + `WHERE Status='Active' AND TotalLicenses > 0 `; if (this.usedOnly) { pslQuery += `AND UsedLicenses > 0 `; } pslQuery += `ORDER BY MasterLabel`; const pslQueryRes = await soqlQuery(pslQuery, conn); const pslLicenses = pslQueryRes.records.map((psl) => { const pslInfo = Object.assign({}, psl); pslInfo.Name = pslInfo.PermissionSetLicenseKey; delete pslInfo.Id; delete pslInfo.attributes; delete pslInfo.PermissionSetLicenseKey; pslInfo.type = 'PermissionSetLicense'; licensesByKey[pslInfo.MasterLabel] = pslInfo.TotalLicenses; if (pslInfo.UsedLicenses > 0) { usedLicenses.push(pslInfo.MasterLabel); } return pslInfo; }); this.licenses.push(...pslLicenses); sortCrossPlatform(usedLicenses); console.table(this.licenses); uxLog(this, c.cyan('Used licenses: ' + usedLicenses.join(', '))); // Generate output CSV file this.outputFile = await generateReportPath('licenses', this.outputFile); this.outputFilesRes = await generateCsvFile(this.licenses, this.outputFile); await setConnectionVariables(flags['target-org']?.getConnection()); // Required for some notifications providers like Email await NotifProvider.postNotifications({ type: 'LICENSES', text: '', severity: 'log', attachedFiles: this.outputFilesRes.xlsxFile ? [this.outputFilesRes.xlsxFile] : [], logElements: this.licenses, data: { activeLicenses: Object.keys(licensesByKey).sort(), usedLicenses: usedLicenses, licenses: licensesByKey, }, metrics: {}, }); // Return an object to be displayed with --json return { status: 0, licenses: this.licenses, csvLogFile: this.outputFile, xlsxLogFile: this.outputFilesRes.xlsxFile, }; } } //# sourceMappingURL=licenses.js.map