From 449e4cfaf681aa7d597831427277ea098b3c1159 Mon Sep 17 00:00:00 2001 From: slightlycyborg Date: Mon, 22 Jul 2019 14:54:01 +0000 Subject: [PATCH] Transaction Chart added to BlockScoutWeb --- apps/block_scout_web/assets/css/app.scss | 1 + .../css/components/_dashboard-banner.scss | 9 +- apps/block_scout_web/assets/js/app.js | 2 +- ...rket_history_chart.js => history_chart.js} | 114 ++++++++++++++---- .../js/lib/transaction_history_chart.js | 114 ++++++++++++++++++ apps/block_scout_web/assets/js/pages/chain.js | 24 +++- apps/block_scout_web/config/config.exs | 8 ++ .../channels/transaction_channel.ex | 4 + .../transaction_history_chart_controller.ex | 38 ++++++ .../controllers/chain_controller.ex | 25 +++- .../lib/block_scout_web/notifier.ex | 12 ++ .../block_scout_web/realtime_event_handler.ex | 1 + .../lib/block_scout_web/router.ex | 5 + .../templates/chain/show.html.eex | 74 +++++++++--- .../templates/layout/app.html.eex | 3 +- apps/block_scout_web/priv/gettext/default.pot | 9 ++ ...nsaction_history_chart_controller_test.exs | 48 ++++++++ 17 files changed, 444 insertions(+), 47 deletions(-) rename apps/block_scout_web/assets/js/lib/{market_history_chart.js => history_chart.js} (54%) create mode 100644 apps/block_scout_web/assets/js/lib/transaction_history_chart.js create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index cf79ee46e8..394a6a5b1f 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -133,6 +133,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color; dashboardLineColorMarket: $dashboard-line-color-market; dashboardLineColorPrice: $dashboard-line-color-price; + dashboardLineColorTransactions: $dashboard-line-color-transactions; primary: $primary; secondary: $secondary; } diff --git a/apps/block_scout_web/assets/css/components/_dashboard-banner.scss b/apps/block_scout_web/assets/css/components/_dashboard-banner.scss index 33d104a9fd..11c43d5583 100644 --- a/apps/block_scout_web/assets/css/components/_dashboard-banner.scss +++ b/apps/block_scout_web/assets/css/components/_dashboard-banner.scss @@ -6,6 +6,7 @@ $dashboard-banner-gradient-end: lighten( $dashboard-banner-network-plain-container-background-color: lighten($dashboard-banner-gradient-end, 5%)!default; $dashboard-line-color-price: lighten($dashboard-banner-gradient-end, 5%) !default; $dashboard-line-color-market: $secondary !default; +$dashboard-line-color-transactions: $warning !default; $dashboard-stats-item-label-color: #fff !default; $dashboard-stats-item-value-color: rgba(#fff, 0.8) !default; $dashboard-banner-chart-legend-label-color: #fff !default; @@ -66,7 +67,7 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa .dashboard-banner-chart-legend { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(var(--numChartData), 0.9fr); padding-bottom: 12px; .dashboard-banner-chart-legend-item { @@ -103,6 +104,10 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa &:nth-child(2)::before { background-color: $dashboard-line-color-market; } + + &:nth-child(3)::before { + background-color: $dashboard-line-color-transactions; + } } .dashboard-banner-chart-legend-label { @@ -219,4 +224,4 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa font-size: 0.9rem; } } -} \ No newline at end of file +} diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index cd690f31db..aa01e24b10 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -42,7 +42,7 @@ import './lib/currency' import './lib/from_now' import './lib/indexing' import './lib/loading_element' -import './lib/market_history_chart' +import './lib/history_chart' import './lib/pending_transactions_toggle' import './lib/pretty_json' import './lib/reload_button' diff --git a/apps/block_scout_web/assets/js/lib/market_history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js similarity index 54% rename from apps/block_scout_web/assets/js/lib/market_history_chart.js rename to apps/block_scout_web/assets/js/lib/history_chart.js index 615f888d8a..59397843eb 100644 --- a/apps/block_scout_web/assets/js/lib/market_history_chart.js +++ b/apps/block_scout_web/assets/js/lib/history_chart.js @@ -44,7 +44,7 @@ const config = { } }, { id: 'marketCap', - position: 'right', + display: false, gridLines: { display: false, drawBorder: false @@ -54,6 +54,20 @@ const config = { maxTicksLimit: 6, drawOnChartArea: false } + }, { + id: 'numTransactions', + display: false, + position: 'right', + gridLines: { + display: false, + drawBorder: false + }, + ticks: { + beginAtZero: true, + callback: (value, index, values) => `${numeral(value).format('0,0')}`, + maxTicksLimit: 4, + fontColor: sassVariables.dashboardBannerChartAxisFontColor + } }] }, tooltips: { @@ -66,6 +80,8 @@ const config = { return `${label}: ${formatUsdValue(yLabel)}` } else if (datasets[datasetIndex].yAxisID === 'marketCap') { return `${label}: ${formatUsdValue(yLabel)}` + } else if (datasets[datasetIndex].yAxisID === 'numTransactions') { + return `${label}: ${yLabel}` } else { return yLabel } @@ -88,32 +104,65 @@ function getMarketCapData (marketHistoryData, availableSupply) { } class MarketHistoryChart { - constructor (el, availableSupply, marketHistoryData) { + constructor (el, availableSupply, marketHistoryData, dataConfig) { + + var axes = config.options.scales.yAxes.reduce(function(solution, elem){ + solution[elem.id] = elem + return solution + }, + {}) + this.price = { label: window.localized['Price'], yAxisID: 'price', - data: getPriceData(marketHistoryData), + data: [], fill: false, pointRadius: 0, backgroundColor: sassVariables.dashboardLineColorPrice, borderColor: sassVariables.dashboardLineColorPrice, lineTension: 0 } + if (dataConfig.market == undefined || dataConfig.market.indexOf("price") == -1){ + this.price.hidden = true + axes["price"].display = false + } + this.marketCap = { label: window.localized['Market Cap'], yAxisID: 'marketCap', - data: getMarketCapData(marketHistoryData, availableSupply), + data: [], fill: false, pointRadius: 0, backgroundColor: sassVariables.dashboardLineColorMarket, borderColor: sassVariables.dashboardLineColorMarket, lineTension: 0 } + if (dataConfig.market == undefined || dataConfig.market.indexOf("market_cap") == -1){ + this.marketCap.hidden = true + axes["marketCap"].display = false + } + + this.numTransactions = { + label: window.localized['Tx/day'], + yAxisID: 'numTransactions', + data: [], + fill: false, + pointRadius: 0, + backgroundColor: sassVariables.dashboardLineColorMarket, + borderColor: sassVariables.dashboardLineColorTransactions, + lineTension: 0, + } + if (dataConfig.transactions == undefined || dataConfig.transactions.indexOf("transactions_per_day") == -1){ + this.numTransactions.hidden = true + axes["numTransactions"].display = false + } + this.availableSupply = availableSupply - config.data.datasets = [this.price, this.marketCap] + //TODO: This is where we dynamically append datasets + config.data.datasets = [this.price, this.marketCap, this.numTransactions] this.chart = new Chart(el, config) } - update (availableSupply, marketHistoryData) { + updateMarketHistory (availableSupply, marketHistoryData) { this.price.data = getPriceData(marketHistoryData) if (this.availableSupply !== null && typeof this.availableSupply === 'object') { const today = new Date().toJSON().slice(0, 10) @@ -124,31 +173,50 @@ class MarketHistoryChart { } this.chart.update() } + updateTransactionHistory (transaction_history) { + this.numTransactions.data = transaction_history.map(dataPoint => { + return {x:dataPoint.date, y:dataPoint.number_of_transactions} + }) + this.chart.update() + } } export function createMarketHistoryChart (el) { - const dataPath = el.dataset.market_history_chart_path + const dataPaths = $(el).data('history_chart_paths') + const dataConfig = $(el).data('history_chart_config') + const $chartLoading = $('[data-chart-loading-message]') const $chartError = $('[data-chart-error-message]') - const chart = new MarketHistoryChart(el, 0, []) - $.getJSON(dataPath, {type: 'JSON'}) - .done(data => { - const availableSupply = JSON.parse(data.supply_data) - const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data)) - $(el).show() - chart.update(availableSupply, marketHistoryData) - }) - .fail(() => { - $chartError.show() - }) - .always(() => { - $chartLoading.hide() - }) - return chart + const chart = new MarketHistoryChart(el, 0, [], dataConfig) + Object.keys(dataPaths).forEach(function(history_source){ + $.getJSON(dataPaths[history_source], {type: 'JSON'}) + .done(data => { + switch(history_source){ + case "market": + const availableSupply = JSON.parse(data.supply_data) + const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data)) + $(el).show() + chart.updateMarketHistory(availableSupply, marketHistoryData) + break; + case "transaction": + const transaction_history = JSON.parse(data.history_data) + + $(el).show() + chart.updateTransactionHistory(transaction_history) + break; + } + }) + .fail(() => { + $chartError.show() + }) + .always(() => { + $chartLoading.hide() + })}) + return chart; } $('[data-chart-error-message]').on('click', _event => { $('[data-chart-loading-message]').show() $('[data-chart-error-message]').hide() - createMarketHistoryChart($('[data-chart="marketHistoryChart"]')[0]) + createMarketHistoryChart($('[data-chart="historyChart"]')[0]) }) diff --git a/apps/block_scout_web/assets/js/lib/transaction_history_chart.js b/apps/block_scout_web/assets/js/lib/transaction_history_chart.js new file mode 100644 index 0000000000..1c0d7fb668 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/transaction_history_chart.js @@ -0,0 +1,114 @@ +import $ from 'jquery' +import Chart from 'chart.js' +import humps from 'humps' +import numeral from 'numeral' +import { formatUsdValue } from '../lib/currency' +import sassVariables from '../../css/app.scss' + +const config = { + type: 'line', + responsive: true, + data: { + datasets: [] + }, + options: { + legend: { + display: false + }, + scales: { + xAxes: [{ + gridLines: { + display: false, + drawBorder: false + }, + type: 'time', + time: { + unit: 'day', + stepSize: 14 + }, + ticks: { + fontColor: sassVariables.dashboardBannerChartAxisFontColor + } + }], + yAxes: [{ + id: 'num_transactions', + gridLines: { + display: false, + drawBorder: false + }, + ticks: { + beginAtZero: true, + callback: (value, index, values) => `$${numeral(value).format('0,0.00')}`, + maxTicksLimit: 4, + fontColor: sassVariables.dashboardBannerChartAxisFontColor + } + }] + }, + tooltips: { + mode: 'index', + intersect: false, + callbacks: { + label: ({datasetIndex, yLabel}, {datasets}) => { + const label = datasets[datasetIndex].label + if (datasets[datasetIndex].yAxisID === 'num_transactions') { + return `${label}: ${formatUsdValue(yLabel)}` + } else { + return yLabel + } + } + } + } + } +} + +function transformData (marketHistoryData) { + return marketHistoryData.map( + ({ date, num_transactions }) => ({x: date, y: num_transactions})) +} + +class TransactionHistoryChart { + constructor (el, transactionHistoryData) { + this.num_transactions = { + label: window.localized['Price'], + yAxisID: 'num_transactions', + data: transformData(transactionHistoryData), + fill: false, + pointRadius: 0, + backgroundColor: sassVariables.dashboardLineColorPrice, + borderColor: sassVariables.dashboardLineColorPrice, + lineTension: 0 + } + config.data.datasets = [this.num_transactions] + this.chart = new Chart(el, config) + } + update (transactionHistoryData) { + this.num_transactions.data = transformData(TransactionHistoryData) + this.chart.update() + } +} + +export function createTransactionHistoryChart (el) { + const dataPath = el.dataset.transaction_history_chart_path + const $chartLoading = $('[data-chart-loading-message]') + const $chartError = $('[data-chart-error-message]') + const chart = new TransactionHistoryChart(el, 0, []) + $.getJSON(dataPath, {type: 'JSON'}) + .done(data => { + const transactionStats = JSON.parse(data.history_data) + $(el).show() + chart.update(transactionStats) + }) + .fail(() => { + $chartError.show() + }) + .always(() => { + $chartLoading.hide() + }) + return chart +} + +$('[data-chart-error-message]').on('click', _event => { + $('[data-chart-loading-message]').show() + $('[data-chart-error-message]').hide() + createTransactionHistoryChart($('[data-chart="marketHistoryChart"]')[0]) +}) diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js index f4cd0e6628..5358ced62d 100644 --- a/apps/block_scout_web/assets/js/pages/chain.js +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -7,7 +7,7 @@ import { exchangeRateChannel, formatUsdValue } from '../lib/currency' import { createStore, connectElements } from '../lib/redux_helpers.js' import { batchChannel } from '../lib/utils' import listMorph from '../lib/list_morph' -import { createMarketHistoryChart } from '../lib/market_history_chart' +import { createMarketHistoryChart } from '../lib/history_chart' const BATCH_THRESHOLD = 6 @@ -103,6 +103,11 @@ function baseReducer (state = initialState, action) { }) } } + case 'RECIEVED_UPDATED_TRANSACTION_STATS':{ + return Object.assign({}, state, { + transactionStats: action.msg.stats, + }) + } case 'START_TRANSACTIONS_FETCH': return Object.assign({}, state, { transactionsError: false, transactionsLoading: true }) case 'TRANSACTIONS_FETCHED': @@ -137,13 +142,17 @@ function withMissingBlocks (reducer) { let chart const elements = { - '[data-chart="marketHistoryChart"]': { + '[data-chart="historyChart"]': { load ($el) { chart = createMarketHistoryChart($el[0]) }, render ($el, state, oldState) { - if (!chart || (oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData) || !state.availableSupply) return - chart.update(state.availableSupply, state.marketHistoryData) + if (chart && !(oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData) && state.availableSupply) { + chart.updateMarketHistory(state.availableSupply, state.marketHistoryData) + } + if (chart && !(_.isEqual(oldState.transactionStats, state.transactionStats))){ + chart.updateTransactionHistory(state.transactionStats) + } } }, '[data-selector="transaction-count"]': { @@ -284,6 +293,13 @@ if ($chainDetailsPage.length) { type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }))) + + const transactionStatsChannel = socket.channel('transactions:stats') + transactionStatsChannel.join() + transactionStatsChannel.on('update', msg => store.dispatch({ + type: 'RECIEVED_UPDATED_TRANSACTION_STATS', + msg: msg + })) } function loadTransactions (store) { diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index 28d142eb5c..88f08c73dc 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -61,6 +61,14 @@ config :block_scout_web, BlockScoutWeb.SocialMedia, facebook: "PoaNetwork", instagram: "PoaNetwork" +# Configures History +config :block_scout_web, +chart_config: %{market: [:price,:market_cap], transactions: [:transactions_per_day]} + +config :block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, + history_size: 30 #days + + config :ex_cldr, default_locale: "en", locales: ["en"], diff --git a/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex index e5ce03b86f..54c2da9fbf 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex @@ -18,6 +18,10 @@ defmodule BlockScoutWeb.TransactionChannel do {:ok, %{}, socket} end + def join("transactions:stats", _params, socket) do + {:ok, %{}, socket} + end + def join("transactions:" <> _transaction_hash, _params, socket) do {:ok, %{}, socket} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex new file mode 100644 index 0000000000..ca1346ba56 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/transaction_history_chart_controller.ex @@ -0,0 +1,38 @@ +defmodule BlockScoutWeb.Chain.TransactionHistoryChartController do + use BlockScoutWeb, :controller + + alias Explorer.Chain.Transaction.History.TransactionStats + + def show(conn, _params) do + with true <- ajax?(conn) do + [{:history_size, history_size}] = Application.get_env(:block_scout_web, __MODULE__, 30) + + latest = Date.utc_today() + earliest = Date.add(latest, -1 * history_size) + + transaction_history_data = TransactionStats.by_date_range(earliest, latest) + |> extract_history + |> encode_transaction_history_data + + json(conn, %{ + history_data: transaction_history_data, + }) + else + _ -> unprocessable_entity(conn) + end + end + + defp extract_history(db_results) do + Enum.map(db_results, fn row -> + %{date: row.date, number_of_transactions: row.number_of_transactions} end) + end + + defp encode_transaction_history_data(transaction_history_data) do + transaction_history_data + |> Jason.encode() + |> case do + {:ok, data} -> data + _ -> [] + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 40a0fd3bbb..303c7cb6fe 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -4,6 +4,7 @@ defmodule BlockScoutWeb.ChainController do alias BlockScoutWeb.ChainView alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.{Address, Block, Transaction} + alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Chain.Supply.RSK alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token @@ -14,6 +15,7 @@ defmodule BlockScoutWeb.ChainController do transaction_estimated_count = Chain.transaction_estimated_count() block_count = Chain.block_estimated_count() + market_cap_calculation = case Application.get_env(:explorer, :supply) do RSK -> @@ -25,20 +27,41 @@ defmodule BlockScoutWeb.ChainController do exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + transaction_stats = get_transaction_stats() + + chart_data_paths = %{market: market_history_chart_path(conn, :show), + transaction: transaction_history_chart_path(conn, :show)} + + chart_config = Application.get_env(:block_scout_web, :chart_config, %{}) + render( conn, "show.html", address_count: Chain.count_addresses_with_balance_from_cache(), average_block_time: AverageBlockTime.average_block_time(), exchange_rate: exchange_rate, - chart_data_path: market_history_chart_path(conn, :show), + chart_config: chart_config, + chart_config_json: Jason.encode!(chart_config), + chart_data_paths: chart_data_paths, market_cap_calculation: market_cap_calculation, transaction_estimated_count: transaction_estimated_count, transactions_path: recent_transactions_path(conn, :index), + transaction_stats: transaction_stats, block_count: block_count ) end + def get_transaction_stats() do + stats_scale = date_range(1) + TransactionStats.by_date_range(stats_scale.earliest, stats_scale.latest) + end + + def date_range(num_days) do + today = Date.utc_today() + x_days_back = Date.add(today, -1*(num_days-1)) + %{earliest: x_days_back, latest: today} + end + def search(conn, %{"q" => query}) do query |> String.trim() diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index a5bc99e242..0139a9020a 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -7,6 +7,7 @@ defmodule BlockScoutWeb.Notifier do alias BlockScoutWeb.Endpoint alias Explorer.{Chain, Market, Repo} alias Explorer.Chain.{Address, InternalTransaction, Transaction} + alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token @@ -85,6 +86,17 @@ defmodule BlockScoutWeb.Notifier do |> Enum.each(&broadcast_transaction/1) end + def handle_event({:chain_event, :transaction_stats}) do + today = Date.utc_today() + [{:history_size, history_size}] = Application.get_env(:block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, 30) + + x_days_back = Date.add(today, -1 * history_size) + stats = TransactionStats.by_date_range(x_days_back, today) + |> Enum.map(fn item -> Map.drop(item, [:__meta__]) end) + + Endpoint.broadcast("transactions:stats", "update", %{stats: stats}) + end + def handle_event(_), do: nil @doc """ diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index f0f73f402c..508ba310c1 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -25,6 +25,7 @@ defmodule BlockScoutWeb.RealtimeEventHandler do Subscriber.to(:address_coin_balances, :on_demand) # Does not come from the indexer Subscriber.to(:exchange_rate) + Subscriber.to(:transaction_stats) {:ok, []} end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index d13dc9c43e..2ec112cda7 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -81,6 +81,11 @@ defmodule BlockScoutWeb.Router do singleton: true ) + resources("/transaction_history_chart", Chain.TransactionHistoryChartController, + only: [:show], + singleton: true + ) + resources "/blocks", BlockController, only: [:index, :show], param: "hash_or_number" do resources("/transactions", BlockTransactionController, only: [:index], as: :transaction) end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 0c29001697..d5f0fcab0f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -15,24 +15,68 @@ - + + "<%= key %>":"<%= value %>" + <%= if x<(map_size(@chart_data_paths)-1) do %> + , + <% end %> + <% end %>}' + + data-history_chart_config = '<%= @chart_config_json %>' + + width="350" height="152" style="display: none;"> +
-
- - <%= gettext "Price" %> - - - -
-
- - <%= gettext "Market Cap" %> - - - -
+ <%= if Map.has_key?(@chart_config, :market) do %> + + <%# THE FOLLOWING LINE PREVENTS COPY/PASTE ERRORS %> + <%# Explicity put @chart_config.market in a variable %> + <%# This is done so that when people add a new chart source, x, %> + <%# They wont just access @chart_config.x w/o first checking if x exists%> + <% market_chart_config = @chart_config.market%> + + <%= if Enum.member?(market_chart_config, :price) do %> +
+ + <%= gettext "Price" %> + + + +
+ <% end %> + <%= if Enum.member?(@chart_config.market, :market_cap) do %> +
+ + <%= gettext "Market Cap" %> + + + +
+ <% end %> + <% end %> + <%= if Map.has_key?(@chart_config, :transactions) do %> + + <% transaction_chart_config = @chart_config.transactions%> + <%= if Enum.member?(transaction_chart_config, :transactions_per_day) do %> +
+ + <%= gettext "Tx/day" %> + + + <%= Enum.at(@transaction_stats, 0).number_of_transactions %> + +
+ <% end %> + <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index b3b6ba0665..c4cdfcb788 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -52,7 +52,8 @@ 'Less than': '<%= gettext("Less than") %>', 'Market Cap': '<%= gettext("Market Cap") %>', 'Price': '<%= gettext("Price") %>', - 'Ether': '<%= gettext("Ether") %>' + 'Ether': '<%= gettext("Ether") %>', + 'Tx/day': '<%= gettext("Tx/day") %>' } diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 41aa7bf553..96ed671590 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -695,6 +695,15 @@ msgstr "" msgid "Success" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:34 +#: lib/block_scout_web/templates/transaction/overview.html.eex:84 +msgid "Tx/day" +msgstr "" + + + #, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_tile.html.eex:34 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs new file mode 100644 index 0000000000..618cb469c8 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/chain/transaction_history_chart_controller_test.exs @@ -0,0 +1,48 @@ +defmodule BlockScoutWeb.Chain.TransactionHistoryChartControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Chain.TransactionHistoryChartController + alias Explorer.Chain.Transaction.History.TransactionStats + alias Explorer.Repo + + describe "GET show/2" do + test "returns error when not an ajax request" do + path = transaction_history_chart_path(BlockScoutWeb.Endpoint, :show) + + conn = get(build_conn(), path) + + assert conn.status == 422 + end + + test "returns ok when request is ajax" do + path = transaction_history_chart_path(BlockScoutWeb.Endpoint, :show) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert json_response(conn, 200) + end + + test "returns appropriate json data" do + td = Date.utc_today() + dts = [Date.utc_today(), Date.add(td, -1), Date.add(td, -2)] + some_transaction_stats = [%{date: Enum.at(dts, 0), number_of_transactions: 10}, + %{date: Enum.at(dts, 1), number_of_transactions: 20}, + %{date: Enum.at(dts, 2), number_of_transactions: 30}] + + {num, _} = Repo.insert_all(TransactionStats, some_transaction_stats) + assert num == 3 + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> TransactionHistoryChartController.show([]) + + expected = "{\"history_data\":\"[{\\\"date\\\":\\\"2019-07-12\\\",\\\"number_of_transactions\\\":10},{\\\"date\\\":\\\"2019-07-11\\\",\\\"number_of_transactions\\\":20},{\\\"date\\\":\\\"2019-07-10\\\",\\\"number_of_transactions\\\":30}]\"}" + + assert conn.resp_body =~ expected + end + end +end