diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a6f72c3..a725484 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -69,7 +69,7 @@ jobs: node-version-file: './dashboard/.nvmrc' - name: Run eslint - run: pnpm lint + run: pnpm lint-staged working-directory: ./dashboard build-front: diff --git a/dashboard/.eslintrc.cjs b/dashboard/.eslintrc.cjs index 6081ce3..ddd8bd2 100644 --- a/dashboard/.eslintrc.cjs +++ b/dashboard/.eslintrc.cjs @@ -9,6 +9,7 @@ module.exports = { "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:storybook/recommended", + "plugin:prettier/recommended", ], ignorePatterns: [".eslintrc.cjs", "*.config.js", "/src/components/ui/*"], parser: "@typescript-eslint/parser", diff --git a/dashboard/.husky/pre-commit b/dashboard/.husky/pre-commit index e7674ef..9de0f2d 100644 --- a/dashboard/.husky/pre-commit +++ b/dashboard/.husky/pre-commit @@ -1,3 +1,3 @@ #!/usr/bin/env sh -cd dashboard && pnpm lint +cd dashboard && pnpm lint-staged pnpm pycommit diff --git a/dashboard/.prettierrc b/dashboard/.prettierrc new file mode 100644 index 0000000..f7d04e2 --- /dev/null +++ b/dashboard/.prettierrc @@ -0,0 +1,9 @@ +{ + "printWidth": 80, + "trailingComma": "all", + "tabWidth": 2, + "semi": true, + "bracketSpacing": true, + "singleQuote": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/dashboard/package.json b/dashboard/package.json index 4d381c3..2971e87 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -8,7 +8,8 @@ "build": "tsc -b && vite build", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", - "lint": "eslint --cache --max-warnings=0 --ext .ts,.tsx,.js,.jsx src --fix", + "lint-staged": "eslint --max-warnings=0 --ext .ts,.tsx,.js,.jsx src", + "lint": "pnpm run lint-staged --fix", "preview": "vite preview", "prepare": "cd .. && husky dashboard/.husky", "pycommit": "cd .. && cd backend && sh pre-commit", @@ -56,15 +57,18 @@ "@typescript-eslint/parser": "^7.13.1", "autoprefixer": "^10.4.19", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-storybook": "^0.8.0", "husky": "^9.0.11", "postcss": "^8.4.38", + "prettier": "3.3.2", "storybook": "^8.1.10", "tailwindcss": "^3.4.4", "typescript": "^5.2.2" } -} +} \ No newline at end of file diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index 4a99b4e..b9119e5 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -126,9 +126,15 @@ importers: eslint: specifier: ^8.57.0 version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 version: 2.29.1(@typescript-eslint/parser@7.13.1(eslint@8.57.0)(typescript@5.2.2))(eslint@8.57.0) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.2) eslint-plugin-react: specifier: ^7.34.3 version: 7.34.3(eslint@8.57.0) @@ -147,6 +153,9 @@ importers: postcss: specifier: ^8.4.38 version: 8.4.38 + prettier: + specifier: 3.3.2 + version: 3.3.2 storybook: specifier: ^8.1.10 version: 8.1.10(@babel/preset-env@7.24.7(@babel/core@7.24.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1396,6 +1405,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -3107,6 +3120,12 @@ packages: engines: {node: '>=6.0'} hasBin: true + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -3141,6 +3160,20 @@ packages: '@typescript-eslint/parser': optional: true + eslint-plugin-prettier@5.1.3: + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + eslint-plugin-react-hooks@4.6.2: resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} @@ -3235,6 +3268,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -4340,6 +4376,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + prettier@3.3.2: resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} @@ -4841,6 +4881,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} + tailwind-merge@2.3.0: resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} @@ -6614,6 +6658,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pkgr/core@0.1.1': {} + '@popperjs/core@2.11.8': {} '@radix-ui/primitive@1.1.0': {} @@ -8916,6 +8962,10 @@ snapshots: optionalDependencies: source-map: 0.6.1 + eslint-config-prettier@9.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 @@ -8961,6 +9011,15 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.2): + dependencies: + eslint: 8.57.0 + prettier: 3.3.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -9149,6 +9208,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -10200,6 +10261,10 @@ snapshots: prelude-ls@1.2.1: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + prettier@3.3.2: {} pretty-format@27.5.1: @@ -10809,6 +10874,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + synckit@0.8.8: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.3 + tailwind-merge@2.3.0: dependencies: '@babel/runtime': 7.24.7 diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index f68a85b..dfaa5d1 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,17 +1,17 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import "./App.css"; -import Dashboard from "./components/Dashboard/Dashboard"; +import './App.css'; +import Dashboard from './components/Dashboard/Dashboard'; const queryClient = new QueryClient(); function App(): JSX.Element { return ( - +
-
+
); } diff --git a/dashboard/src/api/Tree.tsx b/dashboard/src/api/Tree.tsx index 206a7dd..5cdd441 100644 --- a/dashboard/src/api/Tree.tsx +++ b/dashboard/src/api/Tree.tsx @@ -1,15 +1,15 @@ -import { useQuery, UseQueryResult} from "@tanstack/react-query"; +import { useQuery, UseQueryResult } from '@tanstack/react-query'; -import type { Tree } from "../types/tree/Tree"; +import type { Tree } from '../types/tree/Tree'; -const fetchTreeCheckoutData = async(): Promise => { - const res = await fetch('/api/tree'); - if (!res.ok) { - throw new Error('Request to /api/tree response was not ok'); - } - return res.json(); -} +const fetchTreeCheckoutData = async (): Promise => { + const res = await fetch('/api/tree'); + if (!res.ok) { + throw new Error('Request to /api/tree response was not ok'); + } + return res.json(); +}; export const useTreeTable = (): UseQueryResult => { - return useQuery({ queryKey: ['treeData'], queryFn: fetchTreeCheckoutData }); -} + return useQuery({ queryKey: ['treeData'], queryFn: fetchTreeCheckoutData }); +}; diff --git a/dashboard/src/components/Dashboard/Dashboard.tsx b/dashboard/src/components/Dashboard/Dashboard.tsx index dca6e95..670d866 100644 --- a/dashboard/src/components/Dashboard/Dashboard.tsx +++ b/dashboard/src/components/Dashboard/Dashboard.tsx @@ -1,25 +1,28 @@ -import { ChangeEvent, useCallback, useState } from "react"; +import { ChangeEvent, useCallback, useState } from 'react'; -import SideMenu from "../SideMenu/SideMenu"; -import TopBar from "../TopBar/TopBar"; -import TreeListingPage from "../TreeListingPage/TreeListingPage"; -import { useDebounce } from "../../hooks/useDebounce"; +import SideMenu from '../SideMenu/SideMenu'; +import TopBar from '../TopBar/TopBar'; +import TreeListingPage from '../TreeListingPage/TreeListingPage'; +import { useDebounce } from '../../hooks/useDebounce'; -const Dashboard = () : JSX.Element => { +const Dashboard = (): JSX.Element => { const [inputSearchText, setInputSearchText] = useState(''); const debouncedInput = useDebounce(inputSearchText, DEBOUNCE_INTERVAL); - const onInputSearchTextChange = useCallback((e: ChangeEvent) => { - setInputSearchText(e.target.value); - }, []); + const onInputSearchTextChange = useCallback( + (e: ChangeEvent) => { + setInputSearchText(e.target.value); + }, + [], + ); return (
- +
- +
diff --git a/dashboard/src/components/SideMenu/SideMenu.tsx b/dashboard/src/components/SideMenu/SideMenu.tsx index 45836c5..17c7d00 100644 --- a/dashboard/src/components/SideMenu/SideMenu.tsx +++ b/dashboard/src/components/SideMenu/SideMenu.tsx @@ -1,17 +1,17 @@ -import { MdOutlineMonitorHeart } from "react-icons/md"; +import { MdOutlineMonitorHeart } from 'react-icons/md'; -import { ImTree, ImImages } from "react-icons/im"; +import { ImTree, ImImages } from 'react-icons/im'; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage } from 'react-intl'; import { NavigationMenu, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, -} from "../ui/navigation-menu"; +} from '../ui/navigation-menu'; -import { Separator } from "../ui/separator"; +import { Separator } from '../ui/separator'; type MenuItems = { onClick: () => void; @@ -20,24 +20,24 @@ type MenuItems = { selected: boolean; }; -const emptyFunc = () : void => {} +const emptyFunc = (): void => {}; const items: MenuItems[] = [ { onClick: emptyFunc, - idIntl: "routes.treeMonitor", + idIntl: 'routes.treeMonitor', icon: , selected: true, }, { onClick: emptyFunc, - idIntl: "routes.deviceMonitor", + idIntl: 'routes.deviceMonitor', icon: , selected: false, }, { onClick: emptyFunc, - idIntl: "routes.labsMonitor", + idIntl: 'routes.labsMonitor', icon: , selected: false, }, @@ -46,18 +46,22 @@ const items: MenuItems[] = [ const NavLink = ({ icon, idIntl, -}: Pick): JSX.Element => ( +}: Pick): JSX.Element => ( {icon} - + + {' '} + ); const SideMenu = (): JSX.Element => { - const selectedItemClassName = "w-full flex pl-5 py-4 cursor-pointer text-sky-500 bg-black border-l-4 border-sky-500"; - const notSelectedItemClassName = "w-full flex pl-5 py-4 cursor-pointer text-white"; + const selectedItemClassName = + 'w-full flex pl-5 py-4 cursor-pointer text-sky-500 bg-black border-l-4 border-sky-500'; + const notSelectedItemClassName = + 'w-full flex pl-5 py-4 cursor-pointer text-white'; return ( { orientation="vertical" >
- +
- {items.map((item) => - - - - - - )} + {items.map(item => ( + + + + ))}
); diff --git a/dashboard/src/components/Table/BaseTable.tsx b/dashboard/src/components/Table/BaseTable.tsx index d79ba98..a14493d 100644 --- a/dashboard/src/components/Table/BaseTable.tsx +++ b/dashboard/src/components/Table/BaseTable.tsx @@ -1,29 +1,29 @@ -import type { ReactElement } from "react"; +import type { ReactElement } from 'react'; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage } from 'react-intl'; -import { Table, TableHead, TableHeader, TableRow } from "../ui/table" +import { Table, TableHead, TableHeader, TableRow } from '../ui/table'; interface IBaseTable { headers: string[]; body: ReactElement; } -const BaseTable = ({headers, body}: IBaseTable) : JSX.Element => { +const BaseTable = ({ headers, body }: IBaseTable): JSX.Element => { return (
- - - - {headers.map((column) => ( - - - - ))} - - - {body} -
+ + + + {headers.map(column => ( + + + + ))} + + + {body} +
); }; diff --git a/dashboard/src/components/Table/TreeTable.tsx b/dashboard/src/components/Table/TreeTable.tsx index df8461b..8ce88f8 100644 --- a/dashboard/src/components/Table/TreeTable.tsx +++ b/dashboard/src/components/Table/TreeTable.tsx @@ -1,10 +1,10 @@ -import { useMemo } from "react"; +import { useMemo } from 'react'; -import { TableRow, TableCell } from "../ui/table"; +import { TableRow, TableCell } from '../ui/table'; -import { TreeTableBody } from "../../types/tree/Tree"; +import { TreeTableBody } from '../../types/tree/Tree'; -import BaseTable from "./BaseTable"; +import BaseTable from './BaseTable'; interface ITreeTable { treeTableRows: TreeTableBody[]; @@ -15,41 +15,45 @@ const treeTableColumnsLabelId = [ 'treeTable.branch', 'treeTable.commit', 'treeTable.build', - 'treeTable.test' + 'treeTable.test', ]; const TreeTableRow = (row: TreeTableBody): JSX.Element => { - const backgroundClassName = "flex flex-row bg-lightGray w-fit h-fit p-1 rounded-lg"; + const backgroundClassName = + 'flex flex-row bg-lightGray w-fit h-fit p-1 rounded-lg'; return ( {row.name} {row.branch} {row.commit} -
{row.buildStatus}
-
{row.testStatus}
+ +
{row.buildStatus}
+
+ +
{row.testStatus}
+
); }; - const TreeTable = ({treeTableRows}: ITreeTable) : JSX.Element => { +const TreeTable = ({ treeTableRows }: ITreeTable): JSX.Element => { const treeTableBody = useMemo(() => { - return ( - treeTableRows.map((row: TreeTableBody) => ( - - )) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps + return treeTableRows.map((row: TreeTableBody) => ( + + )); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [treeTableRows]); - return( + return ( {treeTableBody}} /> ); -} +}; export default TreeTable; diff --git a/dashboard/src/components/TopBar/TopBar.tsx b/dashboard/src/components/TopBar/TopBar.tsx index 18cf037..c554543 100644 --- a/dashboard/src/components/TopBar/TopBar.tsx +++ b/dashboard/src/components/TopBar/TopBar.tsx @@ -1,14 +1,14 @@ -import { FormattedMessage } from "react-intl"; +import { FormattedMessage } from 'react-intl'; -import { ChangeEvent } from "react"; +import { ChangeEvent } from 'react'; -import { Input } from "../ui/input"; +import { Input } from '../ui/input'; interface ITopBar { onChangeInputText: (e: ChangeEvent) => void; } -const TopBar = ({onChangeInputText}: ITopBar): JSX.Element => { +const TopBar = ({ onChangeInputText }: ITopBar): JSX.Element => { return (
@@ -18,7 +18,12 @@ const TopBar = ({onChangeInputText}: ITopBar): JSX.Element => {
{/* placeholder for search */} {/* TODO: use i18n for the input placeholder */} - +
diff --git a/dashboard/src/components/TreeListingPage/TreeListingPage.tsx b/dashboard/src/components/TreeListingPage/TreeListingPage.tsx index d61d0f9..d605b5b 100644 --- a/dashboard/src/components/TreeListingPage/TreeListingPage.tsx +++ b/dashboard/src/components/TreeListingPage/TreeListingPage.tsx @@ -1,14 +1,17 @@ -import { FormattedMessage } from "react-intl"; +import { FormattedMessage } from 'react-intl'; -import { MdArrowBackIos, MdArrowForwardIos, MdExpandMore } from "react-icons/md"; +import { + MdArrowBackIos, + MdArrowForwardIos, + MdExpandMore, +} from 'react-icons/md'; -import { useCallback, useEffect, useMemo, useState } from "react"; - -import TreeTable from "../Table/TreeTable"; -import { Button } from "../ui/button"; -import { Tree, TreeTableBody } from "../../types/tree/Tree"; -import { useTreeTable } from "../../api/Tree"; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import TreeTable from '../Table/TreeTable'; +import { Button } from '../ui/button'; +import { Tree, TreeTableBody } from '../../types/tree/Tree'; +import { useTreeTable } from '../../api/Tree'; interface ITableInformation { startIndex: number; @@ -16,7 +19,7 @@ interface ITableInformation { totalTrees: number; itemsPerPage: number; onClickForward: () => void; - onClickBack: () => void; + onClickBack: () => void; } interface ITreeListingPage { @@ -31,28 +34,38 @@ const TableInfo = ({ onClickForward, onClickBack, }: ITableInformation): JSX.Element => { - const buttonsClassName = "text-lightBlue font-bold" - const groupsClassName = "flex flex-row items-center gap-2" + const buttonsClassName = 'text-lightBlue font-bold'; + const groupsClassName = 'flex flex-row items-center gap-2'; return (
- - {startIndex} - {endIndex} - + + + {startIndex} - {endIndex} + + {totalTrees} - +
- + {itemsPerPage} - +
- -
@@ -60,8 +73,11 @@ const TableInfo = ({ }; const FilterButton = (): JSX.Element => { - return( -