Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: format currency with commas and strip unnecessary decimal 0s #1120

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/account_activity/table_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl TableData for Vec<Option<AccountActivityQuerySnarks>> {
snark.get_date_time(),
),
convert_to_span(snark.get_prover()),
decorate_with_mina_tag(snark.get_fee()),
decorate_with_mina_tag(strip_decimal_if_zero(snark.get_fee())),
],
None => vec![],
})
Expand Down Expand Up @@ -214,7 +214,7 @@ impl TableData for Vec<Option<AccountActivityQueryBlocks>> {
block.get_date_time(),
),
convert_to_span(block.get_creator_account()),
decorate_with_mina_tag(block.get_coinbase()),
decorate_with_mina_tag(strip_decimal_if_zero(block.get_coinbase())),
convert_to_pill(block.get_transaction_count(), ColorVariant::Blue),
convert_to_pill(block.get_snark_job_count(), ColorVariant::Blue),
convert_to_link(
Expand Down Expand Up @@ -331,7 +331,7 @@ impl TableData for Vec<Option<AccountActivityQueryFeetransfers>> {
internal_command.get_state_hash(),
format!("/blocks/{}", internal_command.get_state_hash()),
),
decorate_with_mina_tag(internal_command.get_fee()),
decorate_with_mina_tag(strip_decimal_if_zero(internal_command.get_fee())),
convert_to_pill(internal_command.get_type(), ColorVariant::Grey),
convert_to_title(
print_time_since(&internal_command.get_block_datetime()),
Expand Down
28 changes: 21 additions & 7 deletions src/blocks/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,21 +222,27 @@ pub fn BlockAnalytics(block: BlocksQueryBlocks) -> impl IntoView {
<AnalyticsSmContainer>
<AnalyticsSimpleInfo
label=convert_to_span("Total User Amounts Transferred".into())
value=decorate_with_mina_tag(nanomina_to_mina(user_command_amount_total()))
value=decorate_with_mina_tag(
strip_decimal_if_zero(nanomina_to_mina(user_command_amount_total())),
)
/>

</AnalyticsSmContainer>
<AnalyticsSmContainer>
<AnalyticsSimpleInfo
label=convert_to_span("Total Internal Fees Transferred".into())
value=decorate_with_mina_tag(get_transaction_fees(&block_sig.get()))
value=decorate_with_mina_tag(
strip_decimal_if_zero(get_transaction_fees(&block_sig.get())),
)
/>
</AnalyticsSmContainer>
<AnalyticsSmContainer>
<AnalyticsSimpleInfo
label=convert_to_span("Total SNARK Fees".into())
value=wrap_in_pill(
decorate_with_mina_tag(get_snark_fees(&block_sig.get())),
decorate_with_mina_tag(
strip_decimal_if_zero(get_snark_fees(&block_sig.get())),
),
ColorVariant::Blue,
)
/>
Expand Down Expand Up @@ -429,7 +435,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView {
},
SpotlightEntry {
label: "Coinbase".to_string(),
any_el: Some(decorate_with_mina_tag(get_coinbase(&block))),
any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero(get_coinbase(
&block,
)))),
..Default::default()
},
SpotlightEntry {
Expand All @@ -445,7 +453,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView {
},
SpotlightEntry {
label: "SNARK Fees".to_string(),
any_el: Some(decorate_with_mina_tag(get_snark_fees(&block))),
any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero(
get_snark_fees(&block),
))),
..Default::default()
},
SpotlightEntry {
Expand All @@ -465,7 +475,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView {
},
SpotlightEntry {
label: "Transaction Fees".to_string(),
any_el: Some(decorate_with_mina_tag(get_transaction_fees(&block))),
any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero(
get_transaction_fees(&block),
))),
..Default::default()
},
SpotlightEntry {
Expand All @@ -478,7 +490,9 @@ pub fn BlockSpotlight(block: BlocksQueryBlocks) -> impl IntoView {
},
SpotlightEntry {
label: "Total MINA".to_string(),
any_el: Some(decorate_with_mina_tag(get_total_currency(&block))),
any_el: Some(decorate_with_mina_tag(strip_decimal_if_zero(
get_total_currency(&block),
))),
..Default::default()
},
SpotlightEntry {
Expand Down
10 changes: 5 additions & 5 deletions src/blocks/table_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl TableData for Vec<Option<BlocksQueryBlocks>> {
get_creator_account(block),
format!("/addresses/accounts/{}", get_creator_account(block)),
),
decorate_with_mina_tag(get_coinbase(block)),
decorate_with_mina_tag(strip_decimal_if_zero(get_coinbase(block))),
convert_to_pill(
get_transaction_count(block).map_or_else(String::new, |o| o.to_string()),
ColorVariant::Blue,
Expand Down Expand Up @@ -134,9 +134,9 @@ impl TableData for Vec<Option<BlocksQueryBlocksTransactionsUserCommandsExt>> {
format!("/addresses/accounts/{}", user_command.get_to()),
),
convert_to_pill(format_number(user_command.get_nonce()), ColorVariant::Grey),
decorate_with_mina_tag(nanomina_to_mina(
decorate_with_mina_tag(strip_decimal_if_zero(nanomina_to_mina(
user_command.get_fee().parse::<u64>().ok().unwrap_or(0),
)),
))),
decorate_with_mina_tag(nanomina_to_mina(
user_command.get_amount().parse::<u64>().ok().unwrap_or(0),
)),
Expand Down Expand Up @@ -230,7 +230,7 @@ impl TableData for Vec<Option<BlocksQueryBlocksSnarkJobs>> {
get_snark_prover(snark),
format!("/addresses/accounts/{}", get_snark_prover(snark)),
),
decorate_with_mina_tag(get_snark_fee(snark)),
decorate_with_mina_tag(strip_decimal_if_zero(get_snark_fee(snark))),
],
None => vec![],
})
Expand All @@ -247,7 +247,7 @@ impl TableData for Vec<Option<BlocksQueryBlocksTransactionsFeeTransfer>> {
fee_transfer.get_receipient(),
format!("/addresses/accounts/{}", fee_transfer.get_receipient()),
),
decorate_with_mina_tag(fee_transfer.get_fee()),
decorate_with_mina_tag(strip_decimal_if_zero(fee_transfer.get_fee())),
convert_to_pill(fee_transfer.get_type(), ColorVariant::Grey),
],
None => vec![],
Expand Down
112 changes: 112 additions & 0 deletions src/common/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,64 @@ pub fn decorate_with_currency_tag(
.into()
}

