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

Sm/qa pr 418 #419

Merged
merged 10 commits into from
Feb 3, 2022
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sfdx plugins:install [email protected]

## Issues

Please report any issues at https://github.com/forcedotcom/cli/issues
Please report any issues at <https://github.com/forcedotcom/cli/issues>

## Contributing

Expand All @@ -43,7 +43,7 @@ Please report any issues at https://github.com/forcedotcom/cli/issues
### CLA

External contributors will be required to sign a Contributor's License
Agreement. You can do so by going to https://cla.salesforce.com/sign-cla.
Agreement. You can do so by going to <https://cla.salesforce.com/sign-cla>.

### Build

Expand Down Expand Up @@ -947,9 +947,9 @@ DESCRIPTION
EXAMPLES
To deploy the source files in a directory:
$ sfdx force:source:deploy -p path/to/source
$ sfdx force:source:deploy -p path/to/source
To deploy a specific Apex class and the objects whose source is in a directory:
$ sfdx force:source:deploy -p "path/to/apex/classes/MyClass.cls,path/to/source/objects"
$ sfdx force:source:deploy -p "path/to/apex/classes/MyClass.cls,path/to/source/objects"
To deploy source files in a comma-separated list that contains spaces:
$ sfdx force:source:deploy -p "path/to/objects/MyCustomObject/fields/MyField.field-meta.xml, path/to/apex/classes"
To deploy all Apex classes:
Expand Down Expand Up @@ -1186,7 +1186,7 @@ OPTIONS
DESCRIPTION
Opens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml,
and are stored in the flexipages directory. If you specify a different type of file, this command opens your org’s
and are stored in the flexipages directory. If you specify a visualforce page (with a .page suffix) this command let you preview your visualforce page. If you specify a different type of file, this command opens your org’s
home page.
The file opens in your default browser.
Expand Down
2 changes: 1 addition & 1 deletion messages/open.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"description": "edit a Lightning Page with Lightning App Builder\nOpens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, and are stored in the flexipages directory. If you specify a different type of file, this command opens your org’s home page.\n\nThe file opens in your default browser.\nIf no browser-based editor is available for the selected file, this command opens your org's home page.\nTo generate a URL for the browser-based editor but not open the editor, use --urlonly.",
"description": "edit a Lightning Page with Lightning App Builder\nOpens the specified Lightning Page in Lightning App Builder. Lightning Page files have the suffix .flexipage-meta.xml, and are stored in the flexipages directory. \nIf you specify a Visualforce page, which has a .page suffix, the page opens in your browser so you can preview it. If you specify a different type of file, this command opens your org’s home page.\n\nThe file opens in your default browser.\nIf no browser-based editor is available for the selected file, this command opens your org's home page.\nTo generate a URL for the browser-based editor but not open the editor, use --urlonly.",
"examples": [
"$ sfdx force:source:open -f path/to/source",
"$ sfdx force:source:open -r -f path/to/source",
Expand Down
36 changes: 24 additions & 12 deletions src/commands/force/source/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export class Open extends SourceCommand {

private async doOpen(): Promise<void> {
const typeName = this.getTypeNameDefinitionByFileName(path.resolve(this.flags.sourcefile as string));
const openPath = typeName === 'FlexiPage' ? await this.setUpOpenPath() : await this.buildFrontdoorUrl();
const openPath = ['FlexiPage', 'ApexPage'].includes(typeName)
? await this.setUpOpenPath(typeName)
: await this.buildFrontdoorUrl();

this.openResult = await this.open(openPath);
}
Expand All @@ -72,6 +74,7 @@ export class Open extends SourceCommand {
const components: SourceComponent[] = metadataResolver.getComponentsFromPath(fsPath);
return components[0].type.name;
}
throw new SfdxError(`File not found: ${fsPath}`, 'FileNotFound');
}

private async buildFrontdoorUrl(): Promise<string> {
Expand All @@ -98,18 +101,27 @@ export class Open extends SourceCommand {
return this.flags.urlonly ? result : this.openBrowser(url, result);
}

private async setUpOpenPath(): Promise<string> {
private async setUpOpenPath(pageType: string): Promise<string> {
try {
const flexipage = await this.org
.getConnection()
.singleRecordQuery<{ Id: string }>(
`SELECT id FROM flexipage WHERE DeveloperName='${path.basename(
this.flags.sourcefile as string,
'.flexipage-meta.xml'
)}'`,
{ tooling: true }
);
return `/visualEditor/appBuilder.app?pageId=${flexipage.Id}`;
if (pageType === 'FlexiPage') {
const flexipage = await this.org
.getConnection()
.singleRecordQuery<{ Id: string }>(
`SELECT id FROM flexipage WHERE DeveloperName='${path.basename(
this.flags.sourcefile as string,
'.flexipage-meta.xml'
)}'`,
{ tooling: true }
);
return `/visualEditor/appBuilder.app?pageId=${flexipage.Id}`;
} else if (pageType === 'ApexPage') {
return `/apex/${path
.basename(this.flags.sourcefile as string)
.replace('.page-meta.xml', '')
.replace('.page', '')}`;
mshanemc marked this conversation as resolved.
Show resolved Hide resolved
} else {
return '_ui/flexipage/ui/FlexiPageFilterListPage';
}
} catch (error) {
return '_ui/flexipage/ui/FlexiPageFilterListPage';
}
Expand Down
26 changes: 26 additions & 0 deletions test/commands/source/open.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ describe('force:source:open', () => {
},
},
] as SourceComponent[];
const apexPageComponents = [
{
name: 'MyPage',
type: {
id: 'page',
name: 'ApexPage',
suffix: 'page',
directoryName: 'pages',
inFolder: false,
strictDirectoryName: false,
},
},
] as SourceComponent[];
const testIp = '1.1.1.1';
const orgId = '00DJ0000003htplMAA';
const defaultDir = join('my', 'default', 'package');
Expand Down Expand Up @@ -108,6 +121,9 @@ describe('force:source:open', () => {
if (fsPath.includes('layout')) {
return layoutComponents;
}
if (fsPath.includes('page')) {
return apexPageComponents;
}
}
);
stubMethod(sandbox, MyDomainResolver.prototype, 'resolve').resolves(testIp);
Expand Down Expand Up @@ -140,4 +156,14 @@ describe('force:source:open', () => {
url: `${frontDoorUrl}&retURL=${encodeURIComponent(decodeURIComponent(frontDoorUrl))}`,
});
});

it('given a visualforce page url return /apex/pagename', async () => {
const sourcefile = 'force-app/main/default/pages/MyPage.page';
const result = await runOpenCmd(['--sourcefile', sourcefile, '--json', '--urlonly']);
expect(result).to.deep.equal({
orgId,
username,
url: `${frontDoorUrl}&retURL=%2Fapex%2FMyPage`,
});
});
});
2 changes: 1 addition & 1 deletion test/nuts/delete.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { execCmd } from '@salesforce/cli-plugins-testkit';
import { SourceTestkit } from '@salesforce/source-testkit';
import { exec } from 'shelljs';
import { FileResponse } from '@salesforce/source-deploy-retrieve';
import { isNameObsolete } from './deployDestructive.nut';
import { isNameObsolete } from './shared/isNameObsolete';

