Add Year to Date option in datepicker

This commit is contained in:
Uku Taht 2022-03-31 13:52:26 +03:00
parent 8616dd46fb
commit 7f58e6be4e
6 changed files with 117 additions and 31 deletions

View File

@ -9,12 +9,14 @@ import {
formatDay,
formatDayShort,
formatMonthYYYY,
formatYear,
formatISO,
isToday,
lastMonth,
nowForSite,
isSameMonth,
isThisMonth,
isThisYear,
parseUTCDate,
isBefore,
isAfter,
@ -86,12 +88,17 @@ function renderArrow(query, site, period, prevDate, nextDate) {
}
function DatePickerArrows({site, query}) {
if (query.period === "month") {
if (query.period === "year") {
const prevDate = formatISO(shiftMonths(query.date, -12));
const nextDate = formatISO(shiftMonths(query.date, 12));
return renderArrow(query, site, "year", prevDate, nextDate);
} else if (query.period === "month") {
const prevDate = formatISO(shiftMonths(query.date, -1));
const nextDate = formatISO(shiftMonths(query.date, 1));
return renderArrow(query, site, "month", prevDate, nextDate);
} if (query.period === "day") {
} else if (query.period === "day") {
const prevDate = formatISO(shiftDays(query.date, -1));
const nextDate = formatISO(shiftDays(query.date, 1));
@ -141,53 +148,44 @@ class DatePicker extends React.Component {
if (e.key === "ArrowLeft") {
const prevDate = formatISO(shiftDays(query.date, -1));
const prevMonth = formatISO(shiftMonths(query.date, -1));
const prevYear = formatISO(shiftMonths(query.date, -12));
if (
query.period === "day" &&
!isBefore(parseUTCDate(prevDate), insertionDate, query.period)
) {
if (query.period === "day" && !isBefore(parseUTCDate(prevDate), insertionDate, query.period)) {
newSearch.period = "day";
newSearch.date = prevDate;
} else if (
query.period === "month" &&
!isBefore(parseUTCDate(prevMonth), insertionDate, query.period)
) {
} else if (query.period === "month" && !isBefore(parseUTCDate(prevMonth), insertionDate, query.period)) {
newSearch.period = "month";
newSearch.date = prevMonth;
} else if (query.period === "year" && !isBefore(parseUTCDate(prevYear), insertionDate, query.period)) {
newSearch.period = "year";
newSearch.date = prevYear;
}
} else if (e.key === "ArrowRight") {
const now = nowForSite(this.props.site)
const nextDate = formatISO(shiftDays(query.date, 1));
const nextMonth = formatISO(shiftMonths(query.date, 1));
const nextYear = formatISO(shiftMonths(query.date, 12));
if (
query.period === "day" &&
!isAfter(
parseUTCDate(nextDate),
nowForSite(this.props.site),
query.period
)
) {
if (query.period === "day" && !isAfter(parseUTCDate(nextDate), now, query.period)) {
newSearch.period = "day";
newSearch.date = nextDate;
} else if (
query.period === "month" &&
!isAfter(
parseUTCDate(nextMonth),
nowForSite(this.props.site),
query.period
)
) {
} else if (query.period === "month" && !isAfter(parseUTCDate(nextMonth), now, query.period)) {
newSearch.period = "month";
newSearch.date = nextMonth;
} else if (query.period === "year" && !isAfter(parseUTCDate(nextYear), now, query.period)) {
newSearch.period = "year";
newSearch.date = nextYear;
}
}
this.setState({open: false});
const keys = ['d', 'r', 'w', 'm', 'y', 't', 's'];
const redirects = [{date: false, period: 'day'}, {period: 'realtime'}, {date: false, period: '7d'}, {date: false, period: 'month'}, {date: false, period: '12mo'}, {date: false, period: '30d'}, {date: false, period: '6mo'}];
const keys = ['d', 'r', 'w', 'm', 'y', 't', 's', 'a'];
const redirects = [{date: false, period: 'day'}, {period: 'realtime'}, {date: false, period: '7d'}, {date: false, period: 'month'}, {date: false, period: 'year'}, {date: false, period: '30d'}, {date: false, period: '6mo'}, {date: false, period: 'all'}];
if (keys.includes(e.key.toLowerCase())) {
console.log(e.key.toLowerCase())
console.log(redirects[keys.indexOf(e.key.toLowerCase())])
navigateToQuery(history, query, {...newSearch, ...(redirects[keys.indexOf(e.key.toLowerCase())])});
} else if (e.key.toLowerCase() === 'c') {
this.setState({mode: 'calendar', open: true}, this.openCalendar);
@ -253,6 +251,11 @@ class DatePicker extends React.Component {
return 'Last 6 months'
} if (query.period === '12mo') {
return 'Last 12 months'
} if (query.period === 'year') {
if (isThisYear(site, query.date)) {
return 'Year to Date'
}
return formatYear(query.date)
} if (query.period === 'all') {
return 'All time'
} if (query.period === 'custom') {
@ -293,9 +296,10 @@ class DatePicker extends React.Component {
'Realtime': 'R',
'Last 7 days': 'W',
'Month to Date': 'M',
'Last 12 months': 'Y',
'Year to Date': 'Y',
'Last 6 months': 'S',
'Last 30 days': 'T',
'All time': 'A',
};
return (
@ -336,8 +340,8 @@ class DatePicker extends React.Component {
{ this.renderLink('month', 'Last month', {date: lastMonth(this.props.site)}) }
</div>
<div className="py-1 border-b border-gray-200 dark:border-gray-500 date-option-group">
{this.renderLink("6mo", "Last 6 months")}
{this.renderLink("12mo", "Last 12 months")}
{this.renderLink("year", "Year to Date")}
</div>
<div className="py-1 date-option-group">
{this.renderLink("all", "All time")}

View File

@ -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', 'all', 'custom']
const PERIODS = ['realtime', 'day', 'month', '7d', '30d', '6mo', '12mo', 'year', 'all', 'custom']
export function parseQuery(querystring, site) {
const q = new URLSearchParams(querystring)

View File

@ -36,6 +36,10 @@ export function formatMonthYYYY(date) {
return `${MONTHS[date.getMonth()]} ${date.getFullYear()}`;
}
export function formatYear(date) {
return `Year of ${date.getFullYear()}`;
}
export function formatDay(date) {
var weekday = DAYS_ABBREV[date.getUTCDay()];
if (date.getFullYear() !== (new Date()).getFullYear()) {
@ -76,6 +80,10 @@ export function isThisMonth(site, date) {
return formatMonthYYYY(date) === formatMonthYYYY(nowForSite(site))
}
export function isThisYear(site, date) {
return date.getFullYear() === nowForSite(site).getFullYear()
}
export function isBefore(date1, date2, period) {
/* assumes 'day' and 'month' are the only valid periods */
if (date1.getFullYear() !== date2.getFullYear()) {

View File

@ -8,6 +8,29 @@ defmodule Plausible.Stats.Query do
@default_sample_threshold 20_000_000
def shift_back(%__MODULE__{period: "year"} = query, site) do
# Querying current year to date
{new_first, new_last} =
if Timex.compare(Timex.now(site.timezone), query.date_range.first, :year) == 0 do
diff =
Timex.diff(
Timex.beginning_of_year(Timex.now(site.timezone)),
Timex.now(site.timezone),
:days
) - 1
{query.date_range.first |> Timex.shift(days: diff),
Timex.now(site.timezone) |> Timex.to_date() |> Timex.shift(days: diff)}
else
diff = Timex.diff(query.date_range.first, query.date_range.last, :days) - 1
{query.date_range.first |> Timex.shift(days: diff),
query.date_range.last |> Timex.shift(days: diff)}
end
Map.put(query, :date_range, Date.range(new_first, new_last))
end
def shift_back(%__MODULE__{period: "month"} = query, site) do
# Querying current month to date
{new_first, new_last} =
@ -146,6 +169,23 @@ defmodule Plausible.Stats.Query do
|> maybe_include_imported(site, params)
end
def from(site, %{"period" => "year"} = params) do
end_date =
parse_single_date(site.timezone, params)
|> Timex.end_of_year()
start_date = Timex.beginning_of_year(end_date)
%__MODULE__{
period: "year",
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" => "all"} = params) do
end_date =
today(site.timezone)

View File

@ -60,6 +60,18 @@ defmodule Plausible.Stats.QueryTest do
assert q.interval == "month"
end
test "parses year to date format" do
q = Query.from(@site, %{"period" => "year"})
assert q.date_range.first ==
Timex.now(@site.timezone) |> Timex.to_date() |> Timex.beginning_of_year()
assert q.date_range.last ==
Timex.now(@site.timezone) |> Timex.to_date() |> Timex.end_of_year()
assert q.interval == "month"
end
test "parses all time" do
q = Query.from(@site, %{"period" => "all"})

View File

@ -159,6 +159,28 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
assert Enum.sum(plot) == 4
end
test "displays visitors for calendar year 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=year&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