From f5f1da2a1945d9706f282152c2ab3fffd6383896 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Tue, 11 Sep 2018 14:41:38 +0200 Subject: [PATCH 01/14] Rename transaction page component to singular --- src/app/states.js | 2 +- src/components/transactions/index.js | 2 +- .../transactions/transaction.component.js | 45 ++++ src/components/transactions/transaction.html | 199 ++++++++++++++++++ 4 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/components/transactions/transaction.component.js create mode 100644 src/components/transactions/transaction.html diff --git a/src/app/states.js b/src/app/states.js index 603499b08..584858c19 100644 --- a/src/app/states.js +++ b/src/app/states.js @@ -35,7 +35,7 @@ App.config(($stateProvider, $urlRouterProvider, $locationProvider) => { .state('transaction', { url: '/tx/:txId', parentDir: 'home', - component: 'transactions', + component: 'transaction', }) .state('address', { url: '/address/:address', diff --git a/src/components/transactions/index.js b/src/components/transactions/index.js index 84de8b0c8..ad556935e 100644 --- a/src/components/transactions/index.js +++ b/src/components/transactions/index.js @@ -14,4 +14,4 @@ * */ import './transactions.module'; -import './transactions.component'; +import './transaction.component'; diff --git a/src/components/transactions/transaction.component.js b/src/components/transactions/transaction.component.js new file mode 100644 index 000000000..bed2acd8f --- /dev/null +++ b/src/components/transactions/transaction.component.js @@ -0,0 +1,45 @@ +/* + * LiskHQ/lisk-explorer + * Copyright © 2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import AppTransaction from './transactions.module'; +import template from './transaction.html'; + +const TransactionConstructor = function ($rootScope, $stateParams, $location, $http) { + const vm = this; + vm.getTransaction = () => { + $http.get('/api/getTransaction', { + params: { + transactionId: $stateParams.txId, + }, + }).then((resp) => { + if (resp.data.success) { + vm.tx = resp.data.transaction; + } else { + throw new Error('Transaction was not found!'); + } + }).catch(() => { + $location.path('/'); + }); + }; + + vm.getTransaction(); +}; + +AppTransaction.component('transaction', { + template, + controller: TransactionConstructor, + controllerAs: 'vm', +}); + diff --git a/src/components/transactions/transaction.html b/src/components/transactions/transaction.html new file mode 100644 index 000000000..413d0d0d9 --- /dev/null +++ b/src/components/transactions/transaction.html @@ -0,0 +1,199 @@ + +
+

Transaction {{vm.tx.id}} 

+
+ Loading Transaction +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sender + {{vm.tx.senderId}} +
+ {{vm.tx.knownSender.owner}} + {{vm.tx.knownSender.description}} +
+
Recipient +
+ {{vm.tx.recipientId}} +
+ {{vm.tx.knownRecipient.owner}} + {{vm.tx.knownRecipient.description}} +
+
+
{{vm.tx | txType}}
+
Confirmations{{vm.tx.confirmations || 0}}
Amount{{vm.tx.amount | currency:$root.currency:$root.decimalPlaces}} {{$root.currency.symbol}}
Fee{{vm.tx.fee | currencyFee:$root.currency:$root.decimalPlaces}} {{$root.currency.symbol}}
Timestamp{{vm.tx.timestamp | timestamp}}
Block + {{vm.tx.blockId}} + Unconfirmed
+ +

{{vm.tx | txType}} details

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Data{{vm.tx.asset.data || '-'}}
Public Key{{vm.tx.asset.signature.publicKey}}
Username{{vm.tx.asset.delegate.username}}
Minimum{{vm.tx.asset.multisignature.min}}
Lifetime{{vm.tx.asset.multisignature.lifetime}} hours
Keys Group + + {{key}} + +
Name{{vm.tx.asset.dapp.name}}
Description{{vm.tx.asset.dapp.description || '-'}}
Tags{{vm.tx.asset.dapp.tags || '-'}}
Type{{vm.tx.asset.dapp.type}}
Link{{vm.tx.asset.dapp.link}}
Category{{vm.tx.asset.dapp.category}}
Icon{{vm.tx.asset.dapp.icon || '-'}}
DApp ID{{vm.tx.asset.inTransfer.dappId}}
DApp ID{{vm.tx.asset.outTransfer.dappId}}
+
+ +
+

Added votes {{vm.tx.votes.added.length}}

+ +
+ +
+

Deleted votes {{vm.tx.votes.deleted.length}}

+ +
+
+
From c0ee1cfc322868299696ea086f1a5ab056530802 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Tue, 11 Sep 2018 16:39:01 +0200 Subject: [PATCH 02/14] Add transactions list page --- api/transactions.js | 7 +++++++ src/app/states.js | 5 +++++ src/assets/styles/common.css | 3 ++- src/components/home/home.html | 5 +++++ src/components/transactions/index.js | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/api/transactions.js b/api/transactions.js index 12fa6e181..0703d7974 100644 --- a/api/transactions.js +++ b/api/transactions.js @@ -26,6 +26,13 @@ module.exports = [ path: 'getLastTransactions', service: 'transactions', params: () => undefined, + }, { + path: 'getTransactions', + service: 'transactions', + params: req => ({ + offset: req.query.offset, + limit: req.query.limit, + }), }, { path: 'getTransactionsByAddress', service: 'transactions', diff --git a/src/app/states.js b/src/app/states.js index 584858c19..ed30f3cd3 100644 --- a/src/app/states.js +++ b/src/app/states.js @@ -32,6 +32,11 @@ App.config(($stateProvider, $urlRouterProvider, $locationProvider) => { parentDir: 'home', component: 'block', }) + .state('transactions', { + url: '/txs/:page', + parentDir: 'home', + component: 'transactions', + }) .state('transaction', { url: '/tx/:txId', parentDir: 'home', diff --git a/src/assets/styles/common.css b/src/assets/styles/common.css index 9c1a5212d..85bf517c3 100644 --- a/src/assets/styles/common.css +++ b/src/assets/styles/common.css @@ -1378,7 +1378,8 @@ table tbody tr:nth-of-type(odd) { margin-right: 2.4rem; } -.paginator .btn-group .see-all-blocks { +.paginator .btn-group .see-all-blocks, +.paginator .btn-group .see-all-transactions { width: 50%; } diff --git a/src/components/home/home.html b/src/components/home/home.html index 9ee716782..6c39bcf3a 100644 --- a/src/components/home/home.html +++ b/src/components/home/home.html @@ -74,6 +74,11 @@

Latest Transactions

+

Latest Blocks

diff --git a/src/components/transactions/index.js b/src/components/transactions/index.js index ad556935e..9571b3d57 100644 --- a/src/components/transactions/index.js +++ b/src/components/transactions/index.js @@ -14,4 +14,5 @@ * */ import './transactions.module'; +import './transactions.component'; import './transaction.component'; From 321efb01a50e645447b8ad900278afe10fe18446 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Tue, 11 Sep 2018 16:44:32 +0200 Subject: [PATCH 03/14] Implement getTransactions endpoint --- lib/api/transactions.js | 41 ++++ .../transactions/transactions.component.js | 21 +- src/components/transactions/transactions.html | 183 +----------------- src/services/all-txs.js | 44 +++++ src/services/index.js | 1 + 5 files changed, 93 insertions(+), 197 deletions(-) create mode 100644 src/services/all-txs.js diff --git a/lib/api/transactions.js b/lib/api/transactions.js index 7a5900cb6..e1bbc41cb 100644 --- a/lib/api/transactions.js +++ b/lib/api/transactions.js @@ -314,6 +314,47 @@ module.exports = function (app) { return directionQueries; }; + this.getTransactions = function (query, error, success) { + const queryList = normalizeTransactionParams(query, error); + if (typeof queryList === 'string') { + return error({ success: false, error: queryList }); + } + + const queryCallsList = queryList.map(directionQuery => (callback) => { + request.get({ + url: `${app.get('lisk address')}/transactions?${directionQuery}`, + json: true, + }, (err, response, body) => { + if (err || response.statusCode !== 200) { + callback(err || 'Response was unsuccessful'); + } else if (body && Array.isArray(body.data)) { + callback(null, body.data); + } else { + callback(body.error || 'Response was unsuccessful'); + } + }); + }); + + return async.waterfall([ + function (cb) { + async.parallel( + queryCallsList, + (err, results) => { + if (err) { + return error({ success: false, error: err }); + } + return cb(null, _.uniq(_.flatten(results), 'id')); + }); + }, + processTransactions, + ], (err, result) => { + if (err) { + return error({ success: false, error: err }); + } + return success({ success: true, transactions: result.map(transactionMapping2) }); + }); + }; + this.getTransactionsByAddress = function (query, error, success) { const queryList = normalizeTransactionParams(query, error); if (typeof queryList === 'string') { diff --git a/src/components/transactions/transactions.component.js b/src/components/transactions/transactions.component.js index f890dfa38..ddbdb56d5 100644 --- a/src/components/transactions/transactions.component.js +++ b/src/components/transactions/transactions.component.js @@ -16,25 +16,9 @@ import AppTransactions from './transactions.module'; import template from './transactions.html'; -const TransactionsConstructor = function ($rootScope, $stateParams, $location, $http) { +const TransactionsConstructor = function (allTxs) { const vm = this; - vm.getTransaction = () => { - $http.get('/api/getTransaction', { - params: { - transactionId: $stateParams.txId, - }, - }).then((resp) => { - if (resp.data.success) { - vm.tx = resp.data.transaction; - } else { - throw new Error('Transaction was not found!'); - } - }).catch(() => { - $location.path('/'); - }); - }; - - vm.getTransaction(); + vm.txs = allTxs(); }; AppTransactions.component('transactions', { @@ -42,4 +26,3 @@ AppTransactions.component('transactions', { controller: TransactionsConstructor, controllerAs: 'vm', }); - diff --git a/src/components/transactions/transactions.html b/src/components/transactions/transactions.html index 413d0d0d9..76bd9e8e1 100644 --- a/src/components/transactions/transactions.html +++ b/src/components/transactions/transactions.html @@ -16,184 +16,11 @@ */ -->
-

Transaction {{vm.tx.id}} 

-
+

Transactions

+
Loading Transaction
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Sender - {{vm.tx.senderId}} -
- {{vm.tx.knownSender.owner}} - {{vm.tx.knownSender.description}} -
-
Recipient -
- {{vm.tx.recipientId}} -
- {{vm.tx.knownRecipient.owner}} - {{vm.tx.knownRecipient.description}} -
-
-
{{vm.tx | txType}}
-
Confirmations{{vm.tx.confirmations || 0}}
Amount{{vm.tx.amount | currency:$root.currency:$root.decimalPlaces}} {{$root.currency.symbol}}
Fee{{vm.tx.fee | currencyFee:$root.currency:$root.decimalPlaces}} {{$root.currency.symbol}}
Timestamp{{vm.tx.timestamp | timestamp}}
Block - {{vm.tx.blockId}} - Unconfirmed
- -

{{vm.tx | txType}} details

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Data{{vm.tx.asset.data || '-'}}
Public Key{{vm.tx.asset.signature.publicKey}}
Username{{vm.tx.asset.delegate.username}}
Minimum{{vm.tx.asset.multisignature.min}}
Lifetime{{vm.tx.asset.multisignature.lifetime}} hours
Keys Group - - {{key}} - -
Name{{vm.tx.asset.dapp.name}}
Description{{vm.tx.asset.dapp.description || '-'}}
Tags{{vm.tx.asset.dapp.tags || '-'}}
Type{{vm.tx.asset.dapp.type}}
Link{{vm.tx.asset.dapp.link}}
Category{{vm.tx.asset.dapp.category}}
Icon{{vm.tx.asset.dapp.icon || '-'}}
DApp ID{{vm.tx.asset.inTransfer.dappId}}
DApp ID{{vm.tx.asset.outTransfer.dappId}}
-
- -
-

Added votes {{vm.tx.votes.added.length}}

- -
- -
-

Deleted votes {{vm.tx.votes.deleted.length}}

- -
-
+
+ +
diff --git a/src/services/all-txs.js b/src/services/all-txs.js new file mode 100644 index 000000000..869ea23ba --- /dev/null +++ b/src/services/all-txs.js @@ -0,0 +1,44 @@ +/* + * LiskHQ/lisk-explorer + * Copyright © 2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import AppServices from './services.module'; +import LessMore from './less-more'; + +AppServices.factory('allTxs', + ($http, $q) => (data) => { + const params = Object.assign({}, data, { + url: '/api/getTransactions', + key: 'transactions', + }); + + const lessMore = new LessMore($http, $q, params); + + lessMore.loadMore = function () { + this.getData(0, 1, (response) => { + let changed = false; + + if (this.results[0] && response[0]) { + changed = (this.results[0].id !== response[0].id); + } + if (changed) { + this.reloadMore(); + } else { + LessMore.prototype.loadMore.call(this); + } + }); + }; + + return lessMore; + }); diff --git a/src/services/index.js b/src/services/index.js index c85813a03..0bcfa8309 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -15,6 +15,7 @@ */ import './services.module'; import './address-txs'; +import './all-txs'; import './block-txs'; import './forging-monitor'; import './forging-status'; From e52616062a1c2b9b1a92ff482acdd242b8a05a3a Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Tue, 11 Sep 2018 16:46:20 +0200 Subject: [PATCH 04/14] Remove address constraint in params normalizer func --- lib/api/transactions.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/api/transactions.js b/lib/api/transactions.js index e1bbc41cb..4fbc9a7b0 100644 --- a/lib/api/transactions.js +++ b/lib/api/transactions.js @@ -264,10 +264,6 @@ module.exports = function (app) { }; const normalizeTransactionParams = (params) => { - if (!params || (!params.address && !params.senderId && !params.recipientId)) { - return 'Missing/Invalid address parameter'; - } - const directionQueries = []; const baseQuery = `sort=timestamp:desc&offset=${param(params.offset, 0)}&limit=${param(params.limit, 100)}`; const address = coreUtils.parseAddress(params.address); From 5265aa676a61c8acb89519a290afacdbb93b8a8e Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Tue, 11 Sep 2018 16:55:36 +0200 Subject: [PATCH 05/14] Apply DRY principle --- lib/api/transactions.js | 46 +++++++---------------------------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/lib/api/transactions.js b/lib/api/transactions.js index 4fbc9a7b0..ad1d20fca 100644 --- a/lib/api/transactions.js +++ b/lib/api/transactions.js @@ -310,8 +310,7 @@ module.exports = function (app) { return directionQueries; }; - this.getTransactions = function (query, error, success) { - const queryList = normalizeTransactionParams(query, error); + this.getTransactionsCall = function (queryList, error, success) { if (typeof queryList === 'string') { return error({ success: false, error: queryList }); } @@ -351,45 +350,14 @@ module.exports = function (app) { }); }; - this.getTransactionsByAddress = function (query, error, success) { + this.getTransactions = function (query, error, success) { const queryList = normalizeTransactionParams(query, error); - if (typeof queryList === 'string') { - return error({ success: false, error: queryList }); - } - - const queryCallsList = queryList.map(directionQuery => (callback) => { - request.get({ - url: `${app.get('lisk address')}/transactions?${directionQuery}`, - json: true, - }, (err, response, body) => { - if (err || response.statusCode !== 200) { - callback(err || 'Response was unsuccessful'); - } else if (body && Array.isArray(body.data)) { - callback(null, body.data); - } else { - callback(body.error || 'Response was unsuccessful'); - } - }); - }); + this.getTransactionsCall(queryList, error, success); + }; - return async.waterfall([ - function (cb) { - async.parallel( - queryCallsList, - (err, results) => { - if (err) { - return error({ success: false, error: err }); - } - return cb(null, _.uniq(_.flatten(results), 'id')); - }); - }, - processTransactions, - ], (err, result) => { - if (err) { - return error({ success: false, error: err }); - } - return success({ success: true, transactions: result.map(transactionMapping2) }); - }); + this.getTransactionsByAddress = function (query, error, success) { + const queryList = normalizeTransactionParams(query, error); + this.getTransactionsCall(queryList, error, success); }; this.getTransactionsByBlock = function (query, error, success) { From 0fe478b71658b23c3d1f8fe6507c346b8bf58e44 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Tue, 11 Sep 2018 17:10:21 +0200 Subject: [PATCH 06/14] Refactoring --- lib/api/transactions.js | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/api/transactions.js b/lib/api/transactions.js index ad1d20fca..7137a94d4 100644 --- a/lib/api/transactions.js +++ b/lib/api/transactions.js @@ -265,45 +265,47 @@ module.exports = function (app) { const normalizeTransactionParams = (params) => { const directionQueries = []; - const baseQuery = `sort=timestamp:desc&offset=${param(params.offset, 0)}&limit=${param(params.limit, 100)}`; + + const offset = param(params.offset, 0); + const limit = param(params.limit, 100); + const sort = params.sort || 'timestamp:desc'; const address = coreUtils.parseAddress(params.address); - if (params.direction === 'sent') { - directionQueries.push(`${baseQuery}&senderId=${address}&type=0`); - } else if (params.direction === 'received') { - directionQueries.push(`${baseQuery}&recipientId=${address}&type=0`); - } else if (params.direction === 'others') { - for (let i = 1; i < 8; i++) { - directionQueries.push(`${baseQuery}&senderId=${address}&type=${i}`); + let baseQuery = `sort=${sort}&offset=${offset}&limit=${limit}`; + + if (address) { + if (params.direction === 'sent') { + directionQueries.push(`${baseQuery}&senderId=${address}&type=0`); + } else if (params.direction === 'received') { + directionQueries.push(`${baseQuery}&recipientId=${address}&type=0`); + } else if (params.direction === 'others') { + for (let i = 1; i < 8; i++) { + directionQueries.push(`${baseQuery}&senderId=${address}&type=${i}`); + } + } else { + directionQueries.push(`${baseQuery}&senderIdOrRecipientId=${address}`); } - } else if (params.address) { - directionQueries.push(`${baseQuery}&senderIdOrRecipientId=${address}`); } else { // advanced search - const offset = param(params.offset, 0); - const limit = param(params.limit, 100); - const sort = params.sort || 'timestamp:desc'; let filters = ['recipientId', 'recipientPublicKey', 'senderId', 'senderPublicKey', 'height', 'minAmount', 'maxAmount', 'fromTimestamp', 'toTimestamp', 'blockId']; - let advancedQuery = `sort=${sort}&offset=${offset}&limit=${limit}`; - // If recipientId is the same as senderId, use senderIdOrRecipientId instead. if (params.senderId && params.recipientId && params.senderId === params.recipientId) { - advancedQuery += `&senderIdOrRecipientId=${params.recipientId}`; + baseQuery += `&senderIdOrRecipientId=${params.recipientId}`; filters = filters.filter(item => item !== 'senderId' && item !== 'recipientId'); } Object.keys(params).forEach((key) => { if ((filters.indexOf(key) >= 0)) { - advancedQuery += `&${key}=${params[key]}`; + baseQuery += `&${key}=${params[key]}`; } }); // type might be comma separate or undefined if (params.type) { - params.type.split(',').forEach(type => directionQueries.push(`${advancedQuery}&type=${type}`)); + params.type.split(',').forEach(type => directionQueries.push(`${baseQuery}&type=${type}`)); } else { - directionQueries.push(`${advancedQuery}`); + directionQueries.push(`${baseQuery}`); } } From ea083b33855bdd6846780d6ec9470cfde5057246 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 11:12:53 +0200 Subject: [PATCH 07/14] Implement transactions filter as directive --- src/components/transactions/transactions.html | 1 + src/shared/index.js | 1 + .../transactions-filter.directive.js | 137 ++++++++++++++++++ .../transactions-filter.html | 30 ++++ 4 files changed, 169 insertions(+) create mode 100644 src/shared/transactions-filter/transactions-filter.directive.js create mode 100644 src/shared/transactions-filter/transactions-filter.html diff --git a/src/components/transactions/transactions.html b/src/components/transactions/transactions.html index 76bd9e8e1..0eda28b19 100644 --- a/src/components/transactions/transactions.html +++ b/src/components/transactions/transactions.html @@ -21,6 +21,7 @@

Transactions

Loading Transaction
+
diff --git a/src/shared/index.js b/src/shared/index.js index cc452f840..51eafaf4f 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -15,6 +15,7 @@ */ import './forging-status/forging-status'; import './orders/orders'; +import './transactions-filter/transactions-filter.directive'; import './transactions-list/transactions-list.directive'; import './transaction-item/transaction-item.directive'; import './peers'; diff --git a/src/shared/transactions-filter/transactions-filter.directive.js b/src/shared/transactions-filter/transactions-filter.directive.js new file mode 100644 index 000000000..90b52db86 --- /dev/null +++ b/src/shared/transactions-filter/transactions-filter.directive.js @@ -0,0 +1,137 @@ +/* + * LiskHQ/lisk-explorer + * Copyright © 2018 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ +import angular from 'angular'; +import AppTools from '../../app/app-tools.module'; +import template from './transactions-filter.html'; + +const TransactionsConstructor = function ($rootScope, $scope, allTxs) { + $scope.searchModel = []; + $scope.searchParams = []; + $scope.availableSearchParams = [ + { key: 'senderId', name: 'Sender', placeholder: 'Sender...' }, + { key: 'recipientId', name: 'Recipient', placeholder: 'Recipient...' }, + { key: 'minAmount', name: 'Min', placeholder: 'Min Amount...' }, + { key: 'maxAmount', name: 'Max', placeholder: 'Max Amount...' }, + { key: 'type', name: 'Type', placeholder: 'Comma separated...' }, + { key: 'senderPublicKey', name: 'SenderPk', placeholder: 'Sender Public Key...' }, + { key: 'recipientPublicKey', name: 'RecipientPk', placeholder: 'Recipient Public Key...' }, + { key: 'height', name: 'Block Height', placeholder: 'Block Id...' }, + { key: 'blockId', name: 'Block Id', placeholder: 'Block Id...' }, + { key: 'fromTimestamp', name: 'fromTimestamp', placeholder: 'From Timestamp...' }, + { key: 'toTimestamp', name: 'toTimestamp', placeholder: 'To Timestamp...' }, + { key: 'limit', name: 'limit', placeholder: 'Limit...' }, + { key: 'offset', name: 'offset', placeholder: 'Offset...' }, + { + key: 'sort', + name: 'orderBy', + placeholder: 'Order By...', + restrictToSuggestedValues: true, + suggestedValues: ['amount:asc', 'amount:desc', 'fee:asc', 'fee:desc', 'type:asc', 'type:desc', 'timestamp:asc', 'timestamp:desc'], + }, + ]; + + $scope.parametersDisplayLimit = $scope.availableSearchParams.length; + + $scope.setTxs = (txs) => { + $scope.txs = txs; + }; + + $scope.onFiltersUsed = () => { + $scope.cleanByFilters = true; + const { removeAll } = angular.element(document.getElementsByClassName('search-parameter-input')[0]).scope(); + if (removeAll) { + removeAll(); + } + }; + + const onSearchBoxCleaned = () => { + if ($scope.cleanByFilters) { + $scope.cleanByFilters = false; + } else { + $scope.invalidParams = false; + $scope.txs = allTxs(); + $scope.txs.loadData(); + } + }; + + const searchByParams = (params) => { + if ($scope.direction !== 'search') { + $scope.lastDirection = $scope.direction; + $scope.direction = 'search'; + } + $scope.invalidParams = false; + $scope.txs = allTxs(params); + $scope.txs.loadData(); + }; + + const onSearchChange = () => { + const params = {}; + Object.keys($scope.searchModel).forEach((key) => { + if ($scope.searchModel[key] !== undefined && $scope.searchModel[key] !== '') { + params[key] = $scope.searchModel[key]; + } + if ((key === 'minAmount' || key === 'maxAmount') && params[key] !== '') { + params[key] = Math.floor(parseFloat(params[key]) * 1e8); + } + }); + + if (params.query) { + params.senderId = params.query; + params.recipientId = params.query; + } + + if (Object.keys(params).length > 0) { + searchByParams(params); + } else if (Object.keys($scope.searchModel).length === 0) { + onSearchBoxCleaned(); + } else { + $scope.invalidParams = true; + } + }; + + $rootScope.$on('advanced-searchbox:modelUpdated', (event, model) => { + if ($scope.searchModel.query !== model.query) { + $scope.searchModel = Object.assign({}, model); + return onSearchChange(); + } + + return $scope.searchModel = Object.assign({}, model); + }); + + $rootScope.$on('advanced-searchbox:removedSearchParam', (event, searchParameter) => { + delete $scope.searchModel[searchParameter.key]; + onSearchChange(); + }); + + $rootScope.$on('advanced-searchbox:removedAllSearchParam', () => { + onSearchBoxCleaned(); + }); + + $rootScope.$on('advanced-searchbox:leavedEditMode', () => { + onSearchChange(); + }); +}; + +const transactionsFilter = AppTools.directive('transactionsFilter', () => ({ + template, + controller: TransactionsConstructor, + scope: { + txs: '=', + address: '=', + }, +})); + +export default transactionsFilter; diff --git a/src/shared/transactions-filter/transactions-filter.html b/src/shared/transactions-filter/transactions-filter.html new file mode 100644 index 000000000..0bfcedd47 --- /dev/null +++ b/src/shared/transactions-filter/transactions-filter.html @@ -0,0 +1,30 @@ + +
+ + + +
From a8abea5464a5d5df09d70d5e6acb5db5f120f9db Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 11:28:00 +0200 Subject: [PATCH 08/14] Replace address transactions filter by directive --- src/components/address/address.component.js | 110 ------------------ src/components/address/address.html | 14 +-- .../transactions-filter.directive.js | 24 ++-- 3 files changed, 11 insertions(+), 137 deletions(-) diff --git a/src/components/address/address.component.js b/src/components/address/address.component.js index 58ed92057..a4440aec1 100644 --- a/src/components/address/address.component.js +++ b/src/components/address/address.component.js @@ -18,15 +18,12 @@ import AppAddress from './address.module'; import template from './address.html'; const AddressConstructor = function ( - $rootScope, $stateParams, $location, $http, - $timeout, addressTxs, ) { const vm = this; - vm.searchModel = []; vm.getAddress = () => { $http.get('/api/getAccount', { @@ -36,7 +33,6 @@ const AddressConstructor = function ( }).then((resp) => { if (resp.data.success) { vm.address = resp.data; - vm.disableAutocomplete(); vm.getVotes(vm.address.publicKey); } else { throw new Error('Account was not found!'); @@ -58,44 +54,12 @@ const AddressConstructor = function ( address: $stateParams.address, }; - // Sets autocomplete attr off - vm.disableAutocomplete = () => { - $timeout(() => { - document.getElementsByClassName('search-parameter-input')[0].setAttribute('autocomplete', 'off'); - }, 0); - }; - // Sets the filter for which transactions to display vm.filterTxs = (direction) => { vm.direction = direction; vm.txs = addressTxs({ address: $stateParams.address, direction }); }; - vm.searchParams = []; - vm.availableSearchParams = [ - { key: 'senderId', name: 'Sender', placeholder: 'Sender...' }, - { key: 'recipientId', name: 'Recipient', placeholder: 'Recipient...' }, - { key: 'minAmount', name: 'Min', placeholder: 'Min Amount...' }, - { key: 'maxAmount', name: 'Max', placeholder: 'Max Amount...' }, - { key: 'type', name: 'Type', placeholder: 'Comma separated...' }, - { key: 'senderPublicKey', name: 'SenderPk', placeholder: 'Sender Public Key...' }, - { key: 'recipientPublicKey', name: 'RecipientPk', placeholder: 'Recipient Public Key...' }, - { key: 'height', name: 'Block Height', placeholder: 'Block Id...' }, - { key: 'blockId', name: 'Block Id', placeholder: 'Block Id...' }, - { key: 'fromTimestamp', name: 'fromTimestamp', placeholder: 'From Timestamp...' }, - { key: 'toTimestamp', name: 'toTimestamp', placeholder: 'To Timestamp...' }, - { key: 'limit', name: 'limit', placeholder: 'Limit...' }, - { key: 'offset', name: 'offset', placeholder: 'Offset...' }, - { - key: 'sort', - name: 'orderBy', - placeholder: 'Order By...', - restrictToSuggestedValues: true, - suggestedValues: ['amount:asc', 'amount:desc', 'fee:asc', 'fee:desc', 'type:asc', 'type:desc', 'timestamp:asc', 'timestamp:desc'], - }, - ]; - vm.parametersDisplayLimit = vm.availableSearchParams.length; - vm.onFiltersUsed = () => { vm.cleanByFilters = true; const { removeAll } = angular.element(document.getElementsByClassName('search-parameter-input')[0]).scope(); @@ -104,80 +68,6 @@ const AddressConstructor = function ( } }; - const onSearchBoxCleaned = () => { - if (vm.cleanByFilters) { - vm.cleanByFilters = false; - } else { - vm.invalidParams = false; - vm.filterTxs(vm.lastDirection); - vm.txs.loadData(); - } - }; - - const searchByParams = (params) => { - if (vm.direction !== 'search') { - vm.lastDirection = vm.direction; - vm.direction = 'search'; - } - vm.invalidParams = false; - vm.txs = addressTxs(params); - vm.txs.loadData(); - }; - - const isValidAddress = id => /([0-9]+)L$/.test(id); - - const onSearchChange = () => { - const params = {}; - Object.keys(vm.searchModel).forEach((key) => { - if (vm.searchModel[key] !== undefined && vm.searchModel[key] !== '') { - params[key] = vm.searchModel[key]; - } - if ((key === 'minAmount' || key === 'maxAmount') && params[key] !== '') { - params[key] = Math.floor(parseFloat(params[key]) * 1e8); - } - }); - - if (params.query) { - params.senderId = params.query; - params.recipientId = params.query; - } else { - params.senderId = params.senderId || $stateParams.address; - params.recipientId = params.recipientId || $stateParams.address; - } - - if (Object.keys(params).length > 0 && - (isValidAddress(params.recipientId) || - isValidAddress(params.senderId))) { - searchByParams(params); - } else if (Object.keys(vm.searchModel).length === 0) { - onSearchBoxCleaned(); - } else { - vm.invalidParams = true; - } - }; - - $rootScope.$on('advanced-searchbox:modelUpdated', (event, model) => { - if (vm.searchModel.query !== model.query) { - vm.searchModel = Object.assign({}, model); - return onSearchChange(); - } - - return vm.searchModel = Object.assign({}, model); - }); - - $rootScope.$on('advanced-searchbox:removedSearchParam', (event, searchParameter) => { - delete vm.searchModel[searchParameter.key]; - onSearchChange(); - }); - - $rootScope.$on('advanced-searchbox:removedAllSearchParam', () => { - onSearchBoxCleaned(); - }); - - $rootScope.$on('advanced-searchbox:leavedEditMode', () => { - onSearchChange(); - }); - vm.getAddress(); vm.txs = addressTxs({ address: $stateParams.address }); }; diff --git a/src/components/address/address.html b/src/components/address/address.html index 7dd1cead3..66dfd3f45 100644 --- a/src/components/address/address.html +++ b/src/components/address/address.html @@ -112,19 +112,7 @@

Transactions

-
- - - -
+
diff --git a/src/shared/transactions-filter/transactions-filter.directive.js b/src/shared/transactions-filter/transactions-filter.directive.js index 90b52db86..f4afe5d93 100644 --- a/src/shared/transactions-filter/transactions-filter.directive.js +++ b/src/shared/transactions-filter/transactions-filter.directive.js @@ -13,11 +13,10 @@ * Removal or modification of this copyright notice is prohibited. * */ -import angular from 'angular'; import AppTools from '../../app/app-tools.module'; import template from './transactions-filter.html'; -const TransactionsConstructor = function ($rootScope, $scope, allTxs) { +const TransactionsConstructor = function ($rootScope, $scope, $timeout, allTxs) { $scope.searchModel = []; $scope.searchParams = []; $scope.availableSearchParams = [ @@ -45,18 +44,6 @@ const TransactionsConstructor = function ($rootScope, $scope, allTxs) { $scope.parametersDisplayLimit = $scope.availableSearchParams.length; - $scope.setTxs = (txs) => { - $scope.txs = txs; - }; - - $scope.onFiltersUsed = () => { - $scope.cleanByFilters = true; - const { removeAll } = angular.element(document.getElementsByClassName('search-parameter-input')[0]).scope(); - if (removeAll) { - removeAll(); - } - }; - const onSearchBoxCleaned = () => { if ($scope.cleanByFilters) { $scope.cleanByFilters = false; @@ -123,6 +110,15 @@ const TransactionsConstructor = function ($rootScope, $scope, allTxs) { $rootScope.$on('advanced-searchbox:leavedEditMode', () => { onSearchChange(); }); + + // Sets autocomplete attr off + const disableAutocomplete = () => { + $timeout(() => { + document.getElementsByClassName('search-parameter-input')[0].setAttribute('autocomplete', 'off'); + }, 0); + }; + + disableAutocomplete(); }; const transactionsFilter = AppTools.directive('transactionsFilter', () => ({ From 6bce4758007fe9f70ef01e707fd295cddd95bafe Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 11:57:13 +0200 Subject: [PATCH 09/14] Return error if no address is given --- lib/api/transactions.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/api/transactions.js b/lib/api/transactions.js index 7137a94d4..3de473072 100644 --- a/lib/api/transactions.js +++ b/lib/api/transactions.js @@ -353,13 +353,16 @@ module.exports = function (app) { }; this.getTransactions = function (query, error, success) { - const queryList = normalizeTransactionParams(query, error); + const queryList = normalizeTransactionParams(query); this.getTransactionsCall(queryList, error, success); }; this.getTransactionsByAddress = function (query, error, success) { - const queryList = normalizeTransactionParams(query, error); - this.getTransactionsCall(queryList, error, success); + if (!query || (!query.address && !query.senderId && !query.recipientId)) { + return error({ success: false, error: 'Missing/Invalid address parameter' }); + } + const queryList = normalizeTransactionParams(query); + return this.getTransactionsCall(queryList, error, success); }; this.getTransactionsByBlock = function (query, error, success) { From ab72352a5c930b203be65fc19bc7548e953f114e Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 12:19:19 +0200 Subject: [PATCH 10/14] Fix getTransactions endpoint params --- api/transactions.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/transactions.js b/api/transactions.js index 0703d7974..789bf330c 100644 --- a/api/transactions.js +++ b/api/transactions.js @@ -29,10 +29,7 @@ module.exports = [ }, { path: 'getTransactions', service: 'transactions', - params: req => ({ - offset: req.query.offset, - limit: req.query.limit, - }), + params: req => req.query, }, { path: 'getTransactionsByAddress', service: 'transactions', From f5a558f56a10019ae22828c890c55dbf2c9a17bd Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 15:33:45 +0200 Subject: [PATCH 11/14] Add tests to getTransactions endpoint --- test/api/transactions.js | 137 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/test/api/transactions.js b/test/api/transactions.js index ad6eeff81..4d943df27 100644 --- a/test/api/transactions.js +++ b/test/api/transactions.js @@ -16,9 +16,11 @@ const node = require('./../node.js'); const params = { - blockId: '6524861224470851795', + blockHeight: 1, + blockId: '16821502558291654665', transactionId: '1465651642158264047', address: '16313739661670634666L', + publicKey: 'c094ebee7ec0c50ebee32918655e089f6e1a604b83bcaa760293c61e0f18ab6f', address_lowercase: '16313739661670634666l', offset: 20, limit: 100, @@ -38,6 +40,10 @@ describe('Transactions API', () => { node.get('/api/getLastTransactions', done); } + function getTransactions(query, done) { + node.get(`/api/getTransactions?${query}`, done); + } + function getTransactionsByAddress(id, id2, id3, done) { node.get(`/api/getTransactionsByAddress?address=${id}&offset=${id2}&limit=${id3}`, done); } @@ -89,8 +95,11 @@ describe('Transactions API', () => { checkTransactionTypes(o); } - function checkTransactions(txs) { + function checkTransactions(txs, checkAttribute) { for (let i = 0; i < txs.length; i++) { + if (checkAttribute) { + node.expect(txs[i]).to.satisfy(checkAttribute); + } checkTransaction(txs[i]); } } @@ -136,6 +145,130 @@ describe('Transactions API', () => { }); }); + describe('GET /api/getTransactions', () => { + it('using no params should be ok', (done) => { + getTransactions('', (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions); + done(); + }); + }); + + it('using senderId should be ok', (done) => { + getTransactions(`senderId=${params.address}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions, tx => (tx.senderId === params.address)); + done(); + }); + }); + + it('using senderPublicKey id should be ok', (done) => { + getTransactions(`senderPublicKey=${params.publicKey}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions, tx => (tx.senderPublicKey === params.publicKey)); + done(); + }); + }); + + it('using recipientId should be ok', (done) => { + getTransactions(`recipientId=${params.address}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(2); + checkTransactions(res.body.transactions, tx => (tx.recipientId === params.address)); + done(); + }); + }); + + it('using recipientPublicKey id should be ok', (done) => { + getTransactions(`recipientPublicKey=${params.publicKey}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(2); + checkTransactions(res.body.transactions, tx => (tx.recipientPublicKey === params.publicKey)); + done(); + }); + }); + + it('using height id should be ok', (done) => { + getTransactions(`height=${params.blockHeight}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions, tx => (tx.height === params.blockHeight)); + done(); + }); + }); + + it('using blockId id should be ok', (done) => { + getTransactions(`blockId=${params.blockId}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(25); + checkTransactions(res.body.transactions, tx => (tx.blockId === params.blockId)); + done(); + }); + }); + + it('using minAmount id should be ok', (done) => { + const minAmount = 10000 * 1e8; + getTransactions(`minAmount=${minAmount}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(3); + checkTransactions(res.body.transactions, tx => (tx.amount >= minAmount)); + done(); + }); + }); + + it('using maxAmount id should be ok', (done) => { + const maxAmount = 100 * 1e8; + getTransactions(`maxAmount=${maxAmount}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions, tx => (tx.amount <= maxAmount)); + done(); + }); + }); + + it('using fromTimestamp id should be ok', (done) => { + const fromTimestamp = 33505109; + getTransactions(`fromTimestamp=${fromTimestamp}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions, tx => (tx.timestamp >= fromTimestamp)); + done(); + }); + }); + + it('using toTimestamp id should be ok', (done) => { + const toTimestamp = 33505109; + getTransactions(`toTimestamp=${toTimestamp}`, (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(true); + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.lengthOf(100); + checkTransactions(res.body.transactions, tx => (tx.timestamp <= toTimestamp)); + done(); + }); + }); + + it('using invalid address should return empty array', (done) => { + getTransactions('recipientId=qwerty', (err, res) => { + node.expect(res.body).to.have.property('success').to.be.equal(false); + node.expect(res.body).to.have.property('error'); + done(); + }); + }); + }); + describe('GET /api/getTransactionsByAddress', () => { it('using known address should be ok', (done) => { getTransactionsByAddress(params.address, '0', params.limit, (err, res) => { From d952e3c1cb198ccaf218cb7711410e482f3011d4 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 16:17:31 +0200 Subject: [PATCH 12/14] Improve search --- .../transactions-filter.directive.js | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/shared/transactions-filter/transactions-filter.directive.js b/src/shared/transactions-filter/transactions-filter.directive.js index f4afe5d93..863c93955 100644 --- a/src/shared/transactions-filter/transactions-filter.directive.js +++ b/src/shared/transactions-filter/transactions-filter.directive.js @@ -20,22 +20,22 @@ const TransactionsConstructor = function ($rootScope, $scope, $timeout, allTxs) $scope.searchModel = []; $scope.searchParams = []; $scope.availableSearchParams = [ - { key: 'senderId', name: 'Sender', placeholder: 'Sender...' }, - { key: 'recipientId', name: 'Recipient', placeholder: 'Recipient...' }, - { key: 'minAmount', name: 'Min', placeholder: 'Min Amount...' }, - { key: 'maxAmount', name: 'Max', placeholder: 'Max Amount...' }, - { key: 'type', name: 'Type', placeholder: 'Comma separated...' }, - { key: 'senderPublicKey', name: 'SenderPk', placeholder: 'Sender Public Key...' }, - { key: 'recipientPublicKey', name: 'RecipientPk', placeholder: 'Recipient Public Key...' }, - { key: 'height', name: 'Block Height', placeholder: 'Block Id...' }, + { key: 'senderId', name: 'Sender ID', placeholder: 'Sender...' }, + { key: 'senderPublicKey', name: 'Sender Public Key', placeholder: 'Sender Public Key...' }, + { key: 'recipientId', name: 'Recipient ID', placeholder: 'Recipient...' }, + { key: 'recipientPublicKey', name: 'Recipient Public Key', placeholder: 'Recipient Public Key...' }, + { key: 'minAmount', name: 'Min Amount', placeholder: 'Min Amount...' }, + { key: 'maxAmount', name: 'Max Amount', placeholder: 'Max Amount...' }, + { key: 'type', name: 'Transaction Type', placeholder: 'Comma separated...' }, + { key: 'height', name: 'Block Height', placeholder: 'Block Height...' }, { key: 'blockId', name: 'Block Id', placeholder: 'Block Id...' }, - { key: 'fromTimestamp', name: 'fromTimestamp', placeholder: 'From Timestamp...' }, - { key: 'toTimestamp', name: 'toTimestamp', placeholder: 'To Timestamp...' }, - { key: 'limit', name: 'limit', placeholder: 'Limit...' }, - { key: 'offset', name: 'offset', placeholder: 'Offset...' }, + { key: 'fromTimestamp', name: 'From Timestamp', placeholder: 'From Timestamp...' }, + { key: 'toTimestamp', name: 'To Timestamp', placeholder: 'To Timestamp...' }, + { key: 'limit', name: 'Limit', placeholder: 'Limit...' }, + { key: 'offset', name: 'Offset', placeholder: 'Offset...' }, { key: 'sort', - name: 'orderBy', + name: 'Order By', placeholder: 'Order By...', restrictToSuggestedValues: true, suggestedValues: ['amount:asc', 'amount:desc', 'fee:asc', 'fee:desc', 'type:asc', 'type:desc', 'timestamp:asc', 'timestamp:desc'], @@ -82,10 +82,8 @@ const TransactionsConstructor = function ($rootScope, $scope, $timeout, allTxs) if (Object.keys(params).length > 0) { searchByParams(params); - } else if (Object.keys($scope.searchModel).length === 0) { - onSearchBoxCleaned(); } else { - $scope.invalidParams = true; + onSearchBoxCleaned(); } }; @@ -107,7 +105,8 @@ const TransactionsConstructor = function ($rootScope, $scope, $timeout, allTxs) onSearchBoxCleaned(); }); - $rootScope.$on('advanced-searchbox:leavedEditMode', () => { + $rootScope.$on('advanced-searchbox:leavedEditMode', (event, searchParameter) => { + $scope.searchModel[searchParameter.key] = searchParameter.value; onSearchChange(); }); From e9df741cea6138dda6638f62ffbb3b7f03660bb0 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 16:49:43 +0200 Subject: [PATCH 13/14] Add transactions e2e tests --- features/transaction.feature | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/features/transaction.feature b/features/transaction.feature index 418c1c8fe..2c102b696 100644 --- a/features/transaction.feature +++ b/features/transaction.feature @@ -24,3 +24,23 @@ Feature: Transaction page And I should see "genesis_51 • genesis_2 • genesis_7 • genesis_3 • genesis_4 • genesis_5 • genesis_6 • genesis_8 • genesis_9 • genesis_10 • genesis_11" in "deleted votes" element And I click "vote deleted link" no. 1 Then I should be on page "/address/2581762640681118072L" + + Scenario: should show transactions list + Given I'm on page "/txs/" + Then I should see "Transactions" in "h1" html element + And I should see "Home Transactions" in "breadcrumb" element + And I should see table "transactions" with 50 rows starting with: + | Transaction ID | Date | Sender | Recipient | Amount | Fee | Confirm. | + |----------------|-------------------------------|--------------------------|------------------|------------------|---------|-----------| + | /\d{18,20}/ | /2017\/06\/19 \d\d:\d\d:\d\d/ | /standby_\d{3}\|\d{20}L/ | Explorer Account | 123.45 LSK | 0.1 LSK | 5 / 101 | + | /\d{18,20}/ | /2017\/06\/19 \d\d:\d\d:\d\d/ | /standby_\d{3}\|\d{20}L/ | Explorer Account | 100 LSK | 0.1 LSK | 6 / 101 | + | /\d{18,20}/ | /2017\/06\/19 \d\d:\d\d:\d\d/ | /standby_\d{3}\|\d{20}L/ | Explorer Account | 100.12345678 LSK | 0.1 LSK | 7 / 101 | + | /\d{18,20}/ | /2017\/06\/19 \d\d:\d\d:\d\d/ | /standby_\d{3}\|\d{20}L/ | Explorer Account | 0.123456 LSK | 0.1 LSK | 8 / 101 | + | /\d{18,20}/ | /2017\/06\/19 \d\d:\d\d:\d\d/ | /standby_\d{3}\|\d{20}L/ | Explorer Account | 123.4567 LSK | 0.1 LSK | 9 / 101 | + + Scenario: should allow to load more transactions + Given I'm on page "/txs/" + And I should see table "transactions" with 50 rows + When I scroll to "more button" + And I click "more button" + Then I should see table "transactions" with 100 rows From 03906f199d1bec83b5c2f6b143b47a818145c253 Mon Sep 17 00:00:00 2001 From: Lucas Silvestre Date: Wed, 12 Sep 2018 17:03:11 +0200 Subject: [PATCH 14/14] Make eslint happy again --- test/api/transactions.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test/api/transactions.js b/test/api/transactions.js index 4d943df27..fc7637515 100644 --- a/test/api/transactions.js +++ b/test/api/transactions.js @@ -161,7 +161,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(100); - checkTransactions(res.body.transactions, tx => (tx.senderId === params.address)); + checkTransactions(res.body.transactions, + tx => (tx.senderId === params.address)); done(); }); }); @@ -171,7 +172,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(100); - checkTransactions(res.body.transactions, tx => (tx.senderPublicKey === params.publicKey)); + checkTransactions(res.body.transactions, + tx => (tx.senderPublicKey === params.publicKey)); done(); }); }); @@ -181,7 +183,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(2); - checkTransactions(res.body.transactions, tx => (tx.recipientId === params.address)); + checkTransactions(res.body.transactions, + tx => (tx.recipientId === params.address)); done(); }); }); @@ -191,7 +194,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(2); - checkTransactions(res.body.transactions, tx => (tx.recipientPublicKey === params.publicKey)); + checkTransactions(res.body.transactions, + tx => (tx.recipientPublicKey === params.publicKey)); done(); }); }); @@ -201,7 +205,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(100); - checkTransactions(res.body.transactions, tx => (tx.height === params.blockHeight)); + checkTransactions(res.body.transactions, + tx => (tx.height === params.blockHeight)); done(); }); }); @@ -211,7 +216,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(25); - checkTransactions(res.body.transactions, tx => (tx.blockId === params.blockId)); + checkTransactions(res.body.transactions, + tx => (tx.blockId === params.blockId)); done(); }); }); @@ -222,7 +228,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(3); - checkTransactions(res.body.transactions, tx => (tx.amount >= minAmount)); + checkTransactions(res.body.transactions, + tx => (tx.amount >= minAmount)); done(); }); }); @@ -233,7 +240,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(100); - checkTransactions(res.body.transactions, tx => (tx.amount <= maxAmount)); + checkTransactions(res.body.transactions, + tx => (tx.amount <= maxAmount)); done(); }); }); @@ -244,7 +252,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(100); - checkTransactions(res.body.transactions, tx => (tx.timestamp >= fromTimestamp)); + checkTransactions(res.body.transactions, + tx => (tx.timestamp >= fromTimestamp)); done(); }); }); @@ -255,7 +264,8 @@ describe('Transactions API', () => { node.expect(res.body).to.have.property('success').to.be.equal(true); node.expect(res.body).to.have.property('transactions').that.is.an('array'); node.expect(res.body.transactions).to.have.lengthOf(100); - checkTransactions(res.body.transactions, tx => (tx.timestamp <= toTimestamp)); + checkTransactions(res.body.transactions, + tx => (tx.timestamp <= toTimestamp)); done(); }); });