Show unconfirmed balances in toplist #112

Merged
raucao merged 10 commits from feature/111-unconfirmed_balances into master 2019-05-01 19:30:36 +00:00
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) {
bumi commented 2019-05-01 18:43:59 +00:00 (Migrated from github.com)
Review

maybe
if(!items) {
return groups;
}

maybe if(!items) { return groups; }
raucao commented 2019-05-01 18:53:01 +00:00 (Migrated from github.com)
Review

Did you see that you can now suggest changes to commit in code comments? It's the first icon button in the comment topbar. Makes it much easier to see what the suggested change is as well, because the code behind the comment isn't cut off then...

Never mind, it's a bigger change.

<del>Did you see that you can now suggest changes to commit in code comments? It's the first icon button in the comment topbar. Makes it much easier to see what the suggested change is as well, because the code behind the comment isn't cut off then...</del> Never mind, it's a bigger change.
raucao commented 2019-05-01 18:55:03 +00:00 (Migrated from github.com)
Review

I think a single return is a bit cleaner here.

I think a single return is a bit cleaner here.
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')));
// });
// });
2
+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, []);
});