Skip to content

Commit

Permalink
#23 Add operations-table directive/service
Browse files Browse the repository at this point in the history
  • Loading branch information
ihorml committed Nov 9, 2022
1 parent 5190c78 commit abb53c6
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@
"Block number symbol": "Block #",
"Operations Count": "Operations Count",
"Asset not found": "Asset {{asset}} not found",
"Account not found": "Account {{account}} not found",
"Please check the asset name": "Please check the asset name or use the id",
"Please check the account name": "Please check the account name or use the id",

"Committee members": "Committee members",
"Current active committee members": "Current active committee members",
Expand Down
184 changes: 184 additions & 0 deletions app/sections/operations/operations-table.directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
Use this directive like this:
<operations-table />
* * * * * * * * * * * * * * *
* * * * * Options * * * * * *
* * * * * * * * * * * * * * *
_ _ _ _ _ _ _ _
| Grouping |
- - - - - - - -
This is a way to display all operations of specific blockchain object (account/creditoffer/pool)
(Examples)
<operations-table group-by-account-id="1.2.0"/> - use if you want to display operations of specific account
<operations-table group-by-credit-offer-id="1.21.0"/> - use if you want to display operations of specific credit offer
<operations-table group-by-pool-id="1.21.0"/> - use if you want to display operations of specific pool
_ _ _ _ _ _ _ _ _ _ _ _ _
| Enable User Filters |
- - - - - - - - - - - - -
This show/hide filtering fields for the user to filter all operations by account/asset/operation
// By default all true
(Examples)
<operations-table filter-by-account-id-enabled="false"/> - use if you want to display operations of specific account
<operations-table filter-by-asset-id-enabled="false"/> - use if you want to display operations of specific credit offer
<operations-table filter-by-operation-type-enabled="false"/> - use if you want to display operations of specific pool
_ _ _ _ _ _ _ _
| Date From |
- - - - - - - -
Use this field to optimize ES search by date limits
// By default: now-1M
(Example)
<operations-table date-from="now-1y"/>
_ _ _ _ _ _ _ _ _
| Show filters |
- - - - - - - - -
Use this field show / hide user filters
// By default: undefined
(Example)
<operations-table show-filters="true"/>
*/
(function () {

angular.module('app.operations')
.directive('operationsTable', [operationsTable]);

function operationsTable() {
return {
restrict: 'E',
replace: true,
scope: {
showFilters: '=',
groupByAccountId: '=',
groupByCreditOfferId: '=',
groupByPoolId: '=',
dateFrom: "@",
filterByAccountIdEnabled: '=',
filterByAssetIdEnabled: '=',
filterByOperationTypeEnabled: '=',
},
templateUrl: 'html/operations-table.html',
controller: ['$scope', '$filter', 'Notify', 'OperationsService', 'utilities', operationsTableController]
};

function operationsTableController($scope, $filter, Notify, OperationsService, utilities) {

// list of values for filter by operation type <select>
$scope.operationTypes = new Array(75).fill('#').map((item, key) => {
const name = utilities.operationType(key)[0]
if(!name)
return false;
return {
id: key,
name,
}
}).filter((item) => !!item);

$scope.dateFrom = $scope.dateFrom || 'now-1M'

// default values for filtering fields
$scope.filters = {
operationType: '-1',
assetIdOrName: undefined,
accountIdOrName: undefined,
}

// options for filtering fields (to prevent instant firing of change)
$scope.inputFilterOptions = {
debounce: 500,
getterSetter: true
};

// this is a variable to track when user opens the page and proper pagination
// see the issue: https://github.com/blocksights/blocksights-open-explorer/issues/15
$scope.userOpenedFirstPageTime = null;

$scope.operationsColumns = $scope.columns || [
OperationsService.columns.OPERATION_TEXT,
OperationsService.columns.OPERATION_ID,
OperationsService.columns.DATETIME,
OperationsService.columns.BLOCK,
OperationsService.columns.TYPE,
];

$scope.select = function (page_operations) {
const page = page_operations - 1;
const limit = 20;
const offset = page * limit;

if(page_operations === 1 || !$scope.userOpenedFirstPageTime) { // if user switches back from page Y (Y>1) to page 1 we need to fetch new transactions and update time range
$scope.userOpenedFirstPageTime = new Date();
}

const date_to = $scope.userOpenedFirstPageTime.toISOString();

$scope.operationsLoading = true;
$scope.operationsLoadingError = false;
OperationsService.getOperationsHistory({
limit,
offset,
date_to,
date_from: $scope.dateFrom || undefined,
assetId: $scope.filters.assetIdOrName,
accountId: $scope.groupByAccountId || $scope.filters.accountIdOrName,
creditOfferId: $scope.groupByCreditOfferId,
poolId: $scope.groupByPoolId,
operationType: $scope.filters.operationType,
}).then((response) => {
$scope.operationsLoading = false;
if(response && (response.asset_not_found || response.account_not_found)) {
let title, message;
if(response.asset_not_found) {
title = $filter('translate')('Asset not found', {
asset: $scope.filters.assetIdOrName
});
message = $filter('translate')('Please check the asset name');
}
if(response.account_not_found) {
title = $filter('translate')('Account not found', {
account: $scope.filters.accountIdOrName
})
message = $filter('translate')('Please check the account name');
}
return Notify.warning({
key: 'httpAssetOrAccountNotFound',
title: title,
message: message,
allowMultiple: true
});
} else {
$scope.operations = response;
$scope.currentPage = page_operations;
if (page_operations == 1) {
if (response.length > 0) {
$scope.total_ops = response[0].operation_id_num;
} else {
$scope.total_ops = 0;
}
}
}
}).catch(err => {
$scope.operationsLoadingError = true;
});
};

$scope.select(1);

$scope.$on('$destroy', () => {

});
}
}

})();
68 changes: 68 additions & 0 deletions app/sections/operations/operations-table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<div class="panel-body table-responsive">

