Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
lebaudantoine committed Jun 23, 2023
1 parent aa9c2a4 commit 9ee1d89
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 81 deletions.
1 change: 1 addition & 0 deletions src/backend/plugins/video/warren_video/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ async def views(
video_id: IRI, filters: Annotated[BaseQueryFilters, Depends()]
) -> VideoViews:
"""Video views."""

query = {
"verb": PlayedVerb().id,
"activity": video_id,
Expand Down
18 changes: 6 additions & 12 deletions src/frontend/apps/web/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -28,10 +22,10 @@ const Web: NextPageWithLayout = () => {
"uuid://e151ee65-7a72-478c-ac57-8a02f19e748b",
];
return (
<QueryClientProvider client={queryClient}>
<AppProvider>
<Wip />
<DailyViews videoIds={videoIds} />
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
</AppProvider>
);
};

Expand Down
24 changes: 24 additions & 0 deletions src/frontend/packages/ui/components/wip.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>(null);
const untilInput = useRef<HTMLInputElement>(null);

return (
<>
<input type="text" ref={sinceInput}/>
<input type="text" ref={untilInput}/>
<button onClick={() => {
// todo : typescript error missing value property on the HTMLInputElement
setDate([sinceInput.current?.value, untilInput.current?.value]);
}}>wip</button>
</>
)
}
export default Wip;
20 changes: 20 additions & 0 deletions src/frontend/packages/ui/contexts/dateContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, {createContext, Dispatch, SetStateAction, useState} from 'react'


export interface DateContextType {
date: [string, string];
setDate: Dispatch<SetStateAction<[string, string]>>,
}

const DateContext = createContext<DateContextType | null>(null)

export const DateProvider: React.FC<{ children: any }> = ({ children }) => {
const [date, setDate] = useState<[string, string]>(['', ''])
return (
<DateContext.Provider value={{date, setDate}}>
{children}
</DateContext.Provider>
)
}

export default DateContext
14 changes: 14 additions & 0 deletions src/frontend/packages/ui/hooks/useDate.tsx
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions src/frontend/packages/ui/libs/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Axios from "axios";


export const axios = Axios.create({
baseURL: `${process.env.NEXT_PUBLIC_WARREN_BACKEND_ROOT_URL}/api/v1/`,
});
10 changes: 10 additions & 0 deletions src/frontend/packages/ui/libs/react-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {DefaultOptions, QueryClient} from "@tanstack/react-query";


const queryConfig: DefaultOptions = {
queries: {
refetchOnWindowFocus: false,
},
};

export const queryClient = new QueryClient({defaultOptions:queryConfig})
19 changes: 19 additions & 0 deletions src/frontend/packages/ui/provider/app.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<DateProvider>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen />
</QueryClientProvider>
</DateProvider>
)
export default AppProvider;
125 changes: 56 additions & 69 deletions src/frontend/packages/ui/video/Views.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,86 @@
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<number>,
type: string,
smooth: number,
symbol: string,
areaStyle: any,
stack: string,
emphasis: any,
};

type VideoViewsResponse = {
total: number;
daily_views: Array<DailyViewsResponseItem>;
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 = {
videoIds: Array<string>;
};

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<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",
},
});
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 <span>Loading...</span>;
Expand Down
32 changes: 32 additions & 0 deletions src/frontend/packages/ui/video/api/getVideosViews.ts
Original file line number Diff line number Diff line change
@@ -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<string>;
since: string;
until: string;
onSuccess: (id: string, data: Array<DailyViewsResponseItem>) => 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;
},
};
}),
});
}
11 changes: 11 additions & 0 deletions src/frontend/packages/ui/video/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@


export type DailyViewsResponseItem = {
day: string;
views: number;
};

export type VideoViewsResponse = {
total: number;
daily_views: Array<DailyViewsResponseItem>;
};

0 comments on commit 9ee1d89

Please sign in to comment.