From cf53b7fbcaa6de9cb4fe79641d9fc50ef7462e31 Mon Sep 17 00:00:00 2001 From: mshanemc Date: Thu, 12 Oct 2023 12:58:22 -0500 Subject: [PATCH 1/2] feat: open a flow --- messages/open.md | 13 +++++++++++++ src/commands/org/open.ts | 21 +++++++++++++++++++++ test/nut/open.nut.ts | 10 ++++++++++ yarn.lock | 2 +- 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/messages/open.md b/messages/open.md index a9a2909c..c5a8ae3c 100644 --- a/messages/open.md +++ b/messages/open.md @@ -30,6 +30,10 @@ To open in a specific browser, use the --browser flag. Supported browsers are "c $ <%= config.bin %> <%= command.id %> --source-path force-app/main/default/flexipages/Hello.flexipage-meta.xml +- Open a local Flow in Flow Builder: + + $ <%= config.bin %> <%= command.id %> --source-path force-app/main/default/flows/Hello.flow-meta.xml + # flags.browser.summary Browser where the org opens. @@ -67,3 +71,12 @@ Waiting to resolve the Lightning Experience-enabled custom domain... # domainTimeoutError The Lightning Experience-enabled custom domain is unavailable. + +# FlowIdNotFound + +No ID not found for Flow %s . + +# FlowIdNotFound.actions + +- Check that the Flow you want to open is deployed to the org. +- Run `sf org open -p lightning/setup/Flows/home` to open the list of Flows diff --git a/src/commands/org/open.ts b/src/commands/org/open.ts index 362654de..a01bb1a2 100644 --- a/src/commands/org/open.ts +++ b/src/commands/org/open.ts @@ -156,10 +156,15 @@ export class OrgOpenCommand extends SfCommand { return `/visualEditor/appBuilder.app?pageId=${flexipage.Id}`; } else if (typeName === 'ApexPage') { return `/apex/${path.basename(file).replace('.page-meta.xml', '').replace('.page', '')}`; + } else if (typeName === 'Flow') { + return `/builder_platform_interaction/flowBuilder.app?flowId=${await flowFileNameToId(this.conn, file)}`; } else { return 'lightning/setup/FlexiPageList/home'; } } catch (error) { + if (error instanceof Error && error.name === 'FlowIdNotFoundError') { + this.error(error); + } return 'lightning/setup/FlexiPageList/home'; } } @@ -170,3 +175,19 @@ export interface OrgOpenOutput { username: string; orgId: string; } + +/** query the tooling API to turn a flow's filepath into a FlowId (starts with 301) */ +const flowFileNameToId = async (conn: Connection, filePath: string): Promise => { + const result = await conn.tooling.query<{ Id: string; FullName: string }>( + 'select id, MasterLabel, FullName from Flow' + ); + const fullName = path.basename(filePath).replace('.flow-meta.xml', ''); + // https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/tooling_api_objects_flow.htm + // unfortunately, you can't query based on the fullname because `field 'FullName' can not be filtered in a query call` + // so we get all the flows and then filter. + const match = (result.records ?? []).find((r) => r.FullName === fullName)?.Id; + if (match) { + return match; + } + throw messages.createError('FlowIdNotFound', [filePath]); +}; diff --git a/test/nut/open.nut.ts b/test/nut/open.nut.ts index 5361a67b..127be591 100644 --- a/test/nut/open.nut.ts +++ b/test/nut/open.nut.ts @@ -21,6 +21,7 @@ config.truncateThreshold = 0; describe('test org:open command', () => { const flexiPagePath = path.join('force-app', 'main', 'default', 'flexipages', 'Property_Explorer.flexipage-meta.xml'); + const flowPath = path.join('force-app', 'main', 'default', 'flows', 'Create_property.flow-meta.xml'); before(async () => { session = await TestSession.create({ @@ -65,6 +66,15 @@ describe('test org:open command', () => { expect(result.url).to.include('/visualEditor/appBuilder.app?pageId'); }); + it('should produce the URL for an existing flow', () => { + const result = execCmd(`force:source:open --source-file ${flowPath} --url-only --json`, { + ensureExitCode: 0, + }).jsonOutput?.result; + assert(result); + expect(result).to.include({ orgId: defaultUserOrgId, username: defaultUsername }); + expect(result.url).to.include('/builder_platform_interaction/flowBuilder.app?flowId=301'); + }); + it("should produce the org's frontdoor url when edition of file is not supported", async () => { const layoutDir = path.join('force-app', 'main', 'default', 'layouts'); const layoutFilePath = path.join(layoutDir, 'MyLayout.layout-meta.xml'); diff --git a/yarn.lock b/yarn.lock index d0a11872..fe0957f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1361,7 +1361,7 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== -"@types/shelljs@^0.8.12", "@types/shelljs@^0.8.13": +"@types/shelljs@^0.8.13": version "0.8.13" resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.13.tgz#a94bf7f2b82b7cd9f4496bbe063c3adb0868a650" integrity sha512-++uMLOQSLlse1kCfEOwhgmHuaABZwinkylmUKCpvcEGZUov3TtM+gJZloSkW/W+9pEAEg/VBOwiSR05oqJsa5A== From 27a620e7c315d4bb2db7a4930056f849879d55ff Mon Sep 17 00:00:00 2001 From: mshanemc Date: Fri, 13 Oct 2023 10:28:40 -0500 Subject: [PATCH 2/2] refactor: pr feedback --- messages/open.md | 2 +- test/nut/open.nut.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/messages/open.md b/messages/open.md index c5a8ae3c..4b729dac 100644 --- a/messages/open.md +++ b/messages/open.md @@ -74,7 +74,7 @@ The Lightning Experience-enabled custom domain is unavailable. # FlowIdNotFound -No ID not found for Flow %s . +No ID not found for Flow %s. # FlowIdNotFound.actions diff --git a/test/nut/open.nut.ts b/test/nut/open.nut.ts index 127be591..6bffceea 100644 --- a/test/nut/open.nut.ts +++ b/test/nut/open.nut.ts @@ -67,7 +67,7 @@ describe('test org:open command', () => { }); it('should produce the URL for an existing flow', () => { - const result = execCmd(`force:source:open --source-file ${flowPath} --url-only --json`, { + const result = execCmd(`force:org:open --source-file ${flowPath} --url-only --json`, { ensureExitCode: 0, }).jsonOutput?.result; assert(result);