2021-02-05 12:23:30 +03:00
defmodule PlausibleWeb.Api.ExternalStatsController do
use PlausibleWeb , :controller
use Plausible.Repo
use Plug.ErrorHandler
alias Plausible.Stats.Query
def realtime_visitors ( conn , _params ) do
site = conn . assigns [ :site ]
query = Query . from ( site . timezone , %{ " period " = > " realtime " } )
json ( conn , Plausible.Stats.Clickhouse . current_visitors ( site , query ) )
end
def aggregate ( conn , params ) do
2021-02-10 17:27:58 +03:00
with :ok <- validate_date ( params ) ,
:ok <- validate_period ( params ) ,
:ok <- validate_metrics ( params ) do
site = conn . assigns [ :site ]
query = Query . from ( site . timezone , params )
metrics =
params [ " metrics " ]
|> String . split ( " , " )
|> Enum . map ( & String . trim / 1 )
result =
if params [ " compare " ] == " previous_period " do
2021-02-22 11:21:25 +03:00
prev_query = Query . shift_back ( query , site )
2021-02-10 17:27:58 +03:00
[ prev_result , curr_result ] =
Task . await_many ( [
Task . async ( fn -> Plausible.Stats . aggregate ( site , prev_query , metrics ) end ) ,
Task . async ( fn -> Plausible.Stats . aggregate ( site , query , metrics ) end )
] )
Enum . map ( curr_result , fn { metric , %{ value : current_val } } ->
%{ value : prev_val } = prev_result [ metric ]
{ metric ,
%{
value : current_val ,
change : percent_change ( prev_val , current_val )
} }
end )
|> Enum . into ( %{ } )
else
Plausible.Stats . aggregate ( site , query , metrics )
end
json ( conn , result )
else
{ :error , msg } ->
conn
|> put_status ( 400 )
|> json ( %{ error : msg } )
end
2021-02-05 12:23:30 +03:00
end
2021-02-22 11:21:25 +03:00
def breakdown ( conn , params ) do
with :ok <- validate_date ( params ) ,
:ok <- validate_period ( params ) ,
{ :ok , property } <- validate_property ( params ) do
site = conn . assigns [ :site ]
query = Query . from ( site . timezone , params )
metrics =
Map . get ( params , " metrics " , " visitors " )
|> String . split ( " , " )
limit = String . to_integer ( Map . get ( params , " limit " , " 100 " ) )
page = String . to_integer ( Map . get ( params , " page " , " 1 " ) )
results = Plausible.Stats . breakdown ( site , query , property , metrics , { limit , page } )
json ( conn , %{ " results " = > results } )
else
{ :error , msg } ->
conn
|> put_status ( 400 )
|> json ( %{ error : msg } )
end
end
defp validate_property ( %{ " property " = > property } ) do
{ :ok , property }
end
defp validate_property ( _ ) do
{ :error ,
" The `property` parameter is required. Please provide at least one property to show a breakdown by. " }
end
2021-02-05 12:23:30 +03:00
def timeseries ( conn , params ) do
2021-02-10 17:27:58 +03:00
with :ok <- validate_date ( params ) ,
:ok <- validate_period ( params ) ,
:ok <- validate_interval ( params ) do
site = conn . assigns [ :site ]
query = Query . from ( site . timezone , params )
{ plot , labels } = Plausible.Stats . timeseries ( site , query )
graph =
Enum . zip ( labels , plot )
2021-02-22 11:21:25 +03:00
|> Enum . map ( fn { label , val } -> %{ date : label , visitors : val } end )
2021-02-10 17:27:58 +03:00
|> Enum . into ( [ ] )
2021-02-22 11:21:25 +03:00
json ( conn , %{ " results " = > graph } )
2021-02-10 17:27:58 +03:00
else
{ :error , msg } ->
conn
|> put_status ( 400 )
|> json ( %{ error : msg } )
end
2021-02-05 12:23:30 +03:00
end
def handle_errors ( conn , %{ kind : kind , reason : reason } ) do
json ( conn , %{ error : Exception . format_banner ( kind , reason ) } )
end
defp percent_change ( old_count , new_count ) do
cond do
old_count == 0 and new_count > 0 ->
100
old_count == 0 and new_count == 0 ->
0
true ->
round ( ( new_count - old_count ) / old_count * 100 )
end
end
2021-02-10 17:27:58 +03:00
2021-02-22 11:21:25 +03:00
defp validate_date ( %{ " period " = > " custom " } = params ) do
with { :ok , date } <- Map . fetch ( params , " date " ) ,
[ from , to ] <- String . split ( date , " , " ) ,
{ :ok , _from } <- Date . from_iso8601 ( String . trim ( from ) ) ,
{ :ok , _to } <- Date . from_iso8601 ( String . trim ( to ) ) do
:ok
else
:error ->
{ :error ,
" The `date` parameter is required when using a custom period. See https://plausible.io/docs/stats-api # time-periods " }
_ ->
{ :error ,
" Invalid format for `date` parameter. When using a custom period, please include two ISO-8601 formatted dates joined by a comma. See https://plausible.io/docs/stats-api # time-periods " }
end
end
2021-02-10 17:27:58 +03:00
defp validate_date ( %{ " date " = > date } ) do
case Date . from_iso8601 ( date ) do
{ :ok , _date } ->
:ok
{ :error , msg } ->
{ :error ,
" Error parsing `date` parameter: #{ msg } . Please specify a valid date in ISO-8601 format. " }
end
end
defp validate_date ( _ ) , do : :ok
defp validate_period ( %{ " period " = > period } ) do
if period in [ " day " , " 7d " , " 30d " , " month " , " 6mo " , " 12mo " , " custom " ] do
:ok
else
{ :error ,
" Error parsing `period` parameter: invalid period ` #{ period } `. Please find accepted values in our docs: https://plausible.io/docs/stats-api # time-periods " }
end
end
defp validate_period ( _ ) , do : :ok
@valid_metrics [ " pageviews " , " visitors " , " bounce_rate " , " visit_duration " ]
@valid_metrics_str Enum . map ( @valid_metrics , & ( " ` " <> &1 <> " ` " ) ) |> Enum . join ( " , " )
defp validate_metrics ( %{ " metrics " = > metrics_str } ) do
metrics =
metrics_str
|> String . split ( " , " )
|> Enum . map ( & String . trim / 1 )
bad_metric = Enum . find ( metrics , fn metric -> metric not in @valid_metrics end )
if bad_metric do
{ :error ,
" Error parsing `metrics` parameter: invalid metric ` #{ bad_metric } `. Valid metrics are #{
@valid_metrics_str
} " }
else
:ok
end
end
defp validate_metrics ( _ ) , do : :ok
@valid_intervals [ " date " , " month " ]
@valid_intervals_str Enum . map ( @valid_intervals , & ( " ` " <> &1 <> " ` " ) ) |> Enum . join ( " , " )
defp validate_interval ( %{ " interval " = > interval } ) do
if interval in @valid_intervals do
:ok
else
{ :error ,
" Error parsing `interval` parameter: invalid interval ` #{ interval } `. Valid intervals are #{
@valid_intervals_str
} " }
end
end
defp validate_interval ( _ ) , do : :ok
2021-02-05 12:23:30 +03:00
end