diff --git a/api/transactions.js b/api/transactions.js index 12fa6e181..789bf330c 100644 --- a/api/transactions.js +++ b/api/transactions.js @@ -26,6 +26,10 @@ module.exports = [ path: 'getLastTransactions', service: 'transactions', params: () => undefined, + }, { + path: 'getTransactions', + service: 'transactions', + params: req => req.query, }, { path: 'getTransactionsByAddress', service: 'transactions', 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 diff --git a/lib/api/transactions.js b/lib/api/transactions.js index 7a5900cb6..3de473072 100644 --- a/lib/api/transactions.js +++ b/lib/api/transactions.js @@ -264,58 +264,55 @@ 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 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}`); } } return directionQueries; }; - this.getTransactionsByAddress = 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 }); } @@ -355,6 +352,19 @@ module.exports = function (app) { }); }; + this.getTransactions = function (query, error, success) { + const queryList = normalizeTransactionParams(query); + this.getTransactionsCall(queryList, error, success); + }; + + this.getTransactionsByAddress = function (query, 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) { if (!query.blockId) { return error({ success: false, error: 'Missing/Invalid blockId parameter' }); diff --git a/src/app/states.js b/src/app/states.js index 603499b08..ed30f3cd3 100644 --- a/src/app/states.js +++ b/src/app/states.js @@ -32,10 +32,15 @@ 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', - component: 'transactions', + component: 'transaction', }) .state('address', { url: '/address/:address', 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/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/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 84de8b0c8..9571b3d57 100644 --- a/src/components/transactions/index.js +++ b/src/components/transactions/index.js @@ -15,3 +15,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}}

+ +
+
+
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..0eda28b19 100644 --- a/src/components/transactions/transactions.html +++ b/src/components/transactions/transactions.html @@ -16,184 +16,12 @@ */ -->
-

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'; 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..863c93955 --- /dev/null +++ b/src/shared/transactions-filter/transactions-filter.directive.js @@ -0,0 +1,132 @@ +/* + * 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 AppTools from '../../app/app-tools.module'; +import template from './transactions-filter.html'; + +const TransactionsConstructor = function ($rootScope, $scope, $timeout, allTxs) { + $scope.searchModel = []; + $scope.searchParams = []; + $scope.availableSearchParams = [ + { 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: '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: 'Order By', + 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; + + 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 { + onSearchBoxCleaned(); + } + }; + + $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', (event, searchParameter) => { + $scope.searchModel[searchParameter.key] = searchParameter.value; + onSearchChange(); + }); + + // Sets autocomplete attr off + const disableAutocomplete = () => { + $timeout(() => { + document.getElementsByClassName('search-parameter-input')[0].setAttribute('autocomplete', 'off'); + }, 0); + }; + + disableAutocomplete(); +}; + +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 @@ + +
+ + + +
diff --git a/test/api/transactions.js b/test/api/transactions.js index ad6eeff81..fc7637515 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,140 @@ 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) => {