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

fix: implement new graphql fields for spec counts #25757

Merged
merged 31 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
df76d1c
add new fixture
marktnoonan Feb 6, 2023
06ca95e
update comment
marktnoonan Feb 6, 2023
55abda9
add unit test based on current functionality
marktnoonan Feb 6, 2023
97d560a
add second test case based on current logic
marktnoonan Feb 6, 2023
cd88393
update test
marktnoonan Feb 6, 2023
5ae892f
[run ci] switch to new fields
marktnoonan Feb 6, 2023
89cbf00
run ci
marktnoonan Feb 6, 2023
75c030f
accept runNumber arg in relevantRunSpecChange
marktnoonan Feb 6, 2023
174e5f1
formatting
marktnoonan Feb 8, 2023
e502f21
revert runNumber arg
marktnoonan Feb 8, 2023
97131cf
commit updated graphql files
marktnoonan Feb 8, 2023
6ec7f87
update conditional logic and test
marktnoonan Feb 9, 2023
c13a6b3
[run ci] only invalidate cache when watching `current`
marktnoonan Feb 9, 2023
4b3a56b
Merge branch 'develop' into marktnoonan/25647-new-cloud-fields
marktnoonan Feb 9, 2023
945a66f
commit updated schema
marktnoonan Feb 9, 2023
0e72f2e
[run ci] - fix build time type error
marktnoonan Feb 10, 2023
9d520fb
Merge branch 'develop' into marktnoonan/25647-new-cloud-fields
marktnoonan Feb 10, 2023
0a727c3
fix types in stubs
marktnoonan Feb 10, 2023
0f2712e
add new properties to query
marktnoonan Feb 10, 2023
6f53db7
test cleanup
marktnoonan Feb 10, 2023
94a33c3
Merge branch 'develop' into marktnoonan/25647-new-cloud-fields
marktnoonan Feb 10, 2023
b4a1a0f
fix condition for cache clearing
marktnoonan Feb 10, 2023
e7a4059
Merge branch 'marktnoonan/25647-new-cloud-fields' of https://github.c…
marktnoonan Feb 10, 2023
83a95b9
update changelog
marktnoonan Feb 10, 2023
762dc2d
update changelog
marktnoonan Feb 10, 2023
97b2b43
Update packages/data-context/test/unit/sources/RelevantRunSpecsDataSo…
marktnoonan Feb 10, 2023
ae96ae7
Update cli/CHANGELOG.md
marktnoonan Feb 10, 2023
8c1e747
updates from feedback
marktnoonan Feb 10, 2023
f138cc3
make helper for repeated code
marktnoonan Feb 10, 2023
e6c08fa
Merge branch 'develop' into marktnoonan/25647-new-cloud-fields
marktnoonan Feb 13, 2023
46e79a5
remove bang
marktnoonan Feb 13, 2023
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
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ _Released 02/14/2023 (PENDING)_

