Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
43a417b8d7
|
|||
|
b2c43746e6
|
|||
|
10727cc55c
|
|||
|
5cc17d57c1
|
|||
|
8c2879063c
|
@@ -23,6 +23,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
@@ -38,6 +39,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
@@ -19,17 +19,8 @@ export default Component.extend({
|
||||
src: alias('avatarURL'),
|
||||
title: alias('contributor.name'),
|
||||
|
||||
// Re-compute whenever the contributor reference itself changes (covers a
|
||||
// previously-undefined contributor being lazily loaded later), as well as
|
||||
// the github_uid property when one is present.
|
||||
avatarURL: computed('contributor', 'contributor.github_uid', 'size', function() {
|
||||
const contributor = this.contributor;
|
||||
|
||||
if (!contributor) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const github_uid = contributor.github_uid;
|
||||
avatarURL: computed('contributor.github_uid', 'size', function() {
|
||||
const github_uid = this.contributor.github_uid;
|
||||
|
||||
if (github_uid) {
|
||||
return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=${SIZES[this.size]}`;
|
||||
|
||||
+2
-37
@@ -36,11 +36,6 @@ export default Service.extend({
|
||||
reimbursements: null,
|
||||
githubAccessToken: null,
|
||||
|
||||
// IDs of contributors currently being lazy-fetched (see ensureContributorLoaded),
|
||||
// used to avoid spawning duplicate concurrent requests when many contributions
|
||||
// for the same missing contributor are loaded in a tight loop.
|
||||
contributorsFetching: null,
|
||||
|
||||
currentUserIsContributor: notEmpty('currentUser'),
|
||||
currentUserIsCore: alias('currentUser.isCore'),
|
||||
hasAccounts: notEmpty('currentUserAccounts'),
|
||||
@@ -61,7 +56,6 @@ export default Service.extend({
|
||||
this.set('contributors', []);
|
||||
this.set('contributions', []);
|
||||
this.set('reimbursements', []);
|
||||
this.set('contributorsFetching', new Set());
|
||||
|
||||
if (window.ethereum) {
|
||||
window.ethereum.on('chainChanged', this.handleUserChainChanged);
|
||||
@@ -193,17 +187,10 @@ export default Service.extend({
|
||||
kreditsByContributor: computed('contributionsUnconfirmed.@each.vetoed', 'contributors.[]', function() {
|
||||
const contributionsUnconfirmed = this.contributionsUnconfirmed.filterBy('vetoed', false);
|
||||
const contributionsGrouped = groupBy(contributionsUnconfirmed, 'contributorId');
|
||||
|
||||
// Drop groups whose contributor isn't loaded yet (e.g. an IPFS profile
|
||||
// failed to deserialize). They'll be re-included once the contributor
|
||||
// gets lazily fetched via loadContributionFromData.
|
||||
const contributionsGroupedKnown = contributionsGrouped.filter(c => {
|
||||
return this.contributors.findBy('id', c.value);
|
||||
});
|
||||
const contributorsWithUnconfirmed = contributionsGroupedKnown.map(c => c.value);
|
||||
const contributorsWithUnconfirmed = contributionsGrouped.map(c => c.value);
|
||||
const contributorsWithOnlyConfirmed = this.contributors.reject(c => contributorsWithUnconfirmed.includes(c.id))
|
||||
|
||||
const kreditsByContributor = contributionsGroupedKnown.map(c => {
|
||||
const kreditsByContributor = contributionsGrouped.map(c => {
|
||||
const amountUnconfirmed = c.items.mapBy('amount').reduce((a, b) => a + b);
|
||||
const contributor = this.contributors.findBy('id', c.value);
|
||||
|
||||
@@ -315,26 +302,6 @@ export default Service.extend({
|
||||
.then(data => this.loadContributorFromData(data))
|
||||
},
|
||||
|
||||
// When a contribution/reimbursement references a contributor that isn't in
|
||||
// the local collection yet (e.g. its IPFS profile failed to deserialize on a
|
||||
// previous attempt, or it was added out-of-band), lazily fetch it in the
|
||||
// background. Fire-and-forget: never blocks the calling loop iteration, and
|
||||
// any rejection is logged so it doesn't break the run loop. The successful
|
||||
// arrival of the contributor will retrigger dependent computeds
|
||||
// (kreditsByContributor, UserAvatar) via the `contributors.[]` dependency.
|
||||
ensureContributorLoaded (id) {
|
||||
if (!id) return;
|
||||
if (this.contributors.findBy('id', id)) return;
|
||||
if (this.contributorsFetching.has(id)) return;
|
||||
|
||||
console.debug(`[kredits] Contributor ${id} not loaded yet; fetching in background`);
|
||||
this.contributorsFetching.add(id);
|
||||
this.fetchContributor(id)
|
||||
.then(() => console.debug(`[kredits] Lazy-loaded contributor ${id}`))
|
||||
.catch(err => console.warn(`[kredits] Failed to lazy-load contributor ${id}:`, err))
|
||||
.finally(() => this.contributorsFetching.delete(id));
|
||||
},
|
||||
|
||||
fetchContributors () {
|
||||
console.debug(`[kredits] Fetching all contributors from the network`);
|
||||
return this.kredits.Contributor.all()
|
||||
@@ -418,7 +385,6 @@ export default Service.extend({
|
||||
const loadedContribution = this.contributions.findBy('id', contribution.id);
|
||||
if (loadedContribution) { this.contributions.removeObject(loadedContribution); }
|
||||
this.contributions.pushObject(contribution);
|
||||
this.ensureContributorLoaded(data.contributorId);
|
||||
return contribution;
|
||||
},
|
||||
|
||||
@@ -654,7 +620,6 @@ export default Service.extend({
|
||||
obj.set('contributor', this.contributors.findBy('id', data.recipientId));
|
||||
this.removeObjectFromCollectionIfLoaded('reimbursements', obj.id);
|
||||
this.reimbursements.pushObject(obj);
|
||||
this.ensureContributorLoaded(data.recipientId);
|
||||
return obj;
|
||||
},
|
||||
|
||||
|
||||
@@ -37,20 +37,4 @@ module('Integration | Component | user-avatar', function(hooks) {
|
||||
`https://avatars2.githubusercontent.com/u/318?v=3&s=512`);
|
||||
});
|
||||
|
||||
test('renders empty src when contributor is undefined', async function(assert) {
|
||||
this.set('contributor', undefined);
|
||||
await render(hbs`{{user-avatar contributor=contributor}}`);
|
||||
|
||||
assert.dom(this.element).hasText('');
|
||||
assert.equal(this.element.querySelector('img').getAttribute('src'), '');
|
||||
});
|
||||
|
||||
test('renders empty src when contributor has no github_uid', async function(assert) {
|
||||
const noGithub = { name: 'Mystery', github_uid: null };
|
||||
this.set('contributor', noGithub);
|
||||
await render(hbs`{{user-avatar contributor=contributor}}`);
|
||||
|
||||
assert.equal(this.element.querySelector('img').getAttribute('src'), '');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,8 +2,6 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import contributors from '../../fixtures/contributors';
|
||||
import contributions from '../../fixtures/contributions';
|
||||
import ContributionModel from 'kredits-web/models/contribution';
|
||||
import processContributionData from 'kredits-web/utils/process-contribution-data';
|
||||
|
||||
module('Unit | Service | kredits', function(hooks) {
|
||||
setupTest(hooks);
|
||||
@@ -59,33 +57,4 @@ module('Unit | Service | kredits', function(hooks) {
|
||||
assert.ok(service.contributorsSorted instanceof Array, 'is an array');
|
||||
assert.equal(service.contributorsSorted[1].name, 'Manuel', 'sorts by name');
|
||||
});
|
||||
|
||||
test('#kreditsByContributor ignores unconfirmed contributions for contributors that are not loaded', function(assert) {
|
||||
let service = this.owner.lookup('service:kredits');
|
||||
service.set('contributors', contributors);
|
||||
|
||||
// Contribution referencing a contributor that is not in the contributors
|
||||
// collection (e.g. its IPFS profile failed to deserialize). The dashboard
|
||||
// must not throw when computing the toplist.
|
||||
const orphanAttrs = { id: 99, contributorId: 999, confirmedAt: 2000, claimed: false, vetoed: false, amount: 7000, kind: 'dev' };
|
||||
const orphan = ContributionModel.create(processContributionData(orphanAttrs));
|
||||
orphan.set('contributor', undefined);
|
||||
|
||||
service.set('contributions', [...contributions, orphan]);
|
||||
service.set('currentBlock', 1023);
|
||||
|
||||
let kreditsByContributor;
|
||||
try {
|
||||
kreditsByContributor = service.kreditsByContributor;
|
||||
} catch (e) {
|
||||
assert.ok(false, `did not expect kreditsByContributor to throw: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
assert.ok(kreditsByContributor, 'computes without throwing');
|
||||
assert.notOk(kreditsByContributor.findBy('contributor', undefined),
|
||||
'no entry for the missing contributor');
|
||||
assert.notOk(kreditsByContributor.find(k => k.contributor && k.contributor.id === 999),
|
||||
'orphan contributor id is absent from the toplist');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user