From 9ee1d893acee2a196537de099ef5a9dc4334c023 Mon Sep 17 00:00:00 2001 From: Lebaud Antoine Date: Mon, 12 Jun 2023 21:56:14 +0200 Subject: [PATCH] wip --- src/backend/plugins/video/warren_video/api.py | 1 + src/frontend/apps/web/pages/index.tsx | 18 +-- src/frontend/packages/ui/components/wip.tsx | 24 ++++ .../packages/ui/contexts/dateContext.tsx | 20 +++ src/frontend/packages/ui/hooks/useDate.tsx | 14 ++ src/frontend/packages/ui/libs/axios.ts | 6 + src/frontend/packages/ui/libs/react-query.ts | 10 ++ src/frontend/packages/ui/provider/app.tsx | 19 +++ src/frontend/packages/ui/video/Views.tsx | 125 ++++++++---------- .../packages/ui/video/api/getVideosViews.ts | 32 +++++ src/frontend/packages/ui/video/types/index.ts | 11 ++ 11 files changed, 199 insertions(+), 81 deletions(-) create mode 100644 src/frontend/packages/ui/components/wip.tsx create mode 100644 src/frontend/packages/ui/contexts/dateContext.tsx create mode 100644 src/frontend/packages/ui/hooks/useDate.tsx create mode 100644 src/frontend/packages/ui/libs/axios.ts create mode 100644 src/frontend/packages/ui/libs/react-query.ts create mode 100644 src/frontend/packages/ui/provider/app.tsx create mode 100644 src/frontend/packages/ui/video/api/getVideosViews.ts create mode 100644 src/frontend/packages/ui/video/types/index.ts diff --git a/src/backend/plugins/video/warren_video/api.py b/src/backend/plugins/video/warren_video/api.py index 625d65c7e..41b049fb0 100644 --- a/src/backend/plugins/video/warren_video/api.py +++ b/src/backend/plugins/video/warren_video/api.py @@ -44,6 +44,7 @@ async def views( video_id: IRI, filters: Annotated[BaseQueryFilters, Depends()] ) -> VideoViews: """Video views.""" + query = { "verb": PlayedVerb().id, "activity": video_id, diff --git a/src/frontend/apps/web/pages/index.tsx b/src/frontend/apps/web/pages/index.tsx index 7c5458334..2ca43f781 100644 --- a/src/frontend/apps/web/pages/index.tsx +++ b/src/frontend/apps/web/pages/index.tsx @@ -1,20 +1,14 @@ import type { ReactElement } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import Layout from "../components/Layout"; import type { NextPageWithLayout } from "./_app"; import { DailyViews } from "ui"; +import Wip from "ui/components/wip"; +import AppProvider from "ui/provider/app"; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - }, - }, -}); const Web: NextPageWithLayout = () => { + const videoIds = [ "uuid://0aecfa93-cef3-45ae-b7f5-a603e9e45f50", "uuid://1c0c127a-f121-4bd1-8db6-918605c2645d", @@ -28,10 +22,10 @@ const Web: NextPageWithLayout = () => { "uuid://e151ee65-7a72-478c-ac57-8a02f19e748b", ]; return ( - + + - - + ); }; diff --git a/src/frontend/packages/ui/components/wip.tsx b/src/frontend/packages/ui/components/wip.tsx new file mode 100644 index 000000000..4dbe6a8d0 --- /dev/null +++ b/src/frontend/packages/ui/components/wip.tsx @@ -0,0 +1,24 @@ +import React, {useRef} from 'react' +import useDate from "../hooks/useDate"; + + +// todo : to be replaced by a proper range component. +const Wip = () => { + + const {setDate} = useDate() + + const sinceInput = useRef(null); + const untilInput = useRef(null); + + return ( + <> + + + + + ) +} +export default Wip; \ No newline at end of file diff --git a/src/frontend/packages/ui/contexts/dateContext.tsx b/src/frontend/packages/ui/contexts/dateContext.tsx new file mode 100644 index 000000000..5ca43904b --- /dev/null +++ b/src/frontend/packages/ui/contexts/dateContext.tsx @@ -0,0 +1,20 @@ +import React, {createContext, Dispatch, SetStateAction, useState} from 'react' + + +export interface DateContextType { + date: [string, string]; + setDate: Dispatch>, +} + +const DateContext = createContext(null) + +export const DateProvider: React.FC<{ children: any }> = ({ children }) => { + const [date, setDate] = useState<[string, string]>(['', '']) + return ( + + {children} + + ) +} + +export default DateContext \ No newline at end of file diff --git a/src/frontend/packages/ui/hooks/useDate.tsx b/src/frontend/packages/ui/hooks/useDate.tsx new file mode 100644 index 000000000..8740463f1 --- /dev/null +++ b/src/frontend/packages/ui/hooks/useDate.tsx @@ -0,0 +1,14 @@ +import { useContext } from 'react' +import DateContext, {DateContextType} from "../contexts/dateContext"; + + +// todo : refactor in the contexts/dateContext.tsx file ? +const useDate = (): DateContextType => { + const value = useContext(DateContext) + if (!value) { + throw new Error(`Missing wrapping Provider for Store DateContextType`); + } + return value; +} + +export default useDate \ No newline at end of file diff --git a/src/frontend/packages/ui/libs/axios.ts b/src/frontend/packages/ui/libs/axios.ts new file mode 100644 index 000000000..73da37020 --- /dev/null +++ b/src/frontend/packages/ui/libs/axios.ts @@ -0,0 +1,6 @@ +import Axios from "axios"; + + +export const axios = Axios.create({ + baseURL: `${process.env.NEXT_PUBLIC_WARREN_BACKEND_ROOT_URL}/api/v1/`, +}); \ No newline at end of file diff --git a/src/frontend/packages/ui/libs/react-query.ts b/src/frontend/packages/ui/libs/react-query.ts new file mode 100644 index 000000000..d47fa0ba4 --- /dev/null +++ b/src/frontend/packages/ui/libs/react-query.ts @@ -0,0 +1,10 @@ +import {DefaultOptions, QueryClient} from "@tanstack/react-query"; + + +const queryConfig: DefaultOptions = { + queries: { + refetchOnWindowFocus: false, + }, +}; + +export const queryClient = new QueryClient({defaultOptions:queryConfig}) \ No newline at end of file diff --git a/src/frontend/packages/ui/provider/app.tsx b/src/frontend/packages/ui/provider/app.tsx new file mode 100644 index 000000000..91013a629 --- /dev/null +++ b/src/frontend/packages/ui/provider/app.tsx @@ -0,0 +1,19 @@ +import {DateProvider} from "../contexts/dateContext"; +import {ReactQueryDevtools} from "@tanstack/react-query-devtools"; +import {QueryClientProvider} from "@tanstack/react-query"; +import {queryClient} from "../libs/react-query"; + + +type AppProviderProps = { + children: React.ReactNode; +}; + +const AppProvider = ({ children }: AppProviderProps) => ( + + + {children} + + + + ) +export default AppProvider; \ No newline at end of file diff --git a/src/frontend/packages/ui/video/Views.tsx b/src/frontend/packages/ui/video/Views.tsx index 0d19ece5c..d8e3de43f 100644 --- a/src/frontend/packages/ui/video/Views.tsx +++ b/src/frontend/packages/ui/video/Views.tsx @@ -1,20 +1,49 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import ReactECharts from "echarts-for-react"; import type { EChartsOption } from "echarts-for-react"; -import { useQuery, useQueries, useQueryClient } from "@tanstack/react-query"; - -import axios from "axios"; import cloneDeep from "lodash.clonedeep"; +import useDate from "../hooks/useDate"; +import {useVideosViews} from "./api/getVideosViews"; +import {DailyViewsResponseItem} from "./types"; -type DailyViewsResponseItem = { - day: string; - views: number; +type Series = { + name: string; + data: Array, + type: string, + smooth: number, + symbol: string, + areaStyle: any, + stack: string, + emphasis: any, }; -type VideoViewsResponse = { - total: number; - daily_views: Array; +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", + }, + textStyle: { + fontFamily: "Roboto, sans-serif", + }, }; type DailyViewsProps = { @@ -22,78 +51,36 @@ type DailyViewsProps = { }; export const DailyViews = ({ videoIds }: DailyViewsProps) => { - 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", - }, - textStyle: { - fontFamily: "Roboto, sans-serif", - }, - }; + const {date: [since, until]} = useDate(); const [option, setOption] = useState(baseOption); const newOption = cloneDeep(option); - function getVideoViews(videoId: string) { - return axios - .get( - `${process.env.NEXT_PUBLIC_WARREN_BACKEND_ROOT_URL}/api/v1/video/${videoId}/views` - ) - .then((res) => res.data); - } - function addOneSeries( videoId: string, daily_views: Array ) { 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", - }, - }); + newOption.series = [ + ...newOption.series.filter((series: Series) => series.name != videoId), + { + 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 results = useQueries({ - queries: videoIds.map((videoId) => { - return { - queryKey: [`videoViews-${videoId}`], - queryFn: () => getVideoViews(videoId), - onSuccess: (data: VideoViewsResponse) => { - addOneSeries(videoId, data.daily_views); - return data; - }, - }; - }), - }); + const results = useVideosViews({videoIds, since, until, onSuccess: addOneSeries}); if (results.some((result) => result.isLoading)) return Loading...; diff --git a/src/frontend/packages/ui/video/api/getVideosViews.ts b/src/frontend/packages/ui/video/api/getVideosViews.ts new file mode 100644 index 000000000..7fc0274e8 --- /dev/null +++ b/src/frontend/packages/ui/video/api/getVideosViews.ts @@ -0,0 +1,32 @@ + +import {useQueries} from "@tanstack/react-query"; +import {DailyViewsResponseItem, VideoViewsResponse} from "../types"; +import {axios} from "../../libs/axios"; + + +const getVideoViews = (videoId: string, since: string, until: string) => { + return axios.get(`video/${videoId}/views`, {params: {...since && {since: since}, ...until && {until: until}}}) + .then((res) => res.data); +}; + +type UseVideosViewsOptions = { + videoIds: Array; + since: string; + until: string; + onSuccess: (id: string, data: Array) => void +}; + +export const useVideosViews = ({videoIds, since, until, onSuccess}: UseVideosViewsOptions) => { + return useQueries({ + queries: videoIds.map((videoId) => { + return { + queryKey: ["videoViews", videoId, since, until], + queryFn: () => getVideoViews(videoId, since, until), + onSuccess: (data: VideoViewsResponse) => { + onSuccess(videoId, data.daily_views); + return data; + }, + }; + }), + }); +} \ No newline at end of file diff --git a/src/frontend/packages/ui/video/types/index.ts b/src/frontend/packages/ui/video/types/index.ts new file mode 100644 index 000000000..7eb740b2e --- /dev/null +++ b/src/frontend/packages/ui/video/types/index.ts @@ -0,0 +1,11 @@ + + +export type DailyViewsResponseItem = { + day: string; + views: number; +}; + +export type VideoViewsResponse = { + total: number; + daily_views: Array; +}; \ No newline at end of file