<script type="text/ng-template" id="operations-table.html">

<div ng-if="index === 'operation_text'" ng-bind-html="row.operation_text | to_trusted"></div>

<div ng-if="index === 'operation_id'">
<a href='#/operations/{{cell}}/'>{{cell}}</a>
</div>

<div ng-if="index === 'time'"> {{ cell | dateFilter }} </div>

<div ng-if="index === 'block_num'">
<a href="#/blocks/{{ cell }}">{{ cell | number }}</a>
</div>

<div ng-if="index === 'type'">
<span class="badge badge-primary" style="background-color: #{{row.color}};">{{row.type}}</span>
</div>

</script>

<div class="operations-table-filters" ng-if="showFilters === true">
<div ng-if="filterByAccountIdEnabled !== false">
<input type="text" ng-change="select(1)" ng-model-options="inputFilterOptions" ng-model="filters.accountIdOrName" placeholder="Type account name or id" class="form-control" style="width: 200px" ng-model="asset_id">
</div>
<div ng-if="filterByAssetIdEnabled !== false">
<input type="text" ng-change="select(1)" ng-model-options="inputFilterOptions" ng-model="filters.assetIdOrName" placeholder="Type asset name or id" class="form-control" style="width: 200px" ng-model="asset_id">
</div>
<div ng-if="filterByOperationTypeEnabled !== false">
<select ng-change="select(1)" class="form-control" style="width: 200px" ng-model="filters.operationType">
<option value="-1">Filter by operation type</option>
<option value="{{operationType.id}}" ng-repeat="operationType in operationTypes">
{{ operationType.name }}
</option>
</select>
</div>
</div>

<responsive-table data-data="operations"
data-columns="operationsColumns"
data-loading="operationsLoading"
data-template="operations-table.html"
data-loading-error="operationsLoadingError"
></responsive-table>

<footer class="table-footer">
<div class="row">
<div class="col-md-6 page-num-info">
</div>
<div class="col-md-6 text-right pagination-container">

<ul
uib-pagination class="pagination-sm"
ng-model="currentPage"
total-items="totalItems"
max-size="4"
ng-change="currentPage===undefined || select(currentPage)"
items-per-page="20"
rotate="false"
previous-text="&lsaquo;" next-text="&rsaquo;"
boundary-links="true"
></ul>

