diff --git a/packages/salesforcedx-sobjects-faux-generator/src/generator/fauxClassGenerator.ts b/packages/salesforcedx-sobjects-faux-generator/src/generator/fauxClassGenerator.ts index 63b224d66b..ff5cd5d95e 100644 --- a/packages/salesforcedx-sobjects-faux-generator/src/generator/fauxClassGenerator.ts +++ b/packages/salesforcedx-sobjects-faux-generator/src/generator/fauxClassGenerator.ts @@ -24,6 +24,21 @@ export interface CancellationToken { isCancellationRequested: boolean; } +export enum SObjectRefreshSource { + Manual = 'manual', + Startup = 'startup' +} + +export interface SObjectRefreshResult { + data: { + source?: SObjectRefreshSource; + cancelled: boolean; + standardObjects?: number; + customObjects?: number; + }; + error?: { message: string; stack?: string }; +} + const SFDX_DIR = '.sfdx'; const TOOLS_DIR = 'tools'; const SOBJECTS_DIR = 'sobjects'; @@ -70,16 +85,20 @@ export class FauxClassGenerator { private emitter: EventEmitter; private cancellationToken: CancellationToken | undefined; + private result: SObjectRefreshResult; constructor(emitter: EventEmitter, cancellationToken?: CancellationToken) { this.emitter = emitter; this.cancellationToken = cancellationToken; + this.result = { data: { cancelled: false } }; } public async generate( projectPath: string, - type: SObjectCategory - ): Promise { + type: SObjectCategory, + source: SObjectRefreshSource + ): Promise { + this.result = { data: { source, cancelled: false } }; const sobjectsFolderPath = path.join( projectPath, SFDX_DIR, @@ -113,8 +132,10 @@ export class FauxClassGenerator { try { sobjects = await describe.describeGlobal(projectPath, type); } catch (e) { + const err = JSON.parse(e); return this.errorExit( - nls.localize('failure_fetching_sobjects_list_text', e) + nls.localize('failure_fetching_sobjects_list_text', err.message), + err.stack ); } let j = 0; @@ -130,9 +151,9 @@ export class FauxClassGenerator { await describe.describeSObjectBatch(projectPath, sobjects, j) ); j = fetchedSObjects.length; - } catch (e) { + } catch (errorMessage) { return this.errorExit( - nls.localize('failure_in_sobject_describe_text', e) + nls.localize('failure_in_sobject_describe_text', errorMessage) ); } } @@ -146,18 +167,21 @@ export class FauxClassGenerator { } } + this.result.data.standardObjects = standardSObjects.length; + this.result.data.customObjects = customSObjects.length; + this.logFetchedObjects(standardSObjects, customSObjects); try { this.generateFauxClasses(standardSObjects, standardSObjectsFolderPath); - } catch (e) { - return this.errorExit(e); + } catch (errorMessage) { + return this.errorExit(errorMessage); } try { this.generateFauxClasses(customSObjects, customSObjectsFolderPath); - } catch (e) { - return this.errorExit(e); + } catch (errorMessage) { + return this.errorExit(errorMessage); } return this.successExit(); @@ -181,35 +205,35 @@ export class FauxClassGenerator { return fauxClassPath; } - private errorExit(errorMessage: string): Promise { - this.emitter.emit(LocalCommandExecution.STDERR_EVENT, `${errorMessage}\n`); - this.emitter.emit( - LocalCommandExecution.ERROR_EVENT, - new Error(errorMessage) - ); + private errorExit( + message: string, + stack?: string + ): Promise { + this.emitter.emit(LocalCommandExecution.STDERR_EVENT, `${message}\n`); + this.emitter.emit(LocalCommandExecution.ERROR_EVENT, new Error(message)); this.emitter.emit( LocalCommandExecution.EXIT_EVENT, LocalCommandExecution.FAILURE_CODE ); - return Promise.reject( - `${LocalCommandExecution.FAILURE_CODE.toString()} - ${errorMessage}` - ); + this.result.error = { message, stack }; + return Promise.reject(this.result); } - private successExit(): Promise { + private successExit(): Promise { this.emitter.emit( LocalCommandExecution.EXIT_EVENT, LocalCommandExecution.SUCCESS_CODE ); - return Promise.resolve(LocalCommandExecution.SUCCESS_CODE.toString()); + return Promise.resolve(this.result); } - private cancelExit(): Promise { + private cancelExit(): Promise { this.emitter.emit( LocalCommandExecution.EXIT_EVENT, LocalCommandExecution.FAILURE_CODE ); - return Promise.resolve(nls.localize('faux_generation_cancelled_text')); + this.result.data.cancelled = true; + return Promise.resolve(this.result); } private stripId(name: string): string { diff --git a/packages/salesforcedx-sobjects-faux-generator/src/generator/index.ts b/packages/salesforcedx-sobjects-faux-generator/src/generator/index.ts index 8dad08816b..393403e9b5 100644 --- a/packages/salesforcedx-sobjects-faux-generator/src/generator/index.ts +++ b/packages/salesforcedx-sobjects-faux-generator/src/generator/index.ts @@ -5,4 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -export { FauxClassGenerator } from './fauxClassGenerator'; +export { + FauxClassGenerator, + SObjectRefreshResult, + SObjectRefreshSource +} from './fauxClassGenerator'; diff --git a/packages/salesforcedx-sobjects-faux-generator/test/integration/fauxGenerate.test.ts b/packages/salesforcedx-sobjects-faux-generator/test/integration/fauxGenerate.test.ts index a5a6dbbd83..e2f5dc95da 100644 --- a/packages/salesforcedx-sobjects-faux-generator/test/integration/fauxGenerate.test.ts +++ b/packages/salesforcedx-sobjects-faux-generator/test/integration/fauxGenerate.test.ts @@ -13,7 +13,11 @@ import { EventEmitter } from 'events'; import { renameSync } from 'fs'; import * as path from 'path'; import { SObjectCategory } from '../../src/describe'; -import { FauxClassGenerator } from '../../src/generator/fauxClassGenerator'; +import { + FauxClassGenerator, + SObjectRefreshResult, + SObjectRefreshSource +} from '../../src/generator/fauxClassGenerator'; import { nls } from '../../src/messages'; import * as util from './integrationTestUtil'; @@ -76,21 +80,30 @@ describe('Generate faux classes for SObjects', function() { }); it('Should be cancellable', async () => { - let result = ''; const generator = getGenerator(); cancellationTokenSource.cancel(); - result = await generator.generate(projectPath, SObjectCategory.CUSTOM); - expect(result).to.contain(nls.localize('faux_generation_cancelled_text')); + const result = await generator.generate( + projectPath, + SObjectCategory.CUSTOM, + SObjectRefreshSource.Manual + ); + expect(result.data.cancelled).to.be.true; }); it('Should fail if outside a project', async () => { - let result = ''; + let result: SObjectRefreshResult; const generator = getGenerator(); invalidateProject(projectPath); try { - result = await generator.generate(projectPath, SObjectCategory.CUSTOM); - } catch (e) { - expect(e).to.contain(nls.localize('no_generate_if_not_in_project', '')); + result = await generator.generate( + projectPath, + SObjectCategory.CUSTOM, + SObjectRefreshSource.Manual + ); + } catch ({ error }) { + expect(error.message).to.contain( + nls.localize('no_generate_if_not_in_project', '') + ); return; } finally { restoreProject(projectPath); @@ -101,7 +114,7 @@ describe('Generate faux classes for SObjects', function() { it('Should emit an error event on failure', async () => { let errorMessage = ''; let exitCode: number = LocalCommandExecution.SUCCESS_CODE; - let rejectOutput = ''; + let rejectOutput: any; const generator = getGenerator(); emitter.addListener(LocalCommandExecution.ERROR_EVENT, (data: Error) => { errorMessage = data.message; @@ -111,13 +124,17 @@ describe('Generate faux classes for SObjects', function() { }); invalidateProject(projectPath); try { - await generator.generate(projectPath, SObjectCategory.CUSTOM); - } catch (e) { - rejectOutput = e; + await generator.generate( + projectPath, + SObjectCategory.CUSTOM, + SObjectRefreshSource.Manual + ); + } catch ({ error }) { + rejectOutput = error; } finally { restoreProject(projectPath); } - expect(rejectOutput).to.contain( + expect(rejectOutput.message).to.contain( nls.localize('no_generate_if_not_in_project', '') ); expect(errorMessage).to.contain( @@ -128,20 +145,24 @@ describe('Generate faux classes for SObjects', function() { it('Should emit message to stderr on failure', async () => { let stderrInfo = ''; - let rejectOutput = ''; + let rejectOutput: any; const generator = getGenerator(); emitter.addListener(LocalCommandExecution.STDERR_EVENT, (data: string) => { stderrInfo = data; }); invalidateProject(projectPath); try { - await generator.generate(projectPath, SObjectCategory.CUSTOM); - } catch (e) { - rejectOutput = e; + await generator.generate( + projectPath, + SObjectCategory.CUSTOM, + SObjectRefreshSource.Manual + ); + } catch ({ error }) { + rejectOutput = error; } finally { restoreProject(projectPath); } - expect(rejectOutput).to.contain( + expect(rejectOutput.message).to.contain( nls.localize('no_generate_if_not_in_project', '') ); expect(stderrInfo).to.contain( @@ -150,37 +171,45 @@ describe('Generate faux classes for SObjects', function() { }); it('Should emit an exit event with code success code 0 on success', async () => { - let result = ''; let exitCode = LocalCommandExecution.FAILURE_CODE; const generator = getGenerator(); emitter.addListener(LocalCommandExecution.EXIT_EVENT, (data: number) => { exitCode = data; }); try { - result = await generator.generate(projectPath, SObjectCategory.CUSTOM); + const result = await generator.generate( + projectPath, + SObjectCategory.CUSTOM, + SObjectRefreshSource.Manual + ); + expect(result.error).to.be.undefined; + expect(exitCode).to.equal(LocalCommandExecution.SUCCESS_CODE); } catch (e) { expect.fail(e, 'undefined', 'generator should not have thrown an error'); } - expect(result).to.equal(LocalCommandExecution.SUCCESS_CODE.toString()); - expect(exitCode).to.equal(LocalCommandExecution.SUCCESS_CODE); }); it('Should log the number of created faux classes on success', async () => { const generator = getGenerator(); let stdoutInfo = ''; - let result = ''; + let result: SObjectRefreshResult; emitter.addListener(LocalCommandExecution.STDOUT_EVENT, (data: string) => { stdoutInfo = data; }); try { - result = await generator.generate(projectPath, SObjectCategory.CUSTOM); + result = await generator.generate( + projectPath, + SObjectCategory.CUSTOM, + SObjectRefreshSource.Manual + ); + expect(result.error).to.be.undefined; + expect(result.data.customObjects).to.eql(3); + expect(stdoutInfo).to.contain( + nls.localize('fetched_sobjects_length_text', 3, 'Custom') + ); } catch (e) { expect.fail(e, 'undefined', 'generator should not have thrown an error'); } - expect(result).to.equal(LocalCommandExecution.SUCCESS_CODE.toString()); - expect(stdoutInfo).to.contain( - nls.localize('fetched_sobjects_length_text', 3, 'Custom') - ); }); }); diff --git a/packages/salesforcedx-vscode-apex/package.json b/packages/salesforcedx-vscode-apex/package.json index 343281fbec..7322804420 100644 --- a/packages/salesforcedx-vscode-apex/package.json +++ b/packages/salesforcedx-vscode-apex/package.json @@ -25,6 +25,7 @@ ], "dependencies": { "@salesforce/apex-tmlanguage": "1.1.0", + "@salesforce/salesforcedx-sobjects-faux-generator": "45.10.0", "@salesforce/salesforcedx-utils-vscode": "45.10.0", "expand-home-dir": "0.0.3", "find-java-home": "0.2.0", @@ -138,6 +139,10 @@ { "command": "sfdx.force.apex.test.last.method.run", "when": "sfdx:project_opened && sfdx:has_cached_test_method" + }, + { + "command": "sfdx.force.internal.refreshsobjects", + "when": "sfdx:project_opened" } ] }, @@ -205,6 +210,10 @@ { "command": "sfdx.force.apex.test.last.method.run", "title": "%force_apex_test_last_method_run_text%" + }, + { + "command": "sfdx.force.internal.refreshsobjects", + "title": "%force_sobjects_refresh%" } ], "configuration": { @@ -225,6 +234,13 @@ ], "default": false, "description": "%apex_semantic_errors_description%" + }, + "salesforcedx-vscode-apex.enable-sobject-refresh-on-startup": { + "type": [ + "boolean" + ], + "default": false, + "description": "%enable_sobject_refresh_on_startup_description%" } } }, diff --git a/packages/salesforcedx-vscode-apex/package.nls.json b/packages/salesforcedx-vscode-apex/package.nls.json index 5a71df8e8e..d8285d6848 100644 --- a/packages/salesforcedx-vscode-apex/package.nls.json +++ b/packages/salesforcedx-vscode-apex/package.nls.json @@ -12,5 +12,7 @@ "force_apex_test_last_class_run_text": "SFDX: Re-Run Last Invoked Apex Test Class", "force_apex_test_last_method_run_text": "SFDX: Re-Run Last Invoked Apex Test Method", "force_apex_test_class_run_text": "SFDX: Invoke Apex Test Class", - "force_apex_test_method_run_text": "SFDX: Invoke Apex Test Method" + "force_apex_test_method_run_text": "SFDX: Invoke Apex Test Method", + "force_sobjects_refresh": "SFDX: Refresh SObject Definitions", + "enable_sobject_refresh_on_startup_description": "If a project has no sObject definitions, specifies whether to automatically refresh sObject definitions on extension activation (true) or not (false)." } diff --git a/packages/salesforcedx-vscode-core/src/commands/forceGenerateFauxClasses.ts b/packages/salesforcedx-vscode-apex/src/commands/forceGenerateFauxClasses.ts similarity index 76% rename from packages/salesforcedx-vscode-core/src/commands/forceGenerateFauxClasses.ts rename to packages/salesforcedx-vscode-apex/src/commands/forceGenerateFauxClasses.ts index c345ecac54..f09c582735 100644 --- a/packages/salesforcedx-vscode-core/src/commands/forceGenerateFauxClasses.ts +++ b/packages/salesforcedx-vscode-apex/src/commands/forceGenerateFauxClasses.ts @@ -11,7 +11,10 @@ import { TOOLS_DIR } from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/constants'; import { SObjectCategory } from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/describe'; -import { FauxClassGenerator } from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/generator'; +import { + FauxClassGenerator, + SObjectRefreshSource +} from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/generator'; import { Command, LocalCommandExecution, @@ -24,22 +27,22 @@ import { import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { channelService } from '../channels'; -import { getDefaultUsernameOrAlias } from '../context'; import { nls } from '../messages'; -import { notificationService, ProgressNotification } from '../notifications'; -import { taskViewService } from '../statuses'; -import { getRootWorkspacePath } from '../util'; -import { +import { telemetryService } from '../telemetry'; + +const sfdxCoreExports = vscode.extensions.getExtension( + 'salesforce.salesforcedx-vscode-core' +)!.exports; +const { + channelService, + getDefaultUsernameOrAlias, + notificationService, + ProgressNotification, SfdxCommandlet, - SfdxCommandletExecutor, - SfdxWorkspaceChecker -} from './commands'; - -export enum SObjectRefreshSource { - Manual = 'manual', - Startup = 'startup' -} + SfdxWorkspaceChecker, + taskViewService +} = sfdxCoreExports; +const SfdxCommandletExecutor = sfdxCoreExports.SfdxCommandletExecutor; export class SObjectRefreshGatherer implements ParametersGatherer { @@ -58,14 +61,10 @@ export class SObjectRefreshGatherer export class ForceGenerateFauxClassesExecutor extends SfdxCommandletExecutor<{}> { private static isActive = false; public build(data: {}): Command { - let logName = 'force_generate_faux_classes_create'; - if (data !== SObjectRefreshSource.Manual) { - logName += `_${data}`; - } return new SfdxCommandBuilder() .withDescription(nls.localize('force_sobjects_refresh')) .withArg('sobject definitions refresh') - .withLogName(logName) + .withLogName('force_generate_faux_classes_create') .build(); } @@ -84,10 +83,6 @@ export class ForceGenerateFauxClassesExecutor extends SfdxCommandletExecutor<{}> const cancellationToken = cancellationTokenSource.token; const execution = new LocalCommandExecution(this.build(response.data)); - execution.processExitSubject.subscribe(() => { - this.logMetric(execution.command.logName, startTime); - }); - channelService.streamCommandOutput(execution); if (this.showChannelOutput) { @@ -112,18 +107,32 @@ export class ForceGenerateFauxClassesExecutor extends SfdxCommandletExecutor<{}> taskViewService.addCommandExecution(execution, cancellationTokenSource); - const projectPath: string = getRootWorkspacePath(); const gen: FauxClassGenerator = new FauxClassGenerator( execution.cmdEmitter, cancellationToken ); + const commandName = execution.command.logName; try { - const result = await gen.generate(projectPath, SObjectCategory.ALL); - console.log('Generate success ' + result); - } catch (e) { - console.log('Generate error ' + e); + const result = await gen.generate( + vscode.workspace.workspaceFolders![0].uri.fsPath, + SObjectCategory.ALL, + refreshSource + ); + console.log('Generate success ' + result.data); + this.logMetric(commandName, startTime, result.data); + } catch (result) { + console.log('Generate error ' + result.error); + const commandData = { + commandName, + executionTime: telemetryService.getEndHRTime(startTime) + }; + telemetryService.sendErrorEvent( + result.error, + Object.assign(result.data, commandData) + ); } + ForceGenerateFauxClassesExecutor.isActive = false; return; } diff --git a/packages/salesforcedx-vscode-apex/src/commands/index.ts b/packages/salesforcedx-vscode-apex/src/commands/index.ts index 22f2b62656..00d34440f9 100644 --- a/packages/salesforcedx-vscode-apex/src/commands/index.ts +++ b/packages/salesforcedx-vscode-apex/src/commands/index.ts @@ -12,3 +12,4 @@ export { forceApexTestMethodRunCodeActionDelegate, ForceApexTestRunCodeActionExecutor } from './forceApexTestRunCodeAction'; +export { forceGenerateFauxClassesCreate, initSObjectDefinitions } from './forceGenerateFauxClasses'; diff --git a/packages/salesforcedx-vscode-apex/src/constants.ts b/packages/salesforcedx-vscode-apex/src/constants.ts index df46226ac8..0df65455a6 100644 --- a/packages/salesforcedx-vscode-apex/src/constants.ts +++ b/packages/salesforcedx-vscode-apex/src/constants.ts @@ -91,3 +91,5 @@ const endPos = new vscode.Position(0, 1); export const APEX_GROUP_RANGE = new vscode.Range(startPos, endPos); export const SET_JAVA_DOC_LINK = 'https://forcedotcom.github.io/salesforcedx-vscode/articles/troubleshooting#set-your-java-version'; +export const SFDX_APEX_CONFIGURATION_NAME = 'salesforcedx-vscode-apex'; +export const ENABLE_SOBJECT_REFRESH_ON_STARTUP = 'enable-sobject-refresh-on-startup'; diff --git a/packages/salesforcedx-vscode-apex/src/index.ts b/packages/salesforcedx-vscode-apex/src/index.ts index 07f0567a97..6fcfc70936 100644 --- a/packages/salesforcedx-vscode-apex/src/index.ts +++ b/packages/salesforcedx-vscode-apex/src/index.ts @@ -15,8 +15,14 @@ import { forceApexTestClassRunCodeAction, forceApexTestClassRunCodeActionDelegate, forceApexTestMethodRunCodeAction, - forceApexTestMethodRunCodeActionDelegate + forceApexTestMethodRunCodeActionDelegate, + forceGenerateFauxClassesCreate, + initSObjectDefinitions } from './commands'; +import { + ENABLE_SOBJECT_REFRESH_ON_STARTUP, + SFDX_APEX_CONFIGURATION_NAME +} from './constants'; import { getApexTests, getExceptionBreakpointInfo, @@ -30,9 +36,11 @@ import { telemetryService } from './telemetry'; import { ApexTestOutlineProvider } from './views/testOutlineProvider'; import { ApexTestRunner, TestRunType } from './views/testRunner'; -const sfdxCoreExtension = vscode.extensions.getExtension( +const sfdxCoreExports = vscode.extensions.getExtension( 'salesforce.salesforcedx-vscode-core' -); +)!.exports; +const getRootWorkspacePath = sfdxCoreExports.getRootWorkspacePath; +const coreTelemetryService = sfdxCoreExports.telemetryService; let languageClient: LanguageClient | undefined; @@ -59,14 +67,10 @@ export async function activate(context: vscode.ExtensionContext) { } // Telemetry - if (sfdxCoreExtension && sfdxCoreExtension.exports) { - sfdxCoreExtension.exports.telemetryService.showTelemetryMessage(); - - telemetryService.initializeService( - sfdxCoreExtension.exports.telemetryService.getReporter(), - sfdxCoreExtension.exports.telemetryService.isTelemetryEnabled() - ); - } + telemetryService.initializeService( + coreTelemetryService.getReporter(), + coreTelemetryService.isTelemetryEnabled() + ); const langClientHRStart = process.hrtime(); languageClient = await languageServer.createLanguageServer(context); @@ -80,6 +84,17 @@ export async function activate(context: vscode.ExtensionContext) { if (languageClient) { languageClient.onNotification('indexer/done', async () => { LanguageClientUtils.indexing = false; + + // Refresh SObject definitions if there aren't any faux classes + const sobjectRefreshStartup: boolean = vscode.workspace + .getConfiguration(SFDX_APEX_CONFIGURATION_NAME) + .get(ENABLE_SOBJECT_REFRESH_ON_STARTUP, false); + if (sobjectRefreshStartup) { + initSObjectDefinitions(getRootWorkspacePath()).catch(e => + telemetryService.sendErrorEvent(e.message, e.stack) + ); + } + await testOutlineProvider.refresh(); }); } @@ -143,6 +158,10 @@ function registerCommands( 'sfdx.force.apex.test.method.run', forceApexTestMethodRunCodeAction ); + const forceGenerateFauxClassesCmd = vscode.commands.registerCommand( + 'sfdx.force.internal.refreshsobjects', + forceGenerateFauxClassesCreate + ); return vscode.Disposable.from( forceApexToggleColorizerCmd, forceApexTestLastClassRunCmd, @@ -150,7 +169,8 @@ function registerCommands( forceApexTestClassRunDelegateCmd, forceApexTestLastMethodRunCmd, forceApexTestMethodRunCmd, - forceApexTestMethodRunDelegateCmd + forceApexTestMethodRunDelegateCmd, + forceGenerateFauxClassesCmd ); } async function registerTestView( diff --git a/packages/salesforcedx-vscode-apex/src/messages/i18n.ts b/packages/salesforcedx-vscode-apex/src/messages/i18n.ts index e9eaec331e..fcd95f2bab 100644 --- a/packages/salesforcedx-vscode-apex/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-apex/src/messages/i18n.ts @@ -24,6 +24,7 @@ export const messages = { '%s points to a missing folder. For more information on how to setup the Salesforce Apex extension, see [Set Your Java Version](%s).', java_runtime_missing_text: 'Java runtime could not be located. Set one using the salesforcedx-vscode-apex.java.home VS Code setting. For more information, go to [Set Your Java Version](%s).', + force_sobjects_refresh: 'SFDX: Refresh SObject Definitions', force_apex_test_run_codeAction_description_text: 'Run Apex test(s)', force_apex_test_run_codeAction_no_class_test_param_text: 'Test class not provided. Run the code action on a class annotated with @isTest.', @@ -49,5 +50,7 @@ export const messages = { 'It looks like this file has been updated. To update your code coverage numbers, run the tests in this file.', colorizer_no_code_coverage_current_file: 'No code coverage information was found for this file. Set "salesforcedx-vscode-core.retrieve-test-code-coverage": true in your user or workspace settings. Then, run Apex tests that include methods in this file. You can run tests from the Apex Tests sidebar or using the Run Tests or Run All Tests code lens within the file.', - colorizer_statusbar_hover_text: 'Highlight Apex Code Coverage' + colorizer_statusbar_hover_text: 'Highlight Apex Code Coverage', + force_sobjects_no_refresh_if_already_active_error_text: + 'A refresh of your sObject definitions is already underway. If you need to restart the process, cancel the running task.' }; diff --git a/packages/salesforcedx-vscode-apex/src/telemetry/telemetry.ts b/packages/salesforcedx-vscode-apex/src/telemetry/telemetry.ts index 94f08f8b7e..e095fbceab 100644 --- a/packages/salesforcedx-vscode-apex/src/telemetry/telemetry.ts +++ b/packages/salesforcedx-vscode-apex/src/telemetry/telemetry.ts @@ -9,6 +9,12 @@ import TelemetryReporter from 'vscode-extension-telemetry'; const EXTENSION_NAME = 'salesforcedx-vscode-apex'; +interface ErrorMetric { + extensionName: string; + errorMessage: string; + errorStack?: string; +} + export class TelemetryService { private static instance: TelemetryService; private reporter: TelemetryReporter | undefined; @@ -79,7 +85,23 @@ export class TelemetryService { } } - private getEndHRTime(hrstart: [number, number]): string { + public sendErrorEvent( + error: { message: string; stack?: string }, + additionalData?: any + ): void { + if (this.reporter !== undefined && this.isTelemetryEnabled) { + const baseTelemetry: ErrorMetric = { + extensionName: EXTENSION_NAME, + errorMessage: error.message, + errorStack: error.stack + }; + + const aggregatedTelemetry = Object.assign(baseTelemetry, additionalData); + this.reporter.sendTelemetryEvent('error', aggregatedTelemetry); + } + } + + public getEndHRTime(hrstart: [number, number]): string { const hrend = process.hrtime(hrstart); return util.format('%d%d', hrend[0], hrend[1] / 1000000); } diff --git a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceGenerateFauxClasses.test.ts b/packages/salesforcedx-vscode-apex/test/vscode-integration/commands/forceGenerateFauxClasses.test.ts similarity index 74% rename from packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceGenerateFauxClasses.test.ts rename to packages/salesforcedx-vscode-apex/test/vscode-integration/commands/forceGenerateFauxClasses.test.ts index 712cfd0d65..816ccdb56a 100644 --- a/packages/salesforcedx-vscode-core/test/vscode-integration/commands/forceGenerateFauxClasses.test.ts +++ b/packages/salesforcedx-vscode-apex/test/vscode-integration/commands/forceGenerateFauxClasses.test.ts @@ -10,21 +10,26 @@ import { SOBJECTS_DIR, TOOLS_DIR } from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/constants'; -import { FauxClassGenerator } from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/generator'; -import { Command } from '@salesforce/salesforcedx-utils-vscode/out/src/cli'; +import { + FauxClassGenerator, + SObjectRefreshSource +} from '@salesforce/salesforcedx-sobjects-faux-generator/out/src/generator'; import { expect } from 'chai'; import * as fs from 'fs'; import * as path from 'path'; import * as sinon from 'sinon'; +import * as vscode from 'vscode'; import { ProgressLocation } from 'vscode'; -import { SfdxCommandlet } from '../../../src/commands'; import { ForceGenerateFauxClassesExecutor, - initSObjectDefinitions, - SObjectRefreshSource + initSObjectDefinitions } from '../../../src/commands/forceGenerateFauxClasses'; -import { ProgressNotification } from '../../../src/notifications'; -import { OrgAuthInfo } from '../../../src/util/authInfo'; +import { telemetryService } from '../../../src/telemetry'; + +const sfdxCoreExports = vscode.extensions.getExtension( + 'salesforce.salesforcedx-vscode-core' +)!.exports; +const { OrgAuthInfo, ProgressNotification, SfdxCommandlet } = sfdxCoreExports; describe('ForceGenerateFauxClasses', () => { describe('initSObjectDefinitions', () => { @@ -87,23 +92,34 @@ describe('ForceGenerateFauxClasses', () => { }); describe('ForceGenerateFauxClassesExecutor', () => { - let generatorStub: sinon.SinonStub; let progressStub: sinon.SinonStub; + let generatorStub: sinon.SinonStub; let logStub: sinon.SinonStub; + let errorStub: sinon.SinonStub; + + const expectedData: any = { + cancelled: false, + standardObjects: 1, + customObjects: 2 + }; beforeEach(() => { progressStub = sinon.stub(ProgressNotification, 'show'); - generatorStub = sinon.stub(FauxClassGenerator.prototype, 'generate'); + generatorStub = sinon + .stub(FauxClassGenerator.prototype, 'generate') + .returns({ data: expectedData }); logStub = sinon.stub( ForceGenerateFauxClassesExecutor.prototype, 'logMetric' ); + errorStub = sinon.stub(telemetryService, 'sendErrorEvent'); }); afterEach(() => { progressStub.restore(); generatorStub.restore(); logStub.restore(); + errorStub.restore(); }); it('Should show progress on the status bar for non-manual refresh source', async () => { @@ -118,22 +134,16 @@ describe('ForceGenerateFauxClasses', () => { ); }); - it('Should append refresh source to log name if not manual', async () => { - const source = SObjectRefreshSource.Startup; - const builder = buildWithSource(source); - expect(builder.logName).to.not.be.undefined; - if (builder.logName) { - expect(builder.logName.endsWith(`_${source}`)).to.be.true; - } - }); + it('Should log correct information to telemetry', async () => { + // Success + await executeWithSource(SObjectRefreshSource.Startup); + expect(logStub.getCall(0).args[2]).to.eqls(expectedData); - it('Should not append refresh source to log name if manual', async () => { - const source = SObjectRefreshSource.Manual; - const builder = buildWithSource(source); - expect(builder.logName).to.not.be.undefined; - if (builder.logName) { - expect(builder.logName.endsWith(`_${source}`)).to.be.false; - } + // Error + const error = { message: 'sample error', stack: 'sample stack' }; + generatorStub.throws({ data: expectedData, error }); + await executeWithSource(SObjectRefreshSource.Startup); + expect(errorStub.calledWith(error, expectedData)); }); async function executeWithSource(source: SObjectRefreshSource) { @@ -143,10 +153,5 @@ describe('ForceGenerateFauxClasses', () => { data: source }); } - - function buildWithSource(source: SObjectRefreshSource): Command { - const executor = new ForceGenerateFauxClassesExecutor(); - return executor.build(source); - } }); }); diff --git a/packages/salesforcedx-vscode-apex/test/vscode-integration/telemetry/telemetry.test.ts b/packages/salesforcedx-vscode-apex/test/vscode-integration/telemetry/telemetry.test.ts index 9ce5030495..ff0ab45c3e 100644 --- a/packages/salesforcedx-vscode-apex/test/vscode-integration/telemetry/telemetry.test.ts +++ b/packages/salesforcedx-vscode-apex/test/vscode-integration/telemetry/telemetry.test.ts @@ -92,4 +92,35 @@ describe('Telemetry', () => { }; assert.calledWith(sendEvent, 'apexLSPError', expectedData); }); + + it('Should send correct data format on sendErrorEvent with additionalData', async () => { + const telemetryService = TelemetryService.getInstance(); + telemetryService.initializeService(reporter, true); + + const additionalData = { + cancelled: false, + standardObjects: 1, + customObjects: 2, + commandName: 'sobject_refresh_command', + executionTime: telemetryService.getEndHRTime([0, 678]) + }; + + telemetryService.sendErrorEvent( + { message: 'sample error', stack: 'sample stack' }, + additionalData + ); + assert.calledOnce(sendEvent); + + const expectedData = { + extensionName: 'salesforcedx-vscode-apex', + errorMessage: 'sample error', + errorStack: 'sample stack', + cancelled: false, + standardObjects: 1, + customObjects: 2, + commandName: 'sobject_refresh_command', + executionTime: match.string + }; + assert.calledWith(sendEvent, 'error', match(expectedData)); + }); }); diff --git a/packages/salesforcedx-vscode-core/package.json b/packages/salesforcedx-vscode-core/package.json index 1d6bc3f282..e119343a07 100644 --- a/packages/salesforcedx-vscode-core/package.json +++ b/packages/salesforcedx-vscode-core/package.json @@ -26,7 +26,6 @@ ], "dependencies": { "@salesforce/core": "1.1.1", - "@salesforce/salesforcedx-sobjects-faux-generator": "45.10.0", "@salesforce/salesforcedx-utils-vscode": "45.10.0", "adm-zip": "0.4.13", "applicationinsights": "1.0.6", @@ -272,10 +271,6 @@ "command": "sfdx.force.org.display.username", "when": "sfdx:project_opened" }, - { - "command": "sfdx.force.internal.refreshsobjects", - "when": "sfdx:project_opened" - }, { "command": "sfdx.force.data.soql.query.input", "when": "sfdx:project_opened && !editorHasSelection" @@ -456,10 +451,6 @@ "command": "sfdx.force.org.display.username", "title": "%force_org_display_username_text%" }, - { - "command": "sfdx.force.internal.refreshsobjects", - "title": "%force_sobjects_refresh%" - }, { "command": "sfdx.force.data.soql.query.input", "title": "%force_data_soql_query_input_text%" @@ -572,13 +563,6 @@ ], "default": false, "description": "%push_or_deploy_on_save_enabled_description%" - }, - "salesforcedx-vscode-core.enable-sobject-refresh-on-startup": { - "type": [ - "boolean" - ], - "default": false, - "description": "%enable_sobject_refresh_on_startup_description%" } } }, diff --git a/packages/salesforcedx-vscode-core/package.nls.json b/packages/salesforcedx-vscode-core/package.nls.json index 057438ca76..ab1219a6a2 100644 --- a/packages/salesforcedx-vscode-core/package.nls.json +++ b/packages/salesforcedx-vscode-core/package.nls.json @@ -27,7 +27,6 @@ "force_alias_list_text": "SFDX: List All Aliases", "force_org_display_default_text": "SFDX: Display Org Details for Default Org", "force_org_display_username_text": "SFDX: Display Org Details...", - "force_sobjects_refresh": "SFDX: Refresh SObject Definitions", "force_data_soql_query_input_text": "SFDX: Execute SOQL Query...", "force_data_soql_query_selection_text": "SFDX: Execute SOQL Query with Currently Selected Text", "force_apex_execute_document_text": "SFDX: Execute Anonymous Apex with Editor Contents", @@ -55,6 +54,5 @@ "force_source_deploy_in_manifest_text": "SFDX: Deploy Source in Manifest to Org", "force_source_delete_text": "SFDX: Delete from Project and Org", "force_source_delete_this_source_text": "SFDX: Delete This from Project and Org", - "force_config_set_org_text": "SFDX: Set a Default Org", - "enable_sobject_refresh_on_startup_description": "If a project has no sObject definitions, specifies whether to automatically refresh sObject definitions on extension activation (true) or not (false)." + "force_config_set_org_text": "SFDX: Set a Default Org" } diff --git a/packages/salesforcedx-vscode-core/src/commands/index.ts b/packages/salesforcedx-vscode-core/src/commands/index.ts index fae4e2b0d0..14b63b913a 100644 --- a/packages/salesforcedx-vscode-core/src/commands/index.ts +++ b/packages/salesforcedx-vscode-core/src/commands/index.ts @@ -36,7 +36,6 @@ export { forceTaskStop } from './forceTaskStop'; export { forceApexClassCreate } from './forceApexClassCreate'; export { forceVisualforcePageCreate } from './forceVisualforcePageCreate'; export { forceLightningAppCreate } from './forceLightningAppCreate'; -export { forceGenerateFauxClassesCreate } from './forceGenerateFauxClasses'; export { forceVisualforceComponentCreate } from './forceVisualforceComponentCreate'; diff --git a/packages/salesforcedx-vscode-core/src/constants.ts b/packages/salesforcedx-vscode-core/src/constants.ts index eb424f02fe..7fa8bc8be5 100644 --- a/packages/salesforcedx-vscode-core/src/constants.ts +++ b/packages/salesforcedx-vscode-core/src/constants.ts @@ -17,7 +17,5 @@ export const TELEMETRY_OPT_OUT_LINK = 'https://forcedotcom.github.io/salesforcedx-vscode/articles/faq/telemetry'; export const PUSH_OR_DEPLOY_ON_SAVE_ENABLED = 'push-or-deploy-on-save.enabled'; export const RETRIEVE_TEST_CODE_COVERAGE = 'retrieve-test-code-coverage'; -export const ENABLE_SOBJECT_REFRESH_ON_STARTUP = - 'enable-sobject-refresh-on-startup'; export const SFDX_CLI_DOWNLOAD_LINK = 'https://developer.salesforce.com/tools/sfdxcli'; diff --git a/packages/salesforcedx-vscode-core/src/index.ts b/packages/salesforcedx-vscode-core/src/index.ts index 0b3656115e..d89189bcd9 100644 --- a/packages/salesforcedx-vscode-core/src/index.ts +++ b/packages/salesforcedx-vscode-core/src/index.ts @@ -23,7 +23,6 @@ import { forceConfigSet, forceDataSoqlQuery, forceDebuggerStop, - forceGenerateFauxClassesCreate, forceLightningAppCreate, forceLightningComponentCreate, forceLightningEventCreate, @@ -54,10 +53,9 @@ import { SfdxWorkspaceChecker, turnOffLogging } from './commands'; -import { initSObjectDefinitions } from './commands/forceGenerateFauxClasses'; import { getUserId } from './commands/forceStartApexDebugLogging'; import { isvDebugBootstrap } from './commands/isvdebugging/bootstrapCmd'; -import { setupWorkspaceOrgType } from './context'; +import { getDefaultUsernameOrAlias, setupWorkspaceOrgType } from './context'; import * as decorators from './decorators'; import { isDemoMode } from './modes/demo-mode'; import { notificationService, ProgressNotification } from './notifications'; @@ -66,11 +64,11 @@ import { registerPushOrDeployOnSave, sfdxCoreSettings } from './settings'; import { taskViewService } from './statuses'; import { telemetryService } from './telemetry'; import { - getRootWorkspacePath, hasRootWorkspace, isCLIInstalled, showCLINotInstalledMessage } from './util'; +import { OrgAuthInfo } from './util/authInfo'; function registerCommands( extensionContext: vscode.ExtensionContext @@ -232,11 +230,6 @@ function registerCommands( forceDataSoqlQuery ); - const forceGenerateFauxClassesCmd = vscode.commands.registerCommand( - 'sfdx.force.internal.refreshsobjects', - forceGenerateFauxClassesCreate - ); - const forceApexExecuteDocumentCmd = vscode.commands.registerCommand( 'sfdx.force.apex.execute.document', forceApexExecute, @@ -328,7 +321,6 @@ function registerCommands( forceAliasListCmd, forceOrgDisplayDefaultCmd, forceOrgDisplayUsernameCmd, - forceGenerateFauxClassesCmd, forceProjectCreateCmd, forceProjectWithManifestCreateCmd, forceApexTriggerCreateCmd, @@ -425,29 +417,24 @@ export async function activate(context: vscode.ExtensionContext) { decorators.showDemoMode(); } - // Refresh SObject definitions if there aren't any faux classes - if (sfdxCoreSettings.getEnableSObjectRefreshOnStartup()) { - initSObjectDefinitions(getRootWorkspacePath()).catch(e => - telemetryService.sendErrorEvent(e.message, e.stack) - ); - } - const api: any = { - ProgressNotification, + channelService, CompositeParametersGatherer, EmptyParametersGatherer, + getDefaultUsernameOrAlias, + getUserId, + isCLIInstalled, + notificationService, + OrgAuthInfo, + ProgressNotification, SelectFileName, SelectOutputDir, SfdxCommandlet, SfdxCommandletExecutor, sfdxCoreSettings, SfdxWorkspaceChecker, - channelService, - notificationService, taskViewService, - telemetryService, - getUserId, - isCLIInstalled + telemetryService }; telemetryService.sendExtensionActivationEvent(extensionHRStart); diff --git a/packages/salesforcedx-vscode-core/src/messages/i18n.ts b/packages/salesforcedx-vscode-core/src/messages/i18n.ts index 79a429c713..e97e8c763a 100644 --- a/packages/salesforcedx-vscode-core/src/messages/i18n.ts +++ b/packages/salesforcedx-vscode-core/src/messages/i18n.ts @@ -140,9 +140,6 @@ export const messages = { 'SFDX: Execute Anonymous Apex with Editor Contents', force_apex_execute_selection_text: 'SFDX: Execute Anonymous Apex with Currently Selected Text', - force_sobjects_refresh: 'SFDX: Refresh SObject Definitions', - force_sobjects_no_refresh_if_already_active_error_text: - 'A refresh of your sObject definitions is already underway. If you need to restart the process, cancel the running task.', force_project_create_text: 'SFDX: Create Project', force_project_create_open_dialog_create_label: 'Create Project', force_apex_trigger_create_text: 'SFDX: Create Apex Trigger', diff --git a/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts b/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts index 7591e5783e..bea5da7a35 100644 --- a/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts +++ b/packages/salesforcedx-vscode-core/src/settings/sfdxCoreSettings.ts @@ -7,7 +7,6 @@ import * as vscode from 'vscode'; import { - ENABLE_SOBJECT_REFRESH_ON_STARTUP, PUSH_OR_DEPLOY_ON_SAVE_ENABLED, RETRIEVE_TEST_CODE_COVERAGE, SFDX_CORE_CONFIGURATION_NAME, @@ -60,10 +59,6 @@ export class SfdxCoreSettings { return this.getConfigValue(RETRIEVE_TEST_CODE_COVERAGE, false); } - public getEnableSObjectRefreshOnStartup(): boolean { - return this.getConfigValue(ENABLE_SOBJECT_REFRESH_ON_STARTUP, false); - } - private getConfigValue(key: string, defaultValue: T): T { return this.getConfiguration().get(key, defaultValue); }