describe('source:delete NUTs', () => {
const executable = path.join(process.cwd(), 'bin', 'run');
Expand Down
17 changes: 15 additions & 2 deletions test/nuts/open.nut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
*/

import * as querystring from 'querystring';
import * as path from 'path';
import * as fs from 'fs';
import { expect } from '@salesforce/command/lib/test';
import { TestSession } from '@salesforce/cli-plugins-testkit';
import { execCmd } from '@salesforce/cli-plugins-testkit';
import { AnyJson, Dictionary, getString, isArray } from '@salesforce/ts-types';

const flexiPagePath = 'force-app/main/default/flexipages/Property_Explorer.flexipage-meta.xml';
const layoutDir = 'force-app/main/default/layouts';
const layoutFilePath = path.join(layoutDir, 'MyLayout.layout-meta.xml');

describe('force:source:open', () => {
let session: TestSession;
Expand All @@ -33,6 +37,10 @@ describe('force:source:open', () => {
defaultUsername = getString(session.setup[0], 'result.username');
defaultUserOrgId = getString(session.setup[0], 'result.orgId');
}
await fs.promises.writeFile(
path.join(session.project.dir, layoutFilePath),
'<layout xmlns="http://soap.sforce.com/2006/04/metadata">\n</layout>'
);
});

after(async () => {
Expand All @@ -49,13 +57,18 @@ describe('force:source:open', () => {
expect(result).to.property('url').to.include(querystring.escape('visualEditor/appBuilder.app'));
});
it("should produce the org's frontdoor url when edition of file is not supported", () => {
const unsupportedFilePath = 'force-app/main/default/layouts/MyLayout.layout-meta.xml';
const result = execCmd<Dictionary>(`force:source:open -f ${unsupportedFilePath} --urlonly --json`, {
const result = execCmd<Dictionary>(`force:source:open -f ${layoutFilePath} --urlonly --json`, {
ensureExitCode: 0,
}).jsonOutput.result;
expect(result).to.be.ok;
expect(result).to.include({ orgId: defaultUserOrgId, username: defaultUsername });
expect(result).to.property('url').to.include(querystring.escape('secur/frontdoor.jsp'));
});
it('should throw for non-existent files', () => {
const result = execCmd<Dictionary>('force:source:open -f NotThere --urlonly', {
ensureExitCode: 1,
});
expect(result.shellOutput.stderr).to.include('NotThere');
});
});
});
2 changes: 1 addition & 1 deletion test/nuts/seeds/deploy.async.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ context('Async Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
}
});

describe('async deploy', () => {
describe.skip('async deploy', () => {
it('should return an id immediately when --wait is set to 0 and deploy:report should report results', async () => {
// delete the lwc test stubs which will cause errors with the source tracking/globbing
await testkit.deleteGlobs(['force-app/test/**/*']);
Expand Down
20 changes: 20 additions & 0 deletions test/nuts/shared/isNameObsolete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020, 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 { AuthInfo, Connection } from '@salesforce/core';

export const isNameObsolete = async (username: string, memberType: string, memberName: string): Promise<boolean> => {
const connection = await Connection.create({
authInfo: await AuthInfo.create({ username }),
});

const res = await connection.singleRecordQuery<{ IsNameObsolete: boolean }>(
`SELECT IsNameObsolete FROM SourceMember WHERE MemberType='${memberType}' AND MemberName='${memberName}'`,
{ tooling: true }
);

return res.IsNameObsolete;
};