Skip to content

Commit 8ab3ea8

Browse files
feat: implement new login and "connect project" logic (#23762)
Co-authored-by: Stokes Player <stokes.player@gmail.com>
1 parent 4e667e5 commit 8ab3ea8

File tree

76 files changed

+2100
-829
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+2100
-829
lines changed

.vscode/cspell.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"topnav",
3838
"unconfigured",
3939
"unplugin",
40+
"unref",
4041
"unrunnable",
4142
"unstaged",
4243
"urql",
@@ -48,4 +49,4 @@
4849
],
4950
"ignoreWords": [],
5051
"import": []
51-
}
52+
}

graphql-codegen.yml

+4-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ vueOperations: &vueOperations
1818
- 'typescript-operations'
1919
- 'typed-document-node':
2020
# Intentionally specified under typed-document-node rather than top level config,
21-
# becuase we don't want it flattening the types for the operations
21+
# because we don't want it flattening the types for the operations
2222
flattenGeneratedTypes: true
2323

2424
vueTesting: &vueTesting
@@ -122,9 +122,10 @@ generates:
122122
'./packages/app/src/generated/graphql-test.ts':
123123
documents:
124124
- './packages/app/src/**/*.{vue,ts,tsx,js,jsx}'
125-
- './packages/frontend-shared/src/**/*.{vue,ts,tsx,js,jsx}'
125+
- './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
126126
<<: *vueTesting
127127

128128
'./packages/frontend-shared/src/generated/graphql-test.ts':
129-
documents: './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
129+
documents:
130+
- './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
130131
<<: *vueTesting

packages/app/cypress/e2e/runs.cy.ts

+30-18
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
116116
obj.result.data.cloudViewer.organizations.nodes = []
117117
}
118118

119+
if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
120+
obj.result.data.cloudViewer.firstOrganization.nodes = []
121+
}
122+
119123
return obj.result
120124
})
121125

@@ -150,8 +154,14 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
150154
cy.startAppServer('component')
151155