</div>
</div>
</footer>
</div>
110 changes: 110 additions & 0 deletions app/sections/operations/operations.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
(function () {
'use strict';

angular.module('app.ui')
.service('OperationsService', ['utilities', 'appConfig', '$filter', '$http', OperationsService]);

function OperationsService(utilities, appConfig, $filter, $http) {

function getOperationsHistory({ poolId = undefined, creditOfferId = undefined, limit = 20, offset = 0, date_from = undefined, date_to = undefined, assetId = undefined, operationType = undefined, accountId = undefined }) {

function filterOperationDuplicates(v, i, a) {
return a.findIndex(t=>(t.operation_id_num === v.operation_id_num))===i
}

function operationMapperToAppFormat(op) {
let operation = {};

operation.block_num = op.block_data.block_num;
operation.operation_id = op.account_history.operation_id;
operation.operation_id_num = op.operation_id_num;
operation.time = op.block_data.block_time;
operation.witness = op.witness;
operation.sequence = op.account_history.sequence;

let parsed_op = op.operation_history.op_object;

if (parsed_op === undefined) {
parsed_op = JSON.parse(op.operation_history.op)[1];
}

if (parsed_op.amount) {
parsed_op.amount_ = parsed_op.amount;
}

const _with_result = {...parsed_op, result: op.operation_history.operation_result};

if (typeof _with_result.result === "string") {
_with_result.result = JSON.parse(op.operation_history.operation_result);
}

utilities.opText(appConfig, $http, op.operation_type, _with_result, function(returnData) {
operation.operation_text = returnData;
});

const type_res = utilities.operationType(op.operation_type);
operation.type = type_res[0];
operation.color = type_res[1];

return operation;
}

const MAGIC_NUMBER = 1000000;

return $http.get(appConfig.urls.elasticsearch_wrapper() + "/history", {
params: {
"limit": limit,
"offset": offset,
"from_date": date_from ? date_from : (offset < MAGIC_NUMBER ? "now-1M" : "2015-10-10"),
"to_date": date_to,
"asset_id": assetId ? assetId : undefined,
"operation_type": Number(operationType) === -1 ? undefined : operationType,
"account_id": accountId ? accountId : undefined,
"creditoffer_id": creditOfferId ? creditOfferId : undefined,
"pool_id": poolId ? poolId : undefined
}
}).then(response => {
if(response && response.data && (response.data.asset_not_found || response.data.account_not_found)) {
return response.data
}

const operations = response.data.filter(filterOperationDuplicates).map(operationMapperToAppFormat);

return operations;
});
}

const COLUMNS = {
OPERATION_TEXT: {
title: $filter('translate')('Operation'),
index: 'operation_text',
},
OPERATION_ID: {
title: $filter('translate')('ID'),
index: 'operation_id',
},
DATETIME: {
title: $filter('translate')('Date and time'),
index: 'time',
hidden: ['xs']
},
BLOCK: {
title: $filter('translate')('Block'),
index: 'block_num',
hidden: ['xs', 'sm']
},
TYPE: {
title: $filter('translate')('Type'),
index: 'type',
hidden: ['xs', 'sm', 'md']
}
}

return {
columns: COLUMNS,
getOperationsHistory
}
}


})();
9 changes: 9 additions & 0 deletions app/styles/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,15 @@ ul.scrollable-tabs-shadow-right:after {
z-index: 1000;
}

.operations-table-filters > div {
margin-right: 12px;
}

.operations-table-filters {
display: flex;
margin-bottom: 16px;
}

.dashboard-operations-filters > div {
margin-right: 12px;
}
Expand Down
2 changes: 2 additions & 0 deletions entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ require("./app/sections/voting/voting.controller.js");
require("./app/sections/witnesses/witnesses.controller.js");
require("./app/sections/workers/workers.controller.js");
require("./app/sections/operations/operations.controller.js");
require("./app/sections/operations/operations-table.directive.js");
require("./app/sections/operations/operations.service.js");
require("./app/sections/blocks/blocks.controller.js");
require("./app/sections/pools/pools.controller.js");
require("./app/sections/credit_offers/credit_offers.controller.js");
Expand Down
Loading

0 comments on commit abb53c6

Please sign in to comment.