- Fixed an issue with the Cloud project selection modal not showing the correct prompts. Fixes [#25520](https://github.com/cypress-io/cypress/issues/25520).
- Fixed an issue in middleware where error-handling code could itself generate an error and fail to report the original issue. Fixes [#22825](https://github.com/cypress-io/cypress/issues/22825).
- Fixed an issue that could cause the Debug page to display a different number of specs for in-progress runs than shown in Cypress Cloud. Fixes [#25647](https://github.com/cypress-io/cypress/issues/25647).

**Features:**

Expand Down
47 changes: 47 additions & 0 deletions packages/app/src/debug/DebugContainer.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ describe('<DebugContainer />', () => {
result.currentProject.cloudProject.runByNumber = {
...CloudRunStubs.running,
runNumber: 1,
completedInstanceCount: 2,
totalInstanceCount: 3,
} as typeof test
}
},
Expand All @@ -255,6 +257,51 @@ describe('<DebugContainer />', () => {
})
})

it('does not render DebugPendingRunSplash and DebugNewRelevantRunBar at the same time', () => {
cy.mountFragment(DebugSpecsFragmentDoc, {
variableTypes: DebugSpecVariableTypes,
variables: {
hasNextRun: false,
runNumber: 1,
nextRunNumber: -1,
},
onResult: (result) => {
if (result.currentProject?.cloudProject?.__typename === 'CloudProject') {
const test = result.currentProject.cloudProject.runByNumber

// Testing this to confirm we are "making impossible states impossible" in the UI,
// and document the expectation in this scenario. For clarity,
// we do not expect a 'RUNNING` current and next run at the same time, so
// the data below represents an invalid state.

result.currentProject.cloudProject.runByNumber = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of modifying this on the fly, should we use the fixtures you added such as FAKE_PROJECT_MULTIPLE_COMPLETED?

I find the modified on the fly ones kind of hard to read - you need to have a mental modal of exactly what you are modifying in the first place. How about you?

...CloudRunStubs.running,
runNumber: 1,
completedInstanceCount: 2,
totalInstanceCount: 3,
} as typeof test

result.currentProject.cloudProject.nextRun = {
...CloudRunStubs.running,
runNumber: 1,
completedInstanceCount: 5,
totalInstanceCount: 6,
} as typeof test
}
},
render: (gqlVal) => <DebugContainer gql={gqlVal} />,
})

cy.findByTestId('debug-header').should('be.visible')
cy.findByTestId('debug-pending-splash')
.should('be.visible')
.within(() => {
cy.findByTestId('debug-pending-counts').should('have.text', '0 of 0 specs completed')
})

cy.findByTestId('newer-relevant-run').should('not.exist')
})

