Skip to content

Commit a218f96

Browse files
authored
feat: distinguish app vs launchpad utm_source when using utm params (#21424)
1 parent 841d288 commit a218f96

File tree

9 files changed

+60
-92
lines changed

9 files changed

+60
-92
lines changed

packages/app/cypress/e2e/top-nav.cy.ts

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -264,34 +264,35 @@ describe('App Top Nav Workflows', () => {
264264
cy.findByRole('heading', { name: 'References', level: 2 })
265265
cy.findByRole('heading', { name: 'Run in CI/CD', level: 2 })
266266

267-
cy.validateExternalLink({
268-
name: 'Write your first test',
269-
href: 'https://on.cypress.io/writing-first-test?utm_medium=Docs+Menu&utm_content=First+Test',
270-
})
271-
272-
cy.validateExternalLink({
273-
name: 'Testing your app',
274-
href: 'https://on.cypress.io/testing-your-app?utm_medium=Docs+Menu&utm_content=Testing+Your+App',
275-
})
276-
277-
cy.validateExternalLink({
278-
name: 'Organizing Tests',
279-
href: 'https://on.cypress.io/writing-and-organizing-tests?utm_medium=Docs+Menu&utm_content=Organizing+Tests',
280-
})
281-
282-
cy.validateExternalLink({
283-
name: 'Best Practices',
284-
href: 'https://on.cypress.io/best-practices?utm_medium=Docs+Menu&utm_content=Best+Practices',
285-
})
286-
287-
cy.validateExternalLink({
288-
name: 'Configuration',
289-
href: 'https://on.cypress.io/configuration?utm_medium=Docs+Menu&utm_content=Configuration',
290-
})
291-
292-
cy.validateExternalLink({
293-
name: 'API',
294-
href: 'https://on.cypress.io/api?utm_medium=Docs+Menu&utm_content=API',
267+
const expectedLinks = [
268+
{
269+
name: 'Write your first test',
270+
href: 'https://on.cypress.io/writing-first-test?utm_medium=Docs+Menu&utm_content=First+Test&utm_source=Binary%3A+App',
271+
},
272+
{
273+
name: 'Testing your app',
274+
href: 'https://on.cypress.io/testing-your-app?utm_medium=Docs+Menu&utm_content=Testing+Your+App&utm_source=Binary%3A+App',
275+
},
276+
{
277+
name: 'Organizing Tests',
278+
href: 'https://on.cypress.io/writing-and-organizing-tests?utm_medium=Docs+Menu&utm_content=Organizing+Tests&utm_source=Binary%3A+App',
279+
},
280+
{
281+
name: 'Best Practices',
282+
href: 'https://on.cypress.io/best-practices?utm_medium=Docs+Menu&utm_content=Best+Practices&utm_source=Binary%3A+App',
283+
},
284+
{
285+
name: 'Configuration',
286+
href: 'https://on.cypress.io/configuration?utm_medium=Docs+Menu&utm_content=Configuration&utm_source=Binary%3A+App',
287+
},
288+
{
289+
name: 'API',
290+
href: 'https://on.cypress.io/api?utm_medium=Docs+Menu&utm_content=API&utm_source=Binary%3A+App',
291+
},
292+
]
293+
294+
expectedLinks.forEach((link) => {
295+
cy.validateExternalLink(link)
295296
})
296297
})
297298

packages/app/src/runs/modals/CreateCloudOrgModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{{ t('runs.connect.modal.createOrg.description') }}
1111
</p>
1212
<ExternalLink
13-
class="border rounded mx-auto outline-none py-11px px-16px border-indigo-500 bg-indigo-500 text-white inline-block hocus-default max-h-60px"
13+
class="border rounded mx-auto outline-none bg-indigo-500 border-indigo-500 text-white max-h-60px py-11px px-16px inline-block hocus-default"
1414
:href="createOrgUrl"
1515
:include-graphql-port="true"
1616
@click="startWaitingOrgToBeCreated()"

packages/frontend-shared/src/gql-components/HeaderBarContent.cy.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ describe('<HeaderBarContent />', { viewportWidth: 1000, viewportHeight: 750 }, (
7777
// because outside of global mode, those are buttons that trigger popups
7878
// so this way we can assert all links at once.
7979
const expectedDocsLinks = {
80-
[text.docsMenu.firstTest]: 'https://on.cypress.io/writing-first-test?utm_medium=Docs+Menu&utm_content=First+Test',
81-
[text.docsMenu.testingApp]: 'https://on.cypress.io/testing-your-app?utm_medium=Docs+Menu&utm_content=Testing+Your+App',
82-
[text.docsMenu.organizingTests]: 'https://on.cypress.io/writing-and-organizing-tests?utm_medium=Docs+Menu&utm_content=Organizing+Tests',
83-
[text.docsMenu.bestPractices]: 'https://on.cypress.io/best-practices?utm_medium=Docs+Menu&utm_content=Best+Practices',
84-
[text.docsMenu.configuration]: 'https://on.cypress.io/configuration?utm_medium=Docs+Menu&utm_content=Configuration',
85-
[text.docsMenu.api]: 'https://on.cypress.io/api?utm_medium=Docs+Menu&utm_content=API',
86-
[text.docsMenu.ciSetup]: 'https://on.cypress.io/ci?utm_medium=Docs+Menu&utm_content=Set+Up+CI',
87-
[text.docsMenu.fasterTests]: 'https://on.cypress.io/parallelization?utm_medium=Docs+Menu&utm_content=Parallelization',
80+
[text.docsMenu.firstTest]: 'https://on.cypress.io/writing-first-test?utm_medium=Docs+Menu&utm_content=First+Test&utm_source=Binary%3A+Launchpad',
81+
[text.docsMenu.testingApp]: 'https://on.cypress.io/testing-your-app?utm_medium=Docs+Menu&utm_content=Testing+Your+App&utm_source=Binary%3A+Launchpad',
82+
[text.docsMenu.organizingTests]: 'https://on.cypress.io/writing-and-organizing-tests?utm_medium=Docs+Menu&utm_content=Organizing+Tests&utm_source=Binary%3A+Launchpad',
83+
[text.docsMenu.bestPractices]: 'https://on.cypress.io/best-practices?utm_medium=Docs+Menu&utm_content=Best+Practices&utm_source=Binary%3A+Launchpad',
84+
[text.docsMenu.configuration]: 'https://on.cypress.io/configuration?utm_medium=Docs+Menu&utm_content=Configuration&utm_source=Binary%3A+Launchpad',
85+
[text.docsMenu.api]: 'https://on.cypress.io/api?utm_medium=Docs+Menu&utm_content=API&utm_source=Binary%3A+Launchpad',
86+
[text.docsMenu.ciSetup]: 'https://on.cypress.io/ci?utm_medium=Docs+Menu&utm_content=Set+Up+CI&utm_source=Binary%3A+Launchpad',
87+
[text.docsMenu.fasterTests]: 'https://on.cypress.io/parallelization?utm_medium=Docs+Menu&utm_content=Parallelization&utm_source=Binary%3A+Launchpad',
8888
}
8989

9090
cy.contains('button', text.docsMenu.docsHeading).click()
@@ -347,12 +347,12 @@ describe('<HeaderBarContent />', { viewportWidth: 1000, viewportHeight: 750 }, (
347347
mountWithSavedState()
348348

349349
cy.contains(
350-
'a[href="https://on.cypress.io/setup-ci?utm_medium=CI+Prompt+1&utm_campaign=Other&utm_content=Automatic"]',
350+
'a[href="https://on.cypress.io/setup-ci?utm_medium=CI+Prompt+1&utm_campaign=Other&utm_content=Automatic&utm_source=Binary%3A+Launchpad"]',
351351
defaultMessages.topNav.docsMenu.prompts.ci1.seeOtherGuides,
352352
).should('be.visible')
353353

354354
cy.contains(
355-
'a[href="https://on.cypress.io/ci?utm_medium=CI+Prompt+1&utm_campaign=Learn+More"]',
355+
'a[href="https://on.cypress.io/ci?utm_medium=CI+Prompt+1&utm_campaign=Learn+More&utm_source=Binary%3A+Launchpad"]',
356356
defaultMessages.topNav.docsMenu.prompts.ci1.intro,
357357
).should('be.visible')
358358
})
@@ -425,7 +425,7 @@ describe('<HeaderBarContent />', { viewportWidth: 1000, viewportHeight: 750 }, (
425425

426426
it('links to more information with expected utm params', () => {
427427
cy.contains(
428-
'a[href="https://on.cypress.io/smart-orchestration?utm_medium=CI+Prompt+1&utm_campaign=Learn+More"]',
428+
'a[href="https://on.cypress.io/smart-orchestration?utm_medium=CI+Prompt+1&utm_campaign=Learn+More&utm_source=Binary%3A+Launchpad"]',
429429
defaultMessages.topNav.docsMenu.prompts.orchestration1.learnMore,
430430
)
431431
.should('be.visible')

packages/frontend-shared/src/gql-components/topnav/PromptContent.cy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ describe('<PromptContent />', { viewportWidth: 500, viewportHeight: 800 }, () =>
2727
.should('have.length', 6)
2828
.eq(0)
2929
.find('a')
30-
.should('have.attr', 'href', 'https://on.cypress.io/setup-ci-circleci?utm_medium=CI+Prompt+1&utm_campaign=Circle&utm_content=Manual')
30+
.should('have.attr', 'href', 'https://on.cypress.io/setup-ci-circleci?utm_medium=CI+Prompt+1&utm_campaign=Circle&utm_content=Manual&utm_source=Binary%3A+Launchpad')
3131

3232
cy.contains('a', defaultMessages.topNav.docsMenu.prompts.ci1.intro)
33-
.should('have.attr', 'href', 'https://on.cypress.io/ci?utm_medium=CI+Prompt+1&utm_campaign=Learn+More')
33+
.should('have.attr', 'href', 'https://on.cypress.io/ci?utm_medium=CI+Prompt+1&utm_campaign=Learn+More&utm_source=Binary%3A+Launchpad')
3434
})
3535
})

packages/frontend-shared/src/utils/getUrlWithParams.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ export type LinkWithParams = {
55

66
export const getUrlWithParams = (link: LinkWithParams) => {
77
let result = link.url
8+
const hasUtmParams = Object.keys(link.params).some((param) => param.startsWith('utm_'))
9+
10+
if (hasUtmParams) {
11+
// __CYPRESS_MODE__ is only set on the window in th browser app -
12+
// checking this allows us to know if links are clicked in the browser app or the launchpad
13+
const utm_source = window.__CYPRESS_MODE__ ? 'Binary: App' : 'Binary: Launchpad'
14+
15+
link.params.utm_source = utm_source
16+
}
817

918
if (link.params) {
1019
result += `?${new URLSearchParams(link.params).toString()}`

packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ export const mutation = mutationType({
9393
resolve: (_, args, ctx) => {
9494
let url = args.url
9595

96+
// the `port` param is included in external links to create a cloud organization
97+
// so that the app can be notified when the org has been created
9698
if (args.includeGraphqlPort && process.env.CYPRESS_INTERNAL_GRAPHQL_PORT) {
97-
url = `${args.url}?port=${process.env.CYPRESS_INTERNAL_GRAPHQL_PORT}`
99+
const joinCharacter = args.url.includes('?') ? '&' : '?'
100+
101+
url = `${args.url}${joinCharacter}port=${process.env.CYPRESS_INTERNAL_GRAPHQL_PORT}`
98102
}
99103

100104
ctx.actions.electron.openExternal(url)

packages/launchpad/cypress/e2e/navigation.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('Navigation', () => {
1010
cy.contains('button', defaultMessages.topNav.docsMenu.docsHeading).click()
1111
cy.contains('a', defaultMessages.topNav.docsMenu.firstTest).click()
1212
cy.wait('@OpenExternal').then((interception: Interception) => {
13-
expect(interception.request.body.variables.url).to.equal('https://on.cypress.io/writing-first-test?utm_medium=Docs+Menu&utm_content=First+Test')
13+
expect(interception.request.body.variables.url).to.equal('https://on.cypress.io/writing-first-test?utm_medium=Docs+Menu&utm_content=First+Test&utm_source=Binary%3A+Launchpad')
1414
})
1515
})
1616
})

packages/server/lib/gui/links.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,6 @@
1-
import _ from 'lodash'
2-
import { URL, URLSearchParams } from 'url'
3-
41
// NOTE: in order for query params to be passed through on links
52
// forwardQueryParams: true must be set for that slug in the on package
63

7-
interface OpenExternalOptions {
8-
url: string
9-
params: { [key: string]: string }
10-
}
11-
12-
export const openExternal = (opts: OpenExternalOptions | string) => {
13-
if (_.isString(opts)) {
14-
return require('electron').shell.openExternal(opts)
15-
}
16-
17-
const url = new URL(opts.url)
18-
19-
if (opts.params) {
20-
// just add the utm_source here so we don't have to duplicate it on every link
21-
if (_.find(opts.params, (_val, key) => _.includes(key, 'utm_'))) {
22-
opts.params.utm_source = 'Test Runner'
23-
}
24-
25-
url.search = new URLSearchParams(opts.params).toString()
26-
}
27-
28-
return require('electron').shell.openExternal(url.href)
4+
export const openExternal = (url: string) => {
5+
return require('electron').shell.openExternal(url)
296
}

packages/server/test/unit/gui/links_spec.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,4 @@ describe('lib/gui/links', () => {
2020
openExternal('https://on.cypress.io/string-link')
2121
expect(shell.openExternal).to.be.calledWith('https://on.cypress.io/string-link')
2222
})
23-
24-
it('appends get parameters', () => {
25-
openExternal({
26-
url: 'https://on.cypress.io/string-link',
27-
params: {
28-
search: 'term',
29-
},
30-
})
31-
32-
expect(shell.openExternal).to.be.calledWith('https://on.cypress.io/string-link?search=term')
33-
})
34-
35-
it('automatically adds utm_source if utm params are present', () => {
36-
openExternal({
37-
url: 'https://on.cypress.io/string-link',
38-
params: {
39-
utm_medium: 'GUI Tab',
40-
utm_campaign: 'Learn More',
41-
},
42-
})
43-
44-
expect(shell.openExternal).to.be.calledWith('https://on.cypress.io/string-link?utm_medium=GUI+Tab&utm_campaign=Learn+More&utm_source=Test+Runner')
45-
})
4623
})

0 commit comments

Comments
 (0)