Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
Merge pull request #4346 from willy-b/payment-history-csv-changes-433…
Browse files Browse the repository at this point in the history
…2-rebase

Payment History CSV export download has correct filename (+row sorting and total row)
  • Loading branch information
diracdeltas authored Sep 29, 2016
2 parents 74790fc + 4c9a994 commit 6c2074d
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 21 deletions.
13 changes: 0 additions & 13 deletions app/ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ const appStore = require('../js/stores/appStore')
const eventStore = require('../js/stores/eventStore')
const rulesolver = require('./extensions/brave/content/scripts/pageInformation.js')
const ledgerUtil = require('./common/lib/ledgerUtil')
const base64Encode = require('../js/lib/base64').encode

// TBD: remove these post beta [MTR]
const logPath = 'ledger-log.json'
Expand Down Expand Up @@ -295,18 +294,6 @@ if (ipc) {
if (balanceTimeoutId) clearTimeout(balanceTimeoutId)
balanceTimeoutId = setTimeout(getBalance, 5 * msecs.second)
})

ipc.on(messages.OPEN_LEDGER_TRANSACTION_CSV, (event, viewingIds, csvFilename) => {
if (client) {
let txCsvText = client.getTransactionCSVText(viewingIds)
let txCsvTextDataURI = 'data:text/csv;base64,' + base64Encode(txCsvText)

const win = electron.BrowserWindow.getFocusedWindow()
if (win && win.webContents) {
win.webContents.downloadURL(txCsvTextDataURI)
}
}
})
}

/*
Expand Down
4 changes: 0 additions & 4 deletions js/about/aboutActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,6 @@ const AboutActions = {
ipc.send(messages.LEDGER_CREATE_WALLET)
},

receiptLinkClick: function (viewingId, receiptFileName) {
ipc.send(messages.OPEN_LEDGER_TRANSACTION_CSV, viewingId, receiptFileName)
},

setLedgerEnabled: function (enabled) {
ipc.send(messages.LEDGER_ENABLE, enabled)
},
Expand Down
7 changes: 4 additions & 3 deletions js/about/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Immutable = require('immutable')
const SwitchControl = require('../components/switchControl')
const ModalOverlay = require('../components/modalOverlay')
const cx = require('../lib/classSet.js')
const transactionsToCSVDataURL = require('../lib/ledgerExportUtil.js').transactionsToCSVDataURL
const { getZoomValuePercentage } = require('../lib/zoom')
const config = require('../constants/config')
const appConfig = require('../constants/appConfig')
Expand Down Expand Up @@ -529,8 +530,8 @@ class PaymentHistoryRow extends ImmutableComponent {
return `brave_ledger${this.numericDateStr}.csv`
}

onReceiptLinkClick () {
aboutActions.receiptLinkClick(this.viewingId, this.receiptFileName)
get dataURL () {
return transactionsToCSVDataURL(this.transaction.toJS())
}

render () {
Expand All @@ -540,7 +541,7 @@ class PaymentHistoryRow extends ImmutableComponent {
return <tr>
<td className='narrow' data-sort={this.timestamp}>{date}</td>
<td className='wide' data-sort={this.satoshis}>{totalAmountStr}</td>
<td className='wide'><a onClick={this.onReceiptLinkClick.bind(this)}>{this.receiptFileName}</a></td>
<td className='wide'><a href={this.dataURL} download={this.receiptFileName}>{this.receiptFileName}</a></td>
</tr>
}
}
Expand Down
1 change: 0 additions & 1 deletion js/constants/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ const messages = {
LEDGER_PUBLISHER: _,
LEDGER_UPDATED: _,
LEDGER_CREATE_WALLET: _,
OPEN_LEDGER_TRANSACTION_CSV: _,
CHECK_BITCOIN_HANDLER: _,
ADD_FUNDS_CLOSED: _
}
Expand Down
285 changes: 285 additions & 0 deletions js/lib/ledgerExportUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
const base64Encode = require('./base64').encode
const underscore = require('underscore')

/**
* Generates a contribution breakdown by publisher as a CSV data URL from an array of one or more transactions
* @param {Object[]} transactions - array of transactions
*/
let transactionsToCSVDataURL = function (transactions) {
let csvText = getTransactionCSVText(transactions, null, true)
return 'data:text/csv;base64,' + base64Encode(csvText)
}

