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: Add messaging for retention state exceeded #25361

Merged
merged 5 commits into from
Jan 5, 2023
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
10 changes: 9 additions & 1 deletion packages/app/src/debug/DebugContainer.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,21 @@ describe('<DebugContainer />', () => {
})

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

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

cy.percySnapshot()
})

it('handles retention exceeded', () => {
mountTestRun('overLimitRetention')

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

cy.percySnapshot()
})
})

context('cancelled', () => {
Expand Down
22 changes: 20 additions & 2 deletions packages/app/src/debug/DebugContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
:status="run.status"
:specs="run.specs"
:cancellation="{ cancelledAt: run.cancelledAt, cancelledBy: run.cancelledBy }"
:is-hidden-by-usage-limits="run.isHiddenByUsageLimits"
:is-hidden="run.isHidden"
:reasons-run-is-hidden="reasonsRunIsHidden"
:over-limit-action-type="run.overLimitActionType"
:over-limit-action-url="run.overLimitActionUrl"
:ci="run.ci"
:errors="run.errors"
:run-age-days="runAgeDays"
/>
<DebugSpecList
v-if="run.totalFailed && shouldDisplaySpecsList(run.status)"
Expand Down Expand Up @@ -65,6 +67,8 @@ import DebugNoRuns from './empty/DebugNoRuns.vue'
import DebugError from './empty/DebugError.vue'
import DebugNewRelevantRunBar from './DebugNewRelevantRunBar.vue'
import { specsList } from './utils/DebugMapping'
import type { CloudRunHidingReason } from './DebugOverLimit.vue'
import dayjs from 'dayjs'

gql`
fragment DebugLocalSpecs on Spec {
Expand Down Expand Up @@ -96,7 +100,16 @@ fragment DebugSpecs on Query {
status
overLimitActionType
overLimitActionUrl
isHiddenByUsageLimits
isHidden
reasonsRunIsHidden {
__typename
... on DataRetentionLimitExceeded {
dataRetentionDays
}
... on UsageLimitExceeded {
monthlyTests
}
}
totalTests
ci {
id
Expand All @@ -114,6 +127,7 @@ fragment DebugSpecs on Query {
id,
...DebugSpecListGroups
}
createdAt
...DebugPendingRunSplash
...DebugNewRelevantRunBar
}
Expand Down Expand Up @@ -175,4 +189,8 @@ const specStatuses = computed(() => run.value?.specs.map((spec) => spec.status |
const newerRelevantRun = computed(() => run.value)

const isFirstPendingRun = computed(() => run.value && run.value.runNumber === 1 && run.value.status === 'RUNNING')

const reasonsRunIsHidden = computed(() => (run.value?.reasonsRunIsHidden || []) as CloudRunHidingReason[])

const runAgeDays = computed(() => run.value?.createdAt && dayjs().diff(dayjs(run.value.createdAt), 'day') || 0)
</script>
78 changes: 72 additions & 6 deletions packages/app/src/debug/DebugOverLimit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,106 @@
<div
class="flex flex-col items-center"
>
<LockedProject class="icon-dark-gray-500" />
<LockedProject :class="iconClasses" />
<span class="font-medium mt-24px text-gray-900">
{{ t('debugPage.overPlanLimit') }}
{{ copy.title }}
</span>
<span class="mt-10px text-center text-gray-600">
{{ copy.message }}
</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') }}
{{ copy.actionLabel }}
</Button>
</div>
</template>

<script lang="ts" setup>
import { gql } from '@urql/core'
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 type { DebugReasonsRunIsHiddenFragment, 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'

export type CloudRunHidingReason = DebugReasonsRunIsHiddenFragment['reasonsRunIsHidden'][number]

type DataRetentionLimitExceeded = Extract<CloudRunHidingReason, { '__typename': 'DataRetentionLimitExceeded' }>

type UsageLimitExceeded = Extract<CloudRunHidingReason, { '__typename': 'UsageLimitExceeded' }>

gql`
fragment DebugReasonsRunIsHidden on CloudRun {
id
reasonsRunIsHidden {
__typename
... on DataRetentionLimitExceeded {
dataRetentionDays
}
... on UsageLimitExceeded {
monthlyTests
}
}
}
`

const { t } = useI18n()

const props = defineProps<{
overLimitReasons: (CloudRunHidingReason | null)[]
overLimitActionType: OverLimitActionTypeEnum
overLimitActionUrl: string
runAgeDays: number
}>()

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

const overLimitReason = computed<CloudRunHidingReason>(() => {
// Prefer showing the "Usage Exceeded" messaging if multiple conditions exist
return props.overLimitReasons.find(isUsageLimit) || props.overLimitReasons.find(isRetentionLimit) || null
})

const isPlanAdmin = computed(() => props.overLimitActionType === 'UPGRADE')

const iconClasses = computed(() => {
return [
'icon-dark-gray-500',
isRetentionLimit(overLimitReason.value)
? 'icon-dark-secondary-orange-400 icon-light-secondary-orange-200'
: 'icon-dark-secondary-jade-400 icon-light-secondary-jade-200',
]
})
const copy = computed(() => {
if (isRetentionLimit(overLimitReason.value)) {
return {
title: t('debugPage.overLimit.retentionExceededTitle'),
message: t('debugPage.overLimit.retentionExceededMessage', { limitDays: overLimitReason.value.dataRetentionDays, runAgeDays: props.runAgeDays }),
actionLabel: isPlanAdmin.value ? t('debugPage.overLimit.upgradePlan') : t('debugPage.overLimit.contactAdmin'),
}
}

const numberTests = overLimitReason.value?.monthlyTests || 0

return {
title: t('debugPage.overLimit.usageExceededTitle'),
message: isPlanAdmin.value ? t('debugPage.overLimit.usageExceededAdminMessage', { numberTests }) : t('debugPage.overLimit.usageExceededUserMessage', { numberTests }),
actionLabel: isPlanAdmin.value ? t('debugPage.overLimit.upgradePlan') : t('debugPage.overLimit.contactAdmin'),
}
})

function isUsageLimit (reason: CloudRunHidingReason | null | undefined): reason is UsageLimitExceeded {
return reason?.__typename === 'UsageLimitExceeded'
}

function isRetentionLimit (reason: CloudRunHidingReason | null | undefined): reason is DataRetentionLimitExceeded {
return reason?.__typename === 'DataRetentionLimitExceeded'
}

</script>
10 changes: 7 additions & 3 deletions packages/app/src/debug/DebugPageDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
:ci="ci"
/>
<DebugOverLimit
v-else-if="status === 'OVERLIMIT' || isHiddenByUsageLimits"
v-else-if="isHidden"
:over-limit-reasons="reasonsRunIsHidden"
:over-limit-action-type="overLimitActionType"
:over-limit-action-url="overLimitActionUrl"
:run-age-days="runAgeDays"
/>
</div>
</template>
Expand All @@ -39,7 +41,7 @@ import DebugPassed from './DebugPassed.vue'
import DebugErrored from './DebugErrored.vue'
import DebugNoTests from './DebugNoTests.vue'
import DebugTimedout from './DebugTimedout.vue'
import DebugOverLimit from './DebugOverLimit.vue'
import DebugOverLimit, { CloudRunHidingReason } from './DebugOverLimit.vue'

gql`
fragment DebugPageDetails_cloudCiBuildInfo on CloudCiBuildInfo {
Expand All @@ -59,12 +61,14 @@ const props = defineProps<{
email?: string | null
} | null
}
isHiddenByUsageLimits: boolean
isHidden: boolean
reasonsRunIsHidden: (CloudRunHidingReason | null)[]
overLimitActionType: OverLimitActionTypeEnum
overLimitActionUrl: string
specs: readonly DebugSpecListSpecFragment[]
ci?: DebugPageDetails_CloudCiBuildInfoFragment
errors: readonly string[]
runAgeDays: number
}>()

const totalSkippedSpecs = computed(() => {
Expand Down
95 changes: 84 additions & 11 deletions packages/app/src/debug/DebugRunStates.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,93 @@ describe('Debug page states', () => {
})

context('over limit', () => {
it('renders type CONTACT_ADMIN', () => {
cy.mount(<DebugOverLimit overLimitActionType="CONTACT_ADMIN" overLimitActionUrl='http://localhost:3000/profile' />)

cy.findByRole('link', { name: 'Contact admin' }).should('be.visible')

cy.percySnapshot()
context('Usage Exceeded', () => {
it('displays messaging for users', () => {
cy.mount(
<DebugOverLimit
overLimitReasons={[{
__typename: 'UsageLimitExceeded',
monthlyTests: 100,
}]}
overLimitActionType="CONTACT_ADMIN"
overLimitActionUrl="#"
runAgeDays={60}
/>,
)

cy.percySnapshot()
})

it('displays messaging for plan admins', () => {
cy.mount(
<DebugOverLimit
overLimitReasons={[{
__typename: 'UsageLimitExceeded',
monthlyTests: 100,
}]}
overLimitActionType="UPGRADE"
overLimitActionUrl="#"
runAgeDays={60}
/>,
)

cy.percySnapshot()
})
})

it('renders type UPGRADE', () => {
cy.mount(<DebugOverLimit overLimitActionType="UPGRADE" overLimitActionUrl='http://localhost:3000/profile' />)

cy.findByRole('link', { name: 'Upgrade plan' }).should('be.visible')
context('Retention Exceeded', () => {
it('displays messaging for users', () => {
cy.mount(
<DebugOverLimit
overLimitReasons={[{
__typename: 'DataRetentionLimitExceeded',
dataRetentionDays: 30,
}]}
overLimitActionType="CONTACT_ADMIN"
overLimitActionUrl="#"
runAgeDays={60}
/>,
)

cy.percySnapshot()
})

it('displays messaging for plan admins', () => {
cy.mount(
<DebugOverLimit
overLimitReasons={[{
__typename: 'DataRetentionLimitExceeded',
dataRetentionDays: 30,
}]}
overLimitActionType="UPGRADE"
overLimitActionUrl="#"
runAgeDays={60}
/>,
)

cy.percySnapshot()
})
})

cy.percySnapshot()
context('both usage and retention exceeded', () => {
it('selects usage exceeded and displays appropriate message', () => {
cy.mount(
<DebugOverLimit
overLimitReasons={[{
__typename: 'DataRetentionLimitExceeded',
dataRetentionDays: 30,
}, {
__typename: 'UsageLimitExceeded',
monthlyTests: 30,
}]}
overLimitActionType="UPGRADE"
overLimitActionUrl="#"
runAgeDays={60}
/>,
)

cy.percySnapshot()
})
})
})

Expand Down
14 changes: 9 additions & 5 deletions packages/frontend-shared/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -651,11 +651,15 @@
"switchTestingTypeAction": "Switch to {testingType} testing"
},
"viewRun": "View run",
"overPlanLimit": "Over plan limit",
"reachedMonthlyUsage": "You've reached the monthly usage limit for your billing plan.",
"upgradeYourPlan": "Upgrade your plan to continue recording runs.",
"upgradePlan": "Upgrade plan",
"contactAdmin": "Contact admin",
"overLimit": {
"usageExceededTitle": "You've reached your monthly test result limit",
"usageExceededUserMessage": "Your Free Cypress Cloud plan includes {numberTests} monthly recorded test results. Contact your Cypress Cloud admin to upgrade your plan and view more test results.",
"usageExceededAdminMessage": "Your Free Cypress Cloud plan includes {numberTests} monthly recorded test results. Upgrade your plan now to view more test results.",
"retentionExceededTitle": "Beyond data retention",
"retentionExceededMessage": "Your data retention limit is {limitDays} days and these tests ran {runAgeDays} days ago. Upgrade your plan to increase your data retention limit.",
"upgradePlan": "Upgrade plan",
"contactAdmin": "Contact admin"
},
"wellDone": "Well Done!",
"allYourTestsPassed": "All your tests passed.",
"incomplete": "Incomplete",
Expand Down
Loading