diff --git a/app/common/lib/ledgerExportUtil.js b/app/common/lib/ledgerExportUtil.js index 53e8c9922b1..cf0aab03629 100644 --- a/app/common/lib/ledgerExportUtil.js +++ b/app/common/lib/ledgerExportUtil.js @@ -2,26 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -const base64Encode = require('../../../js/lib/base64').encode const underscore = require('underscore') const moment = require('moment') -/** - * 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 - */ -module.exports.transactionsToCSVDataURL = (transactions) => { - const csvText = module.exports.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 }, + * // contribution: { fiat: [Object], rates: [Object], fee: 8858 }, * // ... * // }] * @@ -29,7 +19,7 @@ module.exports.transactionsToCSVDataURL = (transactions) => { * @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 */ -module.exports.getTransactionsByViewingIds = (transactions, viewingIds) => { +const getTransactionsByViewingIds = (transactions, viewingIds) => { if (!transactions) { return [] } @@ -66,17 +56,16 @@ module.exports.getTransactionsByViewingIds = (transactions, viewingIds) => { * 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 } + * // { 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 */ -module.exports.getTotalContribution = (transactions, viewingIds) => { - const txs = module.exports.getTransactionsByViewingIds(transactions, viewingIds) +const getTotalContribution = (transactions, viewingIds) => { + const txs = getTransactionsByViewingIds(transactions, viewingIds) const totalContribution = { - satoshis: 0, fiat: { amount: 0, currency: null }, fee: 0 } @@ -85,8 +74,6 @@ module.exports.getTotalContribution = (transactions, viewingIds) => { const tx = txs[i] || {} const txContribution = tx.contribution || {} - totalContribution.satoshis += 0 || txContribution.satoshis - if (txContribution.fiat) { if (!totalContribution.fiat.currency && txContribution.fiat.currency) { totalContribution.fiat.currency = txContribution.fiat.currency @@ -113,38 +100,38 @@ module.exports.getTotalContribution = (transactions, viewingIds) => { * // 'chronicle.com': * // { votes: 2, * // fraction: 0.04081632653061224, - * // contribution: { satoshis: 33221, fiat: 0.2040816326530612, currency: 'USD' } }, + * // contribution: { fiat: 0.2040816326530612, currency: 'USD' } }, * // 'waitbutwhy.com': * // { votes: 3, * // fraction: 0.061224489795918366, - * // contribution: { satoshis: 49832, fiat: 0.30612244897959184, currency: 'USD' } }, + * // contribution: { fiat: 0.30612244897959184, currency: 'USD' } }, * // 'archlinux.org': * // { votes: 1, * // fraction: 0.02040816326530612, - * // contribution: { satoshis: 16611, fiat: 0.1020408163265306, currency: 'USD' } }, + * // contribution: { 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) **/ -module.exports.getPublisherVoteData = (transactions, viewingIds) => { - transactions = module.exports.getTransactionsByViewingIds(transactions, viewingIds) +const getPublisherVoteData = (transactions, viewingIds) => { + transactions = getTransactionsByViewingIds(transactions, viewingIds) const publishersWithVotes = {} let totalVotes = 0 for (let i = transactions.length - 1; i >= 0; i--) { - var tx = transactions[i] - var ballots = tx.ballots + const tx = transactions[i] + const ballots = tx.ballots if (!ballots) { continue } - var publishersOnBallot = underscore.keys(ballots) + const publishersOnBallot = underscore.keys(ballots) - for (var j = publishersOnBallot.length - 1; j >= 0; j--) { + for (let j = publishersOnBallot.length - 1; j >= 0; j--) { let publisher = publishersOnBallot[j] let voteDataForPublisher = publishersWithVotes[publisher] || {} @@ -158,14 +145,12 @@ module.exports.getPublisherVoteData = (transactions, viewingIds) => { } } - var totalContributionAmountSatoshis = null - var totalContributionAmountFiat = null - var currency = null + let totalContributionAmountFiat = null + let currency = null - const totalContribution = module.exports.getTotalContribution(transactions) + const totalContribution = getTotalContribution(transactions) if (totalContribution) { - totalContributionAmountSatoshis = totalContributionAmountSatoshis || totalContribution.satoshis totalContributionAmountFiat = totalContributionAmountFiat || (totalContribution.fiat && totalContribution.fiat.amount) currency = currency || (totalContribution.fiat && totalContribution.fiat.currency) } @@ -175,9 +160,6 @@ module.exports.getPublisherVoteData = (transactions, viewingIds) => { 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 } @@ -197,10 +179,10 @@ module.exports.getPublisherVoteData = (transactions, viewingIds) => { * 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'], + * // [ ['Publisher,Votes,Fraction,USD'], + * // ['chronicle.com,2,0.04081632653061224,0.20 USD'], + * // ['waitbutwhy.com,3,0.061224489795918366,0.31 USD'], + * // ['archlinux.org,1,0.02040816326530612,0.10 USD'], * // /.../ * // ] * @@ -208,16 +190,16 @@ module.exports.getPublisherVoteData = (transactions, viewingIds) => { * @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) **/ -module.exports.getTransactionCSVRows = (transactions, viewingIds, addTotalRow, sortByContribution) => { - let txContribData = module.exports.getPublisherVoteData(transactions, viewingIds) - var publishers = (underscore.keys(txContribData) || []) +const getTransactionCSVRows = (transactions, viewingIds, addTotalRow, sortByContribution) => { + let txContribData = getPublisherVoteData(transactions, viewingIds) + let publishers = (underscore.keys(txContribData) || []) let publisherSortFunction if (sortByContribution) { // sort publishers by contribution publisherSortFunction = function (a, b) { - var getVotes = function (pubStr) { + const getVotes = function (pubStr) { return (pubStr && typeof pubStr === 'string' && txContribData[pubStr] && txContribData[pubStr].votes ? parseInt(txContribData[pubStr].votes) : 0) } return (getVotes(a) > getVotes(b) ? -1 : 1) @@ -234,25 +216,22 @@ module.exports.getTransactionCSVRows = (transactions, viewingIds, addTotalRow, s const currency = (publishers.length ? txContribData[publishers[0]].contribution.currency : 'USD') - const headerRow = ['Publisher', 'Votes', 'Fraction', 'BTC', currency].join(',') + const headerRow = ['Publisher', 'Votes', 'Fraction', currency].join(',') - var totalsRow = { + let totalsRow = { label: 'TOTAL', votes: 0, fraction: 0, - btc: 0, fiat: 0 } - var rows = [headerRow] + let rows = [headerRow] rows = rows.concat(publishers.map(function (pub) { - var pubRow = txContribData[pub] + const 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') @@ -264,7 +243,6 @@ module.exports.getTransactionCSVRows = (transactions, viewingIds, addTotalRow, s pub, pubRow.votes, pubRow.fraction, - rowBTC, pubRow.contribution.fiat.toFixed(2) + ' ' + pubRow.contribution.currency ].join(',') })) @@ -275,7 +253,6 @@ module.exports.getTransactionCSVRows = (transactions, viewingIds, addTotalRow, s totalsRow.label, totalsRow.votes, totalsRow.fraction, - totalsRow.btc, totalsRow.fiat.toFixed(2) + ' ' + currency ].join(',')) } @@ -283,22 +260,6 @@ module.exports.getTransactionCSVRows = (transactions, viewingIds, addTotalRow, s 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) - * - * returns a CSV with only a header row if input is empty or invalid - **/ -module.exports.getTransactionCSVText = (transactions, viewingIds, addTotalRow) => { - return module.exports.getTransactionCSVRows(transactions, viewingIds, addTotalRow).join('\n') -} - /** * Adds an `exportFilenamePrefix` field to the provided transaction(s) * of form `Brave_Payments_${YYYY-MM-DD}`, with "_" added for the nth time a date occurs (n > 1) @@ -307,7 +268,7 @@ module.exports.getTransactionCSVText = (transactions, viewingIds, addTotalRow) = * * @returns {Object[]} transactions (with each element having an added field `exportFilenamePrefix`) */ -module.exports.addExportFilenamePrefixToTransactions = (transactions) => { +const addExportFilenamePrefixToTransactions = (transactions) => { transactions = transactions || [] if (!underscore.isArray(transactions)) { @@ -337,3 +298,24 @@ module.exports.addExportFilenamePrefixToTransactions = (transactions) => { return transaction }) } + +const getMethods = () => { + const publicMethods = { + addExportFilenamePrefixToTransactions, + getTransactionCSVRows + } + + let privateMethods = {} + + if (process.env.NODE_ENV === 'test') { + privateMethods = { + getPublisherVoteData, + getTotalContribution, + getTransactionsByViewingIds + } + } + + return Object.assign({}, publicMethods, privateMethods) +} + +module.exports = getMethods() diff --git a/app/renderer/components/preferences/payment/history.js b/app/renderer/components/preferences/payment/history.js index 5baa0dcaaf4..0722075a60e 100644 --- a/app/renderer/components/preferences/payment/history.js +++ b/app/renderer/components/preferences/payment/history.js @@ -58,10 +58,6 @@ class HistoryRow extends ImmutableComponent { return formattedDateFromTimestamp(timestamp, 'YYYY-MM-DD') } - get satoshis () { - return this.transaction.getIn(['contribution', 'satoshis']) - } - get currency () { return this.transaction.getIn(['contribution', 'fiat', 'currency']) } @@ -86,7 +82,7 @@ class HistoryRow extends ImmutableComponent { render () { return {this.formattedDate} - {this.totalAmountStr} + {this.totalAmountStr} {this.receiptFileName} diff --git a/js/about/contributionStatement.js b/js/about/contributionStatement.js index 355b4c35b2f..29519444e25 100644 --- a/js/about/contributionStatement.js +++ b/js/about/contributionStatement.js @@ -153,10 +153,6 @@ class ContributionStatement extends React.Component { return this.state.synopsis } - get publisherSynopsisMap () { - return this.state.publisherSynopsisMap - } - get timestamp () { if (!this.transaction) { return null @@ -341,7 +337,7 @@ class ContributionStatement extends React.Component { let verified = publisherSynopsis.verified let site = row[0] let fractionStr = (parseFloat(row[2]) * 100).toFixed(2) - let fiatStr = row[4] + let fiatStr = row[3] return ( diff --git a/test/unit/app/common/lib/ledgerExportUtilTest.js b/test/unit/app/common/lib/ledgerExportUtilTest.js index f31c1b5bbd2..a8202bfaa03 100644 --- a/test/unit/app/common/lib/ledgerExportUtilTest.js +++ b/test/unit/app/common/lib/ledgerExportUtilTest.js @@ -7,20 +7,14 @@ const moment = require('moment') require('../../../braveUnit') const ledgerExportUtil = require('../../../../../app/common/lib/ledgerExportUtil') -const base64Encode = require('../../../../../js/lib/base64').encode -const CSV_HEADER_ROW_PREFIX_COLUMNS = ['Publisher', 'Votes', 'Fraction', 'BTC'] +const CSV_HEADER_ROW_PREFIX_COLUMNS = ['Publisher', 'Votes', 'Fraction'] const CSV_HEADER_ROW_PREFIX = CSV_HEADER_ROW_PREFIX_COLUMNS.join(',') const DEFAULT_CSV_HEADER_ROW_COLUMNS = CSV_HEADER_ROW_PREFIX_COLUMNS.concat(['USD']) const DEFAULT_CSV_HEADER_ROW = DEFAULT_CSV_HEADER_ROW_COLUMNS.join(',') const CSV_COLUMN_COUNT = DEFAULT_CSV_HEADER_ROW.split(',').length -const EMPTY_CSV = DEFAULT_CSV_HEADER_ROW // N.B. the expected datatype for the 'USD'/fiat column is a 'string' because it is of form '5.00 USD' -const CSV_EXPECTED_COLUMN_DATATYPES = ['string', 'number', 'number', 'number', 'string'] - -const CSV_CONTENT_TYPE = 'text/csv' -const CSV_DATA_URI_PREFIX = 'data:' + CSV_CONTENT_TYPE + ';base64,' -const EMPTY_CSV_DATA_URL = CSV_DATA_URI_PREFIX + base64Encode(EMPTY_CSV) +const CSV_EXPECTED_COLUMN_DATATYPES = ['string', 'number', 'number', 'string'] const EXPORT_FILENAME_CONST_PREFIX_PART = 'Brave_Payments_' const EXPORT_FILENAME_PREFIX_EXPECTED_FORM = `${EXPORT_FILENAME_CONST_PREFIX_PART}\${YYYY-MM-DD}` @@ -49,131 +43,6 @@ describe('ledger export utilities test', function () { }) }) - describe('transactionsToCSVDataURL', function () { - it(`returns a properly formatted data URL string with expected content-type (${CSV_CONTENT_TYPE})`, function () { - let output = ledgerExportUtil.transactionsToCSVDataURL(exampleTransactions) - - assert.equal(!!output, true) - assert.equal(typeof output, 'string') - assert(output.length > CSV_DATA_URI_PREFIX.length) - assert.equal(output.slice(0, 'data:'.length), 'data:') - assert.equal(output.slice('data:'.length, 'data:'.length + CSV_CONTENT_TYPE.length), CSV_CONTENT_TYPE) - assert.equal(output.slice('data:'.length + CSV_CONTENT_TYPE.length, 'data:'.length + CSV_CONTENT_TYPE.length + ';base64,'.length), ';base64,') - }) - - it('for empty input, returns a CSV data URL containing only a header row', function () { - let output - - output = ledgerExportUtil.transactionsToCSVDataURL(undefined) - assert.equal(output, EMPTY_CSV_DATA_URL) - - output = ledgerExportUtil.transactionsToCSVDataURL(null) - assert.equal(output, EMPTY_CSV_DATA_URL) - - output = ledgerExportUtil.transactionsToCSVDataURL([]) - assert.equal(output, EMPTY_CSV_DATA_URL) - }) - - it('given transactions, it returns a data URL containing the expected CSV', function () { - const ADD_TOTAL_ROW = true - const EXPECTED_CSV_TEXT = ledgerExportUtil.getTransactionCSVText(exampleTransactions, null, ADD_TOTAL_ROW) - - let output = ledgerExportUtil.transactionsToCSVDataURL(exampleTransactions) - - assert.equal(output, CSV_DATA_URI_PREFIX + base64Encode(EXPECTED_CSV_TEXT)) - }) - }) - - describe('getTransactionCSVText', function () { - it('for empty input, returns a CSV string containing only the expected header row', function () { - let output - - output = ledgerExportUtil.getTransactionCSVText(undefined) - assert.equal(output, EMPTY_CSV) - - output = ledgerExportUtil.getTransactionCSVText(null) - assert.equal(output, EMPTY_CSV) - - output = ledgerExportUtil.getTransactionCSVText([]) - assert.equal(output, EMPTY_CSV) - }) - - it('returns a CSV with the expected header row up to variable currency column', function () { - let output = ledgerExportUtil.getTransactionCSVText(exampleTransactions) - assert(!!output, 'expected CsV output to exist') - - let rows = output.split('\n') - - checkHeaderRowPrefixForRows(rows) - }) - - it('returns a CSV with the same number of columns in every row', function () { - let output = ledgerExportUtil.getTransactionCSVText(exampleTransactions) - - let rows = output.split('\n') - - checkColumnCountsForRows(rows) - }) - - it('returns a CSV with expected data types for each column in every row', function () { - let ADD_TOTAL_ROW = true - let output = ledgerExportUtil.getTransactionCSVText(exampleTransactions, null, ADD_TOTAL_ROW) - - let rows = output.split('\n') - - checkColumnDatatypesForRows(rows) - }) - - it('returns same CSV for an array containing one transaction and a single transaction object', function () { - let singleTxOutput = ledgerExportUtil.getTransactionCSVText(exampleTransaction) - let arrayWithSingleTxOutput = ledgerExportUtil.getTransactionCSVText(exampleTransactions) - - assert.equal(singleTxOutput, arrayWithSingleTxOutput) - }) - - it('given a transaction, returns a CSV containing the right number of rows: 1 header row, 1 row per publisher, and if addTotalRow===true, 1 row with totals', function () { - const EXPECTED_CSV_ROW_COUNT_NO_TOTAL = 1 + NUM_PUBLISHERS - const EXPECTED_CSV_ROW_COUNT_WITH_TOTAL = 1 + NUM_PUBLISHERS + 1 - - // output with NO TOTAL ROW - var output = ledgerExportUtil.getTransactionCSVText(exampleTransaction) - - var rows = output.split('\n') - assert.equal(rows.length, EXPECTED_CSV_ROW_COUNT_NO_TOTAL) - - let ADD_TOTAL_ROW = true - output = ledgerExportUtil.getTransactionCSVText(exampleTransaction, null, ADD_TOTAL_ROW) - rows = output.split('\n') - assert.equal(rows.length, EXPECTED_CSV_ROW_COUNT_WITH_TOTAL) - }) - - it('returns CSV text matching the CSV rows returned by getTransactionCSVRows', function () { - let ADD_TOTAL_ROW = true - let output = ledgerExportUtil.getTransactionCSVText(exampleTransactions, null, ADD_TOTAL_ROW) - - let rows = output.split('\n') - - let expectedOutputRows = ledgerExportUtil.getTransactionCSVRows(exampleTransactions, null, ADD_TOTAL_ROW) - assert.deepEqual(rows, expectedOutputRows) - }) - - it('when argument addTotalRow===true, there should be a total row with correct totals for each numeric column', function () { - let ADD_TOTAL_ROW = true - let output = ledgerExportUtil.getTransactionCSVText(exampleTransaction, null, ADD_TOTAL_ROW) - - let rows = output.split('\n') - - checkTotalRow(rows) - }) - - it('returns the same output for a single transaction input as an array of 1 element and a plain transaction object', function () { - let singleTxRows = ledgerExportUtil.getTransactionCSVText(exampleTransaction) - let arrayWithSingleTxRows = ledgerExportUtil.getTransactionCSVText(exampleTransactions) - - assert.deepEqual(singleTxRows, arrayWithSingleTxRows) - }) - }) - describe('getTransactionCSVRows', function () { describe('with invalid input', function () { it('returns an array w/ header if undefined', function () { @@ -328,14 +197,6 @@ describe('ledger export utilities test', function () { }) describe('each publisher->contribution entry', function () { - it('should have "satoshis" (type number, >= 0) defined', function () { - publishers.forEach(function (publisher) { - let publisherContributionEntry = publisherData[publisher].contribution - assert(typeof publisherContributionEntry.satoshis, 'number') - assert(publisherContributionEntry.satoshis >= 0) - }) - }) - it('should have "fiat" (type number, >= 0) defined', function () { publishers.forEach(function (publisher) { let publisherContributionEntry = publisherData[publisher].contribution @@ -402,7 +263,6 @@ describe('ledger export utilities test', function () { describe('getTotalContribution', function () { /** var totalContribution = { - satoshis: 0, fiat: { amount: 0, currency: null }, fee: 0 } @@ -418,11 +278,6 @@ describe('ledger export utilities test', function () { }) describe('total contribution object', function () { - it('has a key "satoshis" with value of type number (>= 0)', function () { - assert.equal(typeof contributionData.satoshis, 'number') - assert(contributionData.satoshis >= 0) - }) - it('has a key "fiat" associated with an object containing two subkeys, "amount" (number) and "currency" (string)', function () { assert.equal(typeof contributionData.fiat, 'object', 'should have a key "fiat" with an object associated') assert.equal(typeof contributionData.fiat.amount, 'number', 'should have a key "amount" with value of type "number"')