Skip to content

Commit

Permalink
chore(cketh): add pagination to minter dashboard (#3046)
Browse files Browse the repository at this point in the history
  • Loading branch information
lpahlavi authored Dec 13, 2024
1 parent 382559b commit 5368bae
Show file tree
Hide file tree
Showing 6 changed files with 604 additions and 26 deletions.
1 change: 1 addition & 0 deletions rs/ethereum/cketh/minter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ rust_test(
srcs = glob(["src/**/*.rs"]),
compile_data = [
"templates/dashboard.html",
"templates/pagination.html",
"templates/principal_to_bytes.js",
],
crate_features = features,
Expand Down
160 changes: 153 additions & 7 deletions rs/ethereum/cketh/minter/src/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod tests;

use askama::Template;
use candid::{Nat, Principal};
use ic_canisters_http_types::HttpRequest;
use ic_cketh_minter::endpoints::{EthTransaction, RetrieveEthStatus};
use ic_cketh_minter::erc20::CkTokenSymbol;
use ic_cketh_minter::eth_logs::{EventSource, ReceivedEvent};
Expand All @@ -22,6 +23,7 @@ use ic_ethereum_types::Address;
use icrc_ledger_types::icrc1::account::Account;
use std::cmp::Reverse;
use std::collections::{BTreeMap, BTreeSet};
use std::str::FromStr;

mod filters {
pub fn timestamp_to_datetime<T: std::fmt::Display>(timestamp: T) -> askama::Result<String> {
Expand Down Expand Up @@ -145,6 +147,123 @@ impl DashboardPendingDeposit {
}
}

// Number of entries per page in dashboard tables (e.g. minted events, finalized transactions).
const DEFAULT_PAGE_SIZE: usize = 100;

#[derive(Default, Clone)]
pub struct DashboardPaginationParameters {
minted_events_start: usize,
finalized_transactions_start: usize,
reimbursed_transactions_start: usize,
}

impl DashboardPaginationParameters {
pub(crate) fn from_query_params(
req: &HttpRequest,
) -> Result<DashboardPaginationParameters, String> {
fn parse_query_param(req: &HttpRequest, param_name: &str) -> Result<usize, String> {
Ok(match req.raw_query_param(param_name) {
Some(arg) => usize::from_str(arg)
.map_err(|_| format!("failed to parse the '{}' parameter", param_name))?,
None => 0,
})
}

Ok(Self {
minted_events_start: parse_query_param(req, "minted_events_start")?,
finalized_transactions_start: parse_query_param(req, "finalized_transactions_start")?,
reimbursed_transactions_start: parse_query_param(req, "reimbursed_transactions_start")?,
})
}
}

#[derive(Clone)]
pub struct DashboardPaginatedTable<T> {
current_page: Vec<T>,
pagination: DashboardTablePagination,
}

impl<T: Clone> DashboardPaginatedTable<T> {
pub fn from_items(
items: &[T],
current_page_offset: usize,
page_size: usize,
num_cols: usize,
table_reference: &str,
page_offset_query_param: &str,
) -> Self {
Self {
current_page: items
.iter()
.skip(current_page_offset)
.take(page_size)
.cloned()
.collect(),
pagination: DashboardTablePagination::pagination(
items.len(),
current_page_offset,
page_size,
num_cols,
table_reference,
page_offset_query_param,
),
}
}

pub fn is_empty(&self) -> bool {
self.current_page.is_empty()
}

pub fn has_more_than_one_page(&self) -> bool {
self.pagination.pages.len() > 1
}
}

#[derive(Clone)]
pub struct DashboardTablePage {
index: usize,
offset: usize,
}

#[derive(Template)]
#[template(path = "pagination.html")]
#[derive(Clone)]
pub struct DashboardTablePagination {
pub table_id: String,
pub table_width: usize,
pub page_offset_query_param: String,
pub current_page_index: usize,
pub pages: Vec<DashboardTablePage>,
}

impl DashboardTablePagination {
fn pagination(
num_items: usize,
current_offset: usize,
page_size: usize,
table_width: usize,
table_reference: &str,
page_offset_query_param: &str,
) -> Self {
let pages = (0..num_items)
.step_by(page_size)
.enumerate()
.map(|(index, offset)| DashboardTablePage {
index: index + 1,
offset,
})
.collect();
let current_page_index = current_offset / page_size + 1;
Self {
table_id: String::from(table_reference),
page_offset_query_param: String::from(page_offset_query_param),
table_width,
current_page_index,
pages,
}
}
}

#[derive(Template)]
#[template(path = "dashboard.html")]
#[derive(Clone)]
Expand All @@ -158,26 +277,35 @@ pub struct DashboardTemplate {
pub first_synced_block: BlockNumber,
pub last_observed_block: Option<BlockNumber>,
pub cketh_ledger_id: Principal,
pub minted_events: Vec<MintedEvent>,
pub minted_events_table: DashboardPaginatedTable<MintedEvent>,
pub pending_deposits: Vec<DashboardPendingDeposit>,
pub invalid_events: BTreeMap<EventSource, InvalidEventReason>,
pub withdrawal_requests: Vec<DashboardWithdrawalRequest>,
pub pending_transactions: Vec<DashboardPendingTransaction>,
pub finalized_transactions: Vec<DashboardFinalizedTransaction>,
pub reimbursed_transactions: Vec<DashboardReimbursedTransaction>,
pub finalized_transactions_table: DashboardPaginatedTable<DashboardFinalizedTransaction>,
pub reimbursed_transactions_table: DashboardPaginatedTable<DashboardReimbursedTransaction>,
pub eth_balance: EthBalance,
pub skipped_blocks: BTreeMap<String, BTreeSet<BlockNumber>>,
pub supported_ckerc20_tokens: Vec<DashboardCkErc20Token>,
}

impl DashboardTemplate {
pub fn from_state(state: &State) -> Self {
pub fn from_state(state: &State, pagination_parameters: DashboardPaginationParameters) -> Self {
let mut minted_events: Vec<_> = state.minted_events.values().cloned().collect();
minted_events.sort_unstable_by_key(|event| {
let deposit_event = &event.deposit_event;
Reverse((deposit_event.block_number(), deposit_event.log_index()))
});

let minted_events_table = DashboardPaginatedTable::from_items(
&minted_events,
pagination_parameters.minted_events_start,
DEFAULT_PAGE_SIZE,
7,
"minted-events",
"minted_events_start",
);

let mut supported_ckerc20_tokens: Vec<_> = state
.supported_ck_erc20_tokens()
.map(|ckerc20| DashboardCkErc20Token {
Expand Down Expand Up @@ -279,6 +407,15 @@ impl DashboardTemplate {
.collect();
finalized_transactions.sort_unstable_by_key(|tx| Reverse(tx.ledger_burn_index));

let finalized_transactions_table = DashboardPaginatedTable::from_items(
&finalized_transactions,
pagination_parameters.finalized_transactions_start,
DEFAULT_PAGE_SIZE,
8,
"finalized-transactions",
"finalized_transactions_start",
);

let mut reimbursed_transactions: Vec<_> = state
.eth_transactions
.reimbursed_transactions_iter()
Expand Down Expand Up @@ -321,6 +458,15 @@ impl DashboardTemplate {
reimbursed_transactions
.sort_unstable_by_key(|reimbursed_tx| Reverse(reimbursed_tx.cketh_ledger_burn_index()));

let reimbursed_transactions_table = DashboardPaginatedTable::from_items(
&reimbursed_transactions,
pagination_parameters.reimbursed_transactions_start,
DEFAULT_PAGE_SIZE,
6,
"reimbursed-transactions",
"reimbursed_transactions_start",
);

DashboardTemplate {
ethereum_network: state.ethereum_network,
ecdsa_key_name: state.ecdsa_key_name.clone(),
Expand All @@ -334,13 +480,13 @@ impl DashboardTemplate {
minimum_withdrawal_amount: state.cketh_minimum_withdrawal_amount,
first_synced_block: state.first_scraped_block_number,
last_observed_block: state.last_observed_block_number,
minted_events,
minted_events_table,
pending_deposits,
invalid_events: state.invalid_events.clone(),
withdrawal_requests,
pending_transactions,
finalized_transactions,
reimbursed_transactions,
finalized_transactions_table,
reimbursed_transactions_table,
eth_balance: state.eth_balance.clone(),
skipped_blocks: state
.skipped_blocks
Expand Down
Loading

0 comments on commit 5368bae

Please sign in to comment.