From db3d2c4c5e09c3bf3aa4bbec26ed221ac8db0130 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Sun, 31 Mar 2024 13:29:56 -0500 Subject: [PATCH 1/9] Rename AppExample.tsx to main.tsx and use createBrowserRouter --- src/App.tsx | 22 ++++++++-------------- src/{AppExample.tsx => main.tsx} | 0 webpack.config.js | 2 +- 3 files changed, 9 insertions(+), 15 deletions(-) rename src/{AppExample.tsx => main.tsx} (100%) diff --git a/src/App.tsx b/src/App.tsx index dae49ed7..e6f9846b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,7 @@ import { ThemeProvider } from "@mui/material"; import React from "react"; import { Provider } from "react-redux"; -import { BrowserRouter as Router } from "react-router-dom"; -import { Route, Routes } from "react-router"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { PageLayout } from "./layouts"; import { @@ -16,17 +15,12 @@ import { condaStoreTheme, grayscaleTheme } from "./theme"; import "../style/index.css"; -const AppRouter = () => { - // for now, trivial routing is sufficient - return ( - - - } /> - - - ); -}; - +const router = createBrowserRouter([ + { + path: "/", + element: + } +]); export interface IAppProps { pref?: Partial; } @@ -86,7 +80,7 @@ export class App< } > - + diff --git a/src/AppExample.tsx b/src/main.tsx similarity index 100% rename from src/AppExample.tsx rename to src/main.tsx diff --git a/webpack.config.js b/webpack.config.js index a73ad539..4c6df21d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -31,7 +31,7 @@ const basicConfig = { port: 8000 }, devtool: isProd ? false : "source-map", - entry: ["src/index.tsx", "src/AppExample.tsx"], + entry: ["src/index.tsx", "src/main.tsx"], watch: false, ...getContext(__dirname), From 11eb62d0e9f33480ac629ebe247c935db78129eb Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 1 Apr 2024 13:56:54 -0500 Subject: [PATCH 2/9] app URL routing --- src/App.tsx | 11 +- .../components/EnvironmentCreate.tsx | 39 +++--- .../components/EnvironmentDetails.tsx | 102 +++++++++++----- .../environments/components/Environment.tsx | 6 +- .../components/EnvironmentDropdown.tsx | 53 ++------ .../environments/components/Environments.tsx | 78 ++++++------ src/features/environments/reducer.ts | 59 +++++---- src/features/namespaces/reducer.ts | 30 +++-- src/features/notification/index.ts | 1 + .../notification/notificationSlice.ts | 30 +++++ .../components/AddRequestedPackage.tsx | 1 - src/features/tabs/components/PageTabs.tsx | 114 ------------------ src/features/tabs/components/index.tsx | 1 - src/features/tabs/index.tsx | 1 - src/features/tabs/tabsSlice.ts | 8 +- src/layouts/PageLayout.tsx | 67 +++------- src/routes.tsx | 26 ++++ src/store/rootReducer.ts | 8 +- src/styles/StyledIconButton.tsx | 2 +- src/styles/StyledTab.tsx | 23 ---- src/styles/StyledTabs.tsx | 13 -- src/styles/index.tsx | 2 - src/utils/helpers/namespaces.ts | 16 ++- test/PageTabs.test.tsx | 93 -------------- .../EnvironmentCreate.test.tsx | 9 +- .../EnvironmentDetails.test.tsx | 17 +-- .../EnvironmentDetailsHeader.test.tsx | 19 +-- .../SpecificationEdit.test.tsx | 13 +- test/environments/Environment.test.tsx | 20 +-- .../environments/EnvironmentDropdown.test.tsx | 20 +-- test/environments/EnvironmentList.test.tsx | 7 +- test/environments/Environments.test.tsx | 9 +- test/playwright/test_ux.py | 43 +++---- .../AddRequestedPackage.test.tsx | 17 +-- webpack.config.js | 5 +- 35 files changed, 382 insertions(+), 581 deletions(-) create mode 100644 src/features/notification/index.ts create mode 100644 src/features/notification/notificationSlice.ts delete mode 100644 src/features/tabs/components/PageTabs.tsx delete mode 100644 src/features/tabs/components/index.tsx create mode 100644 src/routes.tsx delete mode 100644 src/styles/StyledTab.tsx delete mode 100644 src/styles/StyledTabs.tsx delete mode 100644 test/PageTabs.test.tsx diff --git a/src/App.tsx b/src/App.tsx index e6f9846b..d55d142b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,21 @@ import { ThemeProvider } from "@mui/material"; import React from "react"; import { Provider } from "react-redux"; -import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { RouterProvider } from "react-router-dom"; -import { PageLayout } from "./layouts"; import { IPreferences, PrefContext, prefDefault, prefGlobal } from "./preferences"; +import { router } from "./routes"; import { store } from "./store"; import { condaStoreTheme, grayscaleTheme } from "./theme"; import "../style/index.css"; -const router = createBrowserRouter([ - { - path: "/", - element: - } -]); + export interface IAppProps { pref?: Partial; } diff --git a/src/features/environmentCreate/components/EnvironmentCreate.tsx b/src/features/environmentCreate/components/EnvironmentCreate.tsx index f369202c..a3432dfc 100644 --- a/src/features/environmentCreate/components/EnvironmentCreate.tsx +++ b/src/features/environmentCreate/components/EnvironmentCreate.tsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; import Box from "@mui/material/Box"; import Alert from "@mui/material/Alert"; import { stringify } from "yaml"; @@ -16,23 +17,38 @@ import { } from "../../../features/metadata"; import { environmentOpened, - closeCreateNewEnvironmentTab + closeCreateNewEnvironmentTab, + openCreateNewEnvironmentTab } from "../../../features/tabs"; import { useAppDispatch, useAppSelector } from "../../../hooks"; import { SpecificationCreate, SpecificationReadOnly } from "./Specification"; import { descriptionChanged, nameChanged } from "../environmentCreateSlice"; import createLabel from "../../../common/config/labels"; - -export interface IEnvCreate { - environmentNotification: (notification: any) => void; -} +import { showNotification } from "../../notification/notificationSlice"; interface ICreateEnvironmentArgs { code: { channels: string[]; dependencies: string[] }; } -export const EnvironmentCreate = ({ environmentNotification }: IEnvCreate) => { +export const EnvironmentCreate = () => { const dispatch = useAppDispatch(); + + // Url routing params + // If user loads the app at //new-environment + // This will put the app in the correct state + const { namespaceName } = useParams<{ + namespaceName: string; + }>(); + + useEffect(() => { + if (namespaceName) { + dispatch(modeChanged(EnvironmentDetailsModes.CREATE)); + dispatch(openCreateNewEnvironmentTab(namespaceName)); + } + }, [namespaceName]); + + const navigate = useNavigate(); + const { mode } = useAppSelector(state => state.environmentDetails); const { name, description } = useAppSelector( state => state.environmentCreate @@ -84,17 +100,12 @@ export const EnvironmentCreate = ({ environmentNotification }: IEnvCreate) => { dispatch( environmentOpened({ environment, - selectedEnvironmentId: newEnvId, canUpdate: true }) ); + navigate(`/${namespace}/${name}`); dispatch(currentBuildIdChanged(data.build_id)); - environmentNotification({ - data: { - show: true, - description: createLabel(name, "create") - } - }); + dispatch(showNotification(createLabel(name, "create"))); } catch (e) { setError({ message: e?.data?.message ?? createLabel(undefined, "error"), diff --git a/src/features/environmentDetails/components/EnvironmentDetails.tsx b/src/features/environmentDetails/components/EnvironmentDetails.tsx index 7b8e690e..ec2debb5 100644 --- a/src/features/environmentDetails/components/EnvironmentDetails.tsx +++ b/src/features/environmentDetails/components/EnvironmentDetails.tsx @@ -1,4 +1,6 @@ import React, { useEffect, useState } from "react"; +import { skipToken } from "@reduxjs/toolkit/query/react"; +import { useNavigate, useParams } from "react-router-dom"; import Box from "@mui/material/Box"; import Alert from "@mui/material/Alert"; import { stringify } from "yaml"; @@ -8,7 +10,9 @@ import { SpecificationEdit, SpecificationReadOnly } from "./Specification"; import { useGetBuildQuery } from "../environmentDetailsApiSlice"; import { updateEnvironmentBuildId, - environmentClosed + environmentClosed, + environmentOpened, + toggleNewEnvironmentView } from "../../../features/tabs"; import { useGetBuildPackagesQuery, @@ -34,13 +38,15 @@ import artifactList from "../../../utils/helpers/artifact"; import createLabel from "../../../common/config/labels"; import { AlertDialog } from "../../../components/Dialog"; import { useAppDispatch, useAppSelector } from "../../../hooks"; -import { CondaSpecificationPip } from "../../../common/models"; +import { + CondaSpecificationPip, + Environment, + Namespace +} from "../../../common/models"; import { useInterval } from "../../../utils/helpers"; +import { showNotification } from "../../notification/notificationSlice"; +import { useScrollRef } from "../../../layouts/PageLayout"; -interface IEnvDetails { - scrollRef: any; - environmentNotification: (notification: any) => void; -} interface IUpdateEnvironmentArgs { dependencies: (string | CondaSpecificationPip)[]; channels: string[]; @@ -48,16 +54,51 @@ interface IUpdateEnvironmentArgs { const INTERVAL_REFRESHING = 5000; -export const EnvironmentDetails = ({ - scrollRef, - environmentNotification -}: IEnvDetails) => { +export const EnvironmentDetails = () => { const dispatch = useAppDispatch(); + + // Url routing params + // If user loads the app at // + // This will put the app in the correct state + const { namespaceName, environmentName } = useParams<{ + namespaceName: string; + environmentName: string; + }>(); + const namespaces: Namespace[] = useAppSelector( + state => state.namespaces.data + ); + const namespace = namespaces.find(({ name }) => name === namespaceName); + const foundNamespace = Boolean(namespace); + const environments: Environment[] = useAppSelector( + state => state.environments.data + ); + const environment = environments.find( + environment => + environment.namespace.name === namespaceName && + environment.name === environmentName + ); + const foundEnvironment = Boolean(environment); + useEffect(() => { + if (namespace && environment) { + dispatch( + environmentOpened({ + environment, + canUpdate: namespace.canUpdate + }) + ); + dispatch(modeChanged(EnvironmentDetailsModes.READ)); + dispatch(toggleNewEnvironmentView(false)); + } + }, [namespaceName, environmentName, foundNamespace, foundEnvironment]); + + const navigate = useNavigate(); + const { mode } = useAppSelector(state => state.environmentDetails); const { page, dependencies } = useAppSelector(state => state.dependencies); const { selectedEnvironment } = useAppSelector(state => state.tabs); const { currentBuild } = useAppSelector(state => state.enviroments); const [name, setName] = useState(selectedEnvironment?.name || ""); + const scrollRef = useScrollRef(); const [descriptionIsUpdated, setDescriptionIsUpdated] = useState(false); const [description, setDescription] = useState( @@ -81,7 +122,7 @@ export const EnvironmentDetails = ({ const [updateBuildId] = useUpdateBuildIdMutation(); const [deleteEnvironment] = useDeleteEnvironmentMutation(); - useGetEnviromentBuildsQuery(selectedEnvironment, { + useGetEnviromentBuildsQuery(selectedEnvironment ?? skipToken, { pollingInterval: INTERVAL_REFRESHING }); @@ -112,7 +153,7 @@ export const EnvironmentDetails = ({ }; const loadArtifacts = async () => { - if (artifactType.includes("DOCKER_MANIFEST")) { + if (!currentBuildId || artifactType.includes("DOCKER_MANIFEST")) { return; } @@ -122,7 +163,7 @@ export const EnvironmentDetails = ({ }; const loadDependencies = async () => { - if (dependencies.length) { + if (!currentBuildId || dependencies.length) { return; } @@ -171,12 +212,7 @@ export const EnvironmentDetails = ({ dispatch(modeChanged(EnvironmentDetailsModes.READ)); setCurrentBuildId(data.build_id); dispatch(currentBuildIdChanged(data.build_id)); - environmentNotification({ - data: { - show: true, - description: createLabel(environment, "update") - } - }); + dispatch(showNotification(createLabel(environment, "update"))); } catch (e) { setError({ message: @@ -187,7 +223,7 @@ export const EnvironmentDetails = ({ visible: true }); } - scrollRef.current.scrollTo(0, 0); + scrollRef.current?.scrollTo(0, 0); }; const updateBuild = async (buildId: number) => { @@ -201,12 +237,9 @@ export const EnvironmentDetails = ({ buildId }).unwrap(); dispatch(updateEnvironmentBuildId(buildId)); - environmentNotification({ - data: { - show: true, - description: createLabel(selectedEnvironment.name, "updateBuild") - } - }); + dispatch( + showNotification(createLabel(selectedEnvironment.name, "updateBuild")) + ); } catch (e) { setError({ message: createLabel(undefined, "error"), @@ -232,19 +265,17 @@ export const EnvironmentDetails = ({ selectedEnvironmentId: selectedEnvironment.id }) ); - environmentNotification({ - data: { - show: true, - description: createLabel(selectedEnvironment.name, "delete") - } - }); + dispatch( + showNotification(createLabel(selectedEnvironment.name, "delete")) + ); + navigate("/"); } catch (e) { setError({ message: createLabel(undefined, "error"), visible: true }); } - scrollRef.current.scrollTo(0, 0); + scrollRef.current?.scrollTo(0, 0); setShowDialog(false); }; @@ -255,10 +286,15 @@ export const EnvironmentDetails = ({ })(); }, INTERVAL_REFRESHING); + if (!selectedEnvironment) { + return null; + } + return ( diff --git a/src/features/environments/components/Environment.tsx b/src/features/environments/components/Environment.tsx index 1f35642b..31dfc65d 100644 --- a/src/features/environments/components/Environment.tsx +++ b/src/features/environments/components/Environment.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { Link } from "react-router-dom"; import CircleIcon from "@mui/icons-material/Circle"; import ListItemIcon from "@mui/material/ListItemIcon"; import Button from "@mui/material/Button"; @@ -12,13 +13,11 @@ interface IEnvironmentProps { * @param selectedEnvironmentId id of the currently selected environment */ environment: EnvironmentModel; - onClick: () => void; selectedEnvironmentId: number | undefined; } export const Environment = ({ environment, - onClick, selectedEnvironmentId }: IEnvironmentProps) => { const isSelected = selectedEnvironmentId === environment.id; @@ -44,7 +43,8 @@ export const Environment = ({ />