-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
♻️(frontend) refactor Views plotting component
Component is now subscribing to filters state. Note: Time management in the current version of the frontend and the API is still bulky. A lot of work need to be done.
- Loading branch information
1 parent
5244ac6
commit cba757e
Showing
3 changed files
with
129 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,94 @@ | ||
import React, { useState, useEffect } from "react"; | ||
import React, { useMemo } from "react"; | ||
import ReactECharts from "echarts-for-react"; | ||
import type { EChartsOption } from "echarts-for-react"; | ||
import { useQuery, useQueries, useQueryClient } from "@tanstack/react-query"; | ||
|
||
import cloneDeep from "lodash.clonedeep"; | ||
import { axios } from "../libs/axios"; | ||
import { VideoViewsResponse } from "./types"; | ||
import useFilters from "../hooks/useFilters"; | ||
import { useVideosViews } from "./api/getVideoViews"; | ||
|
||
type DailyViewsResponseItem = { | ||
day: string; | ||
views: number; | ||
type Series = { | ||
id: string; | ||
name: string; | ||
data: number[]; | ||
type: string; | ||
smooth: number; | ||
symbol: string; | ||
emphasis: any; | ||
}; | ||
|
||
type VideoViewsResponse = { | ||
total: number; | ||
daily_views: Array<DailyViewsResponseItem>; | ||
}; | ||
|
||
export const DailyViews = () => { | ||
const { videoIds } = useFilters(); | ||
|
||
const baseOption: EChartsOption = { | ||
grid: { top: 80, right: 8, bottom: 100, left: 50 }, | ||
xAxis: { | ||
type: "category", | ||
data: [], | ||
axisTick: { | ||
alignWithLabel: true, | ||
interval: 0, | ||
}, | ||
axisLabel: { | ||
interval: 0, | ||
rotate: 45, | ||
height: 200, | ||
}, | ||
}, | ||
yAxis: { | ||
type: "value", | ||
name: "# views", | ||
}, | ||
series: [], | ||
tooltip: { | ||
trigger: "axis", | ||
const baseOption: EChartsOption = { | ||
grid: { top: 80, right: 8, bottom: 100, left: 50 }, | ||
xAxis: { | ||
type: "category", | ||
data: [], | ||
axisTick: { | ||
alignWithLabel: true, | ||
interval: 0, | ||
}, | ||
textStyle: { | ||
fontFamily: "Roboto, sans-serif", | ||
axisLabel: { | ||
interval: 0, | ||
rotate: 45, | ||
height: 200, | ||
}, | ||
}; | ||
|
||
const [option, setOption] = useState(baseOption); | ||
|
||
const newOption = cloneDeep(option); | ||
}, | ||
yAxis: { | ||
type: "value", | ||
name: "# views", | ||
min: 0, | ||
max: 100, | ||
}, | ||
series: [], | ||
tooltip: { | ||
trigger: "axis", | ||
}, | ||
textStyle: { | ||
fontFamily: "Roboto, sans-serif", | ||
}, | ||
}; | ||
|
||
function getVideoViews(videoId: string) { | ||
return axios.get(`video/${videoId}/views`).then((res) => res.data); | ||
} | ||
export const DailyViews: React.FC = () => { | ||
const { | ||
date: [since, until], | ||
videoIds, | ||
} = useFilters(); | ||
|
||
function addOneSeries( | ||
videoId: string, | ||
daily_views: Array<DailyViewsResponseItem> | ||
) { | ||
newOption.xAxis.data = daily_views.map((d) => d.day); | ||
newOption.series.push({ | ||
name: videoId, | ||
data: daily_views.map((d) => d.views), | ||
type: "line", | ||
smooth: 0.2, | ||
symbol: "none", | ||
areaStyle: {}, | ||
stack: "Total", | ||
emphasis: { | ||
focus: "series", | ||
}, | ||
}); | ||
setOption(newOption); | ||
} | ||
const validData = useVideosViews({ videoIds, since, until }); | ||
|
||
const results = useQueries({ | ||
queries: videoIds.map((videoId) => { | ||
return { | ||
queryKey: [`videoViews-${videoId}`], | ||
queryFn: () => getVideoViews(videoId), | ||
onSuccess: (data: VideoViewsResponse) => { | ||
addOneSeries(videoId, data.daily_views); | ||
return data; | ||
}, | ||
}; | ||
}), | ||
const parseSeries = (item: VideoViewsResponse): Series => ({ | ||
id: item?.id, | ||
name: item.id.slice(-5) || "", | ||
data: item.count_by_date.map((day) => day.count) || [], | ||
type: "line", | ||
smooth: 0.2, | ||
symbol: "none", | ||
emphasis: { | ||
focus: "series", | ||
}, | ||
}); | ||
|
||
if (results.some((result) => result.isLoading)) | ||
return <span>Loading...</span>; | ||
const parseXAxis = (item: VideoViewsResponse): Array<string> => | ||
item.count_by_date.map((day) => day.date) || []; | ||
|
||
const formattedOption = useMemo(() => { | ||
if (!validData.length) { | ||
return baseOption; | ||
} | ||
const newOption = cloneDeep(baseOption); | ||
// todo - ongoing discussion in the team, we assume all requests share the same xAxis. | ||
newOption.xAxis.data = parseXAxis(validData[0]); | ||
newOption.series = validData.map((d) => parseSeries(d)); | ||
return newOption; | ||
}, [validData]); | ||
|
||
return ( | ||
<> | ||
<div className="chart-title">Video: daily views</div> | ||
<ReactECharts option={option} style={{ height: 500 }} /> | ||
<ReactECharts | ||
option={formattedOption} | ||
notMerge={true} // todo - update it with replaceMerge, an issue is opened. | ||
style={{ height: 500 }} | ||
/> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useQueries } from "@tanstack/react-query"; | ||
import { axios } from "../../libs/axios"; | ||
import { VideoViewsResponse } from "../types"; | ||
|
||
const getVideoViews = async ( | ||
videoId: string, | ||
since: string, | ||
until: string | ||
): Promise<VideoViewsResponse> => { | ||
const response = await axios.get(`video/${videoId}/views`, { | ||
params: { | ||
...(since && { since: since }), | ||
...(until && { until: until }), | ||
}, | ||
}); | ||
return { | ||
id: videoId, | ||
...response?.data?.content, | ||
}; | ||
}; | ||
|
||
type UseVideosViewsOptions = { | ||
videoIds: Array<string>; | ||
since: string; | ||
until: string; | ||
}; | ||
|
||
export const useVideosViews = ({ | ||
videoIds, | ||
since, | ||
until, | ||
}: UseVideosViewsOptions): VideoViewsResponse[] => { | ||
// Generate the queries array | ||
const queries = videoIds?.map((videoId) => ({ | ||
queryKey: ["videoViews", videoId, since, until], | ||
queryFn: () => getVideoViews(videoId, since, until), | ||
})); | ||
|
||
// Use useQueries hook to fetch data for all videoIds in parallel | ||
const queryResults = useQueries({ queries }); | ||
|
||
// todo - add checks to make sure data have the same number of day's count. | ||
// Extract the data from the query results | ||
return queryResults | ||
.filter((r) => r.isSuccess) | ||
.map((queryResult) => queryResult.data) as VideoViewsResponse[]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export type VideoViewsResponseItem = { | ||
date: string; | ||
count: number; | ||
}; | ||
|
||
export type VideoViewsResponse = { | ||
id: string; | ||
total_views: number; | ||
count_by_date: Array<VideoViewsResponseItem>; | ||
}; |