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