Skip to content

Commit

Permalink
Merge pull request #124 from forcedotcom/release-1.1.0
Browse files Browse the repository at this point in the history
RELEASE @W-16382721@: Conducting 1.1.0 release
  • Loading branch information
jag-j authored Aug 27, 2024
2 parents 4ed5252 + 2378c29 commit 5ccd634
Show file tree
Hide file tree
Showing 19 changed files with 1,485 additions and 547 deletions.
2 changes: 1 addition & 1 deletion SHA256.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ make sure that their SHA values match the values in the list below.
shasum -a 256 <location_of_the_downloaded_file>

3. Confirm that the SHA in your output matches the value in this list of SHAs.
7ddd2068fbe9a58bbb8fdf81f80964bd645e7a3112aaace72b3da1ca3dad05f5 ./extensions/sfdx-code-analyzer-vscode-0.7.0.vsix
172c7b2973ea3d66feaae5017e7eae0de384340a137d59305ef3c1bc4114a04f ./extensions/sfdx-code-analyzer-vscode-1.0.0.vsix
4. Change the filename extension for the file that you downloaded from .zip to
.vsix.

Expand Down
23 changes: 20 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"color": "#ECECEC",
"theme": "light"
},
"version": "1.0.0",
"version": "1.1.0",
"publisher": "salesforce",
"license": "BSD-3-Clause",
"engines": {
Expand Down Expand Up @@ -105,7 +105,11 @@
},
{
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
"title": "***SFDX: Run Apex Guru Analysis***"
"title": "SFDX: Scan Selected File for Performance Issues with ApexGuru"
},
{
"command": "sfca.runApexGuruAnalysisOnCurrentFile",
"title": "SFDX: Scan Current File for Performance Issues with ApexGuru"
}
],
"configuration": {
Expand Down Expand Up @@ -158,6 +162,11 @@
"codeAnalyzer.rules.category": {
"type": "string",
"description": "The categories of rules to run. Specify multiple values as a comma-separated list. Run 'sf scanner rule list -e' in the terminal for the list of categories associated with a specific engine."
},
"codeAnalyzer.apexGuru.enabled": {
"type": "boolean",
"default": false,
"description": "Discover critical problems and performance issues in your Apex code with ApexGuru, which analyzes your Apex files for you. This feature is in a closed pilot; contact your account representative to learn more."
}
}
},
Expand Down Expand Up @@ -190,6 +199,10 @@
{
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
"when": "false"
},
{
"command": "sfca.runApexGuruAnalysisOnCurrentFile",
"when": "sfca.apexGuruEnabled && resourceExtname =~ /\\.cls|\\.trigger|\\.apex/"
}
],
"editor/context": [
Expand All @@ -201,6 +214,10 @@
},
{
"command": "sfca.removeDiagnosticsOnActiveFile"
},
{
"command": "sfca.runApexGuruAnalysisOnCurrentFile",
"when": "sfca.apexGuruEnabled && resourceExtname =~ /\\.cls|\\.trigger|\\.apex/"
}
],
"explorer/context": [
Expand All @@ -213,7 +230,7 @@
},
{
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
"when": "sfca.apexGuruEnabled"
"when": "sfca.apexGuruEnabled && resourceExtname =~ /\\.cls|\\.trigger|\\.apex/"
}
]
},
Expand Down
185 changes: 185 additions & 0 deletions src/apexguru/apex-guru-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright (c) 2024, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import * as vscode from 'vscode';
import * as fspromises from 'fs/promises';
import { CoreExtensionService, Connection, TelemetryService } from '../lib/core-extension-service';
import * as Constants from '../lib/constants';
import {messages} from '../lib/messages';
import { RuleResult, ApexGuruViolation } from '../types';
import { DiagnosticManager } from '../lib/diagnostics';
import { RunInfo } from '../extension';

export async function isApexGuruEnabledInOrg(outputChannel: vscode.LogOutputChannel): 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('Apex Guru perm check failed with error:' + errMsg);
outputChannel.show();
return false;
}
}

