Skip to content

Commit

Permalink
NEW (Extension) @W-16096144@ Show Apex Guru command on explorer when …
Browse files Browse the repository at this point in the history
…org has Apex Guru perm enabled (#104)
  • Loading branch information
jag-j authored Jul 18, 2024
1 parent 5539249 commit 62231a8
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 71 deletions.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
{
"command": "sfca.runDfa",
"title": "***SFDX: Run Graph-Engine Based Analysis***"
},
{
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
"title": "***SFDX: Run Apex Guru Analysis***"
}
],
"configuration": {
Expand Down Expand Up @@ -197,6 +201,10 @@
{
"command": "sfca.removeDiagnosticsOnSelectedFile",
"when": "true"
},
{
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
"when": "sfca.apexGuruEnabled"
}
]
},
Expand Down
92 changes: 51 additions & 41 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {ScanRunner} from './lib/scanner';
import {SettingsManager} from './lib/settings';
import {SfCli} from './lib/sf-cli';

import {RuleResult} from './types';
import {RuleResult, ApexGuruAuthResponse} from './types';
import {DiagnosticManager} from './lib/diagnostics';
import {messages} from './lib/messages';
import {Fixer} from './lib/fixer';
Expand All @@ -23,7 +23,6 @@ import { SIGKILL } from 'constants';

export type RunInfo = {
diagnosticCollection?: vscode.DiagnosticCollection;
outputChannel: vscode.LogOutputChannel;
commandName: string;
}

Expand All @@ -35,6 +34,8 @@ let diagnosticCollection: vscode.DiagnosticCollection = null;

let customCancellationToken: vscode.CancellationTokenSource | null = null;

let outputChannel: vscode.LogOutputChannel;