152156
cy.remoteGraphQLIntercept(async (obj) => {
153-
if (obj?.result?.data?.cloudViewer?.organizations?.nodes) {
154-
obj.result.data.cloudViewer.organizations.nodes = []
157+
if ((obj.operationName !== 'CreateCloudOrgModal_CloudOrganizationsCheck_refreshOrganizations_cloudViewer')) {
158+
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
159+
obj.result.data.cloudViewer.organizations.nodes = []
160+
}
161+
162+
if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
163+
obj.result.data.cloudViewer.firstOrganization.nodes = []
164+
}
155165
}
156166

157167
return obj.result
@@ -181,7 +191,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
181191
cy.startAppServer('component')
182192

183193
cy.remoteGraphQLIntercept(async (obj, testState) => {
184-
if (obj.operationName === 'CloudConnectModals_RefreshCloudViewer_refreshCloudViewer_cloudViewer') {
194+
if (obj.operationName === 'LoginConnectModals_LoginConnectModalsQuery_cloudViewer') {
185195
testState.refetchCalled = true
186196
}
187197

@@ -312,7 +322,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
312322

313323
moveToRunsPage()
314324
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
315-
cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
325+
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
316326
cy.findByText(defaultMessages.runs.connectSuccessAlert.title).should('be.visible')
317327

318328
cy.withCtx(async (ctx) => {
@@ -351,7 +361,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
351361

352362
cy.get('[href="#/runs"]').click()
353363
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
354-
cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
364+
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
355365

356366
cy.get('[data-cy="alert"]').within(() => {
357367
cy.contains(defaultMessages.runs.connect.errors.baseError.title)
@@ -386,7 +396,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
386396

387397
cy.get('[href="#/runs"]').click()
388398
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
389-
cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
399+
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
390400

391401
cy.get('[data-cy="alert"]').within(() => {
392402
cy.contains(defaultMessages.runs.connect.errors.internalServerError.title)
@@ -417,7 +427,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
417427
}
418428

419429
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
420-
const projectNodes = obj.result.data?.cloudViewer.organizations.nodes[0].projects.nodes
430+
const projectNodes = obj.result.data.cloudViewer.organizations.nodes[0].projects.nodes
421431

422432
projectNodes.push({
423433
__typename: 'CloudProject',
@@ -440,9 +450,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
440450
})
441451

442452
it('opens Connect Project modal after clicking Reconnect Project button', () => {
443-
cy.findByText(defaultMessages.runs.errors.notFound.button).should('be.visible').click()
453+
cy.findByText(defaultMessages.runs.errors.notFound.button).click()
454+
444455
cy.get('[aria-modal="true"]').should('exist')
445-
cy.get('[data-cy="selectProject"] button').should('have.text', 'Mock Project')
456+
cy.contains('[data-cy="selectProject"] button', 'Mock Project')
446457
cy.get('[data-cy="connect-project"]').click()
447458
cy.get('[data-cy="runs"]', { timeout: 7500 })
448459
})
@@ -510,12 +521,13 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
510521

511522
it('updates the button text when the request access button is clicked', () => {
512523
cy.remoteGraphQLIntercept(async (obj, testState) => {
513-
if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
524+
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
514525
const proj = obj!.result!.data!.cloudProjectBySlug
515526

516527
proj.__typename = 'CloudProjectUnauthorized'
517528
proj.message = 'Cloud Project Unauthorized'
518529
proj.hasRequestedAccess = false
530+
519531
testState.project = proj
520532
} else if (obj.operationName === 'RunsErrorRenderer_RequestAccess_cloudProjectRequestAccess') {
521533
obj!.result!.data!.cloudProjectRequestAccess = {
@@ -696,7 +708,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
696708

697709
cy.loginUser()
698710
cy.remoteGraphQLIntercept((obj) => {
699-
if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
711+
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
700712
cloudData = obj.result
701713
obj.result = {}
702714

@@ -714,7 +726,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
714726
// cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435
715727

716728
cy.remoteGraphQLIntercept((obj) => {
717-
if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
729+
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
718730
return cloudData
719731
}
720732

@@ -732,10 +744,6 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
732744
cy.startAppServer('component')
733745
})
734746

735-
afterEach(() => {
736-
cy.goOnline()
737-
})
738-
739747
it('shows alert warning if runs have been returned already', () => {
740748
cy.loginUser()
741749
cy.visitApp()
@@ -773,11 +781,15 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
773781
cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js'])
774782
cy.startAppServer('component')
775783

776-
cy.remoteGraphQLIntercept(async (obj) => {
784+
cy.remoteGraphQLIntercept((obj) => {
777785
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
778786
obj.result.data.cloudViewer.organizations.nodes = []
779787
}
780788

789+
if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
790+
obj.result.data.cloudViewer.firstOrganization.nodes = []
791+
}
792+
781793
return obj.result
782794
})
783795

@@ -845,7 +857,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
845857
cy.openProject('component-tests')
846858
cy.startAppServer('component')
847859
cy.loginUser()
848-
cy.remoteGraphQLIntercept((obj, testState) => {
860+
cy.remoteGraphQLIntercept((obj) => {
849861
if (obj.result.data?.cloudProjectBySlug?.runs?.nodes.length) {
850862
obj.result.data.cloudProjectBySlug.runs.nodes.map((run) => {
851863
run.status = 'RUNNING'

packages/app/cypress/e2e/specs_list_latest_runs.cy.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
160160
})
161161

162162
context('when no runs are recorded', () => {
163-
beforeEach(() => {
163+
it('shows placeholders for all visible specs', { defaultCommandTimeout: 6000 }, () => {
164164
cy.loginUser()
165165

166166
cy.remoteGraphQLIntercept(async (obj) => {
@@ -181,10 +181,6 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
181181
})
182182

183183
cy.visitApp()
184-
cy.findByTestId('sidebar-link-specs-page').click()
185-
})
186-
187-
it('shows placeholders for all visible specs', () => {
188184
allVisibleSpecsShouldBePlaceholders()
189185
})
190186
})

packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
22
import type { SinonStub } from 'sinon'
33

4-
describe('App: Runs', { viewportWidth: 1200 }, () => {
4+
describe('CreateCloudOrgModalSubscription', { viewportWidth: 1200 }, () => {
55
beforeEach(() => {
66
cy.scaffoldProject('component-tests')
77
cy.openProject('component-tests')
@@ -22,6 +22,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
2222
obj.result.data.cloudViewer.organizations.nodes = []
2323
}
2424

25+
if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
26+
obj.result.data.cloudViewer.firstOrganization.nodes = []
27+
}
28+
2529
return obj.result
2630
})
2731

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

+29
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,17 @@ describe('App Top Nav Workflows', () => {
453453
cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js'])
454454
cy.startAppServer()
455455
cy.visitApp()
456+
cy.remoteGraphQLIntercept(async (obj) => {
457+
if (obj.result.data?.cloudViewer) {
458+
obj.result.data.cloudViewer.organizations = {
459+
__typename: 'CloudOrganizationConnection',
460+
id: 'test',
461+
nodes: [{ __typename: 'CloudOrganization', id: '987' }],
462+
}
463+
}
464+
465+
return obj.result
466+
})
456467

457468
mockLogInActionsForUser(mockUser)
458469
logIn({ expectedNextStepText: 'Connect project', displayName: mockUser.name })
@@ -494,6 +505,24 @@ describe('App Top Nav Workflows', () => {
494505
cy.findByTestId('app-header-bar').findByTestId('user-avatar-title').should('be.visible')
495506
})
496507

508+
it('if the project has no runs, shows "record your first run" prompt after clicking', () => {
509+
cy.remoteGraphQLIntercept((obj) => {
510+
if (obj.result?.data?.cloudProjectBySlug?.runs?.nodes?.length) {
511+
obj.result.data.cloudProjectBySlug.runs.nodes = []
512+
}
513+
514+
return obj.result
515+
})
516+
517+
mockLogInActionsForUser(mockUserNoName)
518+
519+
logIn({ expectedNextStepText: 'Continue', displayName: mockUserNoName.email })
520+
521+
cy.contains('[data-cy=standard-modal] h2', defaultMessages.specPage.banners.record.title).should('be.visible')
522+
cy.contains('[data-cy=standard-modal]', defaultMessages.specPage.banners.record.content).should('be.visible')
523+
cy.contains('button', 'Copy').should('be.visible')
524+
})
525+
497526
it('shows correct error when browser cannot launch', () => {
498527
cy.withCtx((ctx, o) => {
499528
o.sinon.stub(ctx._apis.authApi, 'logIn').callsFake(async (onMessage) => {

packages/app/src/App.vue

+19-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,31 @@
44
:is="Component"
55
/>
66
</router-view>
7+
8+
<template v-if="route.name && route.name !== 'SpecRunner'">
9+
<!--
10+
checking for existence of `route.name` here to avoid a flash
11+
of these components if the page is refreshed on the SpecRunner route
12+
-->
13+
<CloudViewerAndProject />
14+
<LoginConnectModals />
15+
</template>
716
</template>
817

18+
<script setup lang="ts">
19+
import LoginConnectModals from '@cy/gql-components/LoginConnectModals.vue'
20+
21+
import { useRoute } from 'vue-router'
22+
import CloudViewerAndProject from '@packages/frontend-shared/src/gql-components/CloudViewerAndProject.vue'
23+
const route = useRoute()
24+
25+
</script>
26+
927
<style lang="scss">
1028
html,
1129
body,
1230
#app {
13-
@apply h-full bg-white;
31+
@apply bg-white h-full;
1432
}
1533
1634
@font-face {

packages/app/src/layouts/default.vue

+2-30
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
v-if="renderSidebar"
1010
class="row-span-full"
1111
/>
12-
1312
<HeaderBar
1413
v-if="showHeader"
1514
:show-browsers="true"
1615
:page-name="currentRoute.name?.toString()"
1716
data-cy="app-header-bar"
1817
:allow-automatic-prompt-open="true"
19-
@connect-project="handleConnectProject"
2018
/>
2119
<div
2220
v-if="query.data.value?.baseError || query.data.value?.currentProject?.isLoadingConfigFile || query.data.value?.currentProject?.isLoadingNodeEvents"
@@ -49,16 +47,6 @@
4947
<component :is="Component" />
5048
</transition>
5149
</router-view>
52-
<!-- "Nav" is the correct UTM medium below because
53-
this is only opened by event emitted from the header bar-->
54-
<CloudConnectModals
55-
v-if="showConnectDialog && cloudModalQuery.data.value"
56-
:show="showConnectDialog"
57-
:gql="cloudModalQuery.data.value"
58-
utm-medium="Nav"
59-
@cancel="showConnectDialog = false"
60-
@success="showConnectDialog = false"
61-
/>
6250
</main>
6351
</div>
6452
</template>
@@ -69,12 +57,11 @@ import SidebarNavigation from '../navigation/SidebarNavigation.vue'
6957
import HeaderBar from '@cy/gql-components/HeaderBar.vue'
7058
import BaseError from '@cy/gql-components/error/BaseError.vue'
7159
import Spinner from '@cy/components/Spinner.vue'
72-
import CloudConnectModals from '../runs/modals/CloudConnectModals.vue'
7360
7461
import { useRoute } from 'vue-router'
75-
import { computed, ref } from 'vue'
62+
import { computed } from 'vue'
7663
77-
import { MainApp_CloudConnectModalsQueryDocument, MainAppQueryDocument, MainApp_ResetErrorsAndLoadConfigDocument } from '../generated/graphql'
64+
import { MainAppQueryDocument, MainApp_ResetErrorsAndLoadConfigDocument } from '../generated/graphql'
7865
7966
gql`
8067
fragment MainAppQueryData on Query {
@@ -96,12 +83,6 @@ query MainAppQuery {
9683
}
9784
`
9885
99-
gql`
100-
query MainApp_CloudConnectModalsQuery {
101-
...CloudConnectModals
102-
}
103-
`
104-
10586
gql`
10687
mutation MainApp_ResetErrorsAndLoadConfig($id: ID!) {
10788
resetErrorAndLoadConfig(id: $id) {
@@ -110,10 +91,6 @@ mutation MainApp_ResetErrorsAndLoadConfig($id: ID!) {
11091
}
11192
`
11293
113-
const showConnectDialog = ref(false)
114-
115-
const cloudModalQuery = useQuery({ query: MainApp_CloudConnectModalsQueryDocument, pause: true })
116-
11794
const currentRoute = useRoute()
11895
11996
const showHeader = computed(() => {
@@ -134,9 +111,4 @@ const resetErrorAndLoadConfig = (id: string) => {
134111
135112
const renderSidebar = window.__CYPRESS_MODE__ !== 'run'
136113
137-
async function handleConnectProject () {
138-
await cloudModalQuery.executeQuery()
139-
showConnectDialog.value = true
140-
}
141-
142114
</script>

0 commit comments

Comments
 (0)