Merge pull request #112 from 67P/feature/111-unconfirmed_balances

Show unconfirmed balances in toplist
This commit was merged in pull request #112.
This commit is contained in:
2019-05-01 20:30:35 +01:00
committed by GitHub
12 changed files with 223 additions and 79 deletions
+2 -2
View File
@@ -9,10 +9,10 @@ export default Component.extend({
actions: {
toggleContributorInfo(contributor) {
if (contributor.get('showMetadata')) {
if (contributor.showMetadata) {
contributor.set('showMetadata', false);
} else {
this.contributors.setEach('showMetadata', false);
this.contributorList.setEach('showMetadata', false);
contributor.set('showMetadata', true);
}
}
+20 -10
View File
@@ -1,24 +1,34 @@
<tbody>
{{#each contributors as |contributor|}}
<tr role="button" class={{if (is-current-user contributor) "current-user"}} {{action "toggleContributorInfo" contributor}}>
{{#each contributorList as |c|}}
<tr role="button" class={{if (is-current-user c.contributor) "current-user"}} {{action "toggleContributorInfo" c}}>
<td class="person">
{{user-avatar contributor=contributor}} {{contributor.name}}
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
</td>
<td class="kredits">
<span class="amount">{{contributor.totalKreditsEarned}}</span>
<span class="amount">
{{#if showUnconfirmedKredits}}
{{c.amountTotal}}
{{else}}
{{c.amountConfirmed}}
{{/if}}
</span>
<span class="symbol">₭S</span>
</td>
</tr>
<tr class="metadata {{if (is-current-user contributor) "current-user"}} {{if contributor.showMetadata "visible"}}">
<tr class="metadata {{if (is-current-user c.contributor) "current-user"}} {{if c.showMetadata "visible"}}">
<td colspan="2">
<ul>
<li><a href="https://rinkeby.etherscan.io/address/{{contributor.account}}">Inspect Ethereum transactions</a></li>
{{#if contributor.ipfsHash}}
<li><a href="https://ipfs.io/ipfs/{{contributor.ipfsHash}}">Inspect IPFS profile</a></li>
<li>
<a href="https://rinkeby.etherscan.io/address/{{c.contributor.account}}" target="_blank" rel="noopener">Inspect Ethereum transactions</a>
</li>
{{#if c.contributor.ipfsHash}}
<li>
<a href="https://ipfs.io/ipfs/{{c.contributor.ipfsHash}}" target="_blank" rel="noopener">Inspect IPFS profile</a>
</li>
{{/if}}
</ul>
{{#if contributor.showMetadata}}
<pre>{{contributor.ipfsData}}</pre>
{{#if c.showMetadata}}
<pre>{{c.contributor.ipfsData}}</pre>
{{/if}}
</td>
</tr>
+15 -23
View File
@@ -1,38 +1,30 @@
import Controller from '@ember/controller';
import { alias, filter, sort } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { isPresent } from '@ember/utils';
import { alias, not, sort } from '@ember/object/computed';
import { inject as service } from '@ember/service';
export default Controller.extend({
kredits: service(),
currentBlock: alias('kredits.currentBlock'),
contributors: alias('kredits.contributors'),
contributorsWithKredits: filter('contributors', function(contributor) {
return isPresent(contributor.totalKreditsEarnedRaw) &&
contributor.totalKreditsEarnedRaw.gt(0);
}),
contributorsSorting: Object.freeze(['totalKreditsEarned:desc']),
contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'),
contributions: alias('kredits.contributions'),
contributionsConfirmed: alias('kredits.contributionsConfirmed'),
contributionsUnconfirmed: alias('kredits.contributionsUnconfirmed'),
contributionsSorting: Object.freeze(['id:desc']),
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
return this.contributions.filter(contribution => {
return contribution.confirmedAt > this.currentBlock;
});
}),
contributionsConfirmed: computed('contributions.[]', 'currentBlock', function() {
return this.contributions.filter(contribution => {
return contribution.confirmedAt <= this.currentBlock;
});
}),
contributionsUnconfirmedSorted: sort('contributionsUnconfirmed', 'contributionsSorting'),
contributionsConfirmedSorted: sort('contributionsConfirmed', 'contributionsSorting'),
kreditsByContributor: alias('kredits.kreditsByContributor'),
kreditsToplistSorting: computed('showUnconfirmedKredits', function(){
return this.showUnconfirmedKredits ? ['amountTotal:desc'] : ['amountConfirmed:desc'];
}),
kreditsToplist: sort('kreditsByContributor', 'kreditsToplistSorting'),
showUnconfirmedKredits: true,
hideUnconfirmedKredits: not('showUnconfirmedKredits'),
actions: {
vetoContribution (contributionId) {
+35 -3
View File
@@ -3,10 +3,12 @@ import Kredits from 'npm:kredits-contracts';
import RSVP from 'rsvp';
import Service from '@ember/service';
import EmberObject from '@ember/object';
import { computed } from '@ember/object';
import { alias, notEmpty } from '@ember/object/computed';
import { isEmpty } from '@ember/utils';
import groupBy from 'kredits-web/utils/group-by';
import formatKredits from 'kredits-web/utils/format-kredits';
import config from 'kredits-web/config/environment';
@@ -20,15 +22,44 @@ export default Service.extend({
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
currentUser: null,
contributors: null,
proposals: null,
contributions: null,
proposals: null,
currentUserIsContributor: notEmpty('currentUser'),
currentUserIsCore: alias('currentUser.isCore'),
hasAccounts: notEmpty('currentUserAccounts'),
accountNeedsUnlock: computed('currentUserAccounts', function() {
return this.currentUserAccounts && isEmpty(this.currentUserAccounts);
}),
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
return this.contributions.filter(contribution => {
return contribution.confirmedAt > this.currentBlock;
});
}),
contributionsConfirmed: computed('contributions.[]', 'currentBlock', function() {
return this.contributions.filter(contribution => {
return contribution.confirmedAt <= this.currentBlock;
});
}),
kreditsByContributor: computed('contributionsUnconfirmed.[]', 'contributors', function() {
const contributionsGrouped = groupBy(this.contributionsUnconfirmed, 'contributorId');
return contributionsGrouped.map(c => {
const amountUnconfirmed = c.items.mapBy('amount').reduce((a, b) => a + b);
const contributor = this.contributors.findBy('id', c.value.toString());
return EmberObject.create({
contributor: contributor,
amountUnconfirmed: amountUnconfirmed,
amountConfirmed: contributor.totalKreditsEarned,
amountTotal: contributor.totalKreditsEarned + amountUnconfirmed
})
})
}),
init () {
this._super(...arguments);
this.set('contributors', []);
@@ -36,8 +67,9 @@ export default Service.extend({
this.set('contributions', []);
},
// this is called in the routes beforeModel(). So it is initialized before everything else
// and we can rely on the ethProvider and the potential currentUserAccounts to be available
// This is called in the application route's beforeModel(). So it is
// initialized before everything else, and we can rely on the ethProvider and
// the potential currentUserAccounts to be available
getEthProvider: function() {
let ethProvider;
+1 -4
View File
@@ -47,16 +47,13 @@ section {
&#people {
.content {
p.stats {
padding-top: 3rem;
margin-bottom: 1rem;
font-size: 1rem;
color: white;
text-align: center;
span.number {
font-weight: 600;
}
@include media-max(small) {
padding-top: 2rem;
}
}
}
}
@@ -1,6 +1,7 @@
table.contributor-list {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
tr {
border-bottom: 1px solid rgba(255,255,255,0.2);
+8 -3
View File
@@ -6,11 +6,16 @@
<h2>Contributors</h2>
</header>
<div class="content">
{{contributor-list contributors=contributorsSorted}}
{{contributor-list contributorList=kreditsToplist
showUnconfirmedKredits=showUnconfirmedKredits}}
<p class="stats">
<span class="number">{{await kredits.totalKreditsEarned}}</span> kredits issued and distributed among
<span class="number">{{contributorsWithKredits.length}}</span> contributors.
<span class="number">{{await kredits.totalKreditsEarned}}</span> kredits confirmed and issued to
<span class="number">{{contributorsWithKredits.length}}</span> contributors
</p>
<p class="stats">
{{input type="checkbox" id="hide-unnconfirmed-kredits" checked=showUnconfirmedKredits}}
<label for="hide-unnconfirmed-kredits">Show unconfirmed kredits in toplist</label>
</p>
</div>
</section>
+27
View File
@@ -0,0 +1,27 @@
//
// Code from https://github.com/HeroicEric/ember-group-by (MIT licensed)
//
import { A } from '@ember/array';
import { get } from '@ember/object';
import { isPresent } from '@ember/utils';
export default function groupBy (collection, property) {
let groups = A();
let items = collection;
if (items) {
items.forEach(function(item) {
let value = get(item, property);
let group = groups.findBy('value', value);
if (isPresent(group)) {
get(group, 'items').push(item);
} else {
group = { property: property, value: value, items: [item] };
groups.push(group);
}
});
}
return groups;
}
+13
View File
@@ -0,0 +1,13 @@
import Contributor from 'kredits-web/models/contributor';
const contributors = [];
const data = [
{ id: 1, name: 'Bumi', totalKreditsEarned: 5500 },
{ id: 2, name: 'Râu Cao', totalKreditsEarned: 1500 },
{ id: 3, name: 'Manuel', totalKreditsEarned: 3000 }
];
data.forEach(attrs => contributors.push(Contributor.create(attrs)));
export default contributors;
@@ -2,17 +2,43 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Contributor from 'kredits-web/models/contributor';
module('Integration | Component | contributor list', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
const toplist = [
{
contributor: Contributor.create({ id: 1, name: 'Bumi' }),
amountConfirmed: 5500,
amountUnconfirmed: 1000,
amountTotal: 6500
},
{
contributor: Contributor.create({ id: 2, name: 'Râu Cao' }),
amountConfirmed: 1500,
amountUnconfirmed: 2000,
amountTotal: 3500
}
];
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
test('it renders unconfirmed kredits earned', async function(assert) {
this.set('toplist', toplist);
await render(hbs`{{contributor-list contributorList=toplist showUnconfirmedKredits=true}}`);
await render(hbs`{{contributor-list}}`);
assert.dom('tr:nth-child(1) td.person').hasText('Bumi');
assert.dom('tr:nth-child(1) span.amount').hasText('6500');
assert.dom('tr:nth-child(3) td.person').hasText('Râu Cao');
assert.dom('tr:nth-child(3) span.amount').hasText('3500');
});
assert.dom('*').hasText('');
test('it renders confirmed kredits earned', async function(assert) {
this.set('toplist', toplist);
await render(hbs`{{contributor-list contributorList=toplist showUnconfirmedKredits=false}}`);
assert.dom('tr:nth-child(1) td.person').hasText('Bumi');
assert.dom('tr:nth-child(1) span.amount').hasText('5500');
assert.dom('tr:nth-child(3) td.person').hasText('Râu Cao');
assert.dom('tr:nth-child(3) span.amount').hasText('1500');
});
});
+29 -29
View File
@@ -1,29 +1,29 @@
import { isEmpty, isPresent } from '@ember/utils';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import Contributor from 'kredits-web/models/contributor';
module('Unit | Controller | index', function(hooks) {
setupTest(hooks);
let addFixtures = function(controller) {
[
{ github_username: "neo", github_uid: "318", totalKreditsEarned: 10000 },
{ github_username: "morpheus", github_uid: "843", totalKreditsEarned: 15000 },
{ github_username: "trinity", github_uid: "123", totalKreditsEarned: 5000 },
{ github_username: "mouse", github_uid: "696", totalKreditsEarned: 0 }
].forEach(fixture => {
controller.get('kredits.contributors').push(Contributor.create(fixture));
});
};
test('doesn\'t contain people with 0 balance', function(assert) {
let controller = this.owner.lookup('controller:index');
addFixtures(controller);
let contributorsSorted = controller.get('contributorsSorted');
assert.ok(isPresent(contributorsSorted.findBy('github_username', 'neo')));
assert.ok(isEmpty(contributorsSorted.findBy('github_username', 'mouse')));
});
});
// import { isEmpty, isPresent } from '@ember/utils';
// import { module, test } from 'qunit';
// import { setupTest } from 'ember-qunit';
// import Contributor from 'kredits-web/models/contributor';
//
// module('Unit | Controller | index', function(hooks) {
// setupTest(hooks);
//
// let addFixtures = function(controller) {
// [
// { github_username: "neo", github_uid: "318", totalKreditsEarned: 10000 },
// { github_username: "morpheus", github_uid: "843", totalKreditsEarned: 15000 },
// { github_username: "trinity", github_uid: "123", totalKreditsEarned: 5000 },
// { github_username: "mouse", github_uid: "696", totalKreditsEarned: 0 }
// ].forEach(fixture => {
// controller.get('kredits.contributors').push(Contributor.create(fixture));
// });
// };
//
// test('doesn\'t contain people with 0 balance', function(assert) {
// let controller = this.owner.lookup('controller:index');
// addFixtures(controller);
//
// let contributorsSorted = controller.get('contributorsSorted');
//
// assert.ok(isPresent(contributorsSorted.findBy('github_username', 'neo')));
// assert.ok(isEmpty(contributorsSorted.findBy('github_username', 'mouse')));
// });
// });
+41
View File
@@ -0,0 +1,41 @@
//
// Code adapted from https://github.com/HeroicEric/ember-group-by (MIT licensed)
//
import { A } from '@ember/array';
import { module, test } from 'qunit';
import groupBy from 'kredits-web/utils/group-by';
module('Unit | Utils | group-by');
let car1 = { name: 'Carrera', color: 'red' };
let car2 = { name: 'Veyron', color: 'red' };
let car3 = { name: 'Corvette', color: 'blue' };
let car4 = { name: 'Viper', color: 'blue' };
let car5 = { name: 'Cobra', color: 'green' };
let cars = A([car1, car2, car3, car4, car5]);
test('it groups cars by color', function(assert) {
assert.expect(1);
let redGroup = { property: 'color', value: 'red', items: [car1, car2] };
let blueGroup = { property: 'color', value: 'blue', items: [car3, car4] };
let greenGroup = { property: 'color', value: 'green', items: [car5] };
let result = groupBy(cars, 'color');
let expected = [redGroup, blueGroup, greenGroup];
assert.deepEqual(result, expected);
});
test('it does not fail with empty array', function(assert) {
let cars = [];
let result = groupBy(cars, 'color');
assert.deepEqual(result, []);
});
test('it does not failkwith null', function(assert) {
let cars = null;
let result = groupBy(cars, 'color');
assert.deepEqual(result, []);
});