Skip to content

Commit

Permalink
feat: implement new login and "connect project" logic (#23762)
Browse files Browse the repository at this point in the history
Co-authored-by: Stokes Player <[email protected]>
  • Loading branch information
marktnoonan and warrensplayer authored Oct 24, 2022
1 parent 4e667e5 commit 8ab3ea8
Show file tree
Hide file tree
Showing 76 changed files with 2,100 additions and 829 deletions.
3 changes: 2 additions & 1 deletion .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"topnav",
"unconfigured",
"unplugin",
"unref",
"unrunnable",
"unstaged",
"urql",
Expand All @@ -48,4 +49,4 @@
],
"ignoreWords": [],
"import": []
}
}
7 changes: 4 additions & 3 deletions graphql-codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ vueOperations: &vueOperations
- 'typescript-operations'
- 'typed-document-node':
# Intentionally specified under typed-document-node rather than top level config,
# becuase we don't want it flattening the types for the operations
# because we don't want it flattening the types for the operations
flattenGeneratedTypes: true

vueTesting: &vueTesting
Expand Down Expand Up @@ -122,9 +122,10 @@ generates:
'./packages/app/src/generated/graphql-test.ts':
documents:
- './packages/app/src/**/*.{vue,ts,tsx,js,jsx}'
- './packages/frontend-shared/src/**/*.{vue,ts,tsx,js,jsx}'
- './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
<<: *vueTesting

'./packages/frontend-shared/src/generated/graphql-test.ts':
documents: './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
documents:
- './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
<<: *vueTesting
48 changes: 30 additions & 18 deletions packages/app/cypress/e2e/runs.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
obj.result.data.cloudViewer.organizations.nodes = []
}

if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
obj.result.data.cloudViewer.firstOrganization.nodes = []
}

return obj.result
})

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