/**
* Filter an array of transactions by an array of viewingIds
* @example
* txUtil.getTransactionsByViewingIds(state.transactions, '0ef3a02d-ffdd-41f1-a074-7a7eb1e8c332')
* // [ { viewingId: '0ef3a02d-ffdd-41f1-a074-7a7eb1e8c332',
* // surveyorId: 'DQfCj8PHdIEJOZp9/L+FZcozgvYoIVSjPSdwqRYQDr0',
* // contribution: { fiat: [Object], rates: [Object], satoshis: 813916, fee: 8858 },
* // ...
* // }]
*
* @param {Object[]} transactions - array of one or more ledger transactions objects (see `client.state.transactions` entries)
* @param {string[]=} viewingIds - OPTIONAL array of one or more viewingIds to filter transactions (single string viewingId supported too)
* if null or undefined, all transactions are returned
*/
let getTransactionsByViewingIds = function getTransactionsByViewingIds (transactions, viewingIds) {
if (!transactions) {
return []
}
if (!underscore.isArray(transactions)) {
if (!underscore.isObject(transactions)) {
return []
}
transactions = [transactions]
}

if (!viewingIds) {
return transactions
}

if (viewingIds && typeof (viewingIds) === 'string') {
viewingIds = [viewingIds]
}
if (viewingIds && !viewingIds.length) {
viewingIds = null
}

if (!viewingIds) {
return []
}

transactions = transactions.filter(function (tx) {
return tx && tx.viewingId && (viewingIds.indexOf(tx.viewingId) > -1)
})

return transactions
}

/**
* Gives a contribution summary for an array of one or more transactions
* @example
* txUtil.getTotalContribution(client.state.transactions)
* // { satoshis: 1627832, fiat: { amount: 10, currency: 'USD' }, fee: 19900 }
*
* @param {Object[]} transactions - array of one or more ledger transactions objects (see `client.state.transactions` entries)
* @param {string[]} viewingIds - OPTIONAL array/string containing one or more viewingIds to filter by
* if null or undefined, all transactions are used
*/
let getTotalContribution = function getTotalContribution (transactions, viewingIds) {
var txs = getTransactionsByViewingIds(transactions, viewingIds)

var totalContribution = {
satoshis: 0,
fiat: { amount: 0, currency: null },
fee: 0
}

for (var i = txs.length - 1; i >= 0; i--) {
var tx = txs[i] || {}
var txContribution = tx.contribution || {}

totalContribution.satoshis += 0 || txContribution.satoshis

if (txContribution.fiat) {
if (!totalContribution.fiat.currency && txContribution.fiat.currency) {
totalContribution.fiat.currency = txContribution.fiat.currency
}

if (totalContribution.fiat.currency === txContribution.fiat.currency) {
totalContribution.fiat.amount += 0 || (txContribution.fiat && txContribution.fiat.amount)
} else {
throw new Error('ledgerUtil.totalContribution cannot handle multiple fiat currencies')
}
}

totalContribution.fee += 0 || txContribution.fee
}

return totalContribution
}

/**
* Gives a summary of votes/contributions by Publisher from an array of one or ore transactions
* @example
* txUtil.getPublisherVoteData(client.state.transactions)
* // {
* // 'chronicle.com':
* // { votes: 2,
* // fraction: 0.04081632653061224,
* // contribution: { satoshis: 33221, fiat: 0.2040816326530612, currency: 'USD' } },
* // 'waitbutwhy.com':
* // { votes: 3,
* // fraction: 0.061224489795918366,
* // contribution: { satoshis: 49832, fiat: 0.30612244897959184, currency: 'USD' } },
* // 'archlinux.org':
* // { votes: 1,
* // fraction: 0.02040816326530612,
* // contribution: { satoshis: 16611, fiat: 0.1020408163265306, currency: 'USD' } },
* // /.../
* // }
*
* @param {Object[]} transactions - array of transactions
* @param {string[]=} viewingIds - OPTIONAL array/string with one or more viewingIds to filter transactions by (if empty, uses all tx)
**/
let getPublisherVoteData = function getPublisherVoteData (transactions, viewingIds) {
transactions = getTransactionsByViewingIds(transactions, viewingIds)

var publishersWithVotes = {}
var totalVotes = 0

for (var i = transactions.length - 1; i >= 0; i--) {
var tx = transactions[i]
var ballots = tx.ballots

if (!ballots) {
continue
}

var publishersOnBallot = underscore.keys(ballots)

for (var j = publishersOnBallot.length - 1; j >= 0; j--) {
let publisher = publishersOnBallot[j]

let voteDataForPublisher = publishersWithVotes[publisher] || {}

let voteCount = ballots[publisher]
let publisherVotes = (voteDataForPublisher.votes || 0) + voteCount
totalVotes += voteCount

voteDataForPublisher.votes = publisherVotes
publishersWithVotes[publisher] = voteDataForPublisher
}
}

var totalContributionAmountSatoshis = null
var totalContributionAmountFiat = null
var currency = null

var totalContribution = getTotalContribution(transactions)

if (totalContribution) {
totalContributionAmountSatoshis = totalContributionAmountSatoshis || totalContribution.satoshis
totalContributionAmountFiat = totalContributionAmountFiat || (totalContribution.fiat && totalContribution.fiat.amount)
currency = currency || (totalContribution.fiat && totalContribution.fiat.currency)
}

for (let publisher in publishersWithVotes) {
let voteDataForPublisher = publishersWithVotes[publisher]
let fraction = voteDataForPublisher.fraction = voteDataForPublisher.votes / totalVotes

let contribution = voteDataForPublisher.contribution || {}
if (totalContributionAmountSatoshis) {
contribution.satoshis = Math.round(totalContributionAmountSatoshis * fraction)
}
if (totalContributionAmountFiat) {
contribution.fiat = totalContributionAmountFiat * fraction
}
if (currency) {
contribution.currency = currency
}

voteDataForPublisher.contribution = contribution

publishersWithVotes[publisher] = voteDataForPublisher
}

return publishersWithVotes
}

