diff --git a/app/controllers/crate/versions.js b/app/controllers/crate/versions.js index 9c28f337d0a..5b4cea78450 100644 --- a/app/controllers/crate/versions.js +++ b/app/controllers/crate/versions.js @@ -12,6 +12,7 @@ function defaultVersionsContext() { } export default class SearchController extends Controller { + @service releaseTracks; @service sentry; @service store; @@ -87,15 +88,8 @@ export default class SearchController extends Controller { }; } - // set release_tracks to crate if (meta.release_tracks) { - let payload = { - crate: { - id: crate.id, - release_tracks: meta.release_tracks, - }, - }; - this.store.pushPayload(payload); + this.releaseTracks.updatePayload(crate.id, meta.release_tracks); } return versions; diff --git a/app/models/version.js b/app/models/version.js index 2083b9581c1..1a89cbf1706 100644 --- a/app/models/version.js +++ b/app/models/version.js @@ -1,4 +1,5 @@ import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; +import { service } from '@ember/service'; import { waitForPromise } from '@ember/test-waiters'; import { cached } from '@glimmer/tracking'; @@ -12,6 +13,8 @@ import ajax from '../utils/ajax'; const EIGHT_DAYS = 8 * 24 * 60 * 60 * 1000; export default class Version extends Model { + @service releaseTracks; + @attr num; @attr dl_path; @attr readme_path; @@ -188,11 +191,13 @@ export default class Version extends Model { let data = { version: { yanked: true } }; let payload = await waitForPromise(apiAction(this, { method: 'PATCH', data })); this.store.pushPayload(payload); + await waitForPromise(this.releaseTracks.refreshTask.perform(this.crateName, true)); }); unyankTask = keepLatestTask(async () => { let data = { version: { yanked: false } }; let payload = await waitForPromise(apiAction(this, { method: 'PATCH', data })); this.store.pushPayload(payload); + await waitForPromise(this.releaseTracks.refreshTask.perform(this.crateName, false)); }); } diff --git a/app/services/release-tracks.js b/app/services/release-tracks.js new file mode 100644 index 00000000000..4121c185343 --- /dev/null +++ b/app/services/release-tracks.js @@ -0,0 +1,41 @@ +import Service, { service } from '@ember/service'; + +import { didCancel, dropTask } from 'ember-concurrency'; + +import { AjaxError } from '../utils/ajax'; + +export default class PristineParamsService extends Service { + @service sentry; + @service store; + + refreshTask = dropTask(async crateName => { + let query = { + include: 'release_tracks', + name: crateName, + per_page: 1, + sort: 'semver', + }; + + try { + let versions = await this.store.query('version', query); + let meta = versions.meta; + if (meta.release_tracks) { + this.updatePayload(crateName, meta.release_tracks); + } + } catch (error) { + if (!didCancel(error) && !(error instanceof AjaxError)) { + this.sentry.captureException(error); + } + } + }); + + updatePayload(crateName, release_tracks) { + let payload = { + crate: { + id: crateName, + release_tracks, + }, + }; + this.store.pushPayload(payload); + } +} diff --git a/e2e/acceptance/versions.spec.ts b/e2e/acceptance/versions.spec.ts index db962b98ca2..cbc61ebb821 100644 --- a/e2e/acceptance/versions.spec.ts +++ b/e2e/acceptance/versions.spec.ts @@ -24,4 +24,46 @@ test.describe('Acceptance | crate versions page', { tag: '@acceptance' }, () => versions = await page.locator('[data-test-version]').evaluateAll(el => el.map(it => it.dataset.testVersion)); expect(versions).toEqual(['0.3.0', '0.2.1', '0.2.0', '0.1.0']); }); + + test('shows correct release tracks label after yanking/unyanking', async ({ page, msw, percy }) => { + let user = msw.db.user.create(); + await msw.authenticateAs(user); + + let crate = msw.db.crate.create({ name: 'nanomsg' }); + msw.db.crateOwnership.create({ crate, user }); + + msw.db.version.create({ crate, num: '0.1.0', created_at: '2017-01-01' }); + msw.db.version.create({ crate, num: '0.2.0', created_at: '2018-01-01' }); + msw.db.version.create({ crate, num: '0.3.0', created_at: '2019-01-01', rust_version: '1.69' }); + msw.db.version.create({ crate, num: '0.2.1', created_at: '2020-01-01' }); + + await page.goto('/crates/nanomsg/versions'); + await expect(page).toHaveURL('/crates/nanomsg/versions'); + + await expect(page.locator('[data-test-version]')).toHaveCount(4); + let versions = await page.locator('[data-test-version]').evaluateAll(el => el.map(it => it.dataset.testVersion)); + expect(versions).toEqual(['0.2.1', '0.3.0', '0.2.0', '0.1.0']); + + let v021 = page.locator('[data-test-version="0.2.1"]'); + let v020 = page.locator('[data-test-version="0.2.0"]'); + + await expect(v021).toHaveClass(/.*latest/); + await expect(v021).not.toHaveClass(/.yanked/); + await expect(v020).not.toHaveClass(/.*latest/); + await expect(v020).not.toHaveClass(/.yanked/); + + // yanking + await page.locator('[data-test-version-yank-button="0.2.1"]').click(); + await expect(v021).not.toHaveClass(/.*latest/); + await expect(v021).toHaveClass(/.yanked/); + await expect(v020).toHaveClass(/.*latest/); + await expect(v020).not.toHaveClass(/.yanked/); + + // unyanking + await page.locator('[data-test-version-unyank-button="0.2.1"]').click(); + await expect(v021).toHaveClass(/.*latest/); + await expect(v021).not.toHaveClass(/.yanked/); + await expect(v020).not.toHaveClass(/.*latest/); + await expect(v020).not.toHaveClass(/.yanked/); + }); }); diff --git a/tests/acceptance/versions-test.js b/tests/acceptance/versions-test.js index 3952295d936..957df382aad 100644 --- a/tests/acceptance/versions-test.js +++ b/tests/acceptance/versions-test.js @@ -29,4 +29,54 @@ module('Acceptance | crate versions page', function (hooks) { versions = findAll('[data-test-version]').map(it => it.dataset.testVersion); assert.deepEqual(versions, ['0.3.0', '0.2.1', '0.2.0', '0.1.0']); }); + + test('shows correct release tracks label after yanking/unyanking', async function (assert) { + let user = this.db.user.create(); + this.authenticateAs(user); + + let crate = this.db.crate.create({ name: 'nanomsg' }); + this.db.crateOwnership.create({ crate, user }); + + this.db.version.create({ crate, num: '0.1.0', created_at: '2017-01-01' }); + this.db.version.create({ crate, num: '0.2.0', created_at: '2018-01-01' }); + this.db.version.create({ crate, num: '0.3.0', created_at: '2019-01-01', rust_version: '1.69' }); + this.db.version.create({ crate, num: '0.2.1', created_at: '2020-01-01' }); + + await visit('/crates/nanomsg/versions'); + assert.strictEqual(currentURL(), '/crates/nanomsg/versions'); + + let versions = findAll('[data-test-version]').map(it => it.dataset.testVersion); + assert.deepEqual(versions, ['0.2.1', '0.3.0', '0.2.0', '0.1.0']); + + assert + .dom('[data-test-version="0.2.1"]') + .hasClass(/.*latest/) + .hasNoClass(/.yanked/); + assert + .dom('[data-test-version="0.2.0"]') + .hasNoClass(/.*latest/) + .hasNoClass(/.yanked/); + + // yanking + await click('[data-test-version-yank-button="0.2.1"]'); + assert + .dom('[data-test-version="0.2.1"]') + .hasNoClass(/.*latest/) + .hasClass(/.*yanked/); + assert + .dom('[data-test-version="0.2.0"]') + .hasClass(/.*latest/) + .hasNoClass(/.*yanked/); + + // unyanking + await click('[data-test-version-unyank-button="0.2.1"]'); + assert + .dom('[data-test-version="0.2.1"]') + .hasClass(/.*latest/) + .hasNoClass(/.yanked/); + assert + .dom('[data-test-version="0.2.0"]') + .hasNoClass(/.*latest/) + .hasNoClass(/.yanked/); + }); });