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-M0 Debug Page #25067

Merged
merged 26 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0678342
adding debug page
warrensplayer Nov 23, 2022
7215efd
Fixing sidebar e2e tests
warrensplayer Nov 23, 2022
02e23a3
Adding more content to the Debug page and header updates
warrensplayer Dec 1, 2022
5b016ee
Merge branch 'feature/IATR-M0' into stokes/IATR-M0-debug-page-24441
warrensplayer Dec 2, 2022
df911d6
Merge branch 'feature/IATR-M0' into stokes/IATR-M0-debug-page-24441
warrensplayer Dec 2, 2022
9cbf00a
Starting to wire up child components
warrensplayer Dec 5, 2022
fd0eacf
created util function for debug specs mapping and wrote tests
amehta265 Dec 7, 2022
8b15773
update to debug page graphql extension parsing and tests for utility …
amehta265 Dec 7, 2022
cbee737
Fix header test
warrensplayer Dec 8, 2022
b3884f0
Type fixes
warrensplayer Dec 8, 2022
97b26cd
Fix test
warrensplayer Dec 8, 2022
a3f6917
Setting hash to same as feature branch
warrensplayer Dec 8, 2022
e8e04fd
Merge branch 'feature/IATR-M0' into stokes/IATR-M0-debug-page-24441
warrensplayer Dec 8, 2022
ca7ef19
New test for DebugContainer
warrensplayer Dec 8, 2022
193328a
Test Ids needed for test
warrensplayer Dec 8, 2022
ffb15c7
Updates to fix type linting
warrensplayer Dec 9, 2022
28c21f4
Merge branch 'feature/IATR-M0' into stokes/IATR-M0-debug-page-24441
warrensplayer Dec 9, 2022
5f51cce
Update to cloud schema and types
warrensplayer Dec 9, 2022
01ea7a2
Fixing tests
warrensplayer Dec 9, 2022
384f922
Minor test fix
warrensplayer Dec 9, 2022
eb93a79
Updates from review comments
warrensplayer Dec 12, 2022
6c9c7db
Make use of existing computed value
warrensplayer Dec 12, 2022
6be9b0c
Improve test
warrensplayer Dec 12, 2022
73b2154
Adding descriptions to cloud fields
warrensplayer Dec 12, 2022
ce2deb1
Encapsulate duration format in composable
warrensplayer Dec 12, 2022
9d62ced
Making changes to align with latest cloud update
warrensplayer Dec 12, 2022
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
13 changes: 13 additions & 0 deletions packages/app/cypress/e2e/sidebar_navigation.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('Sidebar Navigation', { viewportWidth: 1280 }, () => {
.tab().should('have.attr', 'data-cy', 'sidebar-header').should('have.attr', 'role', 'button')
.tab().should('have.attr', 'href', '#/specs').should('have.prop', 'tagName', 'A')
.tab().should('have.attr', 'href', '#/runs').should('have.prop', 'tagName', 'A')
.tab().should('have.attr', 'href', '#/debug').should('have.prop', 'tagName', 'A')
.tab().should('have.attr', 'href', '#/settings').should('have.prop', 'tagName', 'A')
.tab().should('have.attr', 'data-cy', 'keyboard-modal-trigger').should('have.prop', 'tagName', 'BUTTON')
})
Expand Down Expand Up @@ -146,6 +147,10 @@ describe('Sidebar Navigation', { viewportWidth: 1280 }, () => {
cy.contains('.v-popper--some-open--tooltip', 'Specs')
cy.findByTestId('sidebar-link-specs-page').trigger('mouseout')

cy.findByTestId('sidebar-link-debug-page').trigger('mouseenter')
cy.contains('.v-popper--some-open--tooltip', 'Debug')
cy.findByTestId('sidebar-link-debug-page').trigger('mouseout')

cy.findByTestId('sidebar-link-settings-page').trigger('mouseenter')
cy.contains('.v-popper--some-open--tooltip', 'Settings')
cy.findByTestId('sidebar-link-settings-page').trigger('mouseout')
Expand Down Expand Up @@ -237,6 +242,14 @@ describe('Sidebar Navigation', { viewportWidth: 1280 }, () => {
cy.get('.router-link-active').findByText('Specs').should('be.visible')
})

it('has a menu item labeled "Debug" which takes you to the Debug page', () => {
cy.get('[data-cy="app-header-bar"]').findByText('Debug').should('not.exist')

cy.findByTestId('sidebar-link-debug-page').should('have.text', 'Debug').should('be.visible').click()
cy.get('[data-cy="app-header-bar"]').findByText('Debug').should('be.visible')
cy.get('.router-link-active').findByText('Debug').should('be.visible')
})

it('Specs sidebar nav link is not active when a test is running', () => {
cy.location('hash').should('equal', '#/specs')
cy.contains('.router-link-exact-active', 'Specs')
Expand Down
9 changes: 9 additions & 0 deletions packages/app/cypress/e2e/specs_list_latest_runs.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ function simulateRunData () {
status: s,
createdAt: new Date('2022-05-08T03:17:00').toISOString(),
completedAt: new Date('2022-05-08T05:17:00').toISOString(),
basename: idPrefix.substring(idPrefix.lastIndexOf('/') + 1, idPrefix.indexOf('.')),
path: idPrefix,
extension: idPrefix.substring(idPrefix.indexOf('.')),
runNumber: 432,
groupCount: 2,
specDuration: {
Expand Down Expand Up @@ -444,6 +447,9 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
status: s,
createdAt: new Date('2022-05-08T03:17:00').toISOString(),
completedAt: new Date('2022-05-08T05:17:00').toISOString(),
basename: idPrefix.substring(idPrefix.lastIndexOf('/') + 1, idPrefix.indexOf('.')),
path: idPrefix,
extension: idPrefix.substring(idPrefix.indexOf('.')),
runNumber: 432,
groupCount: 2,
specDuration: {
Expand Down Expand Up @@ -550,6 +556,9 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
status: s,
createdAt: new Date('2022-05-08T03:17:00').toISOString(),
completedAt: new Date('2022-05-08T05:17:00').toISOString(),
basename: idPrefix.substring(idPrefix.lastIndexOf('/') + 1, idPrefix.indexOf('.')),
path: idPrefix,
extension: idPrefix.substring(idPrefix.indexOf('.')),
runNumber: 432,
groupCount: 2,
specDuration: {
Expand Down
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {},
"devDependencies": {
"@cypress-design/vue-icon": "^0.4.2",
"@cypress-design/vue-icon": "^0.12.1",
"@graphql-typed-document-node/core": "^3.1.0",
"@headlessui/vue": "1.4.0",
"@iconify/iconify": "2.1.2",
Expand Down
39 changes: 39 additions & 0 deletions packages/app/src/composables/useDurationFormat.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ref } from 'vue'
import { useDurationFormat } from './useDurationFormat'

describe('useDurationFormat', () => {
it('should format duration', () => {
expect(useDurationFormat(1000).value).to.eq('00:01')
expect(useDurationFormat(60000).value).to.eq('01:00')
expect(useDurationFormat(6000000).value).to.eq('01:40:00')

// expects 24 hours and greater to "roll over" and not include day information
expect(useDurationFormat(86400000).value).to.eq('00:00')
})

it('should render with value', () => {
const duration = 1000
const formatted = useDurationFormat(duration)

cy.mount(() => (<div>
{formatted.value}
</div>))

cy.contains('00:01')
})

it('should render with ref and update if ref changes', () => {
const duration = ref(1000)
const formatted = useDurationFormat(duration)

cy.mount(() => (<div>
{formatted.value}
</div>))

cy.contains('00:01').then(() => {
duration.value = 2000
})

cy.contains('00:02')
})
})
13 changes: 13 additions & 0 deletions packages/app/src/composables/useDurationFormat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { computed, Ref, unref } from 'vue'
import { dayjs } from '../runs/utils/day.js'

/*
Format duration to in HH:mm:ss format. The `totalDuration` field is milliseconds. Remove the leading "00:" if the value is less
than an hour. Currently, there is no expectation that a run duration will be greater 24 hours or greater, so it is okay that
this format would "roll-over" in that scenario.
Ex: 1 second which is 1000ms = 00:01
Ex: 1 hour and 1 second which is 3601000ms = 01:00:01
*/
export function useDurationFormat (value: number | Ref<number>) {
warrensplayer marked this conversation as resolved.
Show resolved Hide resolved
return computed(() => dayjs.duration(unref(value)).format('HH:mm:ss').replace(/^0+:/, ''))
}
154 changes: 154 additions & 0 deletions packages/app/src/debug/DebugContainer.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { DebugSpecsFragmentDoc } from '../generated/graphql-test'
import DebugContainer from './DebugContainer.vue'
import { defaultMessages } from '@cy/i18n'
import { useLoginConnectStore } from '@packages/frontend-shared/src/store/login-connect-store'
import { specsList } from './utils/DebugMapping'
import { CloudRunStubs } from '@packages/graphql/test/stubCloudTypes'

describe('<DebugContainer />', () => {
context('empty states', () => {
const validateEmptyState = (expectedMessage: string) => {
cy.mountFragment(DebugSpecsFragmentDoc, {
render: (gqlVal) => {
return (
<DebugContainer
gql={gqlVal}
/>
)
},
})

cy.findByTestId('debug-empty').contains(expectedMessage)
}

it('shows not logged in', () => {
validateEmptyState(defaultMessages.debugPage.notLoggedIn)
})

it('is logged in', () => {
const loginConnectStore = useLoginConnectStore()

loginConnectStore.setUserFlag('isLoggedIn', true)

validateEmptyState(defaultMessages.debugPage.notConnected)
})

it('has no runs', () => {
const loginConnectStore = useLoginConnectStore()

loginConnectStore.setUserFlag('isLoggedIn', true)
loginConnectStore.setProjectFlag('isProjectConnected', true)
cy.mountFragment(DebugSpecsFragmentDoc, {
render: (gqlVal) => {
return (
<DebugContainer
gql={gqlVal}
/>
)
},
})

cy.findByTestId('debug-empty').contains(defaultMessages.debugPage.noRuns)
})
})

describe('render specs and tests', () => {
it('renders data when logged in and connected', () => {
const loginConnectStore = useLoginConnectStore()

loginConnectStore.setUserFlag('isLoggedIn', true)
loginConnectStore.setProjectFlag('isProjectConnected', true)
cy.mountFragment(DebugSpecsFragmentDoc, {
onResult: (result) => {
if (result.currentProject?.cloudProject?.__typename === 'CloudProject') {
const test = result.currentProject.cloudProject.runByNumber
const other = CloudRunStubs.failingWithTests as typeof test

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

// Only asserting that it is rendering the components for failed specs
cy.findByTestId('debug-header').should('be.visible')
cy.findByTestId('debug-spec-item').should('be.visible')
})
})

describe('testing util function: debugMapping', () => {
const createSpecs = (idArr: string[]) => {
const acc: {id: string}[] = []

idArr.forEach((val) => {
acc.push({ id: val })
})

return acc
}

it('maps correctly for a single spec', () => {
const spec = createSpecs(['a1c'])
const tests = [
{ specId: 'a1c', id: 'random1' },
{ specId: 'a1c', id: 'random2' },
]

const debugMappingArray = specsList(spec, tests)

expect(debugMappingArray).to.have.length(1)
expect(debugMappingArray[0]).to.deep.equal({ spec: { id: 'a1c' }, tests: [{ specId: 'a1c', id: 'random1' }, { specId: 'a1c', id: 'random2' }] })
})

it('maps correctly for multiple specs and test', () => {
const specs = createSpecs(['123', '456', '789'])
const tests = [
{ specId: '123', id: 'random1' },
{ specId: '456', id: 'random2' },
{ specId: '456', id: 'random3' },
{ specId: '789', id: 'random4' },
{ specId: '123', id: 'random6' },
]

const debugMappingArray = specsList(specs, tests)

const expected = [
{ spec: { id: '123' }, tests: [{ specId: '123', id: 'random1' }, { specId: '123', id: 'random6' }] },
{ spec: { id: '456' }, tests: [{ specId: '456', id: 'random2' }, { specId: '456', id: 'random3' }] },
{ spec: { id: '789' }, tests: [{ specId: '789', id: 'random4' }] },
]

expect(debugMappingArray).to.deep.equal(expected)
})

it('maps does not show specs that do not have tests', () => {
const specs = createSpecs(['123', '456', '789'])
const tests = [{ specId: '123', id: 'random1' }]

const debugMappingArray = specsList(specs, tests)

expect(debugMappingArray).to.have.length(1)
expect(debugMappingArray).to.deep.equal([{ spec: { id: '123' }, tests: [{ specId: '123', id: 'random1' }] }])
})

it('throws an error when a test does not map to a spec', () => {
const specs = createSpecs(['123'])
const tests = [
{ specId: '123', id: 'random1' },
{ specId: '456', id: 'random2' },
]

const specsListWrapper = () => {
return specsList(specs, tests)
}

expect(specsListWrapper).to.throw('Could not find spec for id 456')
})
})
})
100 changes: 100 additions & 0 deletions packages/app/src/debug/DebugContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<template>
<div>
<div
v-if="loginConnectStore.user.isLoggedIn && loginConnectStore.project.isProjectConnected && run"
>
<DebugPageHeader
:gql="run"
:commits-ahead="0"
warrensplayer marked this conversation as resolved.
Show resolved Hide resolved
/>
<DebugSpecList
:specs="debugSpecsArray"
/>
</div>
<div
v-else
data-cy="debug-empty"
warrensplayer marked this conversation as resolved.
Show resolved Hide resolved
>
<div
v-if="!loginConnectStore.user.isLoggedIn"
>
{{ t('debugPage.notLoggedIn') }}
</div>
<div
v-else-if="!loginConnectStore.project.isProjectConnected"
>
{{ t('debugPage.notConnected' ) }}
</div>
<div
v-else-if="!run"
>
{{ t('debugPage.noRuns') }}
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { gql } from '@urql/vue'
import { computed } from '@vue/reactivity'
import type { DebugSpecsFragment } 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 { useI18n } from 'vue-i18n'
import { specsList } from './utils/DebugMapping'
const { t } = useI18n()
gql`
fragment DebugSpecs on Query {
currentProject {
id
cloudProject {
__typename
... on CloudProject {
id
runByNumber(runNumber: 2) {
...DebugPage
id
runNumber
status
overLimitActionType
overLimitActionUrl
testsForReview {
id
...DebugSpecListTests
}
specs {
id
...DebugSpecListSpec
}
}
}
}
}
}
`
const props = defineProps<{
gql: DebugSpecsFragment
}>()
const loginConnectStore = useLoginConnectStore()
const run = computed(() => {
return props.gql.currentProject?.cloudProject?.__typename === 'CloudProject' ? props.gql.currentProject.cloudProject.runByNumber : null
})
const debugSpecsArray = computed(() => {
if (run.value) {
const specs = run.value.specs || []
const tests = run.value.testsForReview || []
return specsList(specs, tests)
}
return []
})
</script>
Loading