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 @@
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 | +
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}} | +
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 | -
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}} | -