Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: IATR non-failure run statuses #25286

Merged
merged 8 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/app/src/debug/DebugCancelledAlert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<Alert
:title="t('debugPage.manuallyCancelled')"
status="warning"
:icon="ErrorOutlineIcon"
class="flex flex-col mb-24px w-full"
>
<div class="flex items-center">
mike-plummer marked this conversation as resolved.
Show resolved Hide resolved
<div>{{ t('debugPage.specsSkipped', {n: totalSpecs, totalSkippedSpecs}) }}</div>
<template v-if="cancellation.cancelledBy?.email && cancellation.cancelledBy.fullName">
<div class="rounded-full font-semibold bg-orange-500 h-3px mx-10px w-3px" />
<div class="flex items-center">
<UserAvatar
:email="cancellation.cancelledBy.email"
class="h-20px mr-7px w-20px"
data-cy="cancelled-by-user-avatar"
/>
<div>{{ cancellation.cancelledBy.fullName }}</div>
</div>
</template>
<template v-if="cancellation.cancelledAt">
<div class="rounded-full font-semibold bg-orange-500 h-3px mx-10px w-3px" />
<div>
{{ dayjs(cancellation.cancelledAt).local().format('MMM D, YYYY h:mm A') }}
</div>
</template>
</div>
</Alert>
</template>

<script lang="ts" setup>
import UserAvatar from '@packages/frontend-shared/src/gql-components/topnav/UserAvatar.vue'
import ErrorOutlineIcon from '~icons/cy/status-errored-outline_x16.svg'
import Alert from '@cy/components/Alert.vue'
import { dayjs } from '../runs/utils/day'
import { useI18n } from '@cy/i18n'

const { t } = useI18n()

defineProps<{
totalSpecs: number
totalSkippedSpecs: number
cancellation: {
cancelledAt?: string | null
cancelledBy: {
fullName?: string | null
email?: string | null
} | null
}
}>()

</script>
104 changes: 103 additions & 1 deletion packages/app/src/debug/DebugContainer.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { specsList } from './utils/DebugMapping'
import { CloudRunStubs } from '@packages/graphql/test/stubCloudTypes'