cy.remoteGraphQLIntercept(async (obj) => {
if (obj?.result?.data?.cloudViewer?.organizations?.nodes) {
obj.result.data.cloudViewer.organizations.nodes = []
if ((obj.operationName !== 'CreateCloudOrgModal_CloudOrganizationsCheck_refreshOrganizations_cloudViewer')) {
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
obj.result.data.cloudViewer.organizations.nodes = []
}

if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
obj.result.data.cloudViewer.firstOrganization.nodes = []
}
}

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

cy.remoteGraphQLIntercept(async (obj, testState) => {
if (obj.operationName === 'CloudConnectModals_RefreshCloudViewer_refreshCloudViewer_cloudViewer') {
if (obj.operationName === 'LoginConnectModals_LoginConnectModalsQuery_cloudViewer') {
testState.refetchCalled = true
}

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

moveToRunsPage()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.findByText(defaultMessages.runs.connectSuccessAlert.title).should('be.visible')

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

cy.get('[href="#/runs"]').click()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()

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

cy.get('[href="#/runs"]').click()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()

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

if (obj.result.data?.cloudViewer?.organizations?.nodes) {
const projectNodes = obj.result.data?.cloudViewer.organizations.nodes[0].projects.nodes
const projectNodes = obj.result.data.cloudViewer.organizations.nodes[0].projects.nodes

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

it('opens Connect Project modal after clicking Reconnect Project button', () => {
cy.findByText(defaultMessages.runs.errors.notFound.button).should('be.visible').click()
cy.findByText(defaultMessages.runs.errors.notFound.button).click()

cy.get('[aria-modal="true"]').should('exist')
cy.get('[data-cy="selectProject"] button').should('have.text', 'Mock Project')
cy.contains('[data-cy="selectProject"] button', 'Mock Project')
cy.get('[data-cy="connect-project"]').click()
cy.get('[data-cy="runs"]', { timeout: 7500 })
})
Expand Down Expand Up @@ -510,12 +521,13 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {

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

proj.__typename = 'CloudProjectUnauthorized'
proj.message = 'Cloud Project Unauthorized'
proj.hasRequestedAccess = false

testState.project = proj
} else if (obj.operationName === 'RunsErrorRenderer_RequestAccess_cloudProjectRequestAccess') {
obj!.result!.data!.cloudProjectRequestAccess = {
Expand Down Expand Up @@ -696,7 +708,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {

cy.loginUser()
cy.remoteGraphQLIntercept((obj) => {
if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
cloudData = obj.result
obj.result = {}

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

cy.remoteGraphQLIntercept((obj) => {
if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
return cloudData
}

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

afterEach(() => {
cy.goOnline()
})

it('shows alert warning if runs have been returned already', () => {
cy.loginUser()
cy.visitApp()
Expand Down Expand Up @@ -773,11 +781,15 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js'])
cy.startAppServer('component')

cy.remoteGraphQLIntercept(async (obj) => {
cy.remoteGraphQLIntercept((obj) => {
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
obj.result.data.cloudViewer.organizations.nodes = []
}

if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
obj.result.data.cloudViewer.firstOrganization.nodes = []
}

return obj.result
})

Expand Down Expand Up @@ -845,7 +857,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.openProject('component-tests')
cy.startAppServer('component')
cy.loginUser()
cy.remoteGraphQLIntercept((obj, testState) => {
cy.remoteGraphQLIntercept((obj) => {
if (obj.result.data?.cloudProjectBySlug?.runs?.nodes.length) {
obj.result.data.cloudProjectBySlug.runs.nodes.map((run) => {
run.status = 'RUNNING'
Expand Down
6 changes: 1 addition & 5 deletions packages/app/cypress/e2e/specs_list_latest_runs.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
})

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

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

cy.visitApp()
cy.findByTestId('sidebar-link-specs-page').click()
})

it('shows placeholders for all visible specs', () => {
allVisibleSpecsShouldBePlaceholders()
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import type { SinonStub } from 'sinon'

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

if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
obj.result.data.cloudViewer.firstOrganization.nodes = []
}

return obj.result
})

Expand Down
29 changes: 29 additions & 0 deletions packages/app/cypress/e2e/top-nav.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,17 @@ describe('App Top Nav Workflows', () => {
cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js'])
cy.startAppServer()
cy.visitApp()
cy.remoteGraphQLIntercept(async (obj) => {
if (obj.result.data?.cloudViewer) {
obj.result.data.cloudViewer.organizations = {
__typename: 'CloudOrganizationConnection',
id: 'test',
nodes: [{ __typename: 'CloudOrganization', id: '987' }],
}
}

return obj.result
})

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

it('if the project has no runs, shows "record your first run" prompt after clicking', () => {
cy.remoteGraphQLIntercept((obj) => {
if (obj.result?.data?.cloudProjectBySlug?.runs?.nodes?.length) {
obj.result.data.cloudProjectBySlug.runs.nodes = []
}

return obj.result
})

mockLogInActionsForUser(mockUserNoName)

logIn({ expectedNextStepText: 'Continue', displayName: mockUserNoName.email })

cy.contains('[data-cy=standard-modal] h2', defaultMessages.specPage.banners.record.title).should('be.visible')
cy.contains('[data-cy=standard-modal]', defaultMessages.specPage.banners.record.content).should('be.visible')
cy.contains('button', 'Copy').should('be.visible')
})

it('shows correct error when browser cannot launch', () => {
cy.withCtx((ctx, o) => {
o.sinon.stub(ctx._apis.authApi, 'logIn').callsFake(async (onMessage) => {
Expand Down
20 changes: 19 additions & 1 deletion packages/app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,31 @@
:is="Component"
/>
</router-view>

<template v-if="route.name && route.name !== 'SpecRunner'">
<!--
checking for existence of `route.name` here to avoid a flash
of these components if the page is refreshed on the SpecRunner route
-->
<CloudViewerAndProject />
<LoginConnectModals />
</template>
</template>

<script setup lang="ts">
import LoginConnectModals from '@cy/gql-components/LoginConnectModals.vue'
import { useRoute } from 'vue-router'
import CloudViewerAndProject from '@packages/frontend-shared/src/gql-components/CloudViewerAndProject.vue'
const route = useRoute()
</script>

<style lang="scss">
html,
body,
#app {
@apply h-full bg-white;
@apply bg-white h-full;
}
@font-face {
Expand Down
32 changes: 2 additions & 30 deletions packages/app/src/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
v-if="renderSidebar"
class="row-span-full"
/>

<HeaderBar
v-if="showHeader"
:show-browsers="true"
:page-name="currentRoute.name?.toString()"
data-cy="app-header-bar"
:allow-automatic-prompt-open="true"
@connect-project="handleConnectProject"
/>
<div
v-if="query.data.value?.baseError || query.data.value?.currentProject?.isLoadingConfigFile || query.data.value?.currentProject?.isLoadingNodeEvents"
Expand Down Expand Up @@ -49,16 +47,6 @@
<component :is="Component" />
</transition>
</router-view>
<!-- "Nav" is the correct UTM medium below because
this is only opened by event emitted from the header bar-->
<CloudConnectModals
v-if="showConnectDialog && cloudModalQuery.data.value"
:show="showConnectDialog"
:gql="cloudModalQuery.data.value"
utm-medium="Nav"
@cancel="showConnectDialog = false"
@success="showConnectDialog = false"
/>
</main>
</div>
</template>
Expand All @@ -69,12 +57,11 @@ import SidebarNavigation from '../navigation/SidebarNavigation.vue'
import HeaderBar from '@cy/gql-components/HeaderBar.vue'
import BaseError from '@cy/gql-components/error/BaseError.vue'
import Spinner from '@cy/components/Spinner.vue'
import CloudConnectModals from '../runs/modals/CloudConnectModals.vue'
import { useRoute } from 'vue-router'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { MainApp_CloudConnectModalsQueryDocument, MainAppQueryDocument, MainApp_ResetErrorsAndLoadConfigDocument } from '../generated/graphql'
import { MainAppQueryDocument, MainApp_ResetErrorsAndLoadConfigDocument } from '../generated/graphql'
gql`
fragment MainAppQueryData on Query {
Expand All @@ -96,12 +83,6 @@ query MainAppQuery {
}
`
gql`
query MainApp_CloudConnectModalsQuery {
...CloudConnectModals
}
`
gql`
mutation MainApp_ResetErrorsAndLoadConfig($id: ID!) {
resetErrorAndLoadConfig(id: $id) {
Expand All @@ -110,10 +91,6 @@ mutation MainApp_ResetErrorsAndLoadConfig($id: ID!) {
}
`
const showConnectDialog = ref(false)
const cloudModalQuery = useQuery({ query: MainApp_CloudConnectModalsQueryDocument, pause: true })
const currentRoute = useRoute()
const showHeader = computed(() => {
Expand All @@ -134,9 +111,4 @@ const resetErrorAndLoadConfig = (id: string) => {
const renderSidebar = window.__CYPRESS_MODE__ !== 'run'
async function handleConnectProject () {
await cloudModalQuery.executeQuery()
showConnectDialog.value = true
}
</script>
Loading

5 comments on commit 8ab3ea8

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 8ab3ea8 Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.11.0/linux-x64/develop-8ab3ea8f98c8c3fc34d0292f51c7fc435e6601f4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 8ab3ea8 Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.11.0/linux-arm64/develop-8ab3ea8f98c8c3fc34d0292f51c7fc435e6601f4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 8ab3ea8 Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.11.0/darwin-arm64/develop-8ab3ea8f98c8c3fc34d0292f51c7fc435e6601f4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 8ab3ea8 Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.11.0/darwin-x64/develop-8ab3ea8f98c8c3fc34d0292f51c7fc435e6601f4/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 8ab3ea8 Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.11.0/win32-x64/develop-8ab3ea8f98c8c3fc34d0292f51c7fc435e6601f4/cypress.tgz

Please sign in to comment.