diff --git a/app/components/crate-header.js b/app/components/crate-header.js
index 4b732844826..a2649804ec0 100644
--- a/app/components/crate-header.js
+++ b/app/components/crate-header.js
@@ -8,7 +8,6 @@ export default class CrateHeader extends Component {
@service session;
@alias('loadKeywordsTask.last.value') keywords;
- @alias('loadOwnerUserTask.last.value') ownerUser;
constructor() {
super(...arguments);
@@ -16,22 +15,14 @@ export default class CrateHeader extends Component {
this.loadKeywordsTask.perform().catch(() => {
// ignore all errors and just don't display keywords if the request fails
});
- this.loadOwnerUserTask.perform().catch(() => {
- // ignore all errors and just don't display settings if the request fails
- });
}
get isOwner() {
- let ownerUser = this.ownerUser ?? [];
- let currentUserId = this.session.currentUser?.id;
- return ownerUser.some(({ id }) => id === currentUserId);
+ let userId = this.session.currentUser?.id;
+ return this.args.crate?.hasOwnerUser(userId) ?? false;
}
loadKeywordsTask = task(async () => {
return (await this.args.crate?.keywords) ?? [];
});
-
- loadOwnerUserTask = task(async () => {
- return (await this.args.crate?.owner_user) ?? [];
- });
}
diff --git a/app/components/crate-sidebar.hbs b/app/components/crate-sidebar.hbs
index c588da07336..aebe8de6371 100644
--- a/app/components/crate-sidebar.hbs
+++ b/app/components/crate-sidebar.hbs
@@ -94,7 +94,7 @@
{{#unless @crate.categories.isPending}}
- {{#if @crate.categories}}
+ {{#if @crate.categories.length}}
Categories
diff --git a/app/components/version-list/row.js b/app/components/version-list/row.js
index e94e17aafa1..6eccca9108b 100644
--- a/app/components/version-list/row.js
+++ b/app/components/version-list/row.js
@@ -48,7 +48,8 @@ export default class VersionRow extends Component {
}
get isOwner() {
- return this.args.version.crate?.owner_user?.findBy('id', this.session.currentUser?.id);
+ let userId = this.session.currentUser?.id;
+ return this.args.version.crate.hasOwnerUser(userId);
}
@action setFocused(value) {
diff --git a/app/controllers/crate/settings.js b/app/controllers/crate/settings.js
index 94625c7ab3d..72d38bdce9a 100644
--- a/app/controllers/crate/settings.js
+++ b/app/controllers/crate/settings.js
@@ -36,10 +36,12 @@ export default class CrateSettingsController extends Controller {
if (owner.kind === 'team') {
this.notifications.success(`Team ${owner.get('display_name')} removed as crate owner`);
- this.crate.owner_team.removeObject(owner);
+ let owner_team = await this.crate.owner_team;
+ removeOwner(owner_team, owner);
} else {
this.notifications.success(`User ${owner.get('login')} removed as crate owner`);
- this.crate.owner_user.removeObject(owner);
+ let owner_user = await this.crate.owner_user;
+ removeOwner(owner_user, owner);
}
} catch (error) {
let subject = owner.kind === 'team' ? `team ${owner.get('display_name')}` : `user ${owner.get('login')}`;
@@ -54,3 +56,10 @@ export default class CrateSettingsController extends Controller {
}
});
}
+
+function removeOwner(owners, target) {
+ let idx = owners.indexOf(target);
+ if (idx !== -1) {
+ owners.splice(idx, 1);
+ }
+}
diff --git a/app/controllers/crate/version.js b/app/controllers/crate/version.js
index ca857bb165d..d9aa81e6511 100644
--- a/app/controllers/crate/version.js
+++ b/app/controllers/crate/version.js
@@ -24,13 +24,14 @@ export default class CrateVersionController extends Controller {
this.stackedGraph = false;
}
- @alias('downloadsContext.version_downloads.content') downloads;
+ @alias('loadDownloadsTask.last.value') downloads;
@alias('model.crate') crate;
@alias('model.requestedVersion') requestedVersion;
@alias('model.version') currentVersion;
get isOwner() {
- return this.crate.owner_user.findBy('id', this.session.currentUser?.id);
+ let userId = this.session.currentUser?.id;
+ return this.crate.hasOwnerUser(userId);
}
@alias('loadReadmeTask.last.value') readme;
@@ -62,4 +63,10 @@ export default class CrateVersionController extends Controller {
return readme;
});
+
+ // This task would be `perform()` in setupController
+ loadDownloadsTask = task(async () => {
+ let downloads = await this.downloadsContext.version_downloads;
+ return downloads;
+ });
}
diff --git a/app/models/crate.js b/app/models/crate.js
index cb01764c7b8..201be75d20c 100644
--- a/app/models/crate.js
+++ b/app/models/crate.js
@@ -1,8 +1,10 @@
import Model, { attr, hasMany } from '@ember-data/model';
+import { assert } from '@ember/debug';
import { waitForPromise } from '@ember/test-waiters';
import { cached } from '@glimmer/tracking';
import { apiAction } from '@mainmatter/ember-api-actions';
+import { task } from 'ember-concurrency';
export default class Crate extends Model {
@attr name;
@@ -35,13 +37,23 @@ export default class Crate extends Model {
@hasMany('dependency', { async: true, inverse: null }) reverse_dependencies;
@cached get versionIdsBySemver() {
- let versions = this.versions.toArray() ?? [];
- return versions.sort(compareVersionBySemver).map(v => v.id);
+ let { last } = this.loadVersionsTask;
+ assert('`loadVersionsTask.perform()` must be called before calling `versionIdsBySemver`', last != null);
+ let versions = last?.value ?? [];
+ return versions
+ .slice()
+ .sort(compareVersionBySemver)
+ .map(v => v.id);
}
@cached get versionIdsByDate() {
- let versions = this.versions.toArray() ?? [];
- return versions.sort(compareVersionByDate).map(v => v.id);
+ let { last } = this.loadVersionsTask;
+ assert('`loadVersionsTask.perform()` must be called before calling `versionIdsByDate`', last != null);
+ let versions = last?.value ?? [];
+ return versions
+ .slice()
+ .sort(compareVersionByDate)
+ .map(v => v.id);
}
@cached get firstVersionId() {
@@ -49,8 +61,10 @@ export default class Crate extends Model {
}
@cached get versionsObj() {
- let versions = this.versions.toArray() ?? [];
- return Object.fromEntries(versions.map(v => [v.id, v]));
+ let { last } = this.loadVersionsTask;
+ assert('`loadVersionsTask.perform()` must be called before calling `versionsObj`', last != null);
+ let versions = last?.value ?? [];
+ return Object.fromEntries(versions.slice().map(v => [v.id, v]));
}
@cached get releaseTrackSet() {
@@ -65,10 +79,16 @@ export default class Crate extends Model {
return new Set(map.values());
}
+ hasOwnerUser(userId) {
+ let { last } = this.loadOwnerUserTask;
+ assert('`loadOwnerUserTask.perform()` must be called before calling `hasOwnerUser()`', last != null);
+ return (last?.value ?? []).some(({ id }) => id === userId);
+ }
+
get owners() {
- let teams = this.owner_team.toArray() ?? [];
- let users = this.owner_user.toArray() ?? [];
- return [...teams, ...users];
+ let { last } = this.loadOwnersTask;
+ assert('`loadOwnersTask.perform()` must be called before accessing `owners`', last != null);
+ return last?.value ?? [];
}
async follow() {
@@ -100,6 +120,19 @@ export default class Crate extends Model {
throw response;
}
}
+
+ loadOwnerUserTask = task(async () => {
+ return (await this.owner_user) ?? [];
+ });
+
+ loadOwnersTask = task(async () => {
+ let [teams, users] = await Promise.all([this.owner_team, this.owner_user]);
+ return [...(teams ?? []), ...(users ?? [])];
+ });
+
+ loadVersionsTask = task(async () => {
+ return (await this.versions) ?? [];
+ });
}
function compareVersionBySemver(a, b) {
diff --git a/app/routes/crate.js b/app/routes/crate.js
index 393daab8814..f8646db3baa 100644
--- a/app/routes/crate.js
+++ b/app/routes/crate.js
@@ -1,6 +1,7 @@
import { NotFoundError } from '@ember-data/adapter/error';
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
+import { waitForPromise } from '@ember/test-waiters';
export default class CrateRoute extends Route {
@service headData;
@@ -26,6 +27,9 @@ export default class CrateRoute extends Route {
setupController(controller, model) {
super.setupController(...arguments);
this.headData.crate = model;
+ waitForPromise(model.loadOwnerUserTask.perform()).catch(() => {
+ // ignore all errors if the request fails
+ });
}
resetController() {
diff --git a/app/routes/crate/version.js b/app/routes/crate/version.js
index 41bf71de95c..bc83718357c 100644
--- a/app/routes/crate/version.js
+++ b/app/routes/crate/version.js
@@ -51,8 +51,16 @@ export default class VersionRoute extends Route {
waitForPromise(controller.loadReadmeTask.perform()).catch(() => {
// ignored
});
+ waitForPromise(controller.loadDownloadsTask.perform()).catch(() => {
+ // ignored
+ });
let { crate, version } = model;
+
+ waitForPromise(crate.loadOwnersTask.perform()).catch(() => {
+ // ignored
+ });
+
if (!crate.documentation || crate.documentation.startsWith('https://docs.rs/')) {
version.loadDocsStatusTask.perform().catch(error => {
// report unexpected errors to Sentry and ignore `ajax()` errors
diff --git a/app/routes/crate/versions.js b/app/routes/crate/versions.js
new file mode 100644
index 00000000000..1144952055b
--- /dev/null
+++ b/app/routes/crate/versions.js
@@ -0,0 +1,12 @@
+import Route from '@ember/routing/route';
+import { waitForPromise } from '@ember/test-waiters';
+
+export default class VersionsRoute extends Route {
+ setupController(controller) {
+ super.setupController(...arguments);
+ let crate = this.modelFor('crate');
+ controller.set('crate', crate);
+ // TODO: Add error handling
+ waitForPromise(crate.loadVersionsTask.perform());
+ }
+}
diff --git a/tests/components/crate-sidebar/playground-button-test.js b/tests/components/crate-sidebar/playground-button-test.js
index 8bb21b81931..48f7207d30d 100644
--- a/tests/components/crate-sidebar/playground-button-test.js
+++ b/tests/components/crate-sidebar/playground-button-test.js
@@ -33,7 +33,8 @@ module('Component | CrateSidebar | Playground Button', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-playground-button]').doesNotExist();
@@ -45,7 +46,8 @@ module('Component | CrateSidebar | Playground Button', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
let expectedHref =
'https://play.rust-lang.org/?edition=2021&code=use%20aho_corasick%3B%0A%0Afn%20main()%20%7B%0A%20%20%20%20%2F%2F%20try%20using%20the%20%60aho_corasick%60%20crate%20here%0A%7D';
@@ -63,7 +65,8 @@ module('Component | CrateSidebar | Playground Button', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
render(hbs``);
await waitFor('[data-test-owners]');
@@ -81,7 +84,8 @@ module('Component | CrateSidebar | Playground Button', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-playground-button]').doesNotExist();
diff --git a/tests/components/crate-sidebar/toml-snippet-test.js b/tests/components/crate-sidebar/toml-snippet-test.js
index 4fe5cbcb966..33cb47ae654 100644
--- a/tests/components/crate-sidebar/toml-snippet-test.js
+++ b/tests/components/crate-sidebar/toml-snippet-test.js
@@ -17,7 +17,8 @@ module('Component | CrateSidebar | toml snippet', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[title="Copy command to clipboard"]').exists().hasText('cargo add foo');
@@ -34,7 +35,8 @@ module('Component | CrateSidebar | toml snippet', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[title="Copy Cargo.toml snippet to clipboard"]').exists().hasText('foo = "1.0.0"');
@@ -46,7 +48,8 @@ module('Component | CrateSidebar | toml snippet', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- this.version = (await this.crate.versions).firstObject;
+ this.version = (await this.crate.versions).slice()[0];
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[title="Copy Cargo.toml snippet to clipboard"]').exists().hasText('foo = "1.0.0-alpha"');
diff --git a/tests/components/owners-list-test.js b/tests/components/owners-list-test.js
index 9581371108a..18a0c9afe65 100644
--- a/tests/components/owners-list-test.js
+++ b/tests/components/owners-list-test.js
@@ -20,8 +20,7 @@ module('Component | OwnersList', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- await this.crate.hasMany('owner_team').load();
- await this.crate.hasMany('owner_user').load();
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-owners="detailed"]').exists();
@@ -44,8 +43,7 @@ module('Component | OwnersList', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- await this.crate.hasMany('owner_team').load();
- await this.crate.hasMany('owner_user').load();
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-owners="detailed"]').exists();
@@ -70,8 +68,7 @@ module('Component | OwnersList', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- await this.crate.hasMany('owner_team').load();
- await this.crate.hasMany('owner_user').load();
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-owners="detailed"]').exists();
@@ -93,8 +90,7 @@ module('Component | OwnersList', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- await this.crate.hasMany('owner_team').load();
- await this.crate.hasMany('owner_user').load();
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-owners="basic"]').exists();
@@ -120,8 +116,7 @@ module('Component | OwnersList', function (hooks) {
let store = this.owner.lookup('service:store');
this.crate = await store.findRecord('crate', crate.name);
- await this.crate.hasMany('owner_team').load();
- await this.crate.hasMany('owner_user').load();
+ await this.crate.loadOwnersTask.perform();
await render(hbs``);
assert.dom('[data-test-owners="detailed"]').exists();
diff --git a/tests/components/version-list-row-test.js b/tests/components/version-list-row-test.js
index 3bcceac5b3f..e651767cc1a 100644
--- a/tests/components/version-list-row-test.js
+++ b/tests/components/version-list-row-test.js
@@ -18,7 +18,8 @@ module('Component | VersionList::Row', function (hooks) {
let store = this.owner.lookup('service:store');
let crateRecord = await store.findRecord('crate', crate.name);
- let versions = (await crateRecord.versions).slice();
+ let versions = (await crateRecord.loadVersionsTask.perform()).slice();
+ await crateRecord.loadOwnerUserTask.perform();
this.firstVersion = versions[0];
this.secondVersion = versions[1];
@@ -38,7 +39,8 @@ module('Component | VersionList::Row', function (hooks) {
let store = this.owner.lookup('service:store');
let crateRecord = await store.findRecord('crate', crate.name);
- this.version = (await crateRecord.versions).slice()[0];
+ this.version = (await crateRecord.loadVersionsTask.perform()).slice()[0];
+ await crateRecord.loadOwnerUserTask.perform();
await render(hbs``);
assert.dom('[data-test-release-track]').hasText('?');
@@ -71,7 +73,8 @@ module('Component | VersionList::Row', function (hooks) {
let store = this.owner.lookup('service:store');
let crateRecord = await store.findRecord('crate', crate.name);
- let versions = (await crateRecord.versions).slice();
+ let versions = (await crateRecord.loadVersionsTask.perform()).slice();
+ await crateRecord.loadOwnerUserTask.perform();
this.firstVersion = versions[0];
this.secondVersion = versions[1];
this.thirdVersion = versions[2];
diff --git a/tests/models/version-test.js b/tests/models/version-test.js
index 3cac01fcc68..92203fc2e2c 100644
--- a/tests/models/version-test.js
+++ b/tests/models/version-test.js
@@ -167,7 +167,7 @@ module('Model | Version', function (hooks) {
}
let crateRecord = await this.store.findRecord('crate', crate.name);
- let versions = (await crateRecord.versions).slice();
+ let versions = (await crateRecord.loadVersionsTask.perform()).slice();
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),
@@ -198,7 +198,7 @@ module('Model | Version', function (hooks) {
this.server.create('version', { crate, num: '0.4.2', yanked: true });
let crateRecord = await this.store.findRecord('crate', crate.name);
- let versions = (await crateRecord.versions).slice();
+ let versions = (await crateRecord.loadVersionsTask.perform()).slice();
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),
@@ -216,7 +216,7 @@ module('Model | Version', function (hooks) {
this.server.create('version', { crate, num: '0.4.1' });
let crateRecord = await this.store.findRecord('crate', crate.name);
- let versions = (await crateRecord.versions).slice();
+ let versions = (await crateRecord.loadVersionsTask.perform()).slice();
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),
@@ -229,7 +229,7 @@ module('Model | Version', function (hooks) {
this.server.create('version', { crate, num: '0.4.2' });
this.server.create('version', { crate, num: '0.4.3', yanked: true });
crateRecord = await this.store.findRecord('crate', crate.name, { reload: true });
- versions = (await crateRecord.versions).slice();
+ versions = (await crateRecord.loadVersionsTask.perform()).slice();
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),