From 98e469372903f0436afa89131838f3877185ebf3 Mon Sep 17 00:00:00 2001 From: aldbr Date: Wed, 4 Dec 2024 17:39:47 +0100 Subject: [PATCH] feat(style): make the code more consistent --- packages/diracx-web-components/.eslintrc.json | 41 ++++++++- .../DashboardLayout/ApplicationDialog.tsx | 24 +++--- .../DashboardLayout/Dashboard.stories.tsx | 6 +- .../components/DashboardLayout/Dashboard.tsx | 12 +-- .../DashboardDrawer.stories.tsx | 6 +- .../DashboardLayout/DashboardDrawer.tsx | 39 ++++----- .../components/DashboardLayout/DrawerItem.tsx | 40 ++++----- .../DashboardLayout/DrawerItemGroup.tsx | 46 +++++----- .../DashboardLayout/ProfileButton.tsx | 4 +- .../components/JobMonitor/JobDataTable.tsx | 63 +++++++------- .../JobMonitor/JobHistoryDialog.tsx | 12 ++- .../components/Login/LoginForm.tsx | 18 ++-- .../components/OIDC/OIDCProvider.tsx | 4 +- .../components/OIDC/OIDCSecure.tsx | 4 +- .../components/shared/DataTable.tsx | 84 ++++++++++--------- .../components/shared/FilterForm.tsx | 27 +++--- .../components/shared/FilterToolbar.tsx | 38 ++++----- .../contexts/ApplicationsProvider.tsx | 12 +-- .../contexts/DiracXWebProviders.tsx | 14 ++-- .../contexts/NavigationProvider.tsx | 14 ++-- .../contexts/OIDCConfigurationProvider.tsx | 8 +- .../hooks/searchParamsUtils.tsx | 5 +- packages/diracx-web/.eslintrc.json | 41 ++++++++- .../diracx-web/src/app/(dashboard)/page.tsx | 8 +- packages/diracx-web/test/e2e/dashboard.cy.ts | 2 +- packages/diracx-web/test/e2e/jobMonitor.cy.ts | 2 +- packages/diracx-web/test/e2e/login_out.cy.ts | 6 +- 27 files changed, 333 insertions(+), 247 deletions(-) diff --git a/packages/diracx-web-components/.eslintrc.json b/packages/diracx-web-components/.eslintrc.json index 93679404..e880c722 100644 --- a/packages/diracx-web-components/.eslintrc.json +++ b/packages/diracx-web-components/.eslintrc.json @@ -17,7 +17,46 @@ "import/no-useless-path-segments": ["error"], "import/no-unresolved": ["off"], "react/prop-types": "off", - "react/react-in-jsx-scope": "off" + "react/react-in-jsx-scope": "off", + "react/destructuring-assignment": ["error", "always"], + "no-restricted-properties": [ + "error", + { + "object": "React", + "property": "useCallback", + "message": "Please import 'useCallback' directly from 'react' instead of 'React.useCallback'." + }, + { + "object": "React", + "property": "useContext", + "message": "Please import 'useContext' directly from 'react' instead of 'React.useContext'." + }, + { + "object": "React", + "property": "useEffect", + "message": "Please import 'useEffect' directly from 'react' instead of 'React.useEffect'." + }, + { + "object": "React", + "property": "useMemo", + "message": "Please import 'useMemo' directly from 'react' instead of 'React.useMemo'." + }, + { + "object": "React", + "property": "useReducer", + "message": "Please import 'useReducer' directly from 'react' instead of 'React.useReducer'." + }, + { + "object": "React", + "property": "useRef", + "message": "Please import 'useRef' directly from 'react' instead of 'React.useRef'." + }, + { + "object": "React", + "property": "useState", + "message": "Please import 'useState' directly from 'react' instead of 'React.useState'." + } + ] }, "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx b/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx index 232a2314..2412674e 100644 --- a/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx +++ b/packages/diracx-web-components/components/DashboardLayout/ApplicationDialog.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { useState, useContext } from "react"; import { Dialog, DialogTitle, @@ -12,6 +12,15 @@ import { import { Close, SvgIconComponent } from "@mui/icons-material"; import { ApplicationsContext } from "@/contexts/ApplicationsProvider"; +interface AppDialogProps { + /** Determines whether the dialog is open or not. */ + appDialogOpen: boolean; + /** Function to set the open state of the dialog. */ + setAppDialogOpen: React.Dispatch>; + /** Function to handle the creation of a new application. */ + handleCreateApp: (name: string, icon: SvgIconComponent) => void; +} + /** * Renders a dialog component for creating a new application. * @@ -22,16 +31,9 @@ export default function AppDialog({ appDialogOpen, setAppDialogOpen, handleCreateApp, -}: { - /** Determines whether the dialog is open or not. */ - appDialogOpen: boolean; - /** Function to set the open state of the dialog. */ - setAppDialogOpen: React.Dispatch>; - /** Function to handle the creation of a new application. */ - handleCreateApp: (name: string, icon: SvgIconComponent) => void; -}) { - const [appType, setAppType] = React.useState(""); - const applicationList = React.useContext(ApplicationsContext)[2]; +}: AppDialogProps) { + const [appType, setAppType] = useState(""); + const applicationList = useContext(ApplicationsContext)[2]; return ( { - const [userDashboard, setUserDashboard] = React.useState< - DashboardGroup[] - >([ + const [userDashboard, setUserDashboard] = useState([ { title: "Group Title", extended: true, diff --git a/packages/diracx-web-components/components/DashboardLayout/Dashboard.tsx b/packages/diracx-web-components/components/DashboardLayout/Dashboard.tsx index 6b561739..d616ffd9 100644 --- a/packages/diracx-web-components/components/DashboardLayout/Dashboard.tsx +++ b/packages/diracx-web-components/components/DashboardLayout/Dashboard.tsx @@ -1,5 +1,5 @@ "use client"; -import React from "react"; +import { useState } from "react"; import AppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; import IconButton from "@mui/material/IconButton"; @@ -28,9 +28,11 @@ interface DashboardProps { * @param props - children, drawerWidth, logoURL * @return an dashboard layout */ -export default function Dashboard(props: DashboardProps) { - const { children, drawerWidth = 240, logoURL } = props; - +export default function Dashboard({ + children, + drawerWidth = 240, + logoURL, +}: DashboardProps) { const appTitle = useApplicationTitle(); const appType = useApplicationType(); @@ -39,7 +41,7 @@ export default function Dashboard(props: DashboardProps) { const isMobile = useMediaQuery(theme.breakpoints.down("sm")); /** State management for mobile drawer */ - const [mobileOpen, setMobileOpen] = React.useState(false); + const [mobileOpen, setMobileOpen] = useState(false); const handleDrawerToggle = () => { setMobileOpen(!mobileOpen); }; diff --git a/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.stories.tsx b/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.stories.tsx index f9f02092..9f0e4638 100644 --- a/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.stories.tsx +++ b/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.stories.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { Box } from "@mui/material"; @@ -19,9 +19,7 @@ const meta = { tags: ["autodocs"], decorators: [ (Story) => { - const [userDashboard, setUserDashboard] = React.useState< - DashboardGroup[] - >([ + const [userDashboard, setUserDashboard] = useState([ { title: "Group Title", extended: true, diff --git a/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx b/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx index d49b86b1..2f92b458 100644 --- a/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx +++ b/packages/diracx-web-components/components/DashboardLayout/DashboardDrawer.tsx @@ -15,12 +15,7 @@ import { useTheme, } from "@mui/material"; import { MenuBook, Add, SvgIconComponent } from "@mui/icons-material"; -import React, { - ReactEventHandler, - useContext, - useEffect, - useState, -} from "react"; +import React, { useContext, useEffect, useState } from "react"; import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"; import DrawerItemGroup from "./DrawerItemGroup"; @@ -36,7 +31,7 @@ interface DashboardDrawerProps { /** The width of the drawer. */ width: number; /** The function to handle the drawer toggle. */ - handleDrawerToggle: ReactEventHandler; + handleDrawerToggle: React.ReactEventHandler; /** The URL for the logo image. */ logoURL?: string; } @@ -48,16 +43,22 @@ interface DashboardDrawerProps { * @param {DashboardDrawerProps} props - The props for the DashboardDrawer component. * @returns {JSX.Element} The rendered DashboardDrawer component. */ -export default function DashboardDrawer(props: DashboardDrawerProps) { +export default function DashboardDrawer({ + variant, + mobileOpen, + width, + handleDrawerToggle, + logoURL = "/DIRAC-logo.png", +}: DashboardDrawerProps) { // Determine the container for the Drawer based on whether the window object exists. const container = window !== undefined ? () => window.document.body : undefined; // Check if the drawer is in "temporary" mode. - const isTemporary = props.variant === "temporary"; + const isTemporary = variant === "temporary"; // Whether the modal for Application Creation is open const [appDialogOpen, setAppDialogOpen] = useState(false); - const [contextMenu, setContextMenu] = React.useState<{ + const [contextMenu, setContextMenu] = useState<{ mouseX: number; mouseY: number; } | null>(null); @@ -67,19 +68,15 @@ export default function DashboardDrawer(props: DashboardDrawerProps) { id: string | null; }>({ type: null, id: null }); - const [popAnchorEl, setPopAnchorEl] = React.useState( - null, - ); + const [popAnchorEl, setPopAnchorEl] = useState(null); const [renamingItemId, setRenamingItemId] = useState(null); const [renamingGroupId, setRenamingGroupId] = useState(null); - const [renameValue, setRenameValue] = React.useState(""); + const [renameValue, setRenameValue] = useState(""); // Define the applications that are accessible to users. // Each application has an associated icon and path. const [userDashboard, setUserDashboard] = useContext(ApplicationsContext); - const logoURL = props.logoURL || "/DIRAC-logo.png"; - const theme = useTheme(); useEffect(() => { @@ -360,9 +357,9 @@ export default function DashboardDrawer(props: DashboardDrawerProps) { <>
>; /** The function to set the user dashboard state. */ setUserDashboard: React.Dispatch>; -}) { +} + +/** + * Represents a drawer item component. + * + * @returns The rendered JSX for the drawer item. + */ +export default function DrawerItem({ + item: { title, id, icon }, + index, + groupTitle, + renamingItemId, + setRenamingItemId, + renameValue, + setRenameValue, + setUserDashboard, +}: DrawerItemProps) { // Ref to use for the draggable element - const dragRef = React.useRef(null); + const dragRef = useRef(null); // Ref to use for the handle of the draggable element, must be a child of the draggable element - const handleRef = React.useRef(null); + const handleRef = useRef(null); const theme = useTheme(); const { setParam } = useSearchParamsUtils(); // Represents the closest edge to the mouse cursor diff --git a/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx b/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx index a918e3c8..d04120ef 100644 --- a/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx +++ b/packages/diracx-web-components/components/DashboardLayout/DrawerItemGroup.tsx @@ -6,29 +6,12 @@ import { TextField, } from "@mui/material"; import { ExpandMore, Apps } from "@mui/icons-material"; -import React, { useEffect } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import DrawerItem from "./DrawerItem"; import { DashboardGroup } from "@/types/DashboardGroup"; -/** - * Represents a group of items in a drawer. - * - * @component - * @param {Object} props - The component props. - * @returns {JSX.Element} The rendered DrawerItemGroup component. - */ -export default function DrawerItemGroup({ - group: { title, extended: expanded, items }, - setUserDashboard, - handleContextMenu, - renamingGroupId, - setRenamingGroupId, - renamingItemId, - setRenamingItemId, - renameValue, - setRenameValue, -}: { +interface DrawerItemGroupProps { /** The group object containing the title, expanded state, and items. */ group: DashboardGroup; /** The function to set the user dashboard state. */ @@ -50,11 +33,30 @@ export default function DrawerItemGroup({ renameValue: string; /** The function to set the rename input value. */ setRenameValue: React.Dispatch>; -}) { +} + +/** + * Represents a group of items in a drawer. + * + * @component + * @param {Object} props - The component props. + * @returns {JSX.Element} The rendered DrawerItemGroup component. + */ +export default function DrawerItemGroup({ + group: { title, extended: expanded, items }, + setUserDashboard, + handleContextMenu, + renamingGroupId, + setRenamingGroupId, + renamingItemId, + setRenamingItemId, + renameValue, + setRenameValue, +}: DrawerItemGroupProps) { // Ref to use for the drag and drop target - const dropRef = React.useRef(null); + const dropRef = useRef(null); // State to track whether the user is hovering over the item during a drag operation - const [hovered, setHovered] = React.useState(false); + const [hovered, setHovered] = useState(false); useEffect(() => { if (!dropRef.current) return; diff --git a/packages/diracx-web-components/components/DashboardLayout/ProfileButton.tsx b/packages/diracx-web-components/components/DashboardLayout/ProfileButton.tsx index 154e3340..08535370 100644 --- a/packages/diracx-web-components/components/DashboardLayout/ProfileButton.tsx +++ b/packages/diracx-web-components/components/DashboardLayout/ProfileButton.tsx @@ -27,7 +27,7 @@ import { Typography, useTheme, } from "@mui/material"; -import React from "react"; +import React, { useState } from "react"; import { useOIDCContext } from "@/hooks/oidcConfiguration"; /** @@ -41,7 +41,7 @@ export function ProfileButton() { const { accessTokenPayload } = useOidcAccessToken(configuration?.scope); const { logout, isAuthenticated } = useOidc(configuration?.scope); - const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent) => { diff --git a/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx b/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx index 9e80174c..a44caca3 100644 --- a/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx +++ b/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useMemo, useState } from "react"; import Box from "@mui/material/Box"; import { blue, @@ -60,48 +60,47 @@ export function JobDataTable() { const { accessToken } = useOidcAccessToken(configuration?.scope); // State for loading elements - const [backdropOpen, setBackdropOpen] = React.useState(false); - const [snackbarInfo, setSnackbarInfo] = React.useState({ + const [backdropOpen, setBackdropOpen] = useState(false); + const [snackbarInfo, setSnackbarInfo] = useState({ open: false, message: "", severity: "success", }); // States for table settings - const [columnVisibility, setColumnVisibility] = - React.useState({ - JobGroup: false, - JobType: false, - Owner: false, - OwnerGroup: false, - VO: false, - StartExecTime: false, - EndExecTime: false, - UserPriority: false, - }); - const [columnPinning, setColumnPinning] = React.useState({ + const [columnVisibility, setColumnVisibility] = useState({ + JobGroup: false, + JobType: false, + Owner: false, + OwnerGroup: false, + VO: false, + StartExecTime: false, + EndExecTime: false, + UserPriority: false, + }); + const [columnPinning, setColumnPinning] = useState({ left: ["JobID"], // Pin JobID column by default }); - const [rowSelection, setRowSelection] = React.useState({}); - const [pagination, setPagination] = React.useState({ + const [rowSelection, setRowSelection] = useState({}); + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25, }); // State for search body - const [searchBody, setSearchBody] = React.useState({ + const [searchBody, setSearchBody] = useState({ sort: [{ parameter: "JobID", direction: "asc" }], }); // State for selected job - const [selectedJobId, setSelectedJobId] = React.useState(null); + const [selectedJobId, setSelectedJobId] = useState(null); // State for job history - const [isHistoryDialogOpen, setIsHistoryDialogOpen] = React.useState(false); - const [jobHistoryData, setJobHistoryData] = React.useState([]); + const [isHistoryDialogOpen, setIsHistoryDialogOpen] = useState(false); + const [jobHistoryData, setJobHistoryData] = useState([]); // Status colors - const statusColors: Record = React.useMemo( + const statusColors: Record = useMemo( () => ({ Submitting: purple[500], Received: blueGrey[500], @@ -125,7 +124,7 @@ export function JobDataTable() { /** * Renders the status cell with colors */ - const renderStatusCell = React.useCallback( + const renderStatusCell = useCallback( (status: string) => { return ( createColumnHelper(), []); + const columnHelper = useMemo(() => createColumnHelper(), []); /** * The head cells for the data grid (desktop version) */ - const columns = React.useMemo( + const columns = useMemo( () => [ columnHelper.accessor("JobID", { header: "ID", @@ -230,7 +229,7 @@ export function JobDataTable() { ); const dataHeader = data?.headers; - const results = React.useMemo(() => data?.data || [], [data?.data]); + const results = useMemo(() => data?.data || [], [data?.data]); // Parse the headers to get the first item, last item and number of items const contentRange = dataHeader?.get("content-range"); @@ -250,7 +249,7 @@ export function JobDataTable() { /** * Handle the deletion of the selected jobs */ - const handleDelete = React.useCallback(async () => { + const handleDelete = useCallback(async () => { setBackdropOpen(true); try { const selectedIds = Object.keys(rowSelection).map(Number); @@ -293,7 +292,7 @@ export function JobDataTable() { /** * Handle the killing of the selected jobs */ - const handleKill = React.useCallback(async () => { + const handleKill = useCallback(async () => { setBackdropOpen(true); try { const selectedIds = Object.keys(rowSelection).map(Number); @@ -336,7 +335,7 @@ export function JobDataTable() { /** * Handle the rescheduling of the selected jobs */ - const handleReschedule = React.useCallback(async () => { + const handleReschedule = useCallback(async () => { setBackdropOpen(true); try { const selectedIds = Object.keys(rowSelection).map(Number); @@ -379,7 +378,7 @@ export function JobDataTable() { /** * Handle the history of the selected job */ - const handleHistory = React.useCallback( + const handleHistory = useCallback( async (selectedId: number | null) => { if (!selectedId) return; setBackdropOpen(true); @@ -415,7 +414,7 @@ export function JobDataTable() { /** * The toolbar components for the data grid */ - const toolbarComponents = React.useMemo( + const toolbarComponents = useMemo( () => ( <> @@ -441,7 +440,7 @@ export function JobDataTable() { /** * The menu items */ - const menuItems: MenuItem[] = React.useMemo( + const menuItems: MenuItem[] = useMemo( () => [ { label: "Get history", diff --git a/packages/diracx-web-components/components/JobMonitor/JobHistoryDialog.tsx b/packages/diracx-web-components/components/JobMonitor/JobHistoryDialog.tsx index 5b1139f0..45aaface 100644 --- a/packages/diracx-web-components/components/JobMonitor/JobHistoryDialog.tsx +++ b/packages/diracx-web-components/components/JobMonitor/JobHistoryDialog.tsx @@ -12,7 +12,7 @@ import { useTheme, } from "@mui/material"; import { Close } from "@mui/icons-material"; -import React from "react"; +import React, { useMemo } from "react"; import { useReactTable, createColumnHelper, @@ -37,15 +37,19 @@ interface JobHistoryDialogProps { * * @returns The rendered JobHistoryDialog component. */ -export function JobHistoryDialog(props: JobHistoryDialogProps) { - const { open, onClose, historyData, jobId } = props; +export function JobHistoryDialog({ + open, + onClose, + historyData, + jobId, +}: JobHistoryDialogProps) { const theme = useTheme(); // Create column helper const columnHelper = createColumnHelper(); // Define columns - const columns = React.useMemo( + const columns = useMemo( () => [ columnHelper.accessor("Status", { header: "Status", diff --git a/packages/diracx-web-components/components/Login/LoginForm.tsx b/packages/diracx-web-components/components/Login/LoginForm.tsx index 237c9b54..5ffe4b66 100644 --- a/packages/diracx-web-components/components/Login/LoginForm.tsx +++ b/packages/diracx-web-components/components/Login/LoginForm.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import FormControl from "@mui/material/FormControl"; @@ -18,6 +18,11 @@ import { useOIDCContext } from "@/hooks/oidcConfiguration"; import { useSearchParamsUtils } from "@/hooks/searchParamsUtils"; import { NavigationContext } from "@/contexts/NavigationProvider"; +interface LoginFormProps { + /** The URL of the logo, optional */ + logoURL?: string; +} + /** * Login form * @@ -25,11 +30,8 @@ import { NavigationContext } from "@/contexts/NavigationProvider"; */ export function LoginForm({ logoURL = "/DIRAC-logo-minimal.png", -}: { - /** The URL of the logo, optional */ - logoURL?: string; -}) { - const { setPath } = React.useContext(NavigationContext); +}: LoginFormProps) { + const { setPath } = useContext(NavigationContext); const { metadata, error, isLoading } = useMetadata(); const [selectedVO, setSelectedVO] = useState(null); const [selectedGroup, setSelectedGroup] = useState(null); @@ -159,7 +161,7 @@ export function LoginForm({ renderInput={(params) => ( @@ -210,7 +212,7 @@ export function LoginForm({ onClick={handleConfigurationChanges} data-testid="button-login" > - Login through your Identity Provider + Login via your Identity Provider