From ec33a14bd96382d945cb04524e4a6ad47aa0c1f1 Mon Sep 17 00:00:00 2001 From: Dylan Decrulle <81740200+ddecrulle@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:09:09 +0100 Subject: [PATCH] wip vizu --- drama-queen/package.json | 4 +- drama-queen/src/bootstrap.tsx | 4 +- drama-queen/src/hooks/useTranslate.ts | 12 ++++ drama-queen/src/ui/auth.tsx | 17 ------ drama-queen/src/ui/components/appVersion.tsx | 30 +++++++++ .../components/orchestrator/Orchestrator.tsx | 3 + .../src/ui/pages/env/DisplayEnvValues.tsx | 3 +- .../src/ui/pages/visualize/Visualize.tsx | 34 ++++++++--- .../src/ui/pages/visualize/VisualizeForm.tsx | 40 ++++++++++++ drama-queen/src/ui/routing/Layout.tsx | 11 ++++ drama-queen/src/ui/routing/routes.tsx | 61 +++++++++++++++---- .../ui/tools/makeSearchParamsObjectSchema.ts | 30 +++++++++ yarn.lock | 16 ++--- 13 files changed, 212 insertions(+), 53 deletions(-) delete mode 100644 drama-queen/src/ui/auth.tsx create mode 100644 drama-queen/src/ui/components/appVersion.tsx create mode 100644 drama-queen/src/ui/components/orchestrator/Orchestrator.tsx create mode 100644 drama-queen/src/ui/pages/visualize/VisualizeForm.tsx create mode 100644 drama-queen/src/ui/routing/Layout.tsx create mode 100644 drama-queen/src/ui/tools/makeSearchParamsObjectSchema.ts diff --git a/drama-queen/package.json b/drama-queen/package.json index b217a425..5646d283 100644 --- a/drama-queen/package.json +++ b/drama-queen/package.json @@ -23,11 +23,11 @@ "jwt-decode": "^3.1.2", "keycloak-js": "^21.1.2", "memoizee": "^0.4.15", - "oidc-spa": "^2.0.3", + "oidc-spa": "^3.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.14.1", - "redux-clean-architecture": "^4.1.1", + "redux-clean-architecture": "^4.3.1", "tsafe": "^1.6.4", "tss-react": "^4.9.0", "zod": "^3.21.4" diff --git a/drama-queen/src/bootstrap.tsx b/drama-queen/src/bootstrap.tsx index 3c14c52d..ea3ce74f 100644 --- a/drama-queen/src/bootstrap.tsx +++ b/drama-queen/src/bootstrap.tsx @@ -5,7 +5,7 @@ import CircularProgress from "@mui/material/CircularProgress" import { createCoreProvider } from "core"; import { RouterProvider } from "react-router-dom"; -const { CoreProvider } = createCoreProvider({ +const { CoreProvider, prCore } = createCoreProvider({ "apiUrl": import.meta.env.VITE_QUEEN_API_URL, "publicUrl": import.meta.env.BASE_URL, "oidcParams": { @@ -39,4 +39,4 @@ const mount = ({ -export { mount }; \ No newline at end of file +export { mount, prCore }; \ No newline at end of file diff --git a/drama-queen/src/hooks/useTranslate.ts b/drama-queen/src/hooks/useTranslate.ts index 1aa7063e..b0a70741 100644 --- a/drama-queen/src/hooks/useTranslate.ts +++ b/drama-queen/src/hooks/useTranslate.ts @@ -6,6 +6,18 @@ const texts = { "sync.download.questionnaires": "Questionnaires", "sync.upload": "Envoi des données", vizu: "Page de visualisation de questionnaire", + "vizu.input.survey.label": "Questionnaire", + "vizu.input.survey.helper": + "L'url d'un json de questionnaire au format Lunatic-model", + "vizu.input.data.label": "Données", + "vizu.input.data.helper": "L'url d'un json de données (de réponse)", + "vizu.input.nomenclatures.label": "Dictionnaire de nomenclatures", + "vizu.input.nomenclatures.helper": + "Dictionnaire avec en clé le nom de la nomenclature et en valeur l'url", + "vizu.button.label": "Visualiser", + "vizu.queen.label": "Version de Queen", + "vizu.examples.label": "Ou choisir un exemple :", + "vizu.examples.select.label": "Exemples", } as const; const getTranslation = (s: keyof typeof texts) => texts[s] ?? s; diff --git a/drama-queen/src/ui/auth.tsx b/drama-queen/src/ui/auth.tsx deleted file mode 100644 index 9dc5b640..00000000 --- a/drama-queen/src/ui/auth.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactNode } from "react"; -import { useCore } from "core"; - - -export function RequiresAuthentication(props: - { children: ReactNode }) { - - const { children } = props - const { userAuthentication } = useCore().functions; - - if (!userAuthentication.getIsUserLoggedIn()) { - userAuthentication.login(); - return null; - } - - return <>{children} -} \ No newline at end of file diff --git a/drama-queen/src/ui/components/appVersion.tsx b/drama-queen/src/ui/components/appVersion.tsx new file mode 100644 index 00000000..30651ad8 --- /dev/null +++ b/drama-queen/src/ui/components/appVersion.tsx @@ -0,0 +1,30 @@ +import Typography from "@mui/material/Typography" +import { tss } from "tss-react/mui" + +export const AppVersion = () => { + const { classes } = useStyles() + return ( +
+ + Drama Queen version {APP_VERSION} + +
+ ) +} + + + +const useStyles = tss + .create(({ theme }) => ({ + footer: { + backgroundColor: '#f5f5f5', + borderTop: '1px solid black', + position: 'fixed', + left: 0, + bottom: 0, + width: '100%', + textAlign: 'center', + padding: '2px 0 2px 0', + }, + }) + ) \ No newline at end of file diff --git a/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx b/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx new file mode 100644 index 00000000..fc7d7653 --- /dev/null +++ b/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx @@ -0,0 +1,3 @@ +export function Orchestrator() { + return <>Orchestrator +} \ No newline at end of file diff --git a/drama-queen/src/ui/pages/env/DisplayEnvValues.tsx b/drama-queen/src/ui/pages/env/DisplayEnvValues.tsx index 6f2c8176..ab24c7c0 100644 --- a/drama-queen/src/ui/pages/env/DisplayEnvValues.tsx +++ b/drama-queen/src/ui/pages/env/DisplayEnvValues.tsx @@ -4,9 +4,8 @@ import './env.css' export function DisplayEnvValues() { return (
-

Drama Queen v{APP_VERSION}

+

Les variables d'environnements

-

Les variables d'environnements

{Object.entries(import.meta.env).filter(([k,]) => k.startsWith("VITE")).map(([k, v]) => ( diff --git a/drama-queen/src/ui/pages/visualize/Visualize.tsx b/drama-queen/src/ui/pages/visualize/Visualize.tsx index 47860597..904c8c86 100644 --- a/drama-queen/src/ui/pages/visualize/Visualize.tsx +++ b/drama-queen/src/ui/pages/visualize/Visualize.tsx @@ -1,21 +1,35 @@ -import { Container, Typography } from "@mui/material" -import { useTranslate } from "hooks/useTranslate" import { tss } from "tss-react/mui"; +import { useSearchParams } from "react-router-dom"; +import { z } from "zod"; +import { makeSearchParamsObjSchema } from "ui/tools/makeSearchParamsObjectSchema"; +import { VisualizeForm } from "./VisualizeForm"; + +const searchParamsSchema = z.object({ + questionnaire: z.string().optional(), + data: z.string().optional(), + nomenclature: z.record(z.string()).optional(), + readonly: z.boolean().optional() +}) + + export function Visualize() { - const { t } = useTranslate() - const { classes } = useStyles() + + //const { classes } = useStyles(); + + const [searchParams] = useSearchParams() + + const params = makeSearchParamsObjSchema(searchParamsSchema).safeParse(searchParams) + + console.log(params.success ? params.data : params.error); + return ( - - {t("vizu")} - + ) } const useStyles = tss.create( () => ({ - title: { - textAlign: 'center', - } + }) ) \ No newline at end of file diff --git a/drama-queen/src/ui/pages/visualize/VisualizeForm.tsx b/drama-queen/src/ui/pages/visualize/VisualizeForm.tsx new file mode 100644 index 00000000..0a8ba6b7 --- /dev/null +++ b/drama-queen/src/ui/pages/visualize/VisualizeForm.tsx @@ -0,0 +1,40 @@ +import { Button, FormControl, FormControlLabel, FormGroup, InputLabel, MenuItem, Radio, RadioGroup, Select, Stack, Switch, TextField, Typography } from "@mui/material" +import { useTranslate } from "hooks/useTranslate" +import { tss } from "tss-react/mui"; + + +export function VisualizeForm() { + const { t } = useTranslate() + const { classes } = useStyles() + return ( + + + {t("vizu")} + + + + + + + } label="Lecture Seul" /> + + + + + ) +} + + +const useStyles = tss.create( + () => ({ + title: { + textAlign: 'center', + }, + selectExample: { + minWidth: 120 + } + }) +) \ No newline at end of file diff --git a/drama-queen/src/ui/routing/Layout.tsx b/drama-queen/src/ui/routing/Layout.tsx new file mode 100644 index 00000000..fdebe09c --- /dev/null +++ b/drama-queen/src/ui/routing/Layout.tsx @@ -0,0 +1,11 @@ +import { Outlet } from "react-router-dom"; +import { AppVersion } from "ui/components/appVersion"; + +export function Layout() { + return ( + <> + + + + ); +} diff --git a/drama-queen/src/ui/routing/routes.tsx b/drama-queen/src/ui/routing/routes.tsx index fbff622a..b389f931 100644 --- a/drama-queen/src/ui/routing/routes.tsx +++ b/drama-queen/src/ui/routing/routes.tsx @@ -1,18 +1,18 @@ import { DisplayEnvValues } from "ui/pages/env"; import { SurveyUnitMapping, VisualisationMapping } from "ui/pages/queenMapping"; import { READ_ONLY } from "ui/constants"; -import type { RouteObject } from "react-router-dom"; +import type { LoaderFunctionArgs, RouteObject } from "react-router-dom"; import { SurveyMapping } from "ui/pages/queenMapping/SuryveyMapping"; -import { RequiresAuthentication } from "ui/auth"; import { SynchronizeData } from "ui/pages/synchronize/SynchronizeData"; + +import { Layout } from "./Layout"; +import { prCore } from "bootstrap"; import { Visualize } from "ui/pages/visualize/Visualize"; +import { Orchestrator } from "ui/components/orchestrator/Orchestrator"; //ReadOnly path is a bad pattern must be change (affects pearl,moog,queen) export const routes: RouteObject[] = [ - { - path: "/env", - element: - }, + { path: `/:${READ_ONLY}?/survey-unit/:id`, element: @@ -21,12 +21,49 @@ export const routes: RouteObject[] = [ path: `/:${READ_ONLY}?/questionnaire/:questionnaireId/survey-unit/:surveyUnitId`, element: }, + { - path: "/visualize/*", - element: + path: '/', + Component: Layout, + children: [ + { + path: "/env", + Component: DisplayEnvValues, + loader: protectedRouteLoader, + }, + { + path: "/visualize", + Component: Visualize + }, + { + path: "/synchronize", + Component: SynchronizeData, + loader: protectedRouteLoader, + }, + { + path: '/orchestrator', + Component: Orchestrator + } + ] }, - { - path: "/synchronize", - element: + +] + + +async function protectedRouteLoader({ request }: LoaderFunctionArgs) { + + const { functions: { userAuthentication } } = await prCore; + + if (!userAuthentication.getIsUserLoggedIn()) { + // Replace the href without reloading the page. + // This is a way to make oidc-spa know where to redirect the user + // if the authentication process is successful. + history.pushState({}, "", request.url); + + await userAuthentication.login(); + + // Never here, the login method redirects the user to the identity provider. } -] \ No newline at end of file + + return null; +} \ No newline at end of file diff --git a/drama-queen/src/ui/tools/makeSearchParamsObjectSchema.ts b/drama-queen/src/ui/tools/makeSearchParamsObjectSchema.ts new file mode 100644 index 00000000..19c8dd7b --- /dev/null +++ b/drama-queen/src/ui/tools/makeSearchParamsObjectSchema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +function safeParseJSON ( string: string ): any { + try { return JSON.parse( string ) } + catch { return string } +} + +function searchParamsToValues ( searchParams: URLSearchParams ): Record { + return Array.from( searchParams.keys() ).reduce( ( record, key ) => { + const values = searchParams.getAll( key ).map( safeParseJSON ) + return { ...record, [ key ]: values.length > 1 ? values : values[ 0 ] } + }, {} as Record ) +} + +export function makeSearchParamsObjSchema< + Schema extends z.ZodObject +> ( schema: Schema ) { + return z.instanceof( URLSearchParams ) + .transform( searchParamsToValues ) + .pipe( schema ) +} + +function coerceToArray< + Schema extends z.ZodArray +> ( schema: Schema ) { + return z.union( [ + z.any().array(), + z.any().transform( x => [ x ] ), + ] ).pipe( schema ) +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 84a2fc03..db33b528 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12170,10 +12170,10 @@ oidc-client@^1.11.5: crypto-js "^4.0.0" serialize-javascript "^4.0.0" -oidc-spa@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/oidc-spa/-/oidc-spa-2.0.3.tgz#387a6c92b2fbd097d75ca9f3fa4b10f7f5213cdc" - integrity sha512-pZJgjpdVr2CRGKhqQGj6z7qu1INo9/xP6HkkqIata3zXEmFrIK/zv6SCpDdFMfeIVfqQWV946FVbccUqBrUlcA== +oidc-spa@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/oidc-spa/-/oidc-spa-3.0.3.tgz#01e524064e91615e3f2c6d6c906234fc13351787" + integrity sha512-yoAQHaTPw0Eb3AYLQ1ODuCfO0iboUv3agnz45QSm0LWG9o6+9mtN6g71XljrSIPR/G5g4rLria4Ke+Rq/LoJZg== dependencies: jwt-decode "^3.1.2" oidc-client-ts "^2.3.0" @@ -14223,10 +14223,10 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redux-clean-architecture@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/redux-clean-architecture/-/redux-clean-architecture-4.1.1.tgz#1914cee90525765648e9a52ac64e25e7c8dc6f1a" - integrity sha512-ZdaIM4mHoWdjkC6GNf1P3Hb/yY9n4oplxhnOZ7o2WvB2OoaV5GmBjhZsIj7llAnEh0Y92gOGPxrO9kgUO9Evuw== +redux-clean-architecture@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/redux-clean-architecture/-/redux-clean-architecture-4.3.1.tgz#b3c0f661d3813a39f66bd0713d711974f83e1a33" + integrity sha512-WXbvO47tdoh4heJ26ZbfdLOKsXAz6SPCCig99ufTa5zEcm0R44+Xn/h7GYo3wBFTacHlcuv9KEbSap7TdDW1nA== dependencies: "@reduxjs/toolkit" "^1.9.6" minimal-polyfills "^2.2.3"