refactor: use TimeOptions for Listens report

This commit is contained in:
Julian Tölle 2020-07-12 17:17:50 +02:00
parent 5bc76f23d0
commit 67ea28aec7
7 changed files with 51 additions and 59 deletions

View file

@ -79,13 +79,17 @@ export const getRecentListens = async (
export const getListensReport = async ( export const getListensReport = async (
options: ListenReportOptions options: ListenReportOptions
): Promise<ListenReportItem[]> => { ): Promise<ListenReportItem[]> => {
const { timeFrame, timeStart, timeEnd } = options; const {
timeFrame,
time: { timePreset, customTimeStart, customTimeEnd },
} = options;
const res = await fetch( const res = await fetch(
`/api/v1/reports/listens?${qs({ `/api/v1/reports/listens?${qs({
timeFrame, timeFrame,
timeStart: formatISO(timeStart), timePreset,
timeEnd: formatISO(timeEnd), customTimeStart: formatISO(customTimeStart),
customTimeEnd: formatISO(customTimeEnd),
})}`, })}`,
{ {
headers: getDefaultHeaders(), headers: getDefaultHeaders(),

View file

@ -1,5 +1,6 @@
import { TimeOptions } from "./time-options";
export interface ListenReportOptions { export interface ListenReportOptions {
timeFrame: "day" | "week" | "month" | "year"; timeFrame: "day" | "week" | "month" | "year";
timeStart: Date; time: TimeOptions;
timeEnd: Date;
} }

View file

@ -1,5 +1,5 @@
import { format, getTime } from "date-fns"; import { format, getTime } from "date-fns";
import React, { useEffect, useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
import { import {
Area, Area,
@ -14,21 +14,29 @@ import {
import { getListensReport } from "../api/api"; import { getListensReport } from "../api/api";
import { ListenReportItem } from "../api/entities/listen-report-item"; import { ListenReportItem } from "../api/entities/listen-report-item";
import { ListenReportOptions } from "../api/entities/listen-report-options"; import { ListenReportOptions } from "../api/entities/listen-report-options";
import { TimeOptions } from "../api/entities/time-options";
import { TimePreset } from "../api/entities/time-preset.enum";
import { useAsync } from "../hooks/use-async"; import { useAsync } from "../hooks/use-async";
import { useAuth } from "../hooks/use-auth"; import { useAuth } from "../hooks/use-auth";
import { ReportTimeOptions } from "./ReportTimeOptions";
export const ReportListens: React.FC = () => { export const ReportListens: React.FC = () => {
const { user } = useAuth(); const { user } = useAuth();
const [reportOptions, setReportOptions] = useState<ListenReportOptions>({ const [timeFrame, setTimeFrame] = useState<"day" | "week" | "month" | "year">(
timeFrame: "day", "day"
timeStart: new Date("2020-05-01"), );
timeEnd: new Date(),
const [timeOptions, setTimeOptions] = useState<TimeOptions>({
timePreset: TimePreset.LAST_7_DAYS,
customTimeStart: new Date(0),
customTimeEnd: new Date(),
}); });
const fetchData = useMemo(() => () => getListensReport(reportOptions), [ const fetchData = useMemo(
reportOptions, () => () => getListensReport({ timeFrame, time: timeOptions }),
]); [timeFrame, timeOptions]
);
const { value: report, pending: isLoading } = useAsync(fetchData, []); const { value: report, pending: isLoading } = useAsync(fetchData, []);
@ -51,14 +59,9 @@ export const ReportListens: React.FC = () => {
<select <select
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:shadow-outline" className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
onChange={(e) => onChange={(e) =>
setReportOptions({ setTimeFrame(
...reportOptions, e.target.value as "day" | "week" | "month" | "year"
timeFrame: e.target.value as )
| "day"
| "week"
| "month"
| "year",
})
} }
> >
<option value="day">Daily</option> <option value="day">Daily</option>
@ -67,6 +70,10 @@ export const ReportListens: React.FC = () => {
<option value="year">Yearly</option> <option value="year">Yearly</option>
</select> </select>
</div> </div>
<ReportTimeOptions
timeOptions={timeOptions}
setTimeOptions={setTimeOptions}
/>
</div> </div>
{isLoading && ( {isLoading && (
<div> <div>
@ -80,7 +87,7 @@ export const ReportListens: React.FC = () => {
)} )}
{reportHasItems && ( {reportHasItems && (
<div className="w-full text-gray-700 mt-5"> <div className="w-full text-gray-700 mt-5">
<ReportGraph options={reportOptions} data={report} /> <ReportGraph timeFrame={timeFrame} data={report} />
</div> </div>
)} )}
</div> </div>
@ -90,9 +97,9 @@ export const ReportListens: React.FC = () => {
}; };
const ReportGraph: React.FC<{ const ReportGraph: React.FC<{
options: ListenReportOptions; timeFrame: ListenReportOptions["timeFrame"];
data: ListenReportItem[]; data: ListenReportItem[];
}> = ({ options, data }) => { }> = ({ timeFrame, data }) => {
const dataLocal = data.map(({ date, ...other }) => ({ const dataLocal = data.map(({ date, ...other }) => ({
...other, ...other,
date: getTime(date), date: getTime(date),
@ -108,10 +115,7 @@ const ReportGraph: React.FC<{
} }
const [{ value: listens }] = payload; const [{ value: listens }] = payload;
const date = format( const date = format(label as number, dateFormatFromTimeFrame(timeFrame));
label as number,
dateFormatFromTimeFrame(options.timeFrame)
);
return ( return (
<div className="bg-gray-100 shadow-xl p-2 rounded text-sm font-light"> <div className="bg-gray-100 shadow-xl p-2 rounded text-sm font-light">
@ -145,7 +149,7 @@ const ReportGraph: React.FC<{
domain={["auto", "auto"]} domain={["auto", "auto"]}
dataKey="date" dataKey="date"
tickFormatter={(date) => tickFormatter={(date) =>
format(date, shortDateFormatFromTimeFrame(options.timeFrame)) format(date, shortDateFormatFromTimeFrame(timeFrame))
} }
/> />
<YAxis /> <YAxis />

View file

@ -1,6 +1,7 @@
import { IsEnum, IsISO8601 } from "class-validator"; import { IsEnum, ValidateNested } from "class-validator";
import { User } from "../../users/user.entity"; import { User } from "../../users/user.entity";
import { Timeframe } from "../timeframe.enum"; import { Timeframe } from "../timeframe.enum";
import { ReportTimeDto } from "./report-time.dto";
export class GetListenReportDto { export class GetListenReportDto {
user: User; user: User;
@ -8,9 +9,6 @@ export class GetListenReportDto {
@IsEnum(Timeframe) @IsEnum(Timeframe)
timeFrame: Timeframe; timeFrame: Timeframe;
@IsISO8601() @ValidateNested()
timeStart: string; time: ReportTimeDto;
@IsISO8601()
timeEnd: string;
} }

View file

@ -5,10 +5,4 @@ export class ListenReportDto {
date: string; date: string;
count: number; count: number;
}[]; }[];
timeFrame: Timeframe;
timeStart: string;
timeEnd: string;
} }

View file

@ -2,11 +2,11 @@ import { Controller, Get, Query } from "@nestjs/common";
import { Auth } from "src/auth/decorators/auth.decorator"; import { Auth } from "src/auth/decorators/auth.decorator";
import { ReqUser } from "../auth/decorators/req-user.decorator"; import { ReqUser } from "../auth/decorators/req-user.decorator";
import { User } from "../users/user.entity"; import { User } from "../users/user.entity";
import { GetListenReportDto } from "./dto/get-listen-report.dto";
import { GetTopArtistsReportDto } from "./dto/get-top-artists-report.dto";
import { ListenReportDto } from "./dto/listen-report.dto"; import { ListenReportDto } from "./dto/listen-report.dto";
import { ReportTimeDto } from "./dto/report-time.dto";
import { TopArtistsReportDto } from "./dto/top-artists-report.dto"; import { TopArtistsReportDto } from "./dto/top-artists-report.dto";
import { ReportsService } from "./reports.service"; import { ReportsService } from "./reports.service";
import { Timeframe } from "./timeframe.enum";
@Controller("api/v1/reports") @Controller("api/v1/reports")
export class ReportsController { export class ReportsController {
@ -15,10 +15,11 @@ export class ReportsController {
@Get("listens") @Get("listens")
@Auth() @Auth()
async getListens( async getListens(
@Query() options: GetListenReportDto, @Query() time: ReportTimeDto,
@Query("timeFrame") timeFrame: Timeframe,
@ReqUser() user: User @ReqUser() user: User
): Promise<ListenReportDto> { ): Promise<ListenReportDto> {
return this.reportsService.getListens({ ...options, user }); return this.reportsService.getListens({ user, timeFrame, time });
} }
@Get("top-artists") @Get("top-artists")

View file

@ -65,14 +65,9 @@ export class ReportsService {
constructor(private readonly listensService: ListensService) {} constructor(private readonly listensService: ListensService) {}
async getListens(options: GetListenReportDto): Promise<ListenReportDto> { async getListens(options: GetListenReportDto): Promise<ListenReportDto> {
const { user, timeFrame, timeStart, timeEnd } = options; const { user, timeFrame, time: timePreset } = options;
// Function should eventually be rewritten to accept a timepreset const interval = this.getIntervalFromPreset(timePreset);
const interval = this.getIntervalFromPreset({
timePreset: TimePreset.CUSTOM,
customTimeStart: timeStart,
customTimeEnd: timeEnd,
});
const { items: listens } = await this.listensService.getListens({ const { items: listens } = await this.listensService.getListens({
user, user,
@ -81,20 +76,15 @@ export class ReportsService {
limit: PAGINATION_LIMIT_UNLIMITED, limit: PAGINATION_LIMIT_UNLIMITED,
}); });
const reportInterval: Interval = {
start: parseISO(timeStart),
end: parseISO(timeEnd),
};
const { eachOfInterval, isSame } = timeframeToDateFns[timeFrame]; const { eachOfInterval, isSame } = timeframeToDateFns[timeFrame];
const reportItems = eachOfInterval(reportInterval).map((date) => { const reportItems = eachOfInterval(interval).map((date) => {
const count = listens.filter((listen) => isSame(date, listen.playedAt)) const count = listens.filter((listen) => isSame(date, listen.playedAt))
.length; .length;
return { date: formatISO(date), count }; return { date: formatISO(date), count };
}); });
return { items: reportItems, timeStart, timeEnd, timeFrame }; return { items: reportItems };
} }
async getTopArtists( async getTopArtists(