export async function runApexGuruOnFile(selection: vscode.Uri, runInfo: RunInfo) {
const {
diagnosticCollection,
commandName,
outputChannel
} = runInfo;
const startTime = Date.now();
try {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification
}, async (progress) => {
progress.report(messages.apexGuru.progress);
const connection = await CoreExtensionService.getConnection();
const requestId = await initiateApexGuruRequest(selection, outputChannel, connection);
outputChannel.appendLine('Code Analyzer with ApexGuru request Id:' + requestId);

const queryResponse: ApexGuruQueryResponse = await pollAndGetApexGuruResponse(connection, requestId, Constants.APEX_GURU_MAX_TIMEOUT_SECONDS, Constants.APEX_GURU_RETRY_INTERVAL_MILLIS);

const decodedReport = Buffer.from(queryResponse.report, 'base64').toString('utf8');

const ruleResult = transformStringToRuleResult(selection.fsPath, decodedReport);
new DiagnosticManager().displayDiagnostics([selection.fsPath], [ruleResult], diagnosticCollection);
TelemetryService.sendCommandEvent(Constants.TELEM_SUCCESSFUL_APEX_GURU_FILE_ANALYSIS, {
executedCommand: commandName,
duration: (Date.now() - startTime).toString()
});
void vscode.window.showInformationMessage(messages.apexGuru.finishedScan(ruleResult.violations.length));
});
} catch (e) {
const errMsg = e instanceof Error ? e.message : e as string;
outputChannel.error('Initial Code Analyzer with ApexGuru request failed.');
outputChannel.appendLine(errMsg);
}
}

export async function pollAndGetApexGuruResponse(connection: Connection, requestId: string, maxWaitTimeInSeconds: number, retryIntervalInMillis: number): Promise<ApexGuruQueryResponse> {
let queryResponse: ApexGuruQueryResponse;
let lastErrorMessage = '';
const startTime = Date.now();
while ((Date.now() - startTime) < maxWaitTimeInSeconds * 1000) {
try {
queryResponse = await connection.request({
method: 'GET',
url: `${Constants.APEX_GURU_REQUEST}/${requestId}`,
body: ''
});
if (queryResponse.status == 'success') {
return queryResponse;
}
} catch (error) {
lastErrorMessage = (error as Error).message;
}
await new Promise(resolve => setTimeout(resolve, retryIntervalInMillis));

}
if (queryResponse) {
return queryResponse;
}
throw new Error(`Failed to get a successful response from Apex Guru after maximum retries.${lastErrorMessage}`);
}

export async function initiateApexGuruRequest(selection: vscode.Uri, outputChannel: vscode.LogOutputChannel, connection: Connection): Promise<string> {
const fileContent = await fileSystem.readFile(selection.fsPath);
const base64EncodedContent = Buffer.from(fileContent).toString('base64');
const response: ApexGuruInitialResponse = await connection.request({
method: 'POST',
url: Constants.APEX_GURU_REQUEST,
body: JSON.stringify({
classContent: base64EncodedContent
})
});

if (response.status != 'new' && response.status != 'success') {
outputChannel.warn('Code Analyzer with Apex Guru returned unexpected response:' + response.status);
throw Error('Code Analyzer with Apex Guru returned unexpected response:' + response.status);
}

const requestId = response.requestId;
return requestId;
}

export const fileSystem = {
readFile: (path: string) => fspromises.readFile(path, 'utf8')
};

export function transformStringToRuleResult(fileName: string, jsonString: string): RuleResult {
const reports = JSON.parse(jsonString) as ApexGuruReport[];

const ruleResult: RuleResult = {
engine: 'apexguru',
fileName: fileName,
violations: []
};

reports.forEach(parsed => {
const encodedCodeBefore =
parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'code_before')?.value
?? parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'class_before')?.value
?? '';
const encodedCodeAfter =
parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'code_after')?.value
?? parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'class_after')?.value
?? '';
const lineNumber = parseInt(parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'line_number')?.value);

const violation: ApexGuruViolation = {
ruleName: parsed.type,
message: parsed.value,
severity: 1,
category: parsed.type, // Replace with actual category if available
line: lineNumber,
column: 1,
currentCode: Buffer.from(encodedCodeBefore, 'base64').toString('utf8'),
suggestedCode: Buffer.from(encodedCodeAfter, 'base64').toString('utf8'),
url: fileName
};

ruleResult.violations.push(violation);
});

return ruleResult;
}

export type ApexGuruAuthResponse = {
status: string;
}

export type ApexGuruInitialResponse = {
status: string;
requestId: string;
message: string;
}

export type ApexGuruQueryResponse = {
status: string;
message?: string;
report?: string;
}

export type ApexGuruProperty = {
name: string;
value: string;
};

export type ApexGuruReport = {
id: string;
type: string;
value: string;
properties: ApexGuruProperty[];
}
49 changes: 28 additions & 21 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import {ScanRunner} from './lib/scanner';
import {SettingsManager} from './lib/settings';
import {SfCli} from './lib/sf-cli';

import {RuleResult, ApexGuruAuthResponse} from './types';
import {RuleResult} from './types';
import {DiagnosticManager} from './lib/diagnostics';
import {messages} from './lib/messages';
import {Fixer} from './lib/fixer';
import { CoreExtensionService, TelemetryService } from './lib/core-extension-service';
import * as Constants from './lib/constants';
import * as path from 'path';
import { SIGKILL } from 'constants';
import * as ApexGuruFunctions from './apexguru/apex-guru-service'

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

