Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

Get all transactions when exporting account #485

Merged
1 change: 1 addition & 0 deletions client/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<script src="src/accounts/account.service.js"></script>
<script src="src/accounts/account-transactions.controller.js"></script>
<script src="src/accounts/account.controller.js"></script>
<script src="src/accounts/export-account.controller.js"></script>

<script src="src/directives/valid-amount.directive.js"></script>
<script src="src/directives/copy-to-clipboard.directive.js"></script>
Expand Down
21 changes: 8 additions & 13 deletions client/app/src/accounts/account.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1636,20 +1636,15 @@
}

function exportAccount (account) {
var eol = require('os').EOL
var transactions = storageService.get(`transactions-${account.address}`)

var filecontent = 'Account:,' + account.address + eol + 'Balance:,' + account.balance + eol + 'Transactions:' + eol + 'ID,Confirmations,Date,Type,Amount,From,To,Smartbridge' + eol
transactions.forEach(function (trns) {
var date = new Date(trns.date)
filecontent = filecontent + trns.id + ',' + trns.confirmations + ',' + date.toISOString() + ',' + trns.label + ',' + trns.humanTotal + ',' + trns.senderId + ',' + trns.recipientId +
',' + trns.vendorField + eol
$mdDialog.show({
templateUrl: './src/accounts/view/exportAccount.html',
controller: 'ExportAccountController',
escapeToClose: false,
locals: {
account: account,
theme: self.currentTheme
}
})
var blob = new Blob([filecontent])
var downloadLink = document.createElement('a')
downloadLink.setAttribute('download', account.address + '.csv')
downloadLink.setAttribute('href', window.URL.createObjectURL(blob))
downloadLink.click()
}

// Add a second passphrase to an account
Expand Down
106 changes: 106 additions & 0 deletions client/app/src/accounts/account.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,108 @@
return deferred.promise
}

// todo: move to utilityService
// todo: add tests there
function getArkRelativeTimeStamp (date) {
if (!date) {
return null
}

date = new Date(date.toUTCString())

const arkStartDate = new Date(Date.UTC(2017, 2, 21, 13, 0, 0, 0))
return parseInt((date.getTime() - arkStartDate.getTime()) / 1000)
}

// this methods only works correctly, as long as getAllTransactions returns the transactions ordered by new to old!
function getRangedTransactions (address, startDate, endDate, onUpdate) {
const startStamp = getArkRelativeTimeStamp(!startDate ? new Date(Date.UTC(2017, 2, 21, 13, 0, 0, 0)) : startDate)
const endStamp = getArkRelativeTimeStamp(!endDate ? new Date(new Date().setHours(23, 59, 59, 59)) : endDate)

const deferred = $q.defer()

let transactions = []
function onRangeUpdate (updateObj) {
const resultObj = {
transactions: []
}

let isAnyTransactionTooOld = false
updateObj.transactions.forEach(t => {
if (t.timestamp >= startStamp && t.timestamp <= endStamp) {
resultObj.transactions.push(t)
}
isAnyTransactionTooOld |= t.timestamp < startStamp
})

transactions = transactions.concat(resultObj.transactions)

// if any transaction of the current set is already older than our start date, we are finished
if (isAnyTransactionTooOld) {
resultObj.isFinished = true
}

if (onUpdate) {
onUpdate(resultObj)
}

if (resultObj.isFinished || updateObj.isFinished) {
deferred.resolve(transactions)
return true
}
}

getAllTransactions(address, null, onRangeUpdate)
.catch(error => deferred.reject({message: error.message, transactions: transactions}))

return deferred.promise
}

function getAllTransactions (address, totalLimit, onUpdate, offset, transactionCollection, deferred) {
if (!transactionCollection) {
transactionCollection = []
}

if (!offset) {
offset = 0
}

if (!totalLimit) {
totalLimit = Number.MAX_VALUE
}

if (!deferred) {
deferred = $q.defer()
}

getTransactions(address, offset).then(transactions => {
const previousLength = transactionCollection.length
transactionCollection = transactionCollection.concat(transactions)
const updateObj = {
transactions: transactions.slice(0, totalLimit - previousLength),
isFinished: !transactions.length || transactionCollection.length >= totalLimit
}

transactionCollection = transactionCollection.slice(0, totalLimit)

if (onUpdate) {
if (onUpdate(updateObj)) {
deferred.resolve(transactionCollection)
return
}
}

if (updateObj.isFinished) {
deferred.resolve(transactionCollection)
return
}

getAllTransactions(address, totalLimit, onUpdate, offset + transactions.length, transactionCollection, deferred)
}).catch(error => deferred.reject({message: error, transactions: transactionCollection}))

return deferred.promise
}

function getDelegate (publicKey) {
var deferred = $q.defer()
if (!publicKey) {
Expand Down Expand Up @@ -873,6 +975,10 @@

getTransactions: getTransactions,

getAllTransactions: getAllTransactions,

getRangedTransactions: getRangedTransactions,

createTransaction: createTransaction,

verifyMessage: verifyMessage,
Expand Down
97 changes: 97 additions & 0 deletions client/app/src/accounts/export-account.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
;(function () {
'use strict'

angular
.module('arkclient.accounts')
.controller('ExportAccountController', [
'$scope',
'$filter',
'$mdDialog',
'accountService',
'toastService',
'gettextCatalog',
'account',
'theme',
ExportAccountController
])

function ExportAccountController ($scope, $filter, $mdDialog, accountService, toastService, gettextCatalog, account, theme) {
$scope.vm = {}
$scope.vm.account = account
$scope.vm.theme = theme
$scope.vm.numberOfReceivedTransactions = 0
$scope.vm.hasStarted = false

// todo: move to utililityService once merged back
$scope.vm.minDate = new Date(Date.UTC(2017, 2, 21, 13, 0, 0, 0))

$scope.vm.startDate = new Date()
$scope.vm.startDate.setMonth($scope.vm.startDate.getMonth() - 1)
$scope.vm.endDate = new Date()

$scope.vm.exportAccount = () => {
$scope.vm.hasStarted = true

if ($scope.vm.startDate) {
$scope.vm.startDate.setHours(0, 0, 0, 0)
}

if ($scope.vm.endDate) {
$scope.vm.endDate.setHours(23, 59, 59, 59)
}

accountService.getRangedTransactions($scope.vm.account.address, $scope.vm.startDate, $scope.vm.endDate, onUpdate).then(transactions => {
downloadAccountFile($scope.vm.account, transactions)
}).catch(error => {
if (error.transactions.length) {
toastService.error('An error occured when getting your transactions. However we still got ' + error.transactions.length + ' transactions! ' +
'The exported file contains only these!',
10000)
downloadAccountFile($scope.vm.account, error.transactions, true)
} else {
toastService.error('An error occured when getting your transactions. Cannot export account!', 10000)
}
}).finally(() => $mdDialog.hide())
}

$scope.vm.cancel = () => {
$mdDialog.hide()
}

$scope.vm.getStartLabel = () => {
if ($scope.vm.startDate) {
return $filter('date')($scope.vm.startDate, 'mediumDate')
}

return gettextCatalog.getString('the beginning of time')
}

$scope.vm.getEndLabel = () => {
if ($scope.vm.endDate) {
return $filter('date')($scope.vm.endDate, 'mediumDate')
}

return gettextCatalog.getString('now')
}

function onUpdate (updateObj) {
$scope.vm.numberOfReceivedTransactions += updateObj.transactions.length
}

function downloadAccountFile (account, transactions, isInComplete) {
var eol = require('os').EOL

var filecontent = 'Account:,' + account.address + eol + 'Balance:,' + account.balance + eol + 'Transactions' + (isInComplete ? ' (INCOMPLETE):' : ':') + eol + 'ID,Confirmations,Date,Type,Amount,From,To,Smartbridge' + eol
transactions.forEach(function (trns) {
var date = new Date(trns.date)
filecontent = filecontent + trns.id + ',' + trns.confirmations + ',' + date.toISOString() + ',' + trns.label + ',' + trns.humanTotal + ',' + trns.senderId + ',' + trns.recipientId +
',' + trns.vendorField + eol
})
var blob = new Blob([filecontent])
var downloadLink = document.createElement('a')
downloadLink.setAttribute('download', account.address + '.csv')
downloadLink.setAttribute('href', window.URL.createObjectURL(blob))
downloadLink.click()
}
}
})()
57 changes: 57 additions & 0 deletions client/app/src/accounts/view/exportAccount.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<md-dialog aria-label="{{'Export account'|translate}}" ng-cloak md-theme="vm.theme">
<md-toolbar>
<div class="md-toolbar-tools">
<h2 md-truncate flex translate>
Export account
</h2>
</div>
</md-toolbar>
<md-dialog-content>
<div class="md-dialog-content">
<div ng-if="vm.hasStarted" style="text-align:center;">
<md-progress-circular style="margin:0 auto; padding-bottom: 50px;"></md-progress-circular>
<div translate>
Getting transactions from {{vm.getStartLabel()}} - {{vm.getEndLabel()}}!
Depending on your history, this may take a while.
</div>
<div ng-if="vm.numberOfReceivedTransactions" translate>
Received {{vm.numberOfReceivedTransactions}} transactions...
</div>
</div>
<div ng-if="!vm.hasStarted">
<form name="exportForm">
<md-card flex="100">
<md-card-title>
<md-card-title-text>
<span>
<translate>
Select the timespan of the transactions which should be included when you export your account.
Note that it can take quite a while if you choose a wide or old date range!
</translate>
</span>
</md-card-title-text>
</md-card-title>
</md-card>
<div layout="row" flex layout-align="start start">
<md-input-container class="md-block" flex="50">
<translate>Start date</translate>
<md-datepicker ng-model="vm.startDate" md-min-date="vm.minDate"></md-datepicker>
</md-input-container>
<md-input-container class="md-block" flex="50">
<translate>End date</translate>
<md-datepicker ng-model="vm.endDate" md-min-date="vm.minDate"></md-datepicker>
</md-input-container>
</div>
<md-dialog-actions layout="row">
<md-button ng-click="vm.exportAccount()" ng-disabled="exportForm.$invalid">
<span translate>Start export</span>
</md-button>
<md-button ng-click="vm.cancel()">
<span translate>Cancel</span>
</md-button>
</md-dialog-actions>
</form>
</div>
</div>
</md-dialog-content>
</md-dialog>
Loading