diff --git a/.eslintrc b/.eslintrc index 77008378..0167e955 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,6 +17,7 @@ "jest": false, "describe": false, "test": false, + "expect": false, }, "rules": { "camelcase": 0, @@ -34,6 +35,8 @@ "no-shadow": 0, "no-underscore-dangle": 0, "no-use-before-define": 0, + "no-restricted-syntax": 0, + "no-prototype-builtins": 0, "no-void": 0, "react/forbid-prop-types": 0, "react/jsx-curly-brace-presence": 0, diff --git a/electron-builder.yml b/electron-builder.yml index 55bd7f54..bde2f549 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -44,3 +44,6 @@ nsis: files: - dist/internals - "!node_modules/jsqr/test-data${/*}" + - "!node_modules/ledger-test-library/deps/djinni${/*}" + - "!node_modules/ledger-test-library/deps/gyp${/*}" + - "!node_modules/raven-js/dist${/*}" diff --git a/flow-defs/globals.js b/flow-defs/globals.js index c5319c3f..e4b4c70c 100644 --- a/flow-defs/globals.js +++ b/flow-defs/globals.js @@ -7,5 +7,7 @@ declare var __PRINT_MODE__: string declare var __SENTRY_URL__: string declare var __GLOBAL_STYLES__: string declare var __static: string +declare var describe: Function +declare var test: Function declare var ResizeObserver: Class diff --git a/src/components/CalculateBalance.js b/src/components/CalculateBalance.js index 393c435f..8c2a5de0 100644 --- a/src/components/CalculateBalance.js +++ b/src/components/CalculateBalance.js @@ -3,91 +3,32 @@ import { PureComponent } from 'react' import { connect } from 'react-redux' import moment from 'moment' +import first from 'lodash/first' +import last from 'lodash/last' import type { MapStateToProps } from 'react-redux' import type { Accounts } from 'types/common' -import { getDefaultUnitByCoinType } from '@ledgerhq/currencies' - -import first from 'lodash/first' -import get from 'lodash/get' -import last from 'lodash/last' +import { getBalanceHistoryForAccounts } from 'helpers/balance' const mapStateToProps: MapStateToProps<*, *, *> = state => ({ counterValues: state.counterValues, }) -function getAllBalances({ - accounts, - counterValues, - daysCount, -}: { - accounts: Accounts, - counterValues: Object, - daysCount: number, -}) { - const getDate = date => moment(date).format('YYYY-MM-DD') - const getValue = (balance, unit, d) => - balance / 10 ** unit.magnitude * counterValues['BTC-USD'][d] - - const allBalancesByCoinType = accounts.reduce((result, account) => { - const { coinType } = account - - Object.keys(account.balanceByDay).forEach(k => { - if (!result[coinType]) { - result[coinType] = {} - } - result[coinType][k] = account.balanceByDay[k] + get(result, `${coinType}.${k}`, 0) - }) - - return result - }, {}) - - const allBalances = Object.keys(allBalancesByCoinType).reduce((result, coinType) => { - const unit = getDefaultUnitByCoinType(parseInt(coinType, 10)) - - const balanceByDay = allBalancesByCoinType[coinType] - - const balanceByDayKeys = Object.keys(balanceByDay).sort((a, b) => new Date(b) - new Date(a)) - - const lastDay = balanceByDayKeys[0] - const lastBalance = balanceByDay[lastDay] - - let balance = lastBalance - let index = daysCount - - result[lastDay] = getValue(balance, unit, lastDay) - - let d = getDate(moment(lastDay).subtract(1, 'days')) - - while (index !== 0) { - result[d] = getValue(balance, unit, d) + (result[d] || 0) - d = getDate(moment(d).subtract(1, 'days')) - - if (balanceByDay[d]) { - balance = balanceByDay[d] - } - - index-- - } - - return result - }, {}) - - return Object.keys(allBalances) - .sort() - .map(k => ({ - name: k, - value: allBalances[k], - })) -} - function calculateBalance(props) { - const allBalances = getAllBalances({ + const interval = { + start: moment() + .subtract(props.daysCount, 'days') + .format('YYYY-MM-DD'), + end: moment().format('YYYY-MM-DD'), + } + + const allBalances = getBalanceHistoryForAccounts({ + fiat: 'USD', accounts: props.accounts, counterValues: props.counterValues, - daysCount: props.daysCount, - }) + interval, + }).map(e => ({ name: e.date, value: e.balance })) return { allBalances, diff --git a/src/helpers/__tests__/balance.test.js b/src/helpers/__tests__/balance.test.js new file mode 100644 index 00000000..6b2d7e6a --- /dev/null +++ b/src/helpers/__tests__/balance.test.js @@ -0,0 +1,135 @@ +import { getBalanceHistoryForAccount, getBalanceHistoryForAccounts } from 'helpers/balance' + +const counterValues = { + 'BTC-USD': { + '2018-01-01': 1000, + '2018-01-02': 2000, + '2018-01-03': 3000, + '2018-01-04': 4000, + '2018-01-05': 5000, + }, +} + +describe('helpers > balance', () => { + describe('getBalanceHistoryForAccount', () => { + test('should handle a simple case', () => { + const account = { + coinType: 0, + balanceByDay: { + '2018-01-01': 100000000, + '2018-01-02': 200000000, + }, + } + + const interval = { + start: '2018-01-01', + end: '2018-01-02', + } + + const balances = getBalanceHistoryForAccount({ + fiat: 'USD', + account, + counterValues, + interval, + }) + + expect(balances).toEqual([ + { date: '2018-01-01', balance: 1000 }, + { date: '2018-01-02', balance: 4000 }, + ]) + }) + + test('should handle empty days', () => { + const account = { + coinType: 0, + balanceByDay: { + '2018-01-01': 100000000, + '2018-01-03': 200000000, + }, + } + + const interval = { + start: '2018-01-01', + end: '2018-01-03', + } + + const balances = getBalanceHistoryForAccount({ + fiat: 'USD', + account, + counterValues, + interval, + }) + + expect(balances).toEqual([ + { date: '2018-01-01', balance: 1000 }, + { date: '2018-01-02', balance: 2000 }, + { date: '2018-01-03', balance: 6000 }, + ]) + }) + + test('should work if interval dont contain transactions', () => { + const account = { + coinType: 0, + balanceByDay: { + '2018-01-01': 100000000, + }, + } + + const interval = { + start: '2018-01-02', + end: '2018-01-03', + } + + const balances = getBalanceHistoryForAccount({ + fiat: 'USD', + account, + counterValues, + interval, + }) + + expect(balances).toEqual([ + { date: '2018-01-02', balance: 2000 }, + { date: '2018-01-03', balance: 3000 }, + ]) + }) + }) + + describe('getBalanceHistoryForAccounts', () => { + test('should merge multiple accounts balance', () => { + const account1 = { + coinType: 0, + balanceByDay: { + '2018-01-01': 100000000, + '2018-01-02': 200000000, + }, + } + + const account2 = { + coinType: 0, + balanceByDay: { + '2018-01-02': 500000000, + '2018-01-04': 600000000, + }, + } + + const interval = { + start: '2018-01-01', + end: '2018-01-04', + } + + const balances = getBalanceHistoryForAccounts({ + fiat: 'USD', + accounts: [account1, account2], + counterValues, + interval, + }) + + expect(balances).toEqual([ + { date: '2018-01-01', balance: 1000 }, + { date: '2018-01-02', balance: 14000 }, + { date: '2018-01-03', balance: 21000 }, + { date: '2018-01-04', balance: 32000 }, + ]) + }) + }) +}) diff --git a/src/helpers/balance.js b/src/helpers/balance.js new file mode 100644 index 00000000..901323df --- /dev/null +++ b/src/helpers/balance.js @@ -0,0 +1,117 @@ +// @flow + +import moment from 'moment' +import isUndefined from 'lodash/isUndefined' +import { getDefaultUnitByCoinType } from '@ledgerhq/currencies' + +import type { Accounts, Account } from 'types/common' + +type DateInterval = { + start: string, + end: string, +} + +type BalanceHistoryDay = { + date: string, + balance: number, +} + +// Map the given date interval +// iteratee is given day, index, and currently constructed array +// (exactly like Array.map) +function mapInterval(iv: DateInterval, cb: Function) { + const res = [] + let startDate = moment(iv.start) + let i = 0 + const endDate = moment(iv.end) + res.push(cb(startDate.format('YYYY-MM-DD'), i, res)) + while (!startDate.isSame(endDate, 'day')) { + startDate = startDate.add(1, 'day') + res.push(cb(startDate.format('YYYY-MM-DD'), ++i, res)) + } + return res +} + +function getBalanceAtIntervalStart(account: Account, interval: DateInterval): number | null { + const target = moment(interval.start) + let res = 0 + for (const i in account.balanceByDay) { + if (account.balanceByDay.hasOwnProperty(i)) { + const d = moment(i) + if (!d.isBefore(target, 'day')) { + break + } + res = account.balanceByDay[i] || 0 + } + } + return res +} + +export function getBalanceHistoryForAccount({ + account, + fiat, + counterValues, + interval, +}: { + fiat: string, + account: Account, + counterValues: Object, + interval: DateInterval, +}): Array { + const unit = getDefaultUnitByCoinType(account.coinType) + const counterVals = counterValues[`${unit.code}-${fiat}`] + let lastBalance = getBalanceAtIntervalStart(account, interval) + return mapInterval(interval, date => { + let balance = 0 + + if (!counterVals) { + return { balance, date } + } + + // if we don't have data on account balance for that day, + // we take the prev day + if (isUndefined(account.balanceByDay[date])) { + balance = lastBalance === null ? 0 : lastBalance / 10 ** unit.magnitude * counterVals[date] + } else { + const b = account.balanceByDay[date] + lastBalance = b + balance = b / 10 ** unit.magnitude * counterVals[date] + } + + return { date, balance } + }) +} + +export function getBalanceHistoryForAccounts({ + accounts, + fiat, + counterValues, + interval, +}: { + fiat: string, + accounts: Accounts, + counterValues: Object, + interval: DateInterval, +}): Array { + // calculate balance history for each account on the given interval + const balances = accounts.map(account => + getBalanceHistoryForAccount({ + fiat, + account, + counterValues, + interval, + }), + ) + + // if more than one account, addition all balances, day by day + // and returns a big summed up array + return balances.length > 1 + ? balances[0].map((item, i) => { + let b = item.balance + for (let j = 1; j < balances.length; j++) { + b += balances[j][i].balance + } + return { ...item, balance: b } + }) + : balances.length > 0 ? balances[0] : [] +} diff --git a/yarn.lock b/yarn.lock index 3b5f191f..1c79beeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6215,9 +6215,6 @@ ledger-test-library@KhalilBellakrid/ledger-test-library-nodejs: dependencies: axios "^0.17.1" bindings "^1.3.0" - electron "^1.8.2" - electron-builder "^20.0.4" - electron-rebuild "^1.7.3" nan "^2.6.2" prebuild-install "^2.2.2"