From 7f2522f85769b9b72a159db6cf39807a036fba83 Mon Sep 17 00:00:00 2001 From: Stokes Player Date: Mon, 29 Aug 2022 21:10:53 -0400 Subject: [PATCH] feat: show cloud connect options when hoving spec list fields (#23584) Co-authored-by: Mark Noonan --- .../cypress/e2e/specs_list_latest_runs.cy.ts | 35 ++++- .../app/src/composables/useRequestAccess.ts | 24 ++++ packages/app/src/specs/RunStatusDots.cy.tsx | 24 ++++ packages/app/src/specs/RunStatusDots.vue | 115 ++++++++------- packages/app/src/specs/SpecsList.vue | 52 +++++-- .../app/src/specs/SpecsListCloudButton.cy.tsx | 131 ++++++++++++++++++ .../app/src/specs/SpecsListCloudButton.vue | 126 +++++++++++++++++ .../app/src/specs/SpecsListHoverCell.cy.tsx | 61 ++++++++ packages/app/src/specs/SpecsListHoverCell.vue | 76 ++++++++++ .../app/src/specs/SpecsListRowItem.cy.tsx | 26 ++++ packages/app/src/specs/SpecsListRowItem.vue | 45 ++++-- .../src/components/Button.cy.tsx | 14 +- .../frontend-shared/src/components/Button.vue | 1 + .../frontend-shared/src/locales/en-US.json | 9 ++ 14 files changed, 662 insertions(+), 77 deletions(-) create mode 100644 packages/app/src/composables/useRequestAccess.ts create mode 100644 packages/app/src/specs/SpecsListCloudButton.cy.tsx create mode 100644 packages/app/src/specs/SpecsListCloudButton.vue create mode 100644 packages/app/src/specs/SpecsListHoverCell.cy.tsx create mode 100644 packages/app/src/specs/SpecsListHoverCell.vue diff --git a/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts b/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts index 0e5fbf617b77..9cf1ccfb4497 100644 --- a/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts +++ b/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts @@ -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 }, () => { @@ -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', () => { diff --git a/packages/app/src/composables/useRequestAccess.ts b/packages/app/src/composables/useRequestAccess.ts new file mode 100644 index 000000000000..8287fa12f451 --- /dev/null +++ b/packages/app/src/composables/useRequestAccess.ts @@ -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 }) + } + } +} diff --git a/packages/app/src/specs/RunStatusDots.cy.tsx b/packages/app/src/specs/RunStatusDots.cy.tsx index f5e8c84ef4c2..17497c696927 100644 --- a/packages/app/src/specs/RunStatusDots.cy.tsx +++ b/packages/app/src/specs/RunStatusDots.cy.tsx @@ -28,6 +28,16 @@ function mountWithRuns (runs: Required[]) { }) } +function mountWithNoData () { + cy.mount(() => { + return ( +
+ +
+ ) + }) +} + describe('', () => { context('runs scenario 1', () => { beforeEach(() => { @@ -109,6 +119,20 @@ describe('', () => { }) }) + 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)) diff --git a/packages/app/src/specs/RunStatusDots.vue b/packages/app/src/specs/RunStatusDots.vue index 6f63f8e8dee1..b9d6389419f8 100644 --- a/packages/app/src/specs/RunStatusDots.vue +++ b/packages/app/src/specs/RunStatusDots.vue @@ -1,63 +1,74 @@ diff --git a/packages/app/src/specs/SpecsListHoverCell.cy.tsx b/packages/app/src/specs/SpecsListHoverCell.cy.tsx new file mode 100644 index 000000000000..d151e277a95a --- /dev/null +++ b/packages/app/src/specs/SpecsListHoverCell.cy.tsx @@ -0,0 +1,61 @@ +import SpecsListHoverCell from './SpecsListHoverCell.vue' + +const contentSelector = '[data-testid=content]' +const hoverSelector = '[data-testid=hover]' + +describe('', () => { + it('should show hover when hover enabled', () => { + cy.mount(() => ( +
content
, + hover: () =>
Hover
, + }} + /> + )).get(contentSelector) + .should('be.visible') + .get(hoverSelector) + .should('not.exist') + .log('hover content to show hover slot') + .get(contentSelector) + .trigger('mouseenter') + .should('not.exist') + .get(hoverSelector) + .should('be.visible') + .log('hover slot should stay visible if hovering the hover slot') + .get(hoverSelector) + .trigger('mouseenter') + .should('be.visible') + .get(contentSelector) + .should('not.exist') + .log('hover content will be removed from DOM if no longer hovering it or the content slot') + .get(hoverSelector) + .trigger('mouseleave') + .should('not.exist') + .get(contentSelector) + .should('be.visible') + }) + + it('should not show hover when hover disabled', () => { + cy.mount(() => ( +
content
, + hover: () =>
Hover
, + }} + /> + )).get(contentSelector) + .should('be.visible') + .get(hoverSelector) + .should('not.exist') + .get(contentSelector) + .trigger('mouseenter') + .should('exist') + .get(hoverSelector) + .should('not.exist') + }) +}) diff --git a/packages/app/src/specs/SpecsListHoverCell.vue b/packages/app/src/specs/SpecsListHoverCell.vue new file mode 100644 index 000000000000..7dd14a23694e --- /dev/null +++ b/packages/app/src/specs/SpecsListHoverCell.vue @@ -0,0 +1,76 @@ + + +/** + SpecsListHoverCell + + Enables a cell in the SpecsList to have content switched out if the cell is hovered. + + It contains two slots. One for the main `content` and one for the `hover` content. + + Note: This component contains styling specific for the SpecsList. + */ + diff --git a/packages/app/src/specs/SpecsListRowItem.cy.tsx b/packages/app/src/specs/SpecsListRowItem.cy.tsx index 381be356afb3..00ba82a74cda 100644 --- a/packages/app/src/specs/SpecsListRowItem.cy.tsx +++ b/packages/app/src/specs/SpecsListRowItem.cy.tsx @@ -8,6 +8,7 @@ describe('SpecItem', () => { { cy.mount(() => ( { cy.get('[data-cy="spec-item-directory"]').click('right') cy.wrap(toggleRowHandler).should('have.callCount', 2) }) + + it('passes utm parameter to slot', () => { + cy.mount(() => ( + Runs, + 'average-duration': () => Duration, + 'connect-button': (props) => {props.utmMedium}, + }} + />)) + + cy.findByTestId('latest').trigger('mouseenter').wait(300) + cy.findByTestId('button').contains('Specs Latest Runs Empty State') + cy.findByTestId('button').trigger('mouseleave') + + cy.findByTestId('duration').trigger('mouseenter').wait(300) + cy.findByTestId('button').contains('Specs Average Duration Empty State') + cy.findByTestId('button').trigger('mouseleave') + }) }) diff --git a/packages/app/src/specs/SpecsListRowItem.vue b/packages/app/src/specs/SpecsListRowItem.vue index e85b7769b74b..ff2e3d2bcb21 100644 --- a/packages/app/src/specs/SpecsListRowItem.vue +++ b/packages/app/src/specs/SpecsListRowItem.vue @@ -2,7 +2,8 @@
@@ -20,28 +20,54 @@ >
-
- -
- + + + + +
diff --git a/packages/frontend-shared/src/components/Button.cy.tsx b/packages/frontend-shared/src/components/Button.cy.tsx index 074f129645f3..0cc502c93835 100644 --- a/packages/frontend-shared/src/components/Button.cy.tsx +++ b/packages/frontend-shared/src/components/Button.cy.tsx @@ -5,17 +5,27 @@ import { createRouter, createWebHistory } from 'vue-router' const prefixIcon = () => describe(' - + + + + + + + + + + + )) diff --git a/packages/frontend-shared/src/components/Button.vue b/packages/frontend-shared/src/components/Button.vue index e9787af700da..d715f6c1bbc5 100644 --- a/packages/frontend-shared/src/components/Button.vue +++ b/packages/frontend-shared/src/components/Button.vue @@ -85,6 +85,7 @@ const VariantClassesTable = { tertiary: 'text-indigo-500 bg-indigo-50 border-transparent hocus-default', pending: 'bg-gray-500 text-white', link: 'border-transparent text-indigo-600 hocus-default', + linkBold: 'border-transparent text-indigo-500 font-medium', text: 'border-0', secondary: 'bg-jade-500 text-white hocus-secondary', } as const diff --git a/packages/frontend-shared/src/locales/en-US.json b/packages/frontend-shared/src/locales/en-US.json index f7e056891142..d93a9b32bd9b 100644 --- a/packages/frontend-shared/src/locales/en-US.json +++ b/packages/frontend-shared/src/locales/en-US.json @@ -160,6 +160,15 @@ "flakyRuns": "{flakyRuns} flaky runs / {totalRuns} total | {flakyRuns} flaky run / {totalRuns} total | {flakyRuns} flaky runs / {totalRuns} total", "lastFlaky": "Last run flaky | Last flaky {runsSinceLastFlake} run ago | Last flaky {runsSinceLastFlake} runs ago" }, + "hoverButton": { + "connect": "Connect", + "connectProject": "Connect project", + "connectProjectShort": "Connect", + "requestAccess": "Request access", + "requestAccessShort": "Request", + "requestSent": "Request sent", + "requestSentShort": "Sent" + }, "connectProjectButton": "Connect your project", "dashboardLoginButton": "Log in to the Dashboard", "reconnectProjectButton": "Reconnect your project",