/**
Expand Down Expand Up @@ -52,8 +54,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
// We need to do this first in case any other services need access to those provided by the core extension.
await CoreExtensionService.loadDependencies(context, outputChannel);

const apexGuruFeatureFlag = SettingsManager.getApexGuruEnabled();
const apexGuruEnabled = apexGuruFeatureFlag && await ApexGuruFunctions.isApexGuruEnabledInOrg(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());
await vscode.commands.executeCommand('setContext', 'sfca.apexGuruEnabled', apexGuruEnabled);

// Define a diagnostic collection in the `activate()` scope so it can be used repeatedly.
diagnosticCollection = vscode.languages.createDiagnosticCollection('sfca');
Expand Down Expand Up @@ -136,30 +140,33 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
await _runDfa(context);
});
context.subscriptions.push(runOnActiveFile, runOnSelected, runDfaOnSelectedMethodCmd, runDfaOnWorkspaceCmd, removeDiagnosticsOnActiveFile, removeDiagnosticsOnSelectedFile, removeDiagnosticsInRange);

if (apexGuruEnabled) {
const runApexGuruOnSelectedFile = vscode.commands.registerCommand(Constants.COMMAND_RUN_APEX_GURU_ON_FILE, async (selection: vscode.Uri, multiSelect?: vscode.Uri[]) => {
return await ApexGuruFunctions.runApexGuruOnFile(multiSelect && multiSelect.length > 0 ? multiSelect[0] : selection,
{
commandName: Constants.COMMAND_RUN_APEX_GURU_ON_FILE,
diagnosticCollection,
outputChannel: outputChannel
});
});
const runApexGuruOnCurrentFile = vscode.commands.registerCommand(Constants.COMMAND_RUN_APEX_GURU_ON_ACTIVE_FILE, async () => {
const targets: string[] = await targeting.getTargets([]);
return await ApexGuruFunctions.runApexGuruOnFile(vscode.Uri.file(targets[0]),
{
commandName: Constants.COMMAND_RUN_APEX_GURU_ON_ACTIVE_FILE,
diagnosticCollection,
outputChannel: outputChannel
});
});
context.subscriptions.push(runApexGuruOnSelectedFile, runApexGuruOnCurrentFile);
}

TelemetryService.sendExtensionActivationEvent(extensionHrStart);
outputChannel.appendLine(`Extension sfdx-code-analyzer-vscode activated.`);
return Promise.resolve(context);
}

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(
Expand Down
11 changes: 7 additions & 4 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ export const COMMAND_RUN_DFA_ON_SELECTED_METHOD = 'sfca.runDfaOnSelectedMethod';
export const COMMAND_RUN_DFA = 'sfca.runDfa';
export const COMMAND_REMOVE_DIAGNOSTICS_ON_ACTIVE_FILE = 'sfca.removeDiagnosticsOnActiveFile';
export const COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE = 'sfca.removeDiagnosticsOnSelectedFile';
export const COMMAND_DIAGNOSTICS_IN_RANGE = 'sfca.removeDiagnosticsInRange'

export const COMMAND_DIAGNOSTICS_IN_RANGE = 'sfca.removeDiagnosticsInRange';
export const COMMAND_RUN_APEX_GURU_ON_FILE = 'sfca.runApexGuruAnalysisOnSelectedFile';
export const COMMAND_RUN_APEX_GURU_ON_ACTIVE_FILE = 'sfca.runApexGuruAnalysisOnCurrentFile';

// telemetry event keys
export const TELEM_SUCCESSFUL_STATIC_ANALYSIS = 'sfdx__codeanalyzer_static_run_complete';
export const TELEM_FAILED_STATIC_ANALYSIS = 'sfdx__codeanalyzer_static_run_failed';
export const TELEM_SUCCESSFUL_DFA_ANALYSIS = 'sfdx__codeanalyzer_dfa_run_complete';
export const TELEM_FAILED_DFA_ANALYSIS = 'sfdx__codeanalyzer_dfa_run_failed';

export const TELEM_SUCCESSFUL_APEX_GURU_FILE_ANALYSIS = 'sfdx__apexguru_file_run_complete';

// versioning
export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1';
Expand All @@ -34,6 +35,8 @@ export const WORKSPACE_DFA_PROCESS = 'dfaScanProcess';

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

// feature gates
export const APEX_GURU_FEATURE_FLAG_ENABLED = false;
export const APEX_GURU_MAX_TIMEOUT_SECONDS = 60;
export const APEX_GURU_RETRY_INTERVAL_MILLIS = 1000;
Loading

0 comments on commit 5ccd634

Please sign in to comment.