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 24 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 in the Debug page where in-progress runs displayed a different count of specs locally compared to the count in Cypress Cloud. Fixes [#22825](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
60 changes: 32 additions & 28 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 @@ -153,22 +139,38 @@ export class RelevantRunSpecsDataSource {
statuses: {},
}

if (cloudProject.current && cloudProject.current.runNumber && cloudProject.current.status) {
const { current, next } = cloudProject

if (
current
&& current.runNumber
&& current.status
&& typeof current.totalInstanceCount === 'number'
Copy link
Contributor

Choose a reason for hiding this comment

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

You could use Number.isFinite(...) as well to save a few chars and exclude edge cases like *Ininity and NaN

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, sure 👍

&& typeof current.completedInstanceCount === 'number'
) {
runSpecsToReturn.runSpecs.current = {
...this.#calculateSpecMetadata(cloudProject.current.specs || []),
runNumber: cloudProject.current.runNumber,
totalSpecs: current.totalInstanceCount,
completedSpecs: current.completedInstanceCount,
runNumber: current.runNumber,
}

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

if (cloudProject.next && cloudProject.next.runNumber && cloudProject.next.status) {
if (
next
&& next.runNumber
&& next.status
&& typeof next.totalInstanceCount === 'number'
&& typeof next.completedInstanceCount === 'number'
) {
runSpecsToReturn.runSpecs.next = {
...this.#calculateSpecMetadata(cloudProject.next.specs || []),
runNumber: cloudProject.next.runNumber,
totalSpecs: next.totalInstanceCount,
completedSpecs: next.completedInstanceCount,
runNumber: next.runNumber,
}

runSpecsToReturn.statuses.next = cloudProject.next.status
runSpecsToReturn.statuses.next = next.status
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This block is pretty much a duplicate of the one above, worth extracting to a fn?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was marginal on whether to do this or not, and we are about to be right back in here in the next sprint or two doing further modifications. So seems slightly easier on all sides to leave this logic repeated, make the diff super simple, and the shortly we'll combine them in a way that meets the needs we are about to have.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went ahead and made a helper: f138cc3 (#25757)


return runSpecsToReturn
Expand Down Expand Up @@ -208,7 +210,9 @@ export class RelevantRunSpecsDataSource {
debug('Run statuses changed')
const projectSlug = await this.ctx.project.projectId()

if (projectSlug) {
const wasWatchingCurrentProject = this.#cached.statuses.current === 'RUNNING'

if (projectSlug && wasWatchingCurrentProject) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the change that avoids a flash of the full page loading skeleton when a next run transitions from "running" to "completed".

Ideally we would have an end-to-end test for this, I'm working on one that can form part of a separate PR if this merges first. It's also the kind of test that will be much easier to write if we have improvements coming in the test infra soon.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the wasWatchingCurrentProject check will work at its current location. The #cached variable is getting set on line 201, and the statusesChanged variable is calculated before that. When the code gets to the wasWatchingCurrentProject calculation, the #cached.statues.current will no longer be RUNNING.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. It's how the code was in the video I put in the PR description where it's having the intended effect, let me take a look at why exactly that's the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to move this earlier in the flow, everything seems good anyway.

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('RelevantRunsDataSource', () => {
marktnoonan marked this conversation as resolved.
Show resolved Hide resolved
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