Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create apex action from selected method/this class #5950

Merged
merged 9 commits into from
Nov 21, 2024
19 changes: 15 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"markdown-link-check": "^3.9.3",
"ncp": "^2.0.0",
"nyc": "15.1.0",
"openapi-types": "12.1.3",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CristiCanizales please verify the 3pp status of the new modules

"ovsx": "0.8.0",
"prettier": "3.3.3",
"shelljs": "0.8.5",
Expand All @@ -65,7 +66,8 @@
"ts-jest": "^29.1.1",
"ts-loader": "^9.3.0",
"ts-node": "10.9.2",
"typescript": "^5.6.2"
"typescript": "^5.6.2",
"yaml": "2.6.0"
},
"scripts": {
"postinstall": "npm run bootstrap && npm run reformat && npm run check:peer-deps && npm run check:typescript-project-references",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class CompositeCliCommandExecutor {
* Represents a command execution (a process has already been spawned for it).
* This is tightly coupled with the execution model (child_process).
* If we ever use a different executor, this class should be refactored and abstracted
* to take an event emitter/observable instead of child_proces.
* to take an event emitter/observable instead of child_process.
*/
export type CommandExecution = {
readonly command: Command;
Expand Down
26 changes: 26 additions & 0 deletions packages/salesforcedx-vscode-apex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@
]
},
"menus": {
"editor/context": [
{
"command": "sf.create.apex.action.method",
"when": "sf:project_opened && sf:has_target_org && resource =~ /.\\.(cls)?$/"
},
{
"command": "sf.create.apex.action.class",
"when": "sf:project_opened && sf:has_target_org && resource =~ /.\\.(cls)?$/"
}
],
"view/title": [
{
"command": "sf.test.view.run",
Expand Down Expand Up @@ -235,6 +245,14 @@
{
"command": "sf.test.view.collapseAll",
"when": "sf:project_opened"
},
{
"command": "sf.create.apex.action.method",
"when": "false"
},
{
"command": "sf.create.apex.action.class",
"when": "sf:project_opened && sf:has_target_org"
}
]
},
Expand Down Expand Up @@ -338,6 +356,14 @@
{
"command": "sf.apex.test.last.method.run",
"title": "%apex_test_last_method_run_text%"
},
{
"command": "sf.create.apex.action.method",
"title": "%create_apex_action_method%"
},
{
"command": "sf.create.apex.action.class",
"title": "%create_apex_action_class%"
}
],
"configuration": {
Expand Down
2 changes: 2 additions & 0 deletions packages/salesforcedx-vscode-apex/package.nls.ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"apex_verbose_level_trace_description": "Output everything, including details about notifications and responses received by the client, and requests sent by the server.",
"configuration_title": "Salesforce Apex Configuration",
"collapse_tests_title": "SFDX: Apex テストを隠す",
"create_apex_action_method": "SFDX: Create Apex Action from Selected Method",
"create_apex_action_class": "SFDX: Create Apex Action from This Class",
"go_to_definition_title": "定義に移動",
"java_home_description": "Specifies the folder path to the Java 11, Java 17, or Java 21 runtime used to launch the Apex Language Server. Note on Windows the backslashes must be escaped.\n\nMac Example: `/Library/Java/JavaVirtualMachines/openjdk-11.jdk/Contents/Home`\n\nWindows Example: `C:\\\\Program Files\\\\Zulu\\\\zulu-17`\n\nLinux Example: `/usr/lib/jvm/java-21-openjdk-amd64`",
"refresh_test_title": "テストを更新",
Expand Down
4 changes: 3 additions & 1 deletion packages/salesforcedx-vscode-apex/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"apex_messages_level_trace_description": "Only output high-level messages of notifications and responses received by the client, and requests sent by the server.",
"apex_off_level_trace_description": "Don't generate any output. Turn off all tracing.",
"apex_semantic_errors_description": "Allow Apex Language Server to surface semantic errors",
"apex_test_last_method_run_text": "SFDX: Re-Run Last Run Apex Test Method",
"apex_test_class_run_text": "SFDX: Run Apex Test Class",
"apex_test_last_class_run_text": "SFDX: Re-Run Last Run Apex Test Class",
"apex_test_last_method_run_text": "SFDX: Re-Run Last Run Apex Test Method",
"apex_test_method_run_text": "SFDX: Run Apex Test Method",
"apex_test_run_text": "SFDX: Run Apex Tests",
"apex_test_suite_build_text": "SFDX: Add Tests to Apex Test Suite",
Expand All @@ -19,6 +19,8 @@
"apex_verbose_level_trace_description": "Output everything, including details about notifications and responses received by the client, and requests sent by the server.",
"configuration_title": "Salesforce Apex Configuration",
"collapse_tests_title": "SFDX: Collapse All Apex Tests",
"create_apex_action_method": "SFDX: Create Apex Action from Selected Method",
"create_apex_action_class": "SFDX: Create Apex Action from This Class",
"go_to_definition_title": "Go to Definition",
"java_home_description": "Specifies the folder path to the Java 11, Java 17, or Java 21 runtime used to launch the Apex Language Server. Note on Windows the backslashes must be escaped.\n\nMac Example: `/Library/Java/JavaVirtualMachines/openjdk-11.jdk/Contents/Home`\n\nWindows Example: `C:\\\\Program Files\\\\Zulu\\\\zulu-17`\n\nLinux Example: `/usr/lib/jvm/java-21-openjdk-amd64`",
"java_memory_description": "Specifies the amount of memory allocation to the Apex Language Server in MB, or null to use the system default value.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { Progress } from '@salesforce/apex-node-bundle';
import { notificationService, workspaceUtils } from '@salesforce/salesforcedx-utils-vscode';
import * as fs from 'fs';
import { OpenAPIV3 } from 'openapi-types';
import * as path from 'path';
import * as vscode from 'vscode';
import { stringify } from 'yaml';
import { nls } from '../messages';
import { getTelemetryService } from '../telemetry/telemetry';
import { MetadataOrchestrator, MethodMetadata } from './metadataOrchestrator';

export class ApexActionController {
constructor(private metadataOrchestrator: MetadataOrchestrator) {}

/**
* Creates an Apex Action.
* @param isClass - Indicates if the action is for a class or a method.
*/
public createApexAction = async (isClass: boolean): Promise<void> => {
const type = isClass ? 'Class' : 'Method';
const command = isClass
? 'SFDX: Create Apex Action from This Class'
: 'SFDX: Create Apex Action from Selected Method';
let metadata;
let name;
const telemetryService = await getTelemetryService();
try {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: command,
cancellable: true
},
async progress => {
// Step 1: Extract Metadata
progress.report({ message: nls.localize('extract_metadata') });
metadata = isClass
? this.metadataOrchestrator.extractAllMethodsMetadata()
: this.metadataOrchestrator.extractMethodMetadata();
if (!metadata) {
throw new Error(nls.localize('extraction_failed', type));
}

// Step 3: Generate OpenAPI Document
progress.report({ message: nls.localize('generate_openapi_document') });
const openApiDocument = this.generateOpenAPIDocument(Array.isArray(metadata) ? metadata : [metadata]);

// Step 4: Write OpenAPI Document to File
name = Array.isArray(metadata) ? metadata[0].className : metadata.name;
const openApiFileName = `${name}_openapi.yml`;
progress.report({ message: nls.localize('write_openapi_document_to_file') });
await this.saveAndOpenDocument(openApiFileName, openApiDocument);
}
);

// Step 5: Notify Success
notificationService.showInformationMessage(nls.localize('apex_action_created', type.toLowerCase(), name));
telemetryService.sendEventData(`ApexAction${type}Created`, { method: name! });
} catch (error: any) {
void this.handleError(error, `ApexAction${type}CreationFailed`);
}
};

/**
* Saves and opens the OpenAPI document to a file.
* @param fileName - The name of the file.
* @param content - The content of the file.
*/
private saveAndOpenDocument = async (fileName: string, content: string): Promise<void> => {
const openAPIdocumentsPath = path.join(workspaceUtils.getRootWorkspacePath(), 'OpenAPIdocuments');
if (!fs.existsSync(openAPIdocumentsPath)) {
fs.mkdirSync(openAPIdocumentsPath);
}
const saveLocation = path.join(openAPIdocumentsPath, fileName);
fs.writeFileSync(saveLocation, content);
await vscode.workspace.openTextDocument(saveLocation).then((newDocument: vscode.TextDocument) => {
void vscode.window.showTextDocument(newDocument);
});
};

/**
* Generates an OpenAPI document from the provided metadata.
* @param metadata - The metadata of the methods.
* @returns The OpenAPI document as a string.
*/
private generateOpenAPIDocument = (metadata: MethodMetadata[]): string => {
const paths: OpenAPIV3.PathsObject = {};

metadata?.forEach(method => {
paths[`/apex/${method.name}`] = {
post: {
operationId: method.name,
summary: `Invoke ${method.name}`,
parameters: method.parameters as unknown as (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[],
responses: {
200: {
description: 'Success',
content: {
'application/json': { schema: { type: method.returnType as OpenAPIV3.NonArraySchemaObjectType } }
}
}
}
}
};
});

const openAPIDocument: OpenAPIV3.Document = {
openapi: '3.0.0',
info: { title: 'Apex Actions', version: '1.0.0' },
paths
};
// Convert the OpenAPI document to YAML
return stringify(openAPIDocument);
};

/**
* Handles errors by showing a notification and sending telemetry data.
* @param error - The error to handle.
* @param telemetryEvent - The telemetry event name.
*/
private handleError = async (error: any, telemetryEvent: string): Promise<void> => {
const telemetryService = await getTelemetryService();
const errorMessage = error instanceof Error ? error.message : String(error);
notificationService.showErrorMessage(`${nls.localize('create_apex_action_failed')}: ${errorMessage}`);
telemetryService.sendException(telemetryEvent, errorMessage);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { ApexActionController } from './apexActionController';
import { MetadataOrchestrator } from './metadataOrchestrator';

const metadataOrchestrator = new MetadataOrchestrator();
const controller = new ApexActionController(metadataOrchestrator);

/**
* Creates an Apex Action from the method at the current cursor position.
*/
export const createApexActionFromMethod = async (): Promise<void> => {
// Call Controller
await controller.createApexAction(false);
};

/**
* Creates Apex Actions from all methods in the current class.
*/
export const createApexActionFromClass = async (): Promise<void> => {
CristiCanizales marked this conversation as resolved.
Show resolved Hide resolved
// Call Controller
await controller.createApexAction(true);
};
4 changes: 3 additions & 1 deletion packages/salesforcedx-vscode-apex/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
export { anonApexDebug, anonApexExecute } from './anonApexExecute';
export { ApexActionController } from './apexActionController';
export { apexLogGet } from './apexLogGet';
export { apexTestRun } from './apexTestRun';
export {
ApexLibraryTestRunExecutor,
apexDebugClassRunCodeActionDelegate,
apexDebugMethodRunCodeActionDelegate,
ApexLibraryTestRunExecutor,
apexTestClassRunCodeAction,
apexTestClassRunCodeActionDelegate,
apexTestMethodRunCodeAction,
apexTestMethodRunCodeActionDelegate
} from './apexTestRunCodeAction';
export { apexTestSuiteAdd, apexTestSuiteCreate, apexTestSuiteRun } from './apexTestSuite';
export { createApexActionFromMethod, createApexActionFromClass } from './createApexAction';
export { launchApexReplayDebuggerWithCurrentFile } from './launchApexReplayDebuggerWithCurrentFile';
Loading
Loading