feat: render block time with up to one decimal point of accuracy

pull/1300/head
zachdaniel 6 years ago committed by Luke Imhoff
parent 553decc9f1
commit 1c77ef0e19
  1. 2
      apps/block_scout_web/lib/block_scout_web/channels/block_channel.ex
  2. 2
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  3. 10
      apps/explorer/lib/explorer/counters/average_block_time.ex
  4. 99
      apps/explorer/lib/explorer/counters/average_block_time_duration_format.ex
  5. 2
      apps/explorer/test/explorer/counters/average_block_time_test.exs

@ -36,7 +36,7 @@ defmodule BlockScoutWeb.BlockChannel do
)
push(socket, "new_block", %{
average_block_time: Timex.format_duration(average_block_time, :humanized),
average_block_time: Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat),
chain_block_html: rendered_chain_block,
block_html: rendered_block,
block_number: block.number,

@ -41,7 +41,7 @@
<%= gettext "Average block time" %>
</span>
<span class="dashboard-banner-network-stats-value" data-selector="average-block-time">
<%= Timex.format_duration(average_block_time, :humanized) %>
<%= Timex.format_duration(average_block_time, Explorer.Counters.AverageBlockTimeDurationFormat) %>
</span>
</div>
<% end %>

@ -26,7 +26,7 @@ defmodule Explorer.Counters.AverageBlockTime do
|> Keyword.fetch!(:enabled)
if enabled? do
block = if block, do: {block.number, DateTime.to_unix(block.timestamp)}
block = if block, do: {block.number, DateTime.to_unix(block.timestamp, :millisecond)}
GenServer.call(__MODULE__, {:average_block_time, block})
else
{:error, :disabled}
@ -48,7 +48,7 @@ defmodule Explorer.Counters.AverageBlockTime do
timestamps_query
|> Repo.all()
|> Enum.map(fn {number, timestamp} ->
{number, DateTime.to_unix(timestamp)}
{number, DateTime.to_unix(timestamp, :millisecond)}
end)
{:ok, %{timestamps: timestamps, average: average_distance(timestamps)}}
@ -73,8 +73,8 @@ defmodule Explorer.Counters.AverageBlockTime do
%{state | timestamps: timestamps, average: average_distance(timestamps)}
end
defp average_distance([]), do: Duration.from_seconds(0)
defp average_distance([_]), do: Duration.from_seconds(0)
defp average_distance([]), do: Duration.from_milliseconds(0)
defp average_distance([_]), do: Duration.from_milliseconds(0)
defp average_distance(timestamps) do
durations = durations(timestamps)
@ -88,7 +88,7 @@ defmodule Explorer.Counters.AverageBlockTime do
average
|> round()
|> Duration.from_seconds()
|> Duration.from_milliseconds()
end
defp durations(timestamps) do

@ -0,0 +1,99 @@
defmodule Explorer.Counters.AverageBlockTimeDurationFormat do
@moduledoc """
A `Timex.Format.Duration.Formatter` that renders the most significant unit out to one decimal point.
"""
use Timex.Format.Duration.Formatter
alias Timex.Translator
@millisecond 1
@second @millisecond * 1000
@minute @second * 60
@hour @minute * 60
@day @hour * 24
@week @day * 7
@month @day * 30
@year @day * 365
@unit_term_mapping [
{@year, "year"},
{@month, "month"},
{@week, "week"},
{@day, "day"},
{@hour, "hour"},
{@minute, "minute"},
{@second, "second"},
{@millisecond, "millisecond"}
]
@doc """
Formats a duration as a single value and a decimal part.
See `lformat/2` for more information.
iex> use Timex
...> Duration.from_erl({0, 65, 0}) |> #{__MODULE__}.format()
"1.1 minutes"
"""
@spec format(Duration.t()) :: String.t() | {:error, term}
def format(%Duration{} = duration), do: lformat(duration, Translator.default_locale())
def format(_), do: {:error, :invalid_duration}
@doc """
Formats a duration as a single value and a decimal part.
Chooses the greatest whole unit available from:
* year
* month
* week
* day
* hour
* minute
* second
* millisecond
Accepts a translation locale and honors it for the units.
iex> use Timex
...> Duration.from_erl({0, 65, 0}) |> #{__MODULE__}.lformat("en")
"1.1 minutes"
iex> use Timex
...> Duration.from_erl({0, 0, 0}) |> #{__MODULE__}.lformat("en")
"0 milliseconds"
"""
def lformat(%Duration{} = duration, locale) do
duration
|> Duration.to_milliseconds()
|> round()
|> do_format(locale)
end
def lformat(_, _locale), do: {:error, :invalid_duration}
defp do_format(0, locale) do
Translator.translate_plural(locale, "units", "%{count}, millisecond", "%{count} milliseconds", 0)
end
for {unit, name} <- @unit_term_mapping do
defp do_format(value, locale) when value >= unquote(unit) do
format_unit(locale, unquote(unit), value, unquote(name))
end
end
defp format_unit(locale, unit, value, singular) do
decimal_value = value / unit
truncated = trunc(decimal_value)
# remove any trailing `.0`
formatted_value =
if decimal_value == truncated do
truncated
else
Float.round(decimal_value, 1)
end
Translator.translate_plural(locale, "units", "%{count} #{singular}", "%{count} #{singular}s", formatted_value)
end
end

@ -1,6 +1,8 @@
defmodule Explorer.Counters.AverageBlockTimeTest do
use Explorer.DataCase
doctest Explorer.Counters.AverageBlockTimeDurationFormat
alias Explorer.Counters.AverageBlockTime
defp block(number, last, duration), do: %{number: number, timestamp: Timex.shift(last, seconds: duration)}

Loading…
Cancel
Save