it('renders specs and tests when completed run available', () => {
cy.mountFragment(DebugSpecsFragmentDoc, {
variableTypes: DebugSpecVariableTypes,
Expand Down
10 changes: 5 additions & 5 deletions packages/app/src/debug/DebugContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
:gql="run"
:commits-ahead="props.commitsAhead"
/>
<DebugNewRelevantRunBar
v-if="newerRelevantRun"
:gql="newerRelevantRun"
/>

<DebugPendingRunSplash
v-if="isFirstPendingRun"
class="mt-12"
/>
<DebugNewRelevantRunBar
v-else-if="newerRelevantRun"
:gql="newerRelevantRun"
/>

<template v-else>
<DebugPageDetails
v-if="shouldDisplayDetails(run.status, run.isHidden)"
Expand Down
61 changes: 30 additions & 31 deletions packages/data-context/src/sources/RelevantRunSpecsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import debugLib from 'debug'
import { isEqual } from 'lodash'

import type { DataContext } from '../DataContext'
import type { CloudSpecStatus, Query, RelevantRun, CurrentProjectRelevantRunSpecs, CloudSpecRun, CloudRun } from '../gen/graphcache-config.gen'
import type { Query, RelevantRun, CurrentProjectRelevantRunSpecs, CloudRun } from '../gen/graphcache-config.gen'
import { Poller } from '../polling'
import type { CloudRunStatus } from '@packages/graphql/src/gen/cloud-source-types.gen'

Expand All @@ -15,6 +15,8 @@ const RELEVANT_RUN_SPEC_OPERATION_DOC = gql`
id
runNumber
status
completedInstanceCount
totalInstanceCount
specs {
id
status
Expand Down Expand Up @@ -55,8 +57,6 @@ export const SPECS_EMPTY_RETURN: RunSpecReturn = {
statuses: {},
}

const INCOMPLETE_STATUSES: CloudSpecStatus[] = ['RUNNING', 'UNCLAIMED']

export type RunSpecReturn = {
runSpecs: CurrentProjectRelevantRunSpecs
statuses: {
Expand Down Expand Up @@ -86,23 +86,9 @@ export class RelevantRunSpecsDataSource {
return this.#cached.runSpecs
}

#calculateSpecMetadata (specs: CloudSpecRun[]) {
//mimic logic in Cloud to sum up the count of groups per spec to give the total spec counts
const countGroupsForSpec = (specs: CloudSpecRun[]) => {
return specs.map((spec) => spec.groupIds?.length || 0).reduce((acc, curr) => acc += curr, 0)
}

return {
totalSpecs: countGroupsForSpec(specs),
completedSpecs: countGroupsForSpec(specs.filter((spec) => !INCOMPLETE_STATUSES.includes(spec.status || 'UNCLAIMED'))),
}
}

/**
* Pulls runs from the current Cypress Cloud account and determines which runs are considered:
* - "current" the most recent completed run, or if not found, the most recent running run
* - "next" the most recent running run if a completed run is found
* @param shas list of Git commit shas to query the Cloud with for matching runs
* Pulls the specs that match the relevant run.
* @param runs - the current and (optionally) next relevant run
*/
async getRelevantRunSpecs (runs: RelevantRun): Promise<RunSpecReturn> {
const projectSlug = await this.ctx.project.projectId()
Expand Down Expand Up @@ -147,28 +133,40 @@ export class RelevantRunSpecsDataSource {
}
}

function isValidNumber (value: unknown): value is number {
return Number.isFinite(value)
}

if (cloudProject?.__typename === 'CloudProject') {
const runSpecsToReturn: RunSpecReturn = {
runSpecs: {},
statuses: {},
}

if (cloudProject.current && cloudProject.current.runNumber && cloudProject.current.status) {
runSpecsToReturn.runSpecs.current = {
...this.#calculateSpecMetadata(cloudProject.current.specs || []),
runNumber: cloudProject.current.runNumber,
const { current, next } = cloudProject

const formatCloudRunInfo = (cloudRunDetails: Partial<CloudRun>) => {
const { runNumber, totalInstanceCount, completedInstanceCount } = cloudRunDetails

if (runNumber && isValidNumber(totalInstanceCount) && isValidNumber(completedInstanceCount)) {
return {
totalSpecs: totalInstanceCount,
completedSpecs: completedInstanceCount,
runNumber,
}
}

runSpecsToReturn.statuses.current = cloudProject.current.status
return undefined
}

if (cloudProject.next && cloudProject.next.runNumber && cloudProject.next.status) {
runSpecsToReturn.runSpecs.next = {
...this.#calculateSpecMetadata(cloudProject.next.specs || []),
runNumber: cloudProject.next.runNumber,
}
if (current && current.status) {
runSpecsToReturn.runSpecs.current = formatCloudRunInfo(current)
runSpecsToReturn.statuses.current = current.status
}

runSpecsToReturn.statuses.next = cloudProject.next.status
if (next && next.status) {
runSpecsToReturn.runSpecs.next = formatCloudRunInfo(next)
runSpecsToReturn.statuses.next = next.status
}

return runSpecsToReturn
Expand All @@ -193,6 +191,7 @@ export class RelevantRunSpecsDataSource {

debug(`Spec data is `, specs)

const wasWatchingCurrentProject = this.#cached.statuses.current === 'RUNNING'
const specCountsChanged = !isEqual(specs.runSpecs, this.#cached.runSpecs)
const statusesChanged = !isEqual(specs.statuses, this.#cached.statuses)

Expand All @@ -208,7 +207,7 @@ export class RelevantRunSpecsDataSource {
debug('Run statuses changed')
const projectSlug = await this.ctx.project.projectId()

if (projectSlug) {
if (projectSlug && wasWatchingCurrentProject) {
debug(`Invalidate cloudProjectBySlug ${projectSlug}`)
await this.ctx.cloud.invalidate('Query', 'cloudProjectBySlug', { slug: projectSlug })
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { expect } from 'chai'
import sinon from 'sinon'

import { DataContext } from '../../../src'
import { createTestDataContext } from '../helper'
import { RelevantRunSpecsDataSource, SPECS_EMPTY_RETURN } from '../../../src/sources'
import { FAKE_PROJECT_ONE_RUNNING_RUN_ONE_COMPLETED_THREE_SPECS, FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC } from './fixtures/graphqlFixtures'

describe('RelevantRunSpecsDataSource', () => {
let ctx: DataContext
let dataSource: RelevantRunSpecsDataSource

beforeEach(() => {
ctx = createTestDataContext('open')
dataSource = new RelevantRunSpecsDataSource(ctx)
sinon.stub(ctx.project, 'projectId').resolves('test123')
})

describe('getRelevantRunSpecs()', () => {
it('returns no specs or statuses when no specs found for run', async () => {
const result = await dataSource.getRelevantRunSpecs({ current: 11111, next: 22222, commitsAhead: 0 })

expect(result).to.eql(SPECS_EMPTY_RETURN)
})

it('returns expected specs and statuses when one run is found', async () => {
sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC)

const result = await dataSource.getRelevantRunSpecs({ current: 1, next: null, commitsAhead: 0 })

expect(result).to.eql({
runSpecs: {
current: {
runNumber: 1,
completedSpecs: 1,
totalSpecs: 1,
},
},
statuses: { current: 'RUNNING' },
})
})

it('returns expected specs and statuses when one run is completed and one is running', async () => {
sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_COMPLETED_THREE_SPECS)

const result = await dataSource.getRelevantRunSpecs({ current: 1, next: null, commitsAhead: 0 })

expect(result).to.eql({
runSpecs: {
current: {
runNumber: 1,
completedSpecs: 3,
totalSpecs: 3,
},
next: {
runNumber: 2,
completedSpecs: 0,
totalSpecs: 3,
},
},
statuses: {
current: 'PASSED',
next: 'RUNNING',
},
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,42 @@ export const FAKE_PROJECT_NO_RUNS = { data: { cloudProjectBySlug: { __typename:

export const FAKE_PROJECT_ONE_RUNNING_RUN = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [{ runNumber: 1, status: 'RUNNING', commitInfo: { sha: FAKE_SHAS[0] } }] } } }

export const FAKE_PROJECT_MULTIPLE_COMPLETED = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [{ runNumber: 4, status: 'FAILED', commitInfo: { sha: FAKE_SHAS[1] } }, { runNumber: 1, status: 'PASSED', commitInfo: { sha: FAKE_SHAS[0] } }] } } }
export const FAKE_PROJECT_MULTIPLE_COMPLETED = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [
{ runNumber: 4, status: 'FAILED', commitInfo: { sha: FAKE_SHAS[1] } }, { runNumber: 1, status: 'PASSED', commitInfo: { sha: FAKE_SHAS[0] } },
] } } }

export const FAKE_PROJECT_MULTIPLE_COMPLETED_PLUS_RUNNING = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [{ runNumber: 5, status: 'RUNNING', commitInfo: { sha: FAKE_SHAS[2] } }, { runNumber: 4, status: 'FAILED', commitInfo: { sha: FAKE_SHAS[1] } }, { runNumber: 1, status: 'PASSED', commitInfo: { sha: FAKE_SHAS[0] } }] } } }

export const FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC = {
data: {
cloudProjectBySlug: {
__typename: 'CloudProject',
current: {
runNumber: 1,
completedInstanceCount: 1,
totalInstanceCount: 1,
status: 'RUNNING',
},
},
},
}

export const FAKE_PROJECT_ONE_RUNNING_RUN_ONE_COMPLETED_THREE_SPECS = {
data: {
cloudProjectBySlug: {
__typename: 'CloudProject',
current: {
runNumber: 1,
status: 'PASSED',
completedInstanceCount: 3,
totalInstanceCount: 3,
},
next: {
runNumber: 2,
status: 'RUNNING',
completedInstanceCount: 0,
totalInstanceCount: 3,
},
},
},
}
Loading