Skip to content

Commit

Permalink
NEW (Extension) @W-16371431@ Poll ApexGuru API and show the response …
Browse files Browse the repository at this point in the history
…as diagnostic
  • Loading branch information
jag-j committed Aug 6, 2024
1 parent f8e2644 commit fa166fc
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 59 deletions.
94 changes: 85 additions & 9 deletions src/apexguru/apex-guru-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@

import * as vscode from 'vscode';
import * as fspromises from 'fs/promises';
import { CoreExtensionService } from '../lib/core-extension-service';
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 {
Expand All @@ -29,22 +33,66 @@ export async function isApexGuruEnabledInOrg(outputChannel: vscode.LogOutputChan
}
}

export async function runApexGuruOnFile(selection: vscode.Uri, outputChannel: vscode.LogOutputChannel) {
export async function runApexGuruOnFile(selection: vscode.Uri, runInfo: RunInfo) {
const {
diagnosticCollection,
commandName,
outputChannel
} = runInfo;
const startTime = Date.now();
try {
const requestId = await initiateApexGuruRequest(selection, outputChannel);
// TODO: Logging the request Id for easy QA. Future stories will use this requestId to poll and retrieve the Apex Guru report.
outputChannel.appendLine('***Apex Guru request Id:***' + requestId);
return 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('***Apex Guru request Id:***' + requestId);

const queryResponse: ApexGuruQueryResponse = await pollAndGetApexGuruResponse(connection, requestId);

const decodedReport = Buffer.from(queryResponse.report, 'base64').toString('utf8');
outputChannel.appendLine('***Retrieved analysis report from ApexGuru***');

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()
});
});
} catch (e) {
const errMsg = e instanceof Error ? e.message : e as string;
outputChannel.appendLine('***Apex Guru initiate request failed***');
outputChannel.appendLine(errMsg);
}
}

export async function initiateApexGuruRequest(selection: vscode.Uri, outputChannel: vscode.LogOutputChannel): Promise<string> {
export async function pollAndGetApexGuruResponse(connection: Connection, requestId: string) {
let queryResponse: ApexGuruQueryResponse;
let isPolling = true;
const retryInterval = 1000;

while (isPolling) {
queryResponse = await connection.request({
method: 'GET',
url: `${Constants.APEX_GURU_REQUEST}/${requestId}`,
body: ''
});

if (queryResponse.status == 'success') {
isPolling = false;
} else {
// Add a delay between requests
await new Promise(resolve => setTimeout(resolve, retryInterval));
}
}
return queryResponse;
}

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 connection = await CoreExtensionService.getConnection();
const response: ApexGuruInitialResponse = await connection.request({
method: 'POST',
url: Constants.APEX_GURU_REQUEST,
Expand All @@ -66,6 +114,34 @@ 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 encodedClassAfter = parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'code_after')?.value;

const violation: ApexGuruViolation = {
ruleName: parsed.type,
message: parsed.value,
severity: 1,
category: parsed.type, // Replace with actual category if available
line: parseInt(parsed.properties.find((prop: ApexGuruProperty) => prop.name === 'line_number')?.value),
column: 1,
suggestedCode: Buffer.from(encodedClassAfter, 'base64').toString('utf8')
};

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

return ruleResult;
}

export type ApexGuruAuthResponse = {
status: string;
}
Expand All @@ -78,8 +154,8 @@ export type ApexGuruInitialResponse = {

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

export type ApexGuruProperty = {
Expand Down
17 changes: 16 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as ApexGuruFunctions from './apexguru/apex-guru-service'
export type RunInfo = {
diagnosticCollection?: vscode.DiagnosticCollection;
commandName: string;
outputChannel?: vscode.LogOutputChannel;
}

/**
Expand Down Expand Up @@ -53,8 +54,9 @@ 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 apexGuruEnabled = Constants.APEX_GURU_FEATURE_FLAG_ENABLED && 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 ApexGuruFunctions.isApexGuruEnabledInOrg(outputChannel));
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 @@ -137,6 +139,19 @@ 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
});
});
context.subscriptions.push(runApexGuruOnSelectedFile);
}

TelemetryService.sendExtensionActivationEvent(extensionHrStart);
outputChannel.appendLine(`Extension sfdx-code-analyzer-vscode activated.`);
return Promise.resolve(context);
Expand Down
6 changes: 3 additions & 3 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ 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_RUN_APEX_GURU_ON_FILE = 'sfca.runApexGuruAnalysisOnSelectedFile'

// 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 @@ -37,4 +37,4 @@ 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_FEATURE_FLAG_ENABLED = true;
2 changes: 1 addition & 1 deletion src/lib/core-extension-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ interface WorkspaceContext {
alias(): string | undefined;
}

interface Connection {
export interface Connection {
instanceUrl: string;
getApiVersion(): string;
getUsername(): string | undefined;
Expand Down
8 changes: 7 additions & 1 deletion src/lib/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const messages = {
increment: 60
}
},
apexGuru: {
progress: {
message: "Apex Guru analysis in progress."
}
},
info: {
finishedScan: (scannedCount: number, badFileCount: number, violationCount: number) => `Scan complete. Analyzed ${scannedCount} files. ${violationCount} violations found in ${badFileCount} files.`
},
Expand All @@ -33,7 +38,8 @@ export const messages = {
},
fixer: {
supressOnLine: "Suppress violations on this line.",
supressOnClass: "Suppress violations on this class."
supressOnClass: "Suppress violations on this class.",
fixWithApexGuruSuggestions: "***Fix violations with suggestions from Apex Guru***"
},
diagnostics: {
messageGenerator: (severity: number, message: string) => `Sev${severity}: ${message}`,
Expand Down
Loading

0 comments on commit fa166fc

Please sign in to comment.