diff --git a/assets/js/dashboard/datepicker.js b/assets/js/dashboard/datepicker.js
index 1e7816e62..84622b9e3 100644
--- a/assets/js/dashboard/datepicker.js
+++ b/assets/js/dashboard/datepicker.js
@@ -253,6 +253,8 @@ class DatePicker extends React.Component {
return 'Last 6 months'
} if (query.period === '12mo') {
return 'Last 12 months'
+ } if (query.period === 'all') {
+ return 'All time'
} if (query.period === 'custom') {
return `${formatDayShort(query.from)} - ${formatDayShort(query.to)}`
}
@@ -338,6 +340,7 @@ class DatePicker extends React.Component {
{this.renderLink("12mo", "Last 12 months")}
+ {this.renderLink("all", "All time")}
this.setState({mode: 'calendar'}, this.openCalendar)}
onKeyPress={() => this.setState({mode: 'calendar'}, this.openCalendar)}
diff --git a/assets/js/dashboard/query.js b/assets/js/dashboard/query.js
index 6d68bd2df..6a4ff34c6 100644
--- a/assets/js/dashboard/query.js
+++ b/assets/js/dashboard/query.js
@@ -3,7 +3,7 @@ import { Link, withRouter } from 'react-router-dom'
import {formatDay, formatMonthYYYY, nowForSite, parseUTCDate} from './util/date'
import * as storage from './util/storage'
-const PERIODS = ['realtime', 'day', 'month', '7d', '30d', '6mo', '12mo', 'custom']
+const PERIODS = ['realtime', 'day', 'month', '7d', '30d', '6mo', '12mo', 'all', 'custom']
export function parseQuery(querystring, site) {
const q = new URLSearchParams(querystring)
diff --git a/lib/plausible/stats/query.ex b/lib/plausible/stats/query.ex
index 525364f8f..8310c916b 100644
--- a/lib/plausible/stats/query.ex
+++ b/lib/plausible/stats/query.ex
@@ -146,6 +146,27 @@ defmodule Plausible.Stats.Query do
|> maybe_include_imported(site, params)
end
+ def from(site, %{"period" => "all"} = params) do
+ end_date =
+ today(site.timezone)
+ |> Timex.end_of_month()
+
+ start_date =
+ site.inserted_at
+ |> Timex.Timezone.convert("UTC")
+ |> Timex.Timezone.convert(site.timezone)
+ |> Timex.beginning_of_month()
+
+ %__MODULE__{
+ period: "all",
+ date_range: Date.range(start_date, end_date),
+ interval: Map.get(params, "interval", "month"),
+ filters: parse_filters(params),
+ sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
+ }
+ |> maybe_include_imported(site, params)
+ end
+
def from(site, %{"period" => "custom", "from" => from, "to" => to} = params) do
new_params =
params
diff --git a/test/plausible/stats/query_test.exs b/test/plausible/stats/query_test.exs
index 061989db2..5c248fa63 100644
--- a/test/plausible/stats/query_test.exs
+++ b/test/plausible/stats/query_test.exs
@@ -2,7 +2,11 @@ defmodule Plausible.Stats.QueryTest do
use ExUnit.Case, async: true
alias Plausible.Stats.Query
- @site %Plausible.Site{timezone: "UTC"}
+ @site_inserted_at ~D[2020-01-01]
+ @site %Plausible.Site{
+ timezone: "UTC",
+ inserted_at: @site_inserted_at
+ }
test "parses day format" do
q = Query.from(@site, %{"period" => "day", "date" => "2019-01-01"})
@@ -56,6 +60,23 @@ defmodule Plausible.Stats.QueryTest do
assert q.interval == "month"
end
+ test "parses all time" do
+ q = Query.from(@site, %{"period" => "all"})
+
+ assert q.date_range.first == @site_inserted_at
+ assert q.date_range.last == Timex.today() |> Timex.end_of_month()
+ assert q.period == "all"
+ assert q.interval == "month"
+ end
+
+ test "parses all time in correct timezone" do
+ site = Map.put(@site, :timezone, "America/Cancun")
+ q = Query.from(site, %{"period" => "all"})
+
+ assert q.date_range.first == ~D[2019-12-01]
+ assert q.date_range.last == Timex.today("America/Cancun") |> Timex.end_of_month()
+ end
+
test "defaults to 30 days format" do
assert Query.from(@site, %{}) == Query.from(@site, %{"period" => "30d"})
end
diff --git a/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs b/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs
index 720edeb81..282b4fad4 100644
--- a/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs
@@ -137,7 +137,52 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
assert Enum.sum(plot) == 4
end
- # TODO: missing 6, 12 months, 30 days
+ test "displays visitors for 12 months with imported data", %{conn: conn, site: site} do
+ populate_stats(site, [
+ build(:pageview, timestamp: ~N[2021-01-01 00:00:00]),
+ build(:pageview, timestamp: ~N[2021-12-31 00:00:00]),
+ build(:imported_visitors, date: ~D[2021-01-01]),
+ build(:imported_visitors, date: ~D[2021-12-31])
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/main-graph?period=12mo&date=2021-12-31&with_imported=true"
+ )
+
+ assert %{"plot" => plot} = json_response(conn, 200)
+
+ assert Enum.count(plot) == 12
+ assert List.first(plot) == 2
+ assert List.last(plot) == 2
+ assert Enum.sum(plot) == 4
+ end
+
+ test "displays visitors for all time with just native data", %{conn: conn, site: site} do
+ use Plausible.Repo
+
+ Repo.update_all(from(s in "sites", where: s.id == ^site.id),
+ set: [inserted_at: ~N[2020-01-01 00:00:00]]
+ )
+
+ populate_stats(site, [
+ build(:pageview, timestamp: ~N[2020-01-01 00:00:00]),
+ build(:pageview, timestamp: ~N[2021-01-01 00:00:00]),
+ build(:pageview, timestamp: ~N[2021-12-31 00:00:00])
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/main-graph?period=all&with_imported=true"
+ )
+
+ assert %{"plot" => plot} = json_response(conn, 200)
+
+ assert List.first(plot) == 1
+ assert Enum.sum(plot) == 3
+ end
end
describe "GET /api/stats/main-graph - labels" do