describe('<DebugContainer />', () => {
context('empty states', () => {
describe('empty states', () => {
const validateEmptyState = (expectedMessages: string[]) => {
cy.mountFragment(DebugSpecsFragmentDoc, {
render: (gqlVal) => {
Expand Down Expand Up @@ -65,6 +65,108 @@ describe('<DebugContainer />', () => {
})
})

describe('run states', { viewportWidth: 900 }, () => {
beforeEach(() => {
const loginConnectStore = useLoginConnectStore()

loginConnectStore.setUserFlag('isLoggedIn', true)
loginConnectStore.setProjectFlag('isProjectConnected', true)
})

function mountTestRun (runName: string) {
cy.mountFragment(DebugSpecsFragmentDoc, {
onResult: (result) => {
if (result.currentProject?.cloudProject?.__typename === 'CloudProject') {
const test = result.currentProject.cloudProject.runByNumber
const other = CloudRunStubs[runName] as typeof test

result.currentProject.cloudProject.runByNumber = other
}
},
render: (gqlVal) => {
return (
<DebugContainer
gql={gqlVal}
/>
)
},
})
}

context('passed', () => {
it('renders', () => {
mountTestRun('allPassing')

cy.contains('Well Done!').should('be.visible')

cy.percySnapshot()
})
})

context('errored', () => {
it('renders', () => {
mountTestRun('allSkipped')

cy.contains('The browser server never connected.').should('be.visible')
cy.contains('2 of 3 specs skipped').should('be.visible')

cy.percySnapshot()
})
})

context('no tests', () => {
it('renders', () => {
mountTestRun('noTests')

cy.contains('Run has no tests').should('be.visible')

cy.percySnapshot()
})
})

context('timed out', () => {
it('renders with CI information', () => {
mountTestRun('timedOutWithCi')

cy.contains('Circle CI #1234').should('have.attr', 'href', 'https://circleci.com').should('be.visible')
cy.contains('Archive this run to remove it').should('be.visible')

cy.percySnapshot()
})

it('renders without CI information', () => {
mountTestRun('timedOutWithoutCi')

cy.contains('Circle CI #1234').should('not.exist')
cy.contains('Archive this run to remove it').should('be.visible')

cy.percySnapshot()
})
})

context('over limit', () => {
it('renders', () => {
mountTestRun('overLimit')

cy.findByRole('link', { name: 'Contact admin' }).should('have.attr', 'href', 'http://localhost:3000?utmMedium=Debug+Tab&utmSource=Binary%3A+Launchpad')

cy.percySnapshot()
})
})

context('cancelled', () => {
it('renders', () => {
mountTestRun('cancelled')

cy.findByTestId('cancelled-by-user-avatar').should('be.visible')
cy.contains('2 of 3 specs skipped').should('be.visible')
cy.contains('Test Tester').should('be.visible')

cy.percySnapshot()
})
})
})

describe('render specs and tests', () => {
it('renders data when logged in and connected', () => {
const loginConnectStore = useLoginConnectStore()
Expand Down
41 changes: 38 additions & 3 deletions packages/app/src/debug/DebugContainer.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
<template>
<div>
<div class="h-full">
<DebugError
v-if="showError"
/>
<div
v-else-if="loginConnectStore.user.isLoggedIn && loginConnectStore.project.isProjectConnected && run"
v-else-if="loginConnectStore.user.isLoggedIn && loginConnectStore.project.isProjectConnected && run?.status"
class="flex flex-col h-full"
>
<DebugPageHeader
:gql="run"
:commits-ahead="0"
/>
<DebugPageDetails
astone123 marked this conversation as resolved.
Show resolved Hide resolved
v-if="shouldDisplayDetails(run.status)"
:status="run.status"
:specs="run.specs"
:cancellation="{ cancelledAt: run.cancelledAt, cancelledBy: run.cancelledBy }"
:is-hidden-by-usage-limits="run.isHiddenByUsageLimits"
:over-limit-action-type="run.overLimitActionType"
:over-limit-action-url="run.overLimitActionUrl"
:ci="run.ci"
:errors="run.errors"
/>
<DebugSpecList
v-if="run.totalFailed && shouldDisplaySpecsList(run.status)"
:specs="debugSpecsArray"
/>
</div>
Expand All @@ -28,10 +41,11 @@
<script setup lang="ts">
import { gql } from '@urql/vue'
import { computed } from '@vue/reactivity'
import type { DebugSpecsFragment, TestingTypeEnum } from '../generated/graphql'
import type { CloudRunStatus, DebugSpecsFragment, TestingTypeEnum } from '../generated/graphql'
import { useLoginConnectStore } from '@packages/frontend-shared/src/store/login-connect-store'
import DebugPageHeader from './DebugPageHeader.vue'
import DebugSpecList from './DebugSpecList.vue'
import DebugPageDetails from './DebugPageDetails.vue'
import DebugNotLoggedIn from './empty/DebugNotLoggedIn.vue'
import DebugNoProject from './empty/DebugNoProject.vue'
import DebugNoRuns from './empty/DebugNoRuns.vue'
Expand All @@ -56,11 +70,24 @@ fragment DebugSpecs on Query {
id
runByNumber(runNumber: 11) {
...DebugPage
cancelledBy {
id
fullName
email
}
cancelledAt
id
runNumber
errors
status
overLimitActionType
overLimitActionUrl
isHiddenByUsageLimits
totalTests
ci {
id
...DebugPageDetails_cloudCiBuildInfo
}
testsForReview {
id
...DebugSpecListTests
Expand Down Expand Up @@ -97,6 +124,14 @@ const run = computed(() => {
return props.gql?.currentProject?.cloudProject?.__typename === 'CloudProject' ? props.gql.currentProject.cloudProject.runByNumber : null
})

function shouldDisplayDetails (status: CloudRunStatus) {
return !['RUNNING', 'FAILED'].includes(status)
}

function shouldDisplaySpecsList (status: CloudRunStatus) {
return ['ERRORED', 'CANCELLED', 'TIMEDOUT', 'FAILED'].includes(status)
}

const debugSpecsArray = computed(() => {
if (run.value && props.gql?.currentProject) {
const specs = run.value.specs || []
Expand Down
40 changes: 40 additions & 0 deletions packages/app/src/debug/DebugErrored.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<Alert
:title="t('debugPage.incomplete')"
status="warning"
:icon="ErrorOutlineIcon"
class="flex flex-col mb-24px w-full"
>
<div class="ml-5px">
<ul
mike-plummer marked this conversation as resolved.
Show resolved Hide resolved
v-for="(error, index) in errors"
:key="index"
class="list-disc ml-25px"
>
<li><pre>{{ error }}</pre></li>
astone123 marked this conversation as resolved.
Show resolved Hide resolved
</ul>
</div>
<div class="mt-20px">
{{ t('debugPage.specsSkipped', {n: totalSpecs, totalSkippedSpecs}) }}
</div>
</Alert>
</template>

<script lang="ts" setup>
import ErrorOutlineIcon from '~icons/cy/status-errored-outline_x16.svg'
import Alert from '@cy/components/Alert.vue'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { useI18n } from '@cy/i18n'

const { t } = useI18n()

dayjs.extend(utc)

defineProps<{
errors?: readonly string[]
totalSpecs: number
totalSkippedSpecs: number
}>()

</script>
19 changes: 19 additions & 0 deletions packages/app/src/debug/DebugNoTests.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<Alert
:title="t('debugPage.incomplete')"
status="warning"
:icon="ErrorOutlineIcon"
class="flex flex-col mb-24px w-full"
>
{{ t('debugPage.runHasNoTests') }}
</Alert>
</template>

<script lang="ts" setup>
import ErrorOutlineIcon from '~icons/cy/status-errored-outline_x16.svg'
import Alert from '@cy/components/Alert.vue'
import { useI18n } from '@cy/i18n'

const { t } = useI18n()

</script>
41 changes: 41 additions & 0 deletions packages/app/src/debug/DebugOverLimit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<div
class="flex flex-col items-center"
>
<LockedProject class="icon-dark-gray-500" />
<span class="font-medium mt-24px text-gray-900">
{{ t('debugPage.overPlanLimit') }}
</span>
<span class="mt-10px text-gray-600">{{ t('debugPage.reachedMonthlyUsage') }}</span>
<span class="text-gray-600">{{ t('debugPage.upgradeYourPlan') }}</span>
<Button
size="lg"
class="mt-25px"
:href="actionUrl"
>
{{ overLimitActionType === 'CONTACT_ADMIN' ? t('debugPage.contactAdmin') : t('debugPage.upgradePlan') }}
</Button>
</div>
</template>

<script lang="ts" setup>
import LockedProject from '~icons/cy/locked-project_x48.svg'
import Button from '@packages/frontend-shared/src/components/Button.vue'
import type { OverLimitActionTypeEnum } from '../generated/graphql'
import { getUtmSource } from '@packages/frontend-shared/src/utils/getUtmSource'
import { useI18n } from '@cy/i18n'
import { getUrlWithParams } from '@packages/frontend-shared/src/utils/getUrlWithParams'
import { computed } from '@vue/reactivity'

const { t } = useI18n()

const props = defineProps<{
overLimitActionType: OverLimitActionTypeEnum
overLimitActionUrl: string
}>()

const actionUrl = computed(() => {
return getUrlWithParams({ url: props.overLimitActionUrl, params: { utmMedium: 'Debug Tab', utmSource: getUtmSource() } })
})

</script>
Loading