Skip to content

Commit 5ccd634

Browse files
authored
Merge pull request #124 from forcedotcom/release-1.1.0
RELEASE @W-16382721@: Conducting 1.1.0 release
2 parents 4ed5252 + 2378c29 commit 5ccd634

19 files changed

+1485
-547
lines changed

SHA256.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ make sure that their SHA values match the values in the list below.
1515
shasum -a 256 <location_of_the_downloaded_file>
1616

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

package.json

+20-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"color": "#ECECEC",
1414
"theme": "light"
1515
},
16-
"version": "1.0.0",
16+
"version": "1.1.0",
1717
"publisher": "salesforce",
1818
"license": "BSD-3-Clause",
1919
"engines": {
@@ -105,7 +105,11 @@
105105
},
106106
{
107107
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
108-
"title": "***SFDX: Run Apex Guru Analysis***"
108+
"title": "SFDX: Scan Selected File for Performance Issues with ApexGuru"
109+
},
110+
{
111+
"command": "sfca.runApexGuruAnalysisOnCurrentFile",
112+
"title": "SFDX: Scan Current File for Performance Issues with ApexGuru"
109113
}
110114
],
111115
"configuration": {
@@ -158,6 +162,11 @@
158162
"codeAnalyzer.rules.category": {
159163
"type": "string",
160164
"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."
165+
},
166+
"codeAnalyzer.apexGuru.enabled": {
167+
"type": "boolean",
168+
"default": false,
169+
"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."
161170
}
162171
}
163172
},
@@ -190,6 +199,10 @@
190199
{
191200
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
192201
"when": "false"
202+
},
203+
{
204+
"command": "sfca.runApexGuruAnalysisOnCurrentFile",
205+
"when": "sfca.apexGuruEnabled && resourceExtname =~ /\\.cls|\\.trigger|\\.apex/"
193206
}
194207
],
195208
"editor/context": [
@@ -201,6 +214,10 @@
201214
},
202215
{
203216
"command": "sfca.removeDiagnosticsOnActiveFile"
217+
},
218+
{
219+
"command": "sfca.runApexGuruAnalysisOnCurrentFile",
220+
"when": "sfca.apexGuruEnabled && resourceExtname =~ /\\.cls|\\.trigger|\\.apex/"
204221
}
205222
],
206223
"explorer/context": [
@@ -213,7 +230,7 @@
213230
},
214231
{
215232
"command": "sfca.runApexGuruAnalysisOnSelectedFile",
216-
"when": "sfca.apexGuruEnabled"
233+
"when": "sfca.apexGuruEnabled && resourceExtname =~ /\\.cls|\\.trigger|\\.apex/"
217234
}
218235
]
219236
},

