From e2960268eab94adcc913a29ac23d6efb354302ce Mon Sep 17 00:00:00 2001 From: Marco <5818581+marcomura@users.noreply.github.com> Date: Fri, 21 Feb 2025 14:00:54 -0800 Subject: [PATCH] [AXON-109] Expose toggle switch to push branch to remote (Start work) (#137) * Expose toggle switch to push branch to origin (Start work) * Updated CHANGELOG --- CHANGELOG.md | 6 + src/analytics.test.ts | 103 ++++++++++++++++++ src/analytics.ts | 53 ++++----- src/ipc/issueActions.ts | 1 + src/lib/analyticsApi.ts | 2 +- src/lib/ipc/fromUI/startWork.ts | 1 + .../startwork/startWorkActionApi.ts | 1 + .../startwork/startWorkWebviewController.ts | 3 +- .../atlascode/startwork/StartWorkPage.tsx | 27 +++++ .../startwork/startWorkController.ts | 3 + src/vscAnalyticsApi.ts | 4 +- .../startwork/vscStartWorkActionApi.ts | 7 +- .../components/issue/StartWorkPage.tsx | 1 + .../startWorkOnBitbucketIssueWebview.ts | 8 +- src/webviews/startWorkOnIssueWebview.ts | 11 +- 15 files changed, 194 insertions(+), 37 deletions(-) create mode 100644 src/analytics.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ddae8ec..fca66880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## What's new in 3.4.8 + +### Features + +- Added a new toggle switch in 'Start work' page to choose if the new branch should be automatically pushed to remote. + ## What's new in 3.4.7 ### Features diff --git a/src/analytics.test.ts b/src/analytics.test.ts new file mode 100644 index 00000000..eb3e23af --- /dev/null +++ b/src/analytics.test.ts @@ -0,0 +1,103 @@ +import { viewScreenEvent } from './analytics'; + +interface MockedData { + getFirstAAID_value?: boolean; +} + +const mockedData: MockedData = {}; + +jest.mock('./container', () => ({ + Container: { siteManager: { getFirstAAID: () => mockedData.getFirstAAID_value } }, +})); + +function setProcessPlatform(platform: NodeJS.Platform) { + Object.defineProperty(process, 'platform', { + value: platform, + writable: false, + }); +} + +describe('viewScreenEvent', () => { + const originalPlatform = process.platform; + + beforeEach(() => { + setProcessPlatform('win32'); + mockedData.getFirstAAID_value = true; + }); + + afterAll(() => { + setProcessPlatform(originalPlatform); + }); + + it('should create a screen event with the correct screen name', async () => { + const screenName = 'testScreen'; + const event = await viewScreenEvent(screenName); + expect(event.name).toEqual(screenName); + expect(event.screenEvent.attributes).toBeUndefined(); + }); + + it('should exclude from activity if screen name is atlascodeWelcomeScreen', async () => { + const screenName = 'atlascodeWelcomeScreen'; + const event = await viewScreenEvent(screenName); + expect(event.screenEvent.attributes.excludeFromActivity).toBeTruthy(); + }); + + it('should include site information if provided (cloud)', async () => { + const screenName = 'testScreen'; + const site: any = { + id: 'siteId', + product: { name: 'Jira', key: 'jira' }, + isCloud: true, + }; + const event = await viewScreenEvent(screenName, site); + expect(event.screenEvent.attributes.instanceType).toEqual('cloud'); + expect(event.screenEvent.attributes.hostProduct).toEqual('Jira'); + }); + + it('should include site information if provided (server)', async () => { + const screenName = 'testScreen'; + const site: any = { + id: 'siteId', + product: { name: 'Jira', key: 'jira' }, + isCloud: false, + }; + const event = await viewScreenEvent(screenName, site); + expect(event.screenEvent.attributes.instanceType).toEqual('server'); + expect(event.screenEvent.attributes.hostProduct).toEqual('Jira'); + }); + + it('should include product information if provided', async () => { + const screenName = 'testScreen'; + const product = { name: 'Bitbucket', key: 'bitbucket' }; + const event = await viewScreenEvent(screenName, undefined, product); + expect(event.screenEvent.attributes.hostProduct).toEqual('Bitbucket'); + }); + + it('should set platform based on process.platform (win32)', async () => { + setProcessPlatform('win32'); + const screenName = 'testScreen'; + const event = await viewScreenEvent(screenName); + expect(event.screenEvent.platform).toEqual('windows'); + }); + + it('should set platform based on process.platform (darwin)', async () => { + setProcessPlatform('darwin'); + const screenName = 'testScreen'; + const event = await viewScreenEvent(screenName); + expect(event.screenEvent.platform).toEqual('mac'); + }); + + it('should set platform based on process.platform (linux)', async () => { + setProcessPlatform('linux'); + const screenName = 'testScreen'; + const event = await viewScreenEvent(screenName); + expect(event.screenEvent.platform).toEqual('linux'); + }); + + it('should set platform based on process.platform (aix)', async () => { + setProcessPlatform('aix'); + const screenName = 'testScreen'; + const event = await viewScreenEvent(screenName); + expect(event.screenEvent.platform).toEqual('desktop'); + }); +}); diff --git a/src/analytics.ts b/src/analytics.ts index 071e77d6..95b5b907 100644 --- a/src/analytics.ts +++ b/src/analytics.ts @@ -21,7 +21,7 @@ export const Registry = { }; class AnalyticsPlatform { - private static nodeJsPlatformMapping = { + private static nodeJsPlatformMapping: Record = { aix: 'desktop', android: 'android', darwin: 'mac', @@ -31,9 +31,11 @@ class AnalyticsPlatform { sunos: 'desktop', win32: 'windows', cygwin: 'windows', + haiku: 'unknown', + netbsd: 'unknown', }; - static for(p: string): string { + static for(p: NodeJS.Platform): string { return this.nodeJsPlatformMapping[p] || 'unknown'; } } @@ -107,8 +109,13 @@ export async function issueCommentEvent(site: DetailedSiteInfo): Promise { - return instanceTrackEvent(site, 'workStarted', 'issue'); +export async function issueWorkStartedEvent( + site: DetailedSiteInfo, + pushBranchToRemoteChecked: boolean, +): Promise { + const attributesObject = instanceType({}, site); + attributesObject.attributes.pushBranchToRemoteChecked = pushBranchToRemoteChecked; + return instanceTrackEvent(site, 'workStarted', 'issue', attributesObject); } export async function issueUpdatedEvent( @@ -164,7 +171,7 @@ export async function prCommentEvent(site: DetailedSiteInfo): Promise { - const attributesObject: any = instanceType({}, site); + const attributesObject = instanceType({}, site); attributesObject.attributes.source = source; return trackEvent('created', 'pullRequestComment', attributesObject); } @@ -657,33 +664,27 @@ function tenantOrNull(e: Object, tenantId?: string): T { return newObj as T; } -function instanceType(eventProps: Object, site?: DetailedSiteInfo, product?: Product): Object { - let attrs: Object | undefined = undefined; - const newObj = eventProps; - +function instanceType( + eventProps: Record, + site?: DetailedSiteInfo, + product?: Product, +): Record { if (product) { - attrs = { hostProduct: product.name }; + eventProps.attributes = eventProps.attributes || {}; + eventProps.attributes.hostProduct = product.name; } if (site && !isEmptySiteInfo(site)) { - const instanceType: string = site.isCloud ? 'cloud' : 'server'; - attrs = { instanceType: instanceType, hostProduct: site.product.name }; + eventProps.attributes = eventProps.attributes || {}; + eventProps.attributes.instanceType = site.isCloud ? 'cloud' : 'server'; + eventProps.attributes.hostProduct = site.product.name; } - if (attrs) { - newObj['attributes'] = { ...newObj['attributes'], ...attrs }; - } - - return newObj; + return eventProps; } -function excludeFromActivity(eventProps: Object): Object { - const newObj = eventProps; - - if (newObj['attributes']) { - newObj['attributes'] = { ...newObj['attributes'], ...{ excludeFromActivity: true } }; - } else { - Object.assign(newObj, { attributes: { excludeFromActivity: true } }); - } - return newObj; +function excludeFromActivity(eventProps: Record): Record { + eventProps.attributes = eventProps.attributes || {}; + eventProps.attributes.excludeFromActivity = true; + return eventProps; } diff --git a/src/ipc/issueActions.ts b/src/ipc/issueActions.ts index a8df0823..bad0a37c 100644 --- a/src/ipc/issueActions.ts +++ b/src/ipc/issueActions.ts @@ -120,6 +120,7 @@ export interface StartWorkAction extends Action { remoteName: string; setupJira: boolean; setupBitbucket: boolean; + pushBranchToRemote: boolean; } export interface OpenStartWorkPageAction extends Action { diff --git a/src/lib/analyticsApi.ts b/src/lib/analyticsApi.ts index 32d51b76..76f5e9bb 100644 --- a/src/lib/analyticsApi.ts +++ b/src/lib/analyticsApi.ts @@ -12,7 +12,7 @@ export interface AnalyticsApi { fireIssueTransitionedEvent(site: DetailedSiteInfo, issueKey: string): Promise; fireIssueUrlCopiedEvent(): Promise; fireIssueCommentEvent(site: DetailedSiteInfo): Promise; - fireIssueWorkStartedEvent(site: DetailedSiteInfo): Promise; + fireIssueWorkStartedEvent(site: DetailedSiteInfo, pushBranchToRemoteChecked: boolean): Promise; fireIssueUpdatedEvent(site: DetailedSiteInfo, issueKey: string, fieldName: string, fieldKey: string): Promise; fireStartIssueCreationEvent(source: string, product: Product): Promise; fireBBIssueCreatedEvent(site: DetailedSiteInfo): Promise; diff --git a/src/lib/ipc/fromUI/startWork.ts b/src/lib/ipc/fromUI/startWork.ts index 6a2968bc..5e081b24 100644 --- a/src/lib/ipc/fromUI/startWork.ts +++ b/src/lib/ipc/fromUI/startWork.ts @@ -27,6 +27,7 @@ export interface StartRequestAction { sourceBranch: Branch; targetBranch: string; upstream: string; + pushBranchToRemote: boolean; } export interface OpenSettingsAction { diff --git a/src/lib/webview/controller/startwork/startWorkActionApi.ts b/src/lib/webview/controller/startwork/startWorkActionApi.ts index fb182cfa..40143281 100644 --- a/src/lib/webview/controller/startwork/startWorkActionApi.ts +++ b/src/lib/webview/controller/startwork/startWorkActionApi.ts @@ -17,6 +17,7 @@ export interface StartWorkActionApi { destinationBranch: string, sourceBranch: Branch, remote: string, + pushBranchToRemote: boolean, ): Promise; closePage(): void; getStartWorkConfig(): StartWorkBranchTemplate; diff --git a/src/lib/webview/controller/startwork/startWorkWebviewController.ts b/src/lib/webview/controller/startwork/startWorkWebviewController.ts index bd25f91e..894cfb11 100644 --- a/src/lib/webview/controller/startwork/startWorkWebviewController.ts +++ b/src/lib/webview/controller/startwork/startWorkWebviewController.ts @@ -134,6 +134,7 @@ export class StartWorkWebviewController implements WebviewController { const [transitionIssueEnabled, setTransitionIssueEnabled] = useState(true); const [branchSetupEnabled, setbranchSetupEnabled] = useState(true); + const [pushBranchEnabled, setPushBranchEnabled] = useState(true); const [transition, setTransition] = useState(emptyTransition); const [repository, setRepository] = useState(emptyRepoData); const [branchType, setBranchType] = useState(emptyPrefix); @@ -120,6 +121,8 @@ const StartWorkPage: React.FunctionComponent = () => { [branchSetupEnabled], ); + const togglePushBranchEnabled = useCallback(() => setPushBranchEnabled(!pushBranchEnabled), [pushBranchEnabled]); + const handleTransitionChange = useCallback( (event: React.ChangeEvent<{ name?: string | undefined; value: any }>) => { setTransition(event.target.value); @@ -248,6 +251,7 @@ const StartWorkPage: React.FunctionComponent = () => { sourceBranch, localBranch, upstream, + pushBranchEnabled, ); setSubmitState('submit-success'); setSubmitResponse(response); @@ -264,6 +268,7 @@ const StartWorkPage: React.FunctionComponent = () => { sourceBranch, localBranch, upstream, + pushBranchEnabled, ]); const handleOpenSettings = useCallback(() => { @@ -704,6 +709,28 @@ const StartWorkPage: React.FunctionComponent = () => { + +