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

Move sObject Refresh command to apex module and improve telemetry #1236

Merged
merged 12 commits into from
Apr 11, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ export interface CancellationToken {
isCancellationRequested: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes in this file are for improving telemetry data around this command

}

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';
Expand Down Expand Up @@ -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<string> {
type: SObjectCategory,
source: SObjectRefreshSource
): Promise<SObjectRefreshResult> {
this.result = { data: { source, cancelled: false } };
const sobjectsFolderPath = path.join(
projectPath,
SFDX_DIR,
Expand Down Expand Up @@ -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;
Expand All @@ -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)
);
}
}
Expand All @@ -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();
Expand All @@ -181,35 +205,35 @@ export class FauxClassGenerator {
return fauxClassPath;
}

private errorExit(errorMessage: string): Promise<string> {
this.emitter.emit(LocalCommandExecution.STDERR_EVENT, `${errorMessage}\n`);
this.emitter.emit(
LocalCommandExecution.ERROR_EVENT,
new Error(errorMessage)
);
private errorExit(
message: string,
stack?: string
): Promise<SObjectRefreshResult> {
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<string> {
private successExit(): Promise<SObjectRefreshResult> {
this.emitter.emit(
LocalCommandExecution.EXIT_EVENT,
LocalCommandExecution.SUCCESS_CODE
);
return Promise.resolve(LocalCommandExecution.SUCCESS_CODE.toString());
return Promise.resolve(this.result);
}

private cancelExit(): Promise<string> {
private cancelExit(): Promise<SObjectRefreshResult> {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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')
);
});
});

Expand Down
16 changes: 16 additions & 0 deletions packages/salesforcedx-vscode-apex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"vscode-languageclient": "5.1.1"
},
"devDependencies": {
"@salesforce/salesforcedx-sobjects-faux-generator": "45.9.0",
"@salesforce/salesforcedx-test-utils-vscode": "45.9.0",
"@types/chai": "^4.0.0",
"@types/mocha": "2.2.38",
Expand Down Expand Up @@ -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"
}
]
},
Expand Down Expand Up @@ -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",
Copy link
Contributor

Choose a reason for hiding this comment

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

You are missing porting the command under commandPalette.

"title": "%force_sobjects_refresh%"
}
],
"configuration": {
Expand All @@ -225,6 +234,13 @@
],
"default": false,
"description": "%apex_semantic_errors_description%"
},
"salesforcedx-vscode-apex.enable-sobject-refresh-on-startup": {
"type": [
"boolean"
],
"default": false,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As we discussed this morning, the default behavior will now be off. The PR is mostly focused on moving the code to the apex extension and improving telemetry data now.

"description": "%enable_sobject_refresh_on_startup_description%"
brpowell marked this conversation as resolved.
Show resolved Hide resolved
}
}
},
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 @@ -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)."
}
Loading