src/apexguru/apex-guru-service.ts

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright (c) 2024, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import * as vscode from 'vscode';
9+
import * as fspromises from 'fs/promises';
10+
import { CoreExtensionService, Connection, TelemetryService } from '../lib/core-extension-service';
11+
import * as Constants from '../lib/constants';
12+
import {messages} from '../lib/messages';
13+
import { RuleResult, ApexGuruViolation } from '../types';
14+
import { DiagnosticManager } from '../lib/diagnostics';
15+
import { RunInfo } from '../extension';
16+
17+
export async function isApexGuruEnabledInOrg(outputChannel: vscode.LogOutputChannel): Promise<boolean> {
18+
try {
19+
const connection = await CoreExtensionService.getConnection();
20+
const response:ApexGuruAuthResponse = await connection.request({
21+
method: 'GET',
22+
url: Constants.APEX_GURU_AUTH_ENDPOINT,
23+
body: ''
24+
});
25+
return response.status == 'Success';
26+
} catch(e) {
27+
// 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,.
28+
// In all of these scenarios, we return false.
29+
const errMsg = e instanceof Error ? e.message : e as string;
30+
outputChannel.error('Apex Guru perm check failed with error:' + errMsg);
31+
outputChannel.show();
32+
return false;
33+
}
34+
}
35+
36+
export async function runApexGuruOnFile(selection: vscode.Uri, runInfo: RunInfo) {
37+
const {
38+
diagnosticCollection,
39+
commandName,
40+
outputChannel
41+
} = runInfo;
42+
const startTime = Date.now();
43+
try {
44+
await vscode.window.withProgress({
45+
location: vscode.ProgressLocation.Notification
46+
}, async (progress) => {
47+
progress.report(messages.apexGuru.progress);
48+
const connection = await CoreExtensionService.getConnection();
49+
const requestId = await initiateApexGuruRequest(selection, outputChannel, connection);
50+
outputChannel.appendLine('Code Analyzer with ApexGuru request Id:' + requestId);
51+
52+
const queryResponse: ApexGuruQueryResponse = await pollAndGetApexGuruResponse(connection, requestId, Constants.APEX_GURU_MAX_TIMEOUT_SECONDS, Constants.APEX_GURU_RETRY_INTERVAL_MILLIS);
53+
54+
const decodedReport = Buffer.from(queryResponse.report, 'base64').toString('utf8');
55+
56+
const ruleResult = transformStringToRuleResult(selection.fsPath, decodedReport);
57+
new DiagnosticManager().displayDiagnostics([selection.fsPath], [ruleResult], diagnosticCollection);
58+
TelemetryService.sendCommandEvent(Constants.TELEM_SUCCESSFUL_APEX_GURU_FILE_ANALYSIS, {
59+
executedCommand: commandName,
60+
duration: (Date.now() - startTime).toString()
61+
});
62+
void vscode.window.showInformationMessage(messages.apexGuru.finishedScan(ruleResult.violations.length));
63+
});
64+
} catch (e) {
65+
const errMsg = e instanceof Error ? e.message : e as string;
66+
outputChannel.error('Initial Code Analyzer with ApexGuru request failed.');
67+
outputChannel.appendLine(errMsg);
68+
}
69+
}
70+
71+
export async function pollAndGetApexGuruResponse(connection: Connection, requestId: string, maxWaitTimeInSeconds: number, retryIntervalInMillis: number): Promise<ApexGuruQueryResponse> {
72+
let queryResponse: ApexGuruQueryResponse;
73+
let lastErrorMessage = '';
74+
const startTime = Date.now();
75+
while ((Date.now() - startTime) < maxWaitTimeInSeconds * 1000) {
76+
try {
77+
queryResponse = await connection.request({
78+
method: 'GET',
79+
url: `${Constants.APEX_GURU_REQUEST}/${requestId}`,
80+
body: ''
81+
});
82+
if (queryResponse.status == 'success') {
83+
return queryResponse;
84+
}
85+
} catch (error) {
86+
lastErrorMessage = (error as Error).message;
87+
}
88+
await new Promise(resolve => setTimeout(resolve, retryIntervalInMillis));
89+
90+
}
91+
if (queryResponse) {
92+
return queryResponse;
93+
}
94+
throw new Error(`Failed to get a successful response from Apex Guru after maximum retries.${lastErrorMessage}`);
95+
}
96+
97+
export async function initiateApexGuruRequest(selection: vscode.Uri, outputChannel: vscode.LogOutputChannel, connection: Connection): Promise<string> {
98+
const fileContent = await fileSystem.readFile(selection.fsPath);
99+
const base64EncodedContent = Buffer.from(fileContent).toString('base64');
100+
const response: ApexGuruInitialResponse = await connection.request({
101+
method: 'POST',
102+
url: Constants.APEX_GURU_REQUEST,
103+
body: JSON.stringify({
104+
classContent: base64EncodedContent
105+
})
106+
});
107+
108+
if (response.status != 'new' && response.status != 'success') {
109+
outputChannel.warn('Code Analyzer with Apex Guru returned unexpected response:' + response.status);
110+
throw Error('Code Analyzer with Apex Guru returned unexpected response:' + response.status);
111+
}
112+
113+
const requestId = response.requestId;
114+
return requestId;
115+
}
116+
117+
export const fileSystem = {
118+
readFile: (path: string) => fspromises.readFile(path, 'utf8')
119+
};
120+
121+
export function transformStringToRuleResult(fileName: string, jsonString: string): RuleResult {
122+
const reports = JSON.parse(jsonString) as ApexGuruReport[];
123+
124+
const ruleResult: RuleResult = {
125+
engine: 'apexguru',
126+
fileName: fileName,
127+
violations: []
128+
};
129+
130+
reports.forEach(parsed => {
131+
const encodedCodeBefore =
132+
parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'code_before')?.value
133+
?? parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'class_before')?.value
134+
?? '';
135+
const encodedCodeAfter =
136+
parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'code_after')?.value
137+
?? parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'class_after')?.value
138+
?? '';
139+
const lineNumber = parseInt(parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'line_number')?.value);
140+
141+
const violation: ApexGuruViolation = {
142+
ruleName: parsed.type,
143+
message: parsed.value,
144+
severity: 1,
145+
category: parsed.type, // Replace with actual category if available
146+
line: lineNumber,
147+
column: 1,
148+
currentCode: Buffer.from(encodedCodeBefore, 'base64').toString('utf8'),
149+
suggestedCode: Buffer.from(encodedCodeAfter, 'base64').toString('utf8'),
150+
url: fileName
151+
};
152+
153+
ruleResult.violations.push(violation);
154+
});
155+
156+
return ruleResult;
157+
}
158+
159+
export type ApexGuruAuthResponse = {
160+
status: string;
161+
}
162+
163+
export type ApexGuruInitialResponse = {
164+
status: string;
165+
requestId: string;
166+
message: string;
167+
}
168+
169+
export type ApexGuruQueryResponse = {
170+
status: string;
171+
message?: string;
172+
report?: string;
173+
}
174+
175+
export type ApexGuruProperty = {
176+
name: string;
177+
value: string;
178+
};
179+
180+
export type ApexGuruReport = {
181+
id: string;
182+
type: string;
183+
value: string;
184+
properties: ApexGuruProperty[];
185+
}

