Introduce budget, reimbursements for expenses #195

Merged
raucao merged 57 commits from feature/expenses into master 2021-06-03 14:23:45 +00:00
11 changed files with 375 additions and 24 deletions
Showing only changes of commit dec71c6f4f - Show all commits
@@ -0,0 +1,6 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
export default class BudgetBalancesComponent extends Component {
@service communityFunds
}
@@ -0,0 +1,21 @@
<table>
<thead>
<tr>
<td>Token</td>
<td>Amount</td>
<td>Fiat value</td>
</tr>
</thead>
<tbody>
<tr>
<td>WBTC</td>
<td>{{this.communityFunds.balanceWBTC.balance}}</td>
<td>{{this.communityFunds.balanceWBTC.balanceUsd}} USD</td>
</tr>
<tr>
<td>ETH</td>
<td>{{this.communityFunds.balanceETH.balance}}</td>
<td>{{this.communityFunds.balanceETH.balanceUsd}} USD</td>
</tr>
</tbody>
</table>
+4 -2
View File
@@ -3,8 +3,8 @@ import { inject as service } from '@ember/service';
import { schedule } from '@ember/runloop';
export default class ApplicationRoute extends Route {
@service kredits;
@service communityFunds;
beforeModel(/* transition */) {
const kredits = this.kredits;
@@ -30,6 +30,8 @@ export default class ApplicationRoute extends Route {
schedule('afterRender', this.kredits.syncContributors,
this.kredits.syncContributors.perform);
}
}
schedule('afterRender', this.communityFunds.fetchBalances,
this.communityFunds.fetchBalances.perform);
}
}
+32
View File
@@ -0,0 +1,32 @@
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency-decorators';
import config from 'kredits-web/config/environment';
const txServiceBaseUrl = `${config.gnosisSafe.txServiceHost}/api/v1/safes/${config.gnosisSafe.address}`;
export default class CommunityFundsService extends Service {
@tracked balanceETH = { balance: 0, balanceUsd: 0, usdConversion: 0 };
@tracked balanceWBTC = { balance: 0, balanceUsd: 0, usdConversion: 0 };
@task
*fetchBalances () {
const uri = `${txServiceBaseUrl}/balances/usd/`;
yield fetch(uri).then(res => res.json())
.then(res => {
this.processBalances(res);
})
galfert commented 2020-11-13 16:02:52 +00:00 (Migrated from github.com)
Review

fetch doesn't reject the promise on normal HTTP errors (i.e. 400 or 500 errors), but only on network failures. So this should probably also check if res.ok is true.

`fetch` doesn't reject the promise on normal HTTP errors (i.e. 400 or 500 errors), but only on network failures. So this should probably also check if `res.ok` is `true`.
raucao commented 2020-11-17 12:37:45 +00:00 (Migrated from github.com)
Review

This gracefully fails by catching the exception in processBalances I think.

This gracefully fails by catching the exception in `processBalances` I think.
.catch(err => {
console.log(`[community-funds] Fetching balances failed:`);
console.error(err);
});
}
processBalances (res) {
// TODO proper selection/find
const { balance, balanceUsd, usdConversion } = res[0];
this.balanceETH = { balance, balanceUsd, usdConversion };
this.balanceWBTC = res[1];
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ import Contributor from 'kredits-web/models/contributor';
import Contribution from 'kredits-web/models/contribution';
import Reimbursement from 'kredits-web/models/reimbursement';
// Lets us access the model classes dynamically
const models = { Contributor, Contribution, Reimbursement }
const models = { Contributor, Contribution, Reimbursement };
export default Service.extend({
2
+1 -21
View File
@@ -22,27 +22,7 @@
<h2>Community funds</h2>
</header>
<div class="content">
<table>
<thead>
<tr>
<td>Token</td>
<td>Amount</td>
<td>Fiat value</td>
</tr>
</thead>
<tbody>
<tr>
<td>WBTC</td>
<td>1.17215</td>
<td>11031.15 USD</td>
</tr>
<tr>
<td>ETH</td>
<td>0.5</td>
<td>115.60 USD</td>
</tr>
</tbody>
</table>
<BudgetBalances />
</div>
</section>
</div>
+5
View File
@@ -50,6 +50,11 @@ module.exports = function(environment) {
tokens: {
'WBTC': '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'
},
gnosisSafe: {
txServiceHost: 'https://safe-transaction.mainnet.gnosis.io',
address: '0x9CC29b8373FF92B01C1f09F31B5DD862350c167E'
}
};
+266
View File
@@ -2582,6 +2582,15 @@
"integrity": "sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ==",
"dev": true
},
"@ember-decorators/utils": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@ember-decorators/utils/-/utils-6.1.1.tgz",
"integrity": "sha512-0KqnoeoLKb6AyoSU65TRF5T85wmS4uDn06oARddwNPxxf/lt5jQlh41uX3W7V/fWL9tPu8x1L1Vvpc80MN1+YA==",
"dev": true,
"requires": {
"ember-cli-babel": "^7.1.3"
}
},
"@ember/edition-utils": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ember/edition-utils/-/edition-utils-1.2.0.tgz",
@@ -11284,6 +11293,263 @@
"ember-maybe-import-regenerator": "^0.1.6"
}
},
"ember-concurrency-decorators": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ember-concurrency-decorators/-/ember-concurrency-decorators-2.0.1.tgz",
"integrity": "sha512-KJ8mCZxmvgalawh/zytURsc5ParNr9avPeCDR/mE7VQxWvITHtyAuYS2s1PPFQAQggTXTZYcGnfgzjn/cDHNMQ==",
"dev": true,
"requires": {
"@ember-decorators/utils": "^6.1.0",
"ember-cli-babel": "^7.19.0",
"ember-cli-htmlbars": "^4.3.1",
"ember-cli-typescript": "^3.1.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
},
"@babel/plugin-transform-typescript": {
"version": "7.8.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.7.tgz",
"integrity": "sha512-7O0UsPQVNKqpHeHLpfvOG4uXmlw+MOxYvUv6Otc9uH5SYMIxvF6eBdjkWvC3f9G+VXe0RsNExyAQBeTRug/wqQ==",
"dev": true,
"requires": {
"@babel/helper-create-class-features-plugin": "^7.8.3",
"@babel/helper-plugin-utils": "^7.8.3",
"@babel/plugin-syntax-typescript": "^7.8.3"
}
},
"babel-plugin-htmlbars-inline-precompile": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-3.2.0.tgz",
"integrity": "sha512-IUeZmgs9tMUGXYu1vfke5I18yYJFldFGdNFQOWslXTnDWXzpwPih7QFduUqvT+awDpDuNtXpdt5JAf43Q1Hhzg==",
"dev": true
},
"broccoli-persistent-filter": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/broccoli-persistent-filter/-/broccoli-persistent-filter-2.3.1.tgz",
"integrity": "sha512-hVsmIgCDrl2NFM+3Gs4Cr2TA6UPaIZip99hN8mtkaUPgM8UeVnCbxelCvBjUBHo0oaaqP5jzqqnRVvb568Yu5g==",
"dev": true,
"requires": {
"async-disk-cache": "^1.2.1",
"async-promise-queue": "^1.0.3",
"broccoli-plugin": "^1.0.0",
"fs-tree-diff": "^2.0.0",
"hash-for-dep": "^1.5.0",
"heimdalljs": "^0.2.1",
"heimdalljs-logger": "^0.1.7",
"mkdirp": "^0.5.1",
"promise-map-series": "^0.2.1",
"rimraf": "^2.6.1",
"rsvp": "^4.7.0",
"symlink-or-copy": "^1.0.1",
"sync-disk-cache": "^1.3.3",
"walk-sync": "^1.0.0"
},
"dependencies": {
"broccoli-plugin": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-1.3.1.tgz",
"integrity": "sha512-DW8XASZkmorp+q7J4EeDEZz+LoyKLAd2XZULXyD9l4m9/hAKV3vjHmB1kiUshcWAYMgTP1m2i4NnqCE/23h6AQ==",
"dev": true,
"requires": {
"promise-map-series": "^0.2.1",
"quick-temp": "^0.1.3",
"rimraf": "^2.3.4",
"symlink-or-copy": "^1.1.8"
}
},
"walk-sync": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-1.1.4.tgz",
"integrity": "sha512-nowc9thB/Jg0KW4TgxoRjLLYRPvl3DB/98S89r4ZcJqq2B0alNcKDh6pzLkBSkPMzRSMsJghJHQi79qw0YWEkA==",
"dev": true,
"requires": {
"@types/minimatch": "^3.0.3",
"ensure-posix-path": "^1.1.0",
"matcher-collection": "^1.1.1"
}
}
}
},
"broccoli-plugin": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/broccoli-plugin/-/broccoli-plugin-3.1.0.tgz",
"integrity": "sha512-7w7FP8WJYjLvb0eaw27LO678TGGaom++49O1VYIuzjhXjK5kn2+AMlDm7CaUFw4F7CLGoVQeZ84d8gICMJa4lA==",
"dev": true,
"requires": {
"broccoli-node-api": "^1.6.0",
"broccoli-output-wrapper": "^2.0.0",
"fs-merger": "^3.0.1",
"promise-map-series": "^0.2.1",
"quick-temp": "^0.1.3",
"rimraf": "^2.3.4",
"symlink-or-copy": "^1.1.8"
}
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ember-cli-htmlbars": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/ember-cli-htmlbars/-/ember-cli-htmlbars-4.4.0.tgz",
"integrity": "sha512-ohgctqk7dXIZR4TgN0xRoUYltWhghFJgqmtuswQTpZ7p74RxI9PKx+E8WV/95mGcPzraesvMNBg5utQNvcqgNg==",
"dev": true,
"requires": {
"@ember/edition-utils": "^1.2.0",
"babel-plugin-htmlbars-inline-precompile": "^3.2.0",
"broccoli-debug": "^0.6.5",
"broccoli-persistent-filter": "^2.3.1",
"broccoli-plugin": "^3.1.0",
"common-tags": "^1.8.0",
"ember-cli-babel-plugin-helpers": "^1.1.0",
"fs-tree-diff": "^2.0.1",
"hash-for-dep": "^1.5.1",
"heimdalljs-logger": "^0.1.10",
"json-stable-stringify": "^1.0.1",
"semver": "^6.3.0",
"strip-bom": "^4.0.0",
"walk-sync": "^2.0.2"
}
},
"ember-cli-typescript": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/ember-cli-typescript/-/ember-cli-typescript-3.1.4.tgz",
"integrity": "sha512-HJ73kL45OGRmIkPhBNFt31I1SGUvdZND+LCH21+qpq3pPlFpJG8GORyXpP+2ze8PbnITNLzwe5AwUrpyuRswdQ==",
"dev": true,
"requires": {
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
"@babel/plugin-proposal-optional-chaining": "^7.6.0",
"@babel/plugin-transform-typescript": "~7.8.0",
"ansi-to-html": "^0.6.6",
"broccoli-stew": "^3.0.0",
"debug": "^4.0.0",
"ember-cli-babel-plugin-helpers": "^1.0.0",
"execa": "^3.0.0",
"fs-extra": "^8.0.0",
"resolve": "^1.5.0",
"rsvp": "^4.8.1",
"semver": "^6.3.0",
"stagehand": "^1.0.0",
"walk-sync": "^2.0.0"
}
},
"execa": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz",
"integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.0",
"get-stream": "^5.0.0",
"human-signals": "^1.1.1",
"is-stream": "^2.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^4.0.0",
"onetime": "^5.1.0",
"p-finally": "^2.0.0",
"signal-exit": "^3.0.2",
"strip-final-newline": "^2.0.0"
}
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"fs-tree-diff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-2.0.1.tgz",
"integrity": "sha512-x+CfAZ/lJHQqwlD64pYM5QxWjzWhSjroaVsr8PW831zOApL55qPibed0c+xebaLWVr2BnHFoHdrwOv8pzt8R5A==",
"dev": true,
"requires": {
"@types/symlink-or-copy": "^1.2.0",
"heimdalljs-logger": "^0.1.7",
"object-assign": "^4.1.0",
"path-posix": "^1.0.0",
"symlink-or-copy": "^1.1.8"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dev": true,
"requires": {
"path-key": "^3.0.0"
}
},
"rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"walk-sync": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz",
"integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==",
"dev": true,
"requires": {
"@types/minimatch": "^3.0.3",
"ensure-posix-path": "^1.1.0",
"matcher-collection": "^2.0.0",
"minimatch": "^3.0.4"
},
"dependencies": {
"matcher-collection": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz",
"integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==",
"dev": true,
"requires": {
"@types/minimatch": "^3.0.3",
"minimatch": "^3.0.2"
}
}
}
}
}
},
"ember-diff-attrs": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ember-diff-attrs/-/ember-diff-attrs-0.2.2.tgz",
+1
View File
@@ -49,6 +49,7 @@
"ember-cli-sri": "^2.1.1",
"ember-cli-uglify": "^3.0.0",
"ember-concurrency": "^1.1.7",
"ember-concurrency-decorators": "^2.0.1",
"ember-export-application-global": "^2.0.1",
"ember-fetch": "^8.0.1",
"ember-flatpickr": "^2.16.2",
@@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | budget-balances', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<BudgetBalances />`);
assert.equal(this.element.textContent.trim(), '');
// Template block usage:
await render(hbs`
<BudgetBalances>
template block text
</BudgetBalances>
`);
assert.equal(this.element.textContent.trim(), 'template block text');
});
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Service | community-funds', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let service = this.owner.lookup('service:community-funds');
assert.ok(service);
});
});