/// Format currency by stripping out the decimal if it is 0 and calling
/// [format_with_commas].
pub fn strip_decimal_if_zero(num_str: String) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really tricky. You have to take into account localization (different kinds of number formatting). Fortunately I have already developed a function for this:

pub fn normalize_number_format(number: &str) -> Result<String, String> {
. Could you use this function internally to first normalize the number across all locales and then strip the zero?

For context, this is how numbers are represented in German locale:

Screen Shot 2024-10-15 at 8 43 59 AM

Using the normalize function first will make a standard number (see unit tests on how), and then you can go from there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also update how this function works:

fn test_trailing_zeros_dot() {

// Check for special cases first
match num_str.to_lowercase().as_str() {
"inf" | "+inf" | "infinity" | "+infinity" => return "inf".to_string(),
"-inf" | "-infinity" => return "-inf".to_string(),
"nan" => return "NaN".to_string(),
_ => {}
}

// Remove existing commas
let no_commas = num_str.replace(',', "");

// Try to parse as a number
if let Ok(num) = no_commas.parse::<f64>() {
if num == 0.0 {
return "0".to_string();
}

// Split the string into integer and fractional parts
let parts: Vec<&str> = no_commas.split('.').collect();

match parts.as_slice() {
[integer] => format_with_commas(integer.parse::<i64>().unwrap()),
[integer, fraction] => {
let int_with_commas = format_with_commas(integer.parse::<i64>().unwrap());
let trimmed_fraction = fraction.trim_end_matches('0');
if trimmed_fraction.is_empty() {
int_with_commas
} else {
format!("{}.{}", int_with_commas, trimmed_fraction)
}
}
_ => num_str, // Should never happen for valid numbers
}
} else {
// If parsing fails, return the original string
num_str
}
}

/// Format [num][i64] with commas (every 3 digits).
fn format_with_commas(num: i64) -> String {
use std::fmt::Write;

let mut result = String::new();
write!(&mut result, "{}", num).unwrap();
let mut formatted = String::new();
for (count, c) in result.chars().rev().enumerate() {
if count != 0 && count % 3 == 0 {
formatted.push(',');
}
formatted.push(c);
}
formatted.chars().rev().collect()
}

pub fn convert_to_tooltip(tooltip: String) -> HtmlElement<html::AnyElement> {
view! {
<span
Expand Down Expand Up @@ -483,6 +541,60 @@ pub fn convert_to_link(data: String, href: String) -> HtmlElement<html::AnyEleme
.into()
}

#[cfg(test)]
mod strip_decimal_if_zero_tests {
fn strip_decimal_if_zero(num_str: &str) -> String {
super::strip_decimal_if_zero(num_str.to_string())
}

#[test]
fn test_whole_numbers() {
assert_eq!(strip_decimal_if_zero("1440.0"), "1,440");
assert_eq!(strip_decimal_if_zero("100.0"), "100");
assert_eq!(strip_decimal_if_zero("1000"), "1,000");
assert_eq!(strip_decimal_if_zero("1000000"), "1,000,000");
}

#[test]
fn test_decimal_numbers() {
assert_eq!(strip_decimal_if_zero("0.54335"), "0.54335");
assert_eq!(strip_decimal_if_zero("3.14"), "3.14");
assert_eq!(strip_decimal_if_zero("0.1000000000000000"), "0.1");
assert_eq!(strip_decimal_if_zero("1234567.89"), "1,234,567.89");
}

#[test]
fn test_high_precision_numbers() {
assert_eq!(strip_decimal_if_zero("0.100000000000000000"), "0.1");
assert_eq!(
strip_decimal_if_zero("1.234567890123456789"),
"1.234567890123456789"
);
}

#[test]
fn test_numbers_with_commas() {
assert_eq!(strip_decimal_if_zero("1,440.0"), "1,440");
assert_eq!(strip_decimal_if_zero("1,000,000.0"), "1,000,000");
assert_eq!(strip_decimal_if_zero("1,234,567.890"), "1,234,567.89");
}

#[test]
fn test_non_numeric_strings() {
assert_eq!(strip_decimal_if_zero("not a number"), "not a number");
assert_eq!(strip_decimal_if_zero("123abc"), "123abc");
}

#[test]
fn test_edge_cases() {
assert_eq!(strip_decimal_if_zero("0.0"), "0");
assert_eq!(strip_decimal_if_zero("-0.0"), "0");
assert_eq!(strip_decimal_if_zero("inf"), "inf");
assert_eq!(strip_decimal_if_zero("-inf"), "-inf");
assert_eq!(strip_decimal_if_zero("NaN"), "NaN");
}
}

pub fn generate_random_string(len: usize) -> String {
iter::repeat(())
.map(|()| rand::thread_rng().sample(Alphanumeric))
Expand Down
2 changes: 1 addition & 1 deletion src/internal_commands/table_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl TableData for Vec<Option<InternalCommandsQueryFeetransfers>> {
internal_command.get_receipient(),
format!("/addresses/accounts/{}", internal_command.get_receipient()),
),
decorate_with_mina_tag(internal_command.get_fee()),
decorate_with_mina_tag(strip_decimal_if_zero(internal_command.get_fee())),
convert_to_pill(internal_command.get_type(), ColorVariant::Grey),
convert_to_title(
print_time_since(&internal_command.get_block_datetime()),
Expand Down
8 changes: 6 additions & 2 deletions src/user_commands/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pub fn CommandSpotlightPage() -> impl IntoView {
label: "Amount".to_string(),
any_el: {
let amount_el = decorate_with_mina_tag(
transaction.get_amount(),
strip_decimal_if_zero(transaction.get_amount()),
);
Some(
if transaction.get_kind() == STAKE_DELEGATION_TYPE {
Expand All @@ -181,7 +181,11 @@ pub fn CommandSpotlightPage() -> impl IntoView {
},
SpotlightEntry {
label: "Fee".to_string(),
any_el: Some(decorate_with_mina_tag(transaction.get_fee())),
any_el: Some(
decorate_with_mina_tag(
strip_decimal_if_zero(transaction.get_fee()),
),
),
..Default::default()
},
SpotlightEntry {
Expand Down
6 changes: 5 additions & 1 deletion src/zk_apps/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ pub fn ZkAppSpotlight() -> impl IntoView {
spotlight_items=vec![
SpotlightEntry {
label: String::from("Balance"),
any_el: Some(decorate_with_mina_tag("1324.593847562".to_string())),
any_el: Some(
decorate_with_mina_tag(
strip_decimal_if_zero("1324.593847562".to_string()),
),
),
..Default::default()
},
SpotlightEntry {
Expand Down
4 changes: 2 additions & 2 deletions src/zk_apps/table_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ impl TableData for Vec<Option<ZkAppData>> {
.map(|opt_app| match opt_app {
Some(zk_app) => vec![
convert_to_link(zk_app.validator_pk.to_string(), "#".to_string()),
decorate_with_mina_tag(zk_app.balance.to_string()),
decorate_with_mina_tag(strip_decimal_if_zero(zk_app.balance.to_string())),
convert_to_pill(format_number(zk_app.nonce.to_string()), ColorVariant::Blue),
convert_to_link(zk_app.delegate.to_string(), "#".to_string()),
],
Expand Down Expand Up @@ -37,7 +37,7 @@ impl TableData for Vec<Option<ZkAppTransactionData>> {
.collect::<Vec<_>>(),
)
.attr("class", "block"),
decorate_with_mina_tag(txn.fee.to_string()),
decorate_with_mina_tag(strip_decimal_if_zero(txn.fee.to_string())),
],
None => vec![],
})
Expand Down