/**
* Generates a contribution breakdown by publisher in an array of CSV rows from an array of transactions
* @example
* txUtil.getTransactionCSVRows(client.state.transactions)
* // [ 'Publisher,Votes,Fraction,BTC,USD',
* // 'chronicle.com,2,0.04081632653061224,0.0000033221,0.20 USD',
* // 'waitbutwhy.com,3,0.061224489795918366,0.0000049832,0.31 USD',
* // 'archlinux.org,1,0.02040816326530612,0.0000016611,0.10 USD',
* // /.../
* // ]
*
* @param {Object[]} transactions - array of transactions
* @param {string[]=} viewingIds - OPTIONAL array/string with one or more viewingIds to filter transactions by (if empty, uses all tx)
* @param (boolean=) addTotalRow - OPTIONAL boolean indicating whether to add a TOTALS row (defaults false)
**/
let getTransactionCSVRows = function (transactions, viewingIds, addTotalRow) {
let txContribData = getPublisherVoteData(transactions, viewingIds)
var publishers = (underscore.keys(txContribData) || [])

// sort publishers alphabetically
// TODO: take locale argument and pass to localeCompare below
publishers = publishers.sort(function (a, b) {
return (a && typeof a === 'string' ? a : '').localeCompare(b && typeof b === 'string' ? b : '')
})

var currency = txContribData[publishers[0]].contribution.currency
var headerRow = ['Publisher', 'Votes', 'Fraction', 'BTC', currency].join(',')

var totalsRow = {
label: 'TOTAL',
votes: 0,
fraction: 0,
btc: 0,
fiat: 0
}

var rows = [headerRow]

rows = rows.concat(publishers.map(function (pub) {
var pubRow = txContribData[pub]

let rowBTC = pubRow.contribution.satoshis / Math.pow(10, 10)
totalsRow.votes += pubRow.votes
totalsRow.fraction += pubRow.fraction
totalsRow.btc += rowBTC

if (pubRow.contribution.currency === currency) {
totalsRow.fiat += parseFloat(pubRow.contribution.fiat || '0')
} else {
throw new Error('ledgerExportUtil#getTransactionCSVRows does not support mixed currency data (yet)!')
}

return [pub,
pubRow.votes,
pubRow.fraction,
rowBTC,
pubRow.contribution.fiat.toFixed(2) + ' ' + pubRow.contribution.currency
].join(',')
}))

if (addTotalRow) {
rows.push([
totalsRow.label,
totalsRow.votes,
totalsRow.fraction,
totalsRow.btc,
totalsRow.fiat.toFixed(2) + ' ' + currency
].join(','))
}

return rows
}

/**
* Generates a contribution breakdown by publisher in an array of CSV rows from an array of transactions
* @example
* txUtil.getTransactionCSVText(state.transactions)
* // 'Publisher,Votes,Fraction,BTC,USD\nchronicle.com,2,0.04081632653061224,0.0000033221,0.20 USD\nwaitbutwhy.com,3,0.061224489795918366,0.0000049832,0.31 USD\narchlinux.org,1,0.02040816326530612,0.0000016611,0.10 USD /.../'
*
* @param {Object[]} transactions - array of transactions
* @param {string[]=} viewingIds - OPTIONAL array/string with one or more viewingIds to filter transactions by (if empty, uses all tx)
* @param (boolean=) addTotalRow - OPTIONAL boolean indicating whether to add a TOTALS row (defaults false)
**/
let getTransactionCSVText = function (transactions, viewingIds, addTotalRow) {
return getTransactionCSVRows(transactions, viewingIds, addTotalRow).join('\n')
}

module.exports = {
transactionsToCSVDataURL: transactionsToCSVDataURL,
getTransactionCSVText: getTransactionCSVText,
getTransactionCSVRows: getTransactionCSVRows,
getPublisherVoteData: getPublisherVoteData,
getTransactionsByViewingIds: getTransactionsByViewingIds,
getTotalContribution: getTotalContribution
}

0 comments on commit 6c2074d

Please sign in to comment.