@@ -10,7 +10,6 @@ defmodule PlausibleWeb.Api.StatsController do
1010 Query ,
1111 Comparisons ,
1212 Filters ,
13- Time ,
1413 TableDecider ,
1514 TimeOnPage ,
1615 Dashboard ,
@@ -48,173 +47,6 @@ defmodule PlausibleWeb.Api.StatsController do
4847 end
4948 end
5049
51- @ doc """
52- Returns a time-series based on given parameters.
53-
54- ## Parameters
55-
56- This API accepts the following parameters:
57-
58- * `period` - x-axis of the graph, e.g. `12mo`, `day`, `custom`.
59-
60- * `metric` - y-axis of the graph, e.g. `visits`, `visitors`, `pageviews`.
61- See the Stats API ["Metrics"](https://plausible.io/docs/stats-api#metrics)
62- section for more details. Defaults to `visitors`.
63-
64- * `interval` - granularity of the time-series data. You can think of it as
65- a `GROUP BY` clause. Possible values are `minute`, `hour`, `date`, `week`,
66- and `month`. The default depends on the `period` parameter. Check
67- `Plausible.Query.from/2` for each default.
68-
69- * `filters` - optional filters to drill down data. See the Stats API
70- ["Filtering"](https://plausible.io/docs/stats-api#filtering) section for
71- more details.
72-
73- * `with_imported` - boolean indicating whether to include Google Analytics
74- imported data or not. Defaults to `false`.
75-
76- Full example:
77- ```elixir
78- %{
79- "from" => "2021-09-06",
80- "interval" => "month",
81- "metric" => "visitors",
82- "period" => "custom",
83- "to" => "2021-12-13"
84- }
85- ```
86-
87- ## Response
88-
89- Returns a map with the following keys:
90-
91- * `plot` - list of values for the requested metric representing the y-axis
92- of the graph.
93-
94- * `labels` - list of date times representing the x-axis of the graph.
95-
96- * `present_index` - index of the element representing the current date in
97- `labels` and `plot` lists.
98-
99- * `interval` - the interval used for querying.
100-
101- * `includes_imported` - boolean indicating whether imported data
102- was queried or not.
103-
104- * `full_intervals` - map of dates indicating whether the interval has been
105- cut off by the requested date range or not. For example, if looking at a
106- month week-by-week, some weeks may be cut off by the month boundaries.
107- It's useful to adjust the graph display slightly in case the interval is
108- not 'full' so that the user understands why the numbers might be lower for
109- those partial periods.
110-
111- Full example:
112- ```elixir
113- %{
114- "full_intervals" => %{
115- "2021-09-01" => false,
116- "2021-10-01" => true,
117- "2021-11-01" => true,
118- "2021-12-01" => false
119- },
120- "interval" => "month",
121- "labels" => ["2021-09-01", "2021-10-01", "2021-11-01", "2021-12-01"],
122- "plot" => [0, 0, 0, 0],
123- "present_index" => nil,
124- "includes_imported" => false
125- }
126- ```
127-
128- """
129- def main_graph ( conn , params ) do
130- site = conn . assigns [ :site ]
131- now = conn . private [ :now ]
132-
133- with { :ok , dates } <- parse_date_params ( params ) ,
134- :ok <- validate_interval ( params ) ,
135- :ok <- validate_interval_granularity ( site , params , dates ) ,
136- params <- realtime_period_to_30m ( params ) ,
137- query = Query . from ( site , params , debug_metadata: debug_metadata ( conn ) , now: now ) ,
138- query <- Query . set_include ( query , :trim_relative_date_range , true ) ,
139- { :ok , metric } <- parse_and_validate_graph_metric ( params , query ) do
140- { timeseries_result , comparison_result , _meta } = Stats . timeseries ( site , query , [ metric ] )
141-
142- labels = label_timeseries ( timeseries_result , comparison_result )
143- present_index = present_index_for ( site , query , labels )
144- full_intervals = build_full_intervals ( query , labels )
145-
146- json ( conn , % {
147- metric: metric ,
148- plot: plot_timeseries ( timeseries_result , metric ) ,
149- labels: labels ,
150- comparison_plot: comparison_result && plot_timeseries ( comparison_result , metric ) ,
151- comparison_labels: comparison_result && label_timeseries ( comparison_result , nil ) ,
152- present_index: present_index ,
153- full_intervals: full_intervals
154- } )
155- else
156- { :error , message } when is_binary ( message ) -> bad_request ( conn , message )
157- end
158- end
159-
160- defp plot_timeseries ( timeseries , metric ) do
161- Enum . map ( timeseries , & & 1 [ metric ] )
162- end
163-
164- defp label_timeseries ( main_result , nil ) do
165- Enum . map ( main_result , & & 1 . date )
166- end
167-
168- @ blank_value "__blank__"
169- defp label_timeseries ( main_result , comparison_result ) do
170- blanks_to_fill = Enum . count ( comparison_result ) - Enum . count ( main_result )
171-
172- if blanks_to_fill > 0 do
173- blanks = List . duplicate ( @ blank_value , blanks_to_fill )
174- Enum . map ( main_result , & & 1 . date ) ++ blanks
175- else
176- Enum . map ( main_result , & & 1 . date )
177- end
178- end
179-
180- defp build_full_intervals (
181- % Query { interval: "week" } = query ,
182- labels
183- ) do
184- date_range = Query . date_range ( query )
185- build_intervals ( labels , date_range , & Date . beginning_of_week / 1 , & Date . end_of_week / 1 )
186- end
187-
188- defp build_full_intervals (
189- % Query { interval: "month" } = query ,
190- labels
191- ) do
192- date_range = Query . date_range ( query )
193- build_intervals ( labels , date_range , & Date . beginning_of_month / 1 , & Date . end_of_month / 1 )
194- end
195-
196- defp build_full_intervals ( _query , _labels ) do
197- nil
198- end
199-
200- def build_intervals ( labels , date_range , start_fn , end_fn ) do
201- for label <- labels , into: % { } do
202- case Date . from_iso8601 ( label ) do
203- { :ok , date } ->
204- interval_start = start_fn . ( date )
205- interval_end = end_fn . ( date )
206-
207- within_interval? =
208- Enum . member? ( date_range , interval_start ) && Enum . member? ( date_range , interval_end )
209-
210- { label , within_interval? }
211-
212- _ ->
213- { label , false }
214- end
215- end
216- end
217-
21850 def top_stats ( conn , params ) do
21951 site = conn . assigns [ :site ]
22052
@@ -265,52 +97,6 @@ defmodule PlausibleWeb.Api.StatsController do
26597 end
26698 end
26799
268- defp present_index_for ( site , query , dates ) do
269- case query . interval do
270- "hour" ->
271- current_date =
272- DateTime . now! ( site . timezone )
273- |> Calendar . strftime ( "%Y-%m-%d %H:00:00" )
274-
275- Enum . find_index ( dates , & ( & 1 == current_date ) )
276-
277- "day" ->
278- current_date =
279- DateTime . now! ( site . timezone )
280- |> DateTime . to_date ( )
281- |> Date . to_string ( )
282-
283- Enum . find_index ( dates , & ( & 1 == current_date ) )
284-
285- "week" ->
286- date_range = Query . date_range ( query )
287-
288- current_date =
289- DateTime . now! ( site . timezone )
290- |> DateTime . to_date ( )
291- |> Time . date_or_weekstart ( date_range )
292- |> Date . to_string ( )
293-
294- Enum . find_index ( dates , & ( & 1 == current_date ) )
295-
296- "month" ->
297- current_date =
298- DateTime . now! ( site . timezone )
299- |> DateTime . to_date ( )
300- |> Date . beginning_of_month ( )
301- |> Date . to_string ( )
302-
303- Enum . find_index ( dates , & ( & 1 == current_date ) )
304-
305- "minute" ->
306- current_date =
307- DateTime . now! ( site . timezone )
308- |> Calendar . strftime ( "%Y-%m-%d %H:%M:00" )
309-
310- Enum . find_index ( dates , & ( & 1 == current_date ) )
311- end
312- end
313-
314100 defp fetch_top_stats ( site , query ) do
315101 goal_filter? =
316102 toplevel_goal_filter? ( query )
@@ -1717,75 +1503,6 @@ defmodule PlausibleWeb.Api.StatsController do
17171503 end )
17181504 end
17191505
1720- defp validate_interval ( params ) do
1721- with % { "interval" => interval } <- params ,
1722- true <- Plausible.Stats.Interval . valid? ( interval ) do
1723- :ok
1724- else
1725- % { } ->
1726- :ok
1727-
1728- false ->
1729- values = Enum . join ( Plausible.Stats.Interval . list ( ) , ", " )
1730- { :error , "Invalid value for interval. Accepted values are: #{ values } " }
1731- end
1732- end
1733-
1734- defp validate_interval_granularity ( site , params , dates ) do
1735- case params do
1736- % { "interval" => interval , "period" => "custom" , "from" => _ , "to" => _ } ->
1737- if Plausible.Stats.Interval . valid_for_period? ( "custom" , interval ,
1738- site: site ,
1739- from: dates [ "from" ] ,
1740- to: dates [ "to" ]
1741- ) do
1742- :ok
1743- else
1744- { :error ,
1745- "Invalid combination of interval and period. Custom ranges over 12 months must come with greater granularity, e.g. `period=custom,interval=week`" }
1746- end
1747-
1748- % { "interval" => interval , "period" => period } ->
1749- if Plausible.Stats.Interval . valid_for_period? ( period , interval , site: site ) do
1750- :ok
1751- else
1752- { :error ,
1753- "Invalid combination of interval and period. Interval must be smaller than the selected period, e.g. `period=day,interval=minute`" }
1754- end
1755-
1756- _ ->
1757- :ok
1758- end
1759- end
1760-
1761- defp parse_and_validate_graph_metric ( params , query ) do
1762- metric =
1763- case params [ "metric" ] do
1764- nil -> :visitors
1765- "conversions" -> :visitors
1766- m -> Plausible.Stats.Metrics . from_string! ( m )
1767- end
1768-
1769- requires_goal_filter? = metric in [ :conversion_rate , :events ]
1770- has_goal_filter? = toplevel_goal_filter? ( query )
1771-
1772- requires_page_filter? = metric == :scroll_depth
1773-
1774- has_page_filter? =
1775- Filters . filtering_on_dimension? ( query , "event:page" , behavioral_filters: :ignore )
1776-
1777- cond do
1778- requires_goal_filter? and not has_goal_filter? ->
1779- { :error , "Metric `#{ metric } ` can only be queried with a goal filter" }
1780-
1781- requires_page_filter? and not has_page_filter? ->
1782- { :error , "Metric `#{ metric } ` can only be queried with a page filter" }
1783-
1784- true ->
1785- { :ok , metric }
1786- end
1787- end
1788-
17891506 defp bad_request ( conn , message , extra \\ % { } ) do
17901507 payload = Map . merge ( extra , % { error: message } )
17911508
0 commit comments