Skip to content

Commit

Permalink
feat: show cloud connect options when hoving spec list fields (#23584)
Browse files Browse the repository at this point in the history
Co-authored-by: Mark Noonan <mark@cypress.io>
  • Loading branch information
warrensplayer and marktnoonan authored Aug 30, 2022
1 parent 72b8a65 commit 7f2522f
Show file tree
Hide file tree
Showing 14 changed files with 662 additions and 77 deletions.
35 changes: 28 additions & 7 deletions packages/app/cypress/e2e/specs_list_latest_runs.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,13 @@ function simulateRunData () {
}

function allVisibleSpecsShouldBePlaceholders () {
cy.findAllByTestId('run-status-dot-0').should('have.class', 'icon-light-gray-300')
cy.findAllByTestId('run-status-dot-1').should('have.class', 'icon-light-gray-300')
cy.findAllByTestId('run-status-dot-2').should('have.class', 'icon-light-gray-300')
cy.findAllByTestId('run-status-dot-latest')
.should('not.have.class', 'animate-spin')
.and('have.attr', 'data-cy-run-status', 'PLACEHOLDER')
cy.findAllByTestId('run-status-empty').should('be.visible').should('have.class', 'text-gray-400')
cy.findAllByTestId('run-status-dot-0').should('not.exist')
cy.findAllByTestId('run-status-dot-1').should('not.exist')
cy.findAllByTestId('run-status-dot-2').should('not.exist')
cy.findAllByTestId('run-status-dot-latest').should('not.exist')

cy.get('.spec-list-container').scrollTo('bottom')
cy.get('.spec-list-container').scrollTo('bottom')
}

describe('App/Cloud Integration - Latest runs and Average duration', { viewportWidth: 1200, viewportHeight: 900 }, () => {
Expand Down Expand Up @@ -202,6 +200,29 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW

cy.findByTestId('average-duration-header').trigger('mouseleave')
})

it('shows login/connect button in row when hovering', () => {
cy.get('[data-cy="spec-list-file"] [data-cy="specs-list-row-latest-runs"]')
.eq(0)
.as('latestRunsCell')
.trigger('mouseenter')

cy.contains('[data-cy="specs-list-row-latest-runs"] [data-cy="cloud-button"]', 'Connect').should('be.visible')

cy.get('@latestRunsCell').trigger('mouseleave')

cy.contains('[data-cy="cloud-button"]', 'Connect').should('not.exist')

cy.get('[data-cy="spec-list-file"] [data-cy="specs-list-row-average-duration"]')
.eq(0)
.as('averageDurationCell')
.trigger('mouseenter')

cy.contains('[data-cy="specs-list-row-average-duration"] [data-cy="cloud-button"]', 'Connect').should('be.visible')
cy.get('@averageDurationCell').trigger('mouseleave')

cy.contains('[data-cy="cloud-button"]', 'Connect').should('not.exist')
})
})

context('when project disconnected', () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/app/src/composables/useRequestAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RequestAccessComposable_RequestAccessDocument } from '../generated/graphql'
import { gql, useMutation } from '@urql/vue'

gql`
mutation RequestAccessComposable_RequestAccess( $projectId: String! ) {
cloudProjectRequestAccess(projectSlug: $projectId) {
__typename
... on CloudProjectUnauthorized {
message
hasRequestedAccess
}
}
}
`

export function useRequestAccess () {
const requestAccessMutation = useMutation(RequestAccessComposable_RequestAccessDocument)

return async function requestAccess (projectId: string | null | undefined) {
if (projectId) {
await requestAccessMutation.executeMutation({ projectId })
}
}
}
24 changes: 24 additions & 0 deletions packages/app/src/specs/RunStatusDots.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ function mountWithRuns (runs: Required<CloudSpecRun>[]) {
})
}

function mountWithNoData () {
cy.mount(() => {
return (
<div class="flex justify-center">
<RunStatusDots gql={null} specFileExtension=".cy.ts" specFileName="spec"/>
</div>
)
})
}