src/extension.ts

+28-21
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ import {ScanRunner} from './lib/scanner';
1212
import {SettingsManager} from './lib/settings';
1313
import {SfCli} from './lib/sf-cli';
1414

15-
import {RuleResult, ApexGuruAuthResponse} from './types';
15+
import {RuleResult} from './types';
1616
import {DiagnosticManager} from './lib/diagnostics';
1717
import {messages} from './lib/messages';
1818
import {Fixer} from './lib/fixer';
1919
import { CoreExtensionService, TelemetryService } from './lib/core-extension-service';
2020
import * as Constants from './lib/constants';
2121
import * as path from 'path';
2222
import { SIGKILL } from 'constants';
23+
import * as ApexGuruFunctions from './apexguru/apex-guru-service'
2324

2425
export type RunInfo = {
2526
diagnosticCollection?: vscode.DiagnosticCollection;
2627
commandName: string;
28+
outputChannel?: vscode.LogOutputChannel;
2729
}
2830

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

57+
const apexGuruFeatureFlag = SettingsManager.getApexGuruEnabled();
58+
const apexGuruEnabled = apexGuruFeatureFlag && await ApexGuruFunctions.isApexGuruEnabledInOrg(outputChannel);
5559
// Set the necessary flags to control showing the command
56-
await vscode.commands.executeCommand('setContext', 'sfca.apexGuruEnabled', Constants.APEX_GURU_FEATURE_FLAG_ENABLED && await _isApexGuruEnabledInOrg());
60+
await vscode.commands.executeCommand('setContext', 'sfca.apexGuruEnabled', apexGuruEnabled);
5761

5862
// Define a diagnostic collection in the `activate()` scope so it can be used repeatedly.
5963
diagnosticCollection = vscode.languages.createDiagnosticCollection('sfca');
@@ -136,30 +140,33 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
136140
await _runDfa(context);
137141
});
138142
context.subscriptions.push(runOnActiveFile, runOnSelected, runDfaOnSelectedMethodCmd, runDfaOnWorkspaceCmd, removeDiagnosticsOnActiveFile, removeDiagnosticsOnSelectedFile, removeDiagnosticsInRange);
143+
144+
if (apexGuruEnabled) {
145+
const runApexGuruOnSelectedFile = vscode.commands.registerCommand(Constants.COMMAND_RUN_APEX_GURU_ON_FILE, async (selection: vscode.Uri, multiSelect?: vscode.Uri[]) => {
146+
return await ApexGuruFunctions.runApexGuruOnFile(multiSelect && multiSelect.length > 0 ? multiSelect[0] : selection,
147+
{
148+
commandName: Constants.COMMAND_RUN_APEX_GURU_ON_FILE,
149+
diagnosticCollection,
150+
outputChannel: outputChannel
151+
});
152+
});
153+
const runApexGuruOnCurrentFile = vscode.commands.registerCommand(Constants.COMMAND_RUN_APEX_GURU_ON_ACTIVE_FILE, async () => {
154+
const targets: string[] = await targeting.getTargets([]);
155+
return await ApexGuruFunctions.runApexGuruOnFile(vscode.Uri.file(targets[0]),
156+
{
157+
commandName: Constants.COMMAND_RUN_APEX_GURU_ON_ACTIVE_FILE,
158+
diagnosticCollection,
159+
outputChannel: outputChannel
160+
});
161+
});
162+
context.subscriptions.push(runApexGuruOnSelectedFile, runApexGuruOnCurrentFile);
163+
}
164+
139165
TelemetryService.sendExtensionActivationEvent(extensionHrStart);
140166
outputChannel.appendLine(`Extension sfdx-code-analyzer-vscode activated.`);
141167
return Promise.resolve(context);
142168
}
143169