/**
* This method is invoked when the extension is first activated (this is currently configured to be when a sfdx project is loaded).
* The activation trigger can be changed by changing activationEvents in package.json
Expand All @@ -43,8 +44,16 @@ let customCancellationToken: vscode.CancellationTokenSource | null = null;
export async function activate(context: vscode.ExtensionContext): Promise<vscode.ExtensionContext> {
const extensionHrStart = process.hrtime();

// Define a log output channel that we can use, and clear it so it's fresh.
outputChannel = vscode.window.createOutputChannel('sfca', {log: true});
outputChannel.clear();
outputChannel.show();

// We need to do this first in case any other services need access to those provided by the core extension.
await CoreExtensionService.loadDependencies(context);
await CoreExtensionService.loadDependencies(context, outputChannel);

// Set the necessary flags to control showing the command
await vscode.commands.executeCommand('setContext', 'sfca.apexGuruEnabled', Constants.APEX_GURU_FEATURE_FLAG_ENABLED && await _isApexGuruEnabledInOrg());

// Define a diagnostic collection in the `activate()` scope so it can be used repeatedly.
diagnosticCollection = vscode.languages.createDiagnosticCollection('sfca');
Expand All @@ -58,46 +67,37 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
})
);

// Define a log output channel that we can use, and clear it so it's fresh.
const outputChannel: vscode.LogOutputChannel = vscode.window.createOutputChannel('sfca', {log: true});
outputChannel.clear();
outputChannel.show();

// Declare our commands.
const runOnActiveFile = vscode.commands.registerCommand(Constants.COMMAND_RUN_ON_ACTIVE_FILE, async () => {
return _runAndDisplayPathless([], {
commandName: Constants.COMMAND_RUN_ON_ACTIVE_FILE,
diagnosticCollection,
outputChannel
diagnosticCollection
});
});
const runOnSelected = vscode.commands.registerCommand(Constants.COMMAND_RUN_ON_SELECTED, async (selection: vscode.Uri, multiSelect?: vscode.Uri[]) => {
return _runAndDisplayPathless(multiSelect && multiSelect.length > 0 ? multiSelect : [selection], {
commandName: Constants.COMMAND_RUN_ON_SELECTED,
diagnosticCollection,
outputChannel
diagnosticCollection
});
});
const removeDiagnosticsOnActiveFile = vscode.commands.registerCommand(Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_ACTIVE_FILE, async () => {
return _clearDiagnosticsForSelectedFiles([], {
commandName: Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_ACTIVE_FILE,
diagnosticCollection,
outputChannel
diagnosticCollection
});
});
const removeDiagnosticsOnSelectedFile = vscode.commands.registerCommand(Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE, async (selection: vscode.Uri, multiSelect?: vscode.Uri[]) => {
return _clearDiagnosticsForSelectedFiles(multiSelect && multiSelect.length > 0 ? multiSelect : [selection], {
commandName: Constants.COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE,
diagnosticCollection,
outputChannel
diagnosticCollection
});
});
const removeDiagnosticsInRange = vscode.commands.registerCommand(Constants.COMMAND_DIAGNOSTICS_IN_RANGE, (uri: vscode.Uri, range: vscode.Range) => {
_removeDiagnosticsInRange(uri, range, diagnosticCollection);
});
outputChannel.appendLine(`Registered command as part of sfdx-code-analyzer-vscode activation.`);
registerScanOnSave(outputChannel);
registerScanOnOpen(outputChannel);
registerScanOnSave();
registerScanOnOpen();
outputChannel.appendLine('Registered scanOnSave as part of sfdx-code-analyzer-vscode activation.');

// It is possible that the cache was not cleared when VS Code exited the last time. Just to be on the safe side, we clear the DFA process cache at activation.
Expand All @@ -111,7 +111,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
cancellable: true
}, async (progress, token) => {
token.onCancellationRequested(async () => {
await _stopExistingDfaRun(context, outputChannel);
await _stopExistingDfaRun(context);
});
customCancellationToken = new vscode.CancellationTokenSource();
customCancellationToken.token.onCancellationRequested(async () => {
Expand All @@ -126,23 +126,41 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
const projectDir: string = targeting.getProjectDir(currentFile);

return _runAndDisplayDfa(context, {
commandName: Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD,
outputChannel
commandName: Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD
}, customCancellationToken, methodLevelTarget, projectDir);
});
}
});

const runDfaOnWorkspaceCmd = vscode.commands.registerCommand(Constants.COMMAND_RUN_DFA, async () => {
await _runDfa(context, outputChannel);
await _runDfa(context);
});
context.subscriptions.push(runOnActiveFile, runOnSelected, runDfaOnSelectedMethodCmd, runDfaOnWorkspaceCmd, removeDiagnosticsOnActiveFile, removeDiagnosticsOnSelectedFile, removeDiagnosticsInRange);
TelemetryService.sendExtensionActivationEvent(extensionHrStart);
outputChannel.appendLine(`Extension sfdx-code-analyzer-vscode activated.`);
return Promise.resolve(context);
}

async function _runDfa(context: vscode.ExtensionContext, outputChannel: vscode.LogOutputChannel) {
export async function _isApexGuruEnabledInOrg(): Promise<boolean> {
try {
const connection = await CoreExtensionService.getConnection();
const response:ApexGuruAuthResponse = await connection.request({
method: 'GET',
url: Constants.APEX_GURU_AUTH_ENDPOINT,
body: ''
});
return response.status == 'Success';
} catch(e) {
// This could throw an error for a variety of reasons. The API endpoint has not been deployed to the instance, org has no perms, timeouts etc,.
// In all of these scenarios, we return false.
const errMsg = e instanceof Error ? e.message : e as string;
outputChannel.error('***ApexGuru perm check failed with error:***' + errMsg);
outputChannel.show();
return false;
}
}

async function _runDfa(context: vscode.ExtensionContext) {
if (violationsCacheExists()) {
const choice = await vscode.window.showQuickPick(
['***Yes***', '***No***'],
Expand All @@ -160,21 +178,21 @@ async function _runDfa(context: vscode.ExtensionContext, outputChannel: vscode.L
return;
} else {
void vscode.window.showWarningMessage('***A full run of the graph engine will happen in the background. You can cancel this by clicking on the status progress.***');
await runDfaOnWorkspace(context, outputChannel);
await runDfaOnWorkspace(context);
}
} else {
await runDfaOnWorkspace(context, outputChannel);
await runDfaOnWorkspace(context);
}
}

async function runDfaOnWorkspace(context: vscode.ExtensionContext, outputChannel: vscode.LogOutputChannel) {
async function runDfaOnWorkspace(context: vscode.ExtensionContext) {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Window,
title: messages.graphEngine.spinnerText,
cancellable: true
}, async (progress, token) => {
token.onCancellationRequested(async () => {
await _stopExistingDfaRun(context, outputChannel);
await _stopExistingDfaRun(context);
});
customCancellationToken = new vscode.CancellationTokenSource();
customCancellationToken.token.onCancellationRequested(async () => {
Expand All @@ -187,8 +205,7 @@ async function runDfaOnWorkspace(context: vscode.ExtensionContext, outputChannel
// We only have one project loaded on VSCode at once. So, projectDir should have only one entry and we use
// the root directory of that project as the projectDir argument to run DFA.
return _runAndDisplayDfa(context, {
commandName: Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD,
outputChannel,
commandName: Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD
}, customCancellationToken, null, targeting.getProjectDir());
});
}
Expand All @@ -208,7 +225,7 @@ function filterOutDiagnosticsInRange(currentDiagnostics: readonly vscode.Diagnos
return currentDiagnostics.filter(diagnostic => (diagnostic.range.start.line != range.start.line && diagnostic.range.end.line != range.end.line));
}

export async function _stopExistingDfaRun(context: vscode.ExtensionContext, outputChannel: vscode.LogOutputChannel): Promise<void> {
export async function _stopExistingDfaRun(context: vscode.ExtensionContext): Promise<void> {
const pid = context.workspaceState.get(Constants.WORKSPACE_DFA_PROCESS);
if (pid) {
try {
Expand Down Expand Up @@ -239,7 +256,7 @@ export async function verifyPluginInstallation(): Promise<void> {
}
}

export function registerScanOnSave(outputChannel: vscode.LogOutputChannel) {
export function registerScanOnSave() {
vscode.workspace.onDidSaveTextDocument(
async (textDocument: vscode.TextDocument) => {
const documentUri = textDocument.uri;
Expand All @@ -248,15 +265,14 @@ export function registerScanOnSave(outputChannel: vscode.LogOutputChannel) {
) {
await _runAndDisplayPathless([documentUri], {
commandName: Constants.COMMAND_RUN_ON_ACTIVE_FILE,
diagnosticCollection,
outputChannel
diagnosticCollection
});
}
}
);
}

export function registerScanOnOpen(outputChannel: vscode.LogOutputChannel) {
export function registerScanOnOpen() {
vscode.workspace.onDidOpenTextDocument(
async (textDocument: vscode.TextDocument) => {
const documentUri = textDocument.uri;
Expand All @@ -266,8 +282,7 @@ export function registerScanOnOpen(outputChannel: vscode.LogOutputChannel) {
if (_isValidFileForAnalysis(documentUri)) {
await _runAndDisplayPathless([documentUri], {
commandName: Constants.COMMAND_RUN_ON_ACTIVE_FILE,
diagnosticCollection,
outputChannel
diagnosticCollection
});
}
}
Expand All @@ -280,14 +295,12 @@ export function registerScanOnOpen(outputChannel: vscode.LogOutputChannel) {
* @param selections The files/directories manually selected by the user.
* @param runInfo A collection of services and information used to properly run the command.
* @param runInfo.diagnosticCollection The collection to which diagnostics representing violations should be added.
* @param runInfo.outputChannel The output channel where information should be logged as needed.
* @param runinfo.commandName The specific command being executed
* @returns
*/
export async function _runAndDisplayPathless(selections: vscode.Uri[], runInfo: RunInfo): Promise<void> {
const {
diagnosticCollection,
outputChannel,
commandName
} = runInfo;
const startTime = Date.now();
Expand Down Expand Up @@ -335,12 +348,10 @@ export async function _runAndDisplayPathless(selections: vscode.Uri[], runInfo:
* Run Path-based rules against the method the user has clicked on.
* @param statusBarItem The item to use in the status bar for displaying progress
* @param runInfo A collection of services and information used to properly run the command
* @param runInfo.outputChannel The output channel where information should be logged as needed
* @param runInfo.commandName The specific command being run
*/
export async function _runAndDisplayDfa(context:vscode.ExtensionContext ,runInfo: RunInfo, cancelToken: vscode.CancellationTokenSource, methodLevelTarget: string, projectDir: string): Promise<void> {
const {
outputChannel,
commandName
} = runInfo;
const startTime = Date.now();
Expand Down Expand Up @@ -403,7 +414,6 @@ export function _clearDiagnostics(): void {
export async function _clearDiagnosticsForSelectedFiles(selections: vscode.Uri[], runInfo: RunInfo): Promise<void> {
const {
diagnosticCollection,
outputChannel,
commandName
} = runInfo;
const startTime = Date.now();
Expand Down
6 changes: 6 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1';

// cache names
export const WORKSPACE_DFA_PROCESS = 'dfaScanProcess';

// apex guru APIS
export const APEX_GURU_AUTH_ENDPOINT = '/services/data/v62.0/apexguru/validate'

// feature gates
export const APEX_GURU_FEATURE_FLAG_ENABLED = false;
Loading

0 comments on commit 62231a8

Please sign in to comment.