describe('<RunStatusDots />', () => {
context('runs scenario 1', () => {
beforeEach(() => {
Expand Down Expand Up @@ -109,6 +119,20 @@ describe('<RunStatusDots />', () => {
})
})

context('runs not loaded', () => {
beforeEach(() => {
mountWithNoData()
})

it('renders placeholder without tooltip or link', () => {
cy.findByTestId('external').should('not.exist')
cy.findByTestId('run-status-empty').contains('--')
cy.findByTestId('run-status-empty').trigger('mouseenter')
cy.get('.v-popper__popper--shown').should('not.exist')
cy.findByTestId('run-status-dots').should('not.exist')
})
})

context('unknown/unhandled statuses', () => {
beforeEach(() => {
const runs = fakeRuns(fill(['', '', '', ''], 'FAKE_UNKNOWN_STATUS' as any))
Expand Down
115 changes: 65 additions & 50 deletions packages/app/src/specs/RunStatusDots.vue
Original file line number Diff line number Diff line change
@@ -1,63 +1,74 @@
<template>
<component
:is="latestRun? Tooltip : 'div'"
placement="top"
:is-interactive="true"
class="h-16px"
:hide-delay="0"
:show-group="props.gql?.id"
:distance="7"
popper-class="RunStatusDots_Tooltip"
>
<div>
<component
:is="latestRun? ExternalLink : 'div'"
:href="dashboardUrl"
:is="latestRun? Tooltip : 'div'"
v-if="isRunsLoaded"
placement="top"
:is-interactive="true"
class="h-16px"
:hide-delay="0"
:show-group="props.gql?.id"
:distance="7"
popper-class="RunStatusDots_Tooltip"
>
<div
class="flex justify-end items-center"
data-cy="run-status-dots"
<component
:is="latestRun? ExternalLink : 'div'"
:href="dashboardUrl"
>
<div
v-for="(dot,i) in dotClasses"
:key="i"
class="ml-4px"
v-if="isRunsLoaded"
class="flex justify-end items-center"
data-cy="run-status-dots"
>
<i-cy-dot-solid_x4
width="4"
height="4"
:class="dot"
:data-cy="'run-status-dot-'+i"
/>
</div>
<div>
<component
:is="latestDot.icon"
width="16"
height="16"
:class="{'animate-spin': latestDot.spin}"
:data-cy="'run-status-dot-latest'"
:data-cy-run-status="latestDot.status"
<div
v-for="(dot,i) in dotClasses"
:key="i"
class="ml-4px"
/>
>
<i-cy-dot-solid_x4
width="4"
height="4"
:class="dot"
:data-cy="'run-status-dot-'+i"
/>
</div>
<div>
<component
:is="latestDot.icon"
width="16"
height="16"
:class="{'animate-spin': latestDot.spin}"
:data-cy="'run-status-dot-latest'"
:data-cy-run-status="latestDot.status"
class="ml-4px"
/>
</div>
</div>
</div>
</component>
<template
#popper
>
<ExternalLink
v-if="latestRun"
:href="dashboardUrl"
:use-default-hocus="false"
>
<SpecRunSummary
:run="latestRun"
:spec-file-no-extension="props.specFileName"
:spec-file-extension="props.specFileExtension"
/>
</ExternalLink>
</template>
</component>
<template
#popper
<div
v-if="!isRunsLoaded"
data-cy="run-status-empty"
class="text-gray-400"
>
<ExternalLink
v-if="latestRun"
:href="dashboardUrl"
:use-default-hocus="false"
>
<SpecRunSummary
:run="latestRun"
:spec-file-no-extension="props.specFileName"
:spec-file-extension="props.specFileExtension"
/>
</ExternalLink>
</template>
</component>
--
</div>
</div>
</template>
<script setup lang="ts">
Expand Down Expand Up @@ -135,6 +146,10 @@ const runs = computed(() => {
return props.gql?.data?.__typename === 'CloudProjectSpec' ? props.gql?.data?.specRuns?.nodes ?? [] : []
})
const isRunsLoaded = computed(() => {
return !!props.gql?.data
})
const dotClasses = computed(() => {
const statuses = ['placeholder', 'placeholder', 'placeholder']
Expand Down
52 changes: 43 additions & 9 deletions packages/app/src/specs/SpecsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@
/>
<div
v-if="specs.length"
class="mb-4 grid grid-cols-7 md:grid-cols-9 children:font-medium children:text-gray-800"
class="mb-4 grid children:font-medium children:text-gray-800"
:style="`padding-right: ${scrollbarOffset + 20}px`"
:class="tableGridColumns"
>
<div
class="flex col-span-4 items-center justify-between"
class="flex items-center justify-between"
data-cy="specs-testing-type-header"
>
{{ props.gql.currentProject?.currentTestingType === 'component' ?
t('specPage.componentSpecsHeader') : t('specPage.e2eSpecsHeader') }}
</div>
<div class="flex col-span-2 items-center justify-between truncate">
<div class="flex items-center justify-between truncate">
<LastUpdatedHeader :is-git-available="isGitAvailable" />
</div>
<div class="flex items-center justify-end whitespace-nowrap">
Expand All @@ -50,7 +51,7 @@
@showConnectToProject="showConnectToProject"
/>
</div>
<div class="hidden items-center justify-end truncate md:flex md:col-span-2">
<div class="hidden items-center justify-end truncate md:flex">
<SpecHeaderCloudDataTooltip
:gql="props.gql"
mode="AVG_DURATION"
Expand Down Expand Up @@ -84,6 +85,8 @@
:data-cy="row.data.isLeaf ? 'spec-list-file' : 'spec-list-directory'"
:data-cy-row="row.data.data?.baseName"
:is-leaf="row.data.isLeaf"
:is-project-connected="projectConnectionStatus === 'CONNECTED'"
:grid-columns="tableGridColumns"
:route="{ path: '/specs/runner', query: { file: row.data.data?.relative?.replace(/\\/g, '/') } }"
@toggleRow="row.data.toggle"
>
Expand Down Expand Up @@ -123,9 +126,20 @@
/>
</template>
<template #connect-button="{ utmMedium }">
<SpecsListCloudButton
v-if="projectConnectionStatus !== 'CONNECTED' && row.data.isLeaf && row.data.data && (row.data.data.cloudSpec?.data || row.data.data.cloudSpec?.fetchingStatus !== 'FETCHING')"
:gql="props.gql"
:project-connection-status="projectConnectionStatus"
@showLogin="showLogin(utmMedium)"
@showConnectToProject="showConnectToProject"
@request-access="requestAccess(props.gql?.currentProject?.projectId)"
/>
</template>
<template #latest-runs>
<div
class="h-full grid justify-items-end items-center"
class="h-full grid justify-items-end items-center relative"
>
<RunStatusDots
v-if="row.data.isLeaf && row.data.data && (row.data.data.cloudSpec?.data || row.data.data.cloudSpec?.fetchingStatus !== 'FETCHING')"
Expand Down Expand Up @@ -178,6 +192,7 @@ import SpecsListBanners from './SpecsListBanners.vue'
import LastUpdatedHeader from './LastUpdatedHeader.vue'
import SpecHeaderCloudDataTooltip from './SpecHeaderCloudDataTooltip.vue'
import LoginModal from '@cy/gql-components/topnav/LoginModal.vue'
import SpecsListCloudButton from './SpecsListCloudButton.vue'
import CloudConnectModals from '../runs/modals/CloudConnectModals.vue'
import SpecsListHeader from './SpecsListHeader.vue'
import SpecListGitInfo from './SpecListGitInfo.vue'
Expand All @@ -201,6 +216,7 @@ import { useRoute } from 'vue-router'
import FlakyInformation from './flaky-badge/FlakyInformation.vue'
import { useCloudSpecData } from '../composables/useCloudSpecData'
import { useSpecFilter } from '../composables/useSpecFilter'
import { useRequestAccess } from '../composables/useRequestAccess'
const route = useRoute()
const { t } = useI18n()
Expand All @@ -210,18 +226,34 @@ const isOffline = ref(false)
watch(isOnline, (newIsOnlineValue) => isOffline.value = !newIsOnlineValue, { immediate: true })
const tableGridColumns = 'grid-cols-[1fr,135px,130px] md:grid-cols-[1fr,135px,130px,130px] lg:grid-cols-[1fr,160px,160px,180px]'
const isProjectConnectOpen = ref(false)
const isLoginOpen = ref(false)
const loginUtmMedium = ref('')
const cloudProjectType = computed(() => props.gql.currentProject?.cloudProject?.__typename)
const projectConnectionStatus = computed(() => {
if (!props.gql.cloudViewer) return 'LOGGED_OUT'
if (!props.gql.currentProject?.cloudProject?.__typename) return 'NOT_CONNECTED'
if (props.gql.currentProject?.cloudProject?.__typename === 'CloudProjectNotFound') return 'NOT_FOUND'
const hasRequestedAccess = computed(() => {
if (props.gql.currentProject?.cloudProject?.__typename === 'CloudProjectUnauthorized') {
return props.gql.currentProject.cloudProject.hasRequestedAccess || false
if (props.gql.currentProject?.cloudProject?.hasRequestedAccess) {
return 'ACCESS_REQUESTED'
}
return 'UNAUTHORIZED'
}
return false
return 'CONNECTED'
})
const cloudProjectType = computed(() => props.gql.currentProject?.cloudProject?.__typename)
const hasRequestedAccess = computed(() => {
return projectConnectionStatus.value === 'ACCESS_REQUESTED'
})
const showLogin = (utmMedium: string) => {
Expand All @@ -233,6 +265,8 @@ const showConnectToProject = () => {
isProjectConnectOpen.value = true
}
const requestAccess = useRequestAccess()
const isGitAvailable = computed(() => {
return !(props.gql.currentProject?.specs.some((s) => s.gitInfo?.statusType === 'noGitInfo') ?? false)
})
Expand Down
Loading

5 comments on commit 7f2522f

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 7f2522f Aug 30, 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.7.0/linux-x64/develop-7f2522f85769b9b72a159db6cf39807a036fba83/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 7f2522f Aug 30, 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.7.0/linux-arm64/develop-7f2522f85769b9b72a159db6cf39807a036fba83/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 7f2522f Aug 30, 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.7.0/darwin-arm64/develop-7f2522f85769b9b72a159db6cf39807a036fba83/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 7f2522f Aug 30, 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.7.0/darwin-x64/develop-7f2522f85769b9b72a159db6cf39807a036fba83/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 7f2522f Aug 30, 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.7.0/win32-x64/develop-7f2522f85769b9b72a159db6cf39807a036fba83/cypress.tgz

Please sign in to comment.