144-
export async function _isApexGuruEnabledInOrg(): Promise<boolean> {
145-
try {
146-
const connection = await CoreExtensionService.getConnection();
147-
const response:ApexGuruAuthResponse = await connection.request({
148-
method: 'GET',
149-
url: Constants.APEX_GURU_AUTH_ENDPOINT,
150-
body: ''
151-
});
152-
return response.status == 'Success';
153-
} catch(e) {
154-
// 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,.
155-
// In all of these scenarios, we return false.
156-
const errMsg = e instanceof Error ? e.message : e as string;
157-
outputChannel.error('***ApexGuru perm check failed with error:***' + errMsg);
158-
outputChannel.show();
159-
return false;
160-
}
161-
}
162-
163170
async function _runDfa(context: vscode.ExtensionContext) {
164171
if (violationsCacheExists()) {
165172
const choice = await vscode.window.showQuickPick(

src/lib/constants.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ export const COMMAND_RUN_DFA_ON_SELECTED_METHOD = 'sfca.runDfaOnSelectedMethod';
1616
export const COMMAND_RUN_DFA = 'sfca.runDfa';
1717
export const COMMAND_REMOVE_DIAGNOSTICS_ON_ACTIVE_FILE = 'sfca.removeDiagnosticsOnActiveFile';
1818
export const COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE = 'sfca.removeDiagnosticsOnSelectedFile';
19-
export const COMMAND_DIAGNOSTICS_IN_RANGE = 'sfca.removeDiagnosticsInRange'
20-
19+
export const COMMAND_DIAGNOSTICS_IN_RANGE = 'sfca.removeDiagnosticsInRange';
20+
export const COMMAND_RUN_APEX_GURU_ON_FILE = 'sfca.runApexGuruAnalysisOnSelectedFile';
21+
export const COMMAND_RUN_APEX_GURU_ON_ACTIVE_FILE = 'sfca.runApexGuruAnalysisOnCurrentFile';
2122

2223
// telemetry event keys
2324
export const TELEM_SUCCESSFUL_STATIC_ANALYSIS = 'sfdx__codeanalyzer_static_run_complete';
2425
export const TELEM_FAILED_STATIC_ANALYSIS = 'sfdx__codeanalyzer_static_run_failed';
2526
export const TELEM_SUCCESSFUL_DFA_ANALYSIS = 'sfdx__codeanalyzer_dfa_run_complete';
2627
export const TELEM_FAILED_DFA_ANALYSIS = 'sfdx__codeanalyzer_dfa_run_failed';
27-
28+
export const TELEM_SUCCESSFUL_APEX_GURU_FILE_ANALYSIS = 'sfdx__apexguru_file_run_complete';
2829

2930
// versioning
3031
export const MINIMUM_REQUIRED_VERSION_CORE_EXTENSION = '58.4.1';
@@ -34,6 +35,8 @@ export const WORKSPACE_DFA_PROCESS = 'dfaScanProcess';
3435

3536
// apex guru APIS
3637
export const APEX_GURU_AUTH_ENDPOINT = '/services/data/v62.0/apexguru/validate'
38+
export const APEX_GURU_REQUEST = '/services/data/v62.0/apexguru/request'
3739

3840
// feature gates
39-
export const APEX_GURU_FEATURE_FLAG_ENABLED = false;
41+
export const APEX_GURU_MAX_TIMEOUT_SECONDS = 60;
42+
export const APEX_GURU_RETRY_INTERVAL_MILLIS = 1000;

0 commit comments

Comments
 (0)