Skip to content
This repository has been archived by the owner on Jan 14, 2022. It is now read-only.

URL-based filters - Closes #814 #860

Merged
merged 15 commits into from
Dec 18, 2018
38 changes: 27 additions & 11 deletions lib/api/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const coreUtils = require('../../utils/core.js');
module.exports = function (app) {
const self = this;
const knowledge = app.knownAddresses;
const invalidAddress = '__invalid_address';

const param = (p, d) => {
p = parseInt(p, 10);
Expand Down Expand Up @@ -263,13 +264,26 @@ module.exports = function (app) {
});
};

const normalizeTransactionParams = (params) => {
const parseLiskAddress = async (address) => {
const liskIdPattern = /^[0-9]{1,21}[L|l]$/g;

if (!address || typeof address !== 'string') return '';
if (address.match(liskIdPattern)) return coreUtils.parseAddress(address);
address = await knowledge.getByUser(address);
if (typeof address === 'object' && typeof address.address === 'string') return address.address;
return invalidAddress;
};

const normalizeTransactionParams = async (params) => {
const directionQueries = [];

const offset = param(params.offset, 0);
const limit = param(params.limit, 100);
const sort = params.sort || 'timestamp:desc';
const address = coreUtils.parseAddress(params.address);

const address = await parseLiskAddress(params.address);
params.senderId = await parseLiskAddress(params.senderId);
params.recipientId = await parseLiskAddress(params.recipientId);

let baseQuery = `sort=${sort}&offset=${offset}&limit=${limit}`;

Expand All @@ -295,11 +309,13 @@ module.exports = function (app) {
filters = filters.filter(item => item !== 'senderId' && item !== 'recipientId');
}

Object.keys(params).forEach((key) => {
if ((filters.indexOf(key) >= 0)) {
baseQuery += `&${key}=${params[key]}`;
}
});
Object.keys(params)
.filter(p => !!params[p])
.forEach((key) => {
if ((filters.indexOf(key) >= 0)) {
baseQuery += `&${key}=${params[key]}`;
}
});

// type might be comma separate or undefined
if (params.type) {
Expand Down Expand Up @@ -352,16 +368,16 @@ module.exports = function (app) {
});
};

this.getTransactions = function (query, error, success) {
const queryList = normalizeTransactionParams(query);
this.getTransactions = async function (query, error, success) {
const queryList = await normalizeTransactionParams(query);
this.getTransactionsCall(queryList, error, success);
};

this.getTransactionsByAddress = function (query, error, success) {
this.getTransactionsByAddress = async 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);
const queryList = await normalizeTransactionParams(query);
return this.getTransactionsCall(queryList, error, success);
};

Expand Down
2 changes: 1 addition & 1 deletion src/app/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ App.config(($stateProvider, $urlRouterProvider, $locationProvider) => {
component: 'block',
})
.state('transactions', {
url: '/txs/:page',
url: '/txs/:page?senderId&senderPublicKey&recipientId&recipientPublicKey&minAmount&maxAmount&type&height&blockId&fromTimestamp&toTimestamp&sort&limit&offset',
parentDir: 'home',
component: 'transactions',
})
Expand Down
29 changes: 29 additions & 0 deletions src/assets/styles/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -1605,3 +1605,32 @@ li.dropdown.disabled a {
#footer .social-networks {
padding-top: 5px;
}

.transaction-filter button {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
user-select: none;
background-image: none;
border: 1px solid #ccc;
border-style: solid none;
height: auto;
width: auto;
}

.transaction-filter button:first-of-type {
border-style: solid none solid none;
}

.transaction-filter button:last-of-type {
border-style: solid solid solid none;
}

2 changes: 1 addition & 1 deletion src/components/address/address.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ <h1>
<button class="received-tab" data-ng-disabled="vm.direction==='received'" data-ng-click="vm.filterTxs('received');vm.txs.loadData();vm.onFiltersUsed();">Received</button>
<button class="others-tab" data-ng-disabled="vm.direction==='others'" data-ng-click="vm.filterTxs('others');vm.txs.loadData();vm.onFiltersUsed();">Others</button>
</div>
<transactions-filter data-txs="vm.txs" data-address="vm.address.address"></transactions-filter>
<!-- <transactions-filter data-txs="vm.txs" data-address="vm.address.address"></transactions-filter> -->
<transactions-list data-txs="vm.txs" data-address="vm.address.address"></transactions-list>
</div>
</uib-tab>
Expand Down
30 changes: 27 additions & 3 deletions src/components/transactions/transactions.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,36 @@ import template from './transactions.html';

const TransactionsConstructor = function ($rootScope, $stateParams, $state, $http, $interval) {
const vm = this;

const filters = Object.keys($stateParams)
.filter(key => key !== 'page')
.filter(key => key !== '#')
.filter(key => typeof $stateParams[key] !== 'undefined')
.map(key => `${key}=${$stateParams[key]}`);

vm.getLastTransactions = (n) => {
const limit = 20 + 1;
let offset = 0;
if (n) offset = (n - 1) * limit;

$http.get(`/api/getTransactions?limit=${limit}&offset=${offset}`).then((resp) => {
let requestUrl = `/api/getTransactions?limit=${limit}&offset=${offset}`;
requestUrl += filters.length ? `&${filters.join('&')}` : '';

$http.get(requestUrl).then((resp) => {
if (resp.data.success) {
const removedTx = resp.data.transactions.splice(-1, 1);
let removedTx;
if (resp.data.transactions.length > limit - 1) {
removedTx = resp.data.transactions.splice(-1, 1);
}

vm.txs = { results: resp.data.transactions };
vm.txs.hasPrev = !!offset;
vm.txs.hasNext = !!removedTx;
vm.txs.page = $stateParams.page || 0;
vm.txs.page = $stateParams.page || 1;
vm.txs.loadPageOffset = vm.loadPageOffset;
vm.txs.loadPage = vm.loadPage;
vm.txs.activeSort = vm.activeSort;
vm.txs.applySort = vm.applySort;
} else {
vm.txs = {};
}
Expand All @@ -47,6 +62,15 @@ const TransactionsConstructor = function ($rootScope, $stateParams, $state, $htt
$state.go($state.current.component, { page: pageNumber });
};

vm.applySort = (predicate) => {
const direction = (predicate === vm.activeSort.predicate && vm.activeSort.direction === 'asc') ? 'desc' : 'asc';
$state.go($state.current.component, { sort: `${predicate}:${direction}` });
};

vm.activeSort = typeof $stateParams.sort === 'string'
? { predicate: $stateParams.sort.split(':')[0], direction: $stateParams.sort.split(':')[1] }
: { predicate: 'timestamp', direction: 'desc' };

const update = () => {
vm.getLastTransactions($stateParams.page || 1);
};
Expand Down
8 changes: 4 additions & 4 deletions src/components/transactions/transactions.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
-->
<section>
<h1>Transactions</h1>
<div data-ng-init="vm.txs.loadData()">
<transactions-filter data-txs="vm.txs"></transactions-filter>
<transactions-list data-txs="vm.txs"></transactions-list>
</div>
<div class="text-muted" data-ng-if="!vm.txs">
<div class="jumbotron">
<span>Loading transactions list <i class="fa fa-spinner fa-spin"></i>
</div>
</div>
<div data-ng-init="vm.txs.loadData()">
<!-- <transactions-filter data-txs="vm.txs"></transactions-filter> -->
<transactions-list data-txs="vm.txs"></transactions-list>
</div>
</section>
1 change: 1 addition & 0 deletions src/services/pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const Pagination = function ($http, $q, params) {
this.key = params.key || '';
this.offset = Number(params.offset) || 0;
this.currentPage = Number(params.currentPage) || 1;
this.page = this.currentPage;
this.limit = Number(params.limit) || 25;

['url', 'parent', 'key', 'offset', 'limit'].forEach((key) => {
Expand Down
144 changes: 57 additions & 87 deletions src/shared/transactions-filter/transactions-filter.directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,108 +16,78 @@
import AppTools from '../../app/app-tools.module';
import template from './transactions-filter.html';

const TransactionsConstructor = function ($rootScope, $scope, $timeout, allTxs) {
const TransactionsConstructor = function ($rootScope, $scope, $stateParams, $element, $state) {
$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'],
},
{ key: 'senderId', name: 'Sender ID', placeholder: 'Sender...', example: '12317412804123L' },
{ key: 'senderPublicKey', name: 'Sender Public Key', placeholder: 'Sender Public Key...', example: 'b550ede5...a26c78d8' },
{ key: 'recipientId', name: 'Recipient ID', placeholder: 'Recipient...', example: '12317412804123L' },
{ key: 'recipientPublicKey', name: 'Recipient Public Key', placeholder: 'Recipient Public Key...', example: 'b550ede5...a26c78d8' },
{ key: 'minAmount', name: 'Min Amount', placeholder: 'Min Amount...', example: '1.25' },
{ key: 'maxAmount', name: 'Max Amount', placeholder: 'Max Amount...', example: '1000.5' },
{ key: 'type', name: 'Comma separated transaction types', placeholder: 'Comma separated...', example: '1,3' },
{ key: 'height', name: 'Block height', placeholder: 'Block Height...', example: '2963014' },
{ key: 'blockId', name: 'Block Id', placeholder: 'Block Id...', example: '17238091754034756025' },
{ key: 'fromTimestamp', name: 'From Timestamp', placeholder: 'From Timestamp...', example: '17238091754034756025' },
{ key: 'toTimestamp', name: 'To Timestamp', placeholder: 'To Timestamp...', example: '12317412804123L' },
// { key: 'limit', name: 'Limit', placeholder: 'Limit...', example: '12317412804123L' },
// { key: 'offset', name: 'Offset', placeholder: 'Offset...', example: '12317412804123L' },
// {
// 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 convertToUrl = (key, param) => {
switch (key) {
case 'minAmount':
case 'maxAmount':
return Number(param) * Math.pow(10, 8);
default:
return param;
}
};

const searchByParams = (params) => {
if ($scope.direction !== 'search') {
$scope.lastDirection = $scope.direction;
$scope.direction = 'search';
const convertFromUrl = (key, param) => {
switch (key) {
case 'minAmount':
case 'maxAmount':
return Number(param) / Math.pow(10, 8);
default:
return encodeURI(param);
}
$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();
}
$scope.addToQueryText = (key) => {
$scope.queryText += ` ${key}=`;
};

$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);
$scope.performSearch = () => {
const query = $scope.queryText.split(' ')
.map(param => param.split('='))
.reduce((acc, param) => {
acc[param[0]] = convertToUrl(param[0], param[1]);
return acc;
}, {});
$state.go($state.current.component, Object.assign({ page: 1 }, query), { inherit: false });
};

disableAutocomplete();
$scope.queryText = Object.keys($stateParams)
.filter(key => key !== 'page')
.filter(key => key !== 'address')
.filter(key => key !== 'sort')
.filter(key => key !== '#')
.filter(key => typeof $stateParams[key] !== 'undefined')
.map(key => `${key}=${convertFromUrl(key, $stateParams[key])}`)
.join(' ');

$scope.parametersDisplayLimit = $scope.availableSearchParams.length;
};

const transactionsFilter = AppTools.directive('transactionsFilter', () => ({
Expand Down
Loading