From d1d89e04eb5ff1598d1a6d6e04f7e0fe6d275fd0 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Fri, 11 Aug 2023 13:20:12 -0600 Subject: [PATCH 01/21] npm install material-react-table Signed-off-by: Zack Cerza --- package-lock.json | 297 +++++++++++++++++++++++++++++++++++++++++----- package.json | 3 +- 2 files changed, 269 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e30be7..59cf172 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "date-fns": "^2.30.0", "dotenv": "^16.3.1", "json-to-pretty-yaml": "^1.2.2", + "material-react-table": "^2.0.0", "mdi-material-ui": "^7.7.0", "react": "^18.2.0", "react-cookie": "^5.0.0", @@ -1841,11 +1842,11 @@ "license": "MIT" }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", + "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -2106,6 +2107,44 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.1.tgz", + "integrity": "sha512-QgcKYwzcc8vvZ4n/5uklchy8KVdjJwcOeI+HnnTNclJjs2nYsy23DOCf+sSV1kBwD9yDAoVKCkv/gEPzgQU3Pw==", + "peer": true, + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "peer": true, + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "peer": true, + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==", + "peer": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -2450,11 +2489,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.10", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.10.tgz", + "integrity": "sha512-wX1vbDC+lzF7FlhT6A3ffRZgEoKWPF8VqRoTu4lZwouFX2t90KyCMsgepMw5DxLak1BSp/KP86CmtZttikb/gQ==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -2463,13 +2502,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.13.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.7.tgz", - "integrity": "sha512-/3BLptG/q0u36eYED7Nhf4fKXmcKb6LjjT7ZMwhZIZSdSxVqDqSTmATW3a56n3KEPQUXCU9TpxAfCBQhs6brVA==", + "version": "5.14.19", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.19.tgz", + "integrity": "sha512-qAHvTXzk7basbyqPvhgWqN6JbmI2wLB/mf97GkSlz5c76MiKYV6Ffjvw9BjKZQ1YRb8rDX9kgdjRezOcoB91oQ==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", + "@babel/runtime": "^7.23.4", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -2478,10 +2516,16 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@mui/utils/node_modules/react-is": { @@ -2514,6 +2558,113 @@ "react-dom": "^17.0.0 || ^18.0.0" } }, + "node_modules/@mui/x-date-pickers": { + "version": "6.18.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.2.tgz", + "integrity": "sha512-HJq4uoFQSu5isa/mesWw2BKh8KBRYUQb+KaSlVlWfJNgP3YhPvWZ6yqCNYyxOAiPMxb0n3nBjS9ErO27OHjFMA==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "^5.0.0-beta.22", + "@mui/utils": "^5.14.16", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "date-fns": "^2.25.0", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/base": { + "version": "5.0.0-beta.25", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.25.tgz", + "integrity": "sha512-Iiv+IcappRRv6IBlknIVmLkXxfp51NEX1+l9f+dIbBuPU4PaRULegr1lCeHKsC45KU5ruxM5xMg4R/de03aJQg==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.4", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.10", + "@mui/utils": "^5.14.19", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "dev": true, @@ -2897,6 +3048,62 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.10.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.10.7.tgz", + "integrity": "sha512-bXhjA7xsTcsW8JPTTYlUg/FuBpn8MNjiEPhkNhIGCUR6iRQM2+WEco4OBpvDeVcR9SE+bmWLzdfiY7bCbCSVuA==", + "dependencies": { + "@tanstack/table-core": "8.10.7" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.0.0-beta.68", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.68.tgz", + "integrity": "sha512-YEFNCf+N3ZlNou2r4qnh+GscMe24foYEjTL05RS0ZHvah2RoUDPGuhnuedTv0z66dO2vIq6+Bl4TXatht5T7GQ==", + "dependencies": { + "@tanstack/virtual-core": "3.0.0-beta.68" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.10.7", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.10.7.tgz", + "integrity": "sha512-KQk5OMg5OH6rmbHZxuNROvdI+hKDIUxANaHlV+dPlNN7ED3qYQ/WkpY2qlXww1SIdeMlkIhpN/2L00rof0fXFw==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.0.0-beta.68", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.68.tgz", + "integrity": "sha512-CnvsEJWK7cugigckt13AeY80FMzH+OMdEP0j0bS3/zjs44NiRe49x8FZC6R9suRXGMVMXtUHet0zbTp/Ec9Wfg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "9.2.0", "license": "MIT", @@ -3076,8 +3283,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "license": "MIT" + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/react": { "version": "18.2.14", @@ -3104,14 +3312,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-router": { "version": "5.1.20", "dev": true, @@ -3132,9 +3332,9 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.9.tgz", + "integrity": "sha512-ZVNmWumUIh5NhH8aMD9CR2hdW0fNuYInlocZHaZ+dgk/1K49j1w/HoAuK1ki+pgscQrOFRTlXeoURtuzEkV3dg==", "dependencies": { "@types/react": "*" } @@ -5847,6 +6047,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/highlight-words": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-1.2.2.tgz", + "integrity": "sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ==", + "engines": { + "node": ">= 16", + "npm": ">= 8" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "license": "BSD-3-Clause", @@ -6609,6 +6818,33 @@ "node": ">=12" } }, + "node_modules/material-react-table": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-2.0.4.tgz", + "integrity": "sha512-Jl3j1VyStr4JO2TG0J3PeFHu/d7+23bQ7b5LhUogWiTM0n2j9Uio4gyQ5C2QOzgMhvELt3RgemNPYGqt0bGBHA==", + "dependencies": { + "@tanstack/match-sorter-utils": "8.8.4", + "@tanstack/react-table": "8.10.7", + "@tanstack/react-virtual": "3.0.0-beta.68", + "highlight-words": "1.2.2" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kevinvandy" + }, + "peerDependencies": { + "@emotion/react": ">=11.11", + "@emotion/styled": ">=11.11", + "@mui/icons-material": ">=5.11", + "@mui/material": ">=5.13", + "@mui/x-date-pickers": ">=6.15.0", + "react": ">=18.0", + "react-dom": ">=18.0" + } + }, "node_modules/mdi-material-ui": { "version": "7.7.0", "license": "MIT", @@ -7358,8 +7594,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "license": "MIT" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" }, "node_modules/regenerator-transform": { "version": "0.15.1", diff --git a/package.json b/package.json index 348ab02..a8888a0 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "date-fns": "^2.30.0", "dotenv": "^16.3.1", "json-to-pretty-yaml": "^1.2.2", + "material-react-table": "^2.0.0", "mdi-material-ui": "^7.7.0", "react": "^18.2.0", "react-cookie": "^5.0.0", @@ -75,4 +76,4 @@ "typescript": "^5.1.6", "vite": "^4.4.1" } -} +} \ No newline at end of file From 3810cc4a37248d8167f07c7f74d338b873644fa1 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Fri, 11 Aug 2023 15:43:02 -0600 Subject: [PATCH 02/21] Replace @mui/x-data-grid with material-react-table Fixes: #61 Signed-off-by: Zack Cerza --- src/components/DataGrid/index.jsx | 184 ---------------- src/components/JobList/index.tsx | 291 ++++++++++++------------- src/components/Link/index.jsx | 1 + src/components/NodeList/index.tsx | 179 ++++++++-------- src/components/RunList/index.tsx | 327 ++++++++++++++++++----------- src/index.tsx | 26 ++- src/lib/paddles.d.ts | 38 +++- src/lib/paddles.ts | 11 +- src/lib/table.ts | 61 ++++++ src/lib/utils.js | 9 +- src/pages/Node/index.tsx | 6 +- src/pages/Nodes/index.tsx | 7 +- src/pages/Run/index.tsx | 39 +--- src/pages/Runs/index.tsx | 79 +------ src/pages/StatsNodesJobs/index.tsx | 194 +++++++++-------- src/pages/StatsNodesLock/index.tsx | 140 ++++++------ 16 files changed, 760 insertions(+), 832 deletions(-) delete mode 100644 src/components/DataGrid/index.jsx create mode 100644 src/lib/table.ts diff --git a/src/components/DataGrid/index.jsx b/src/components/DataGrid/index.jsx deleted file mode 100644 index 5de3d38..0000000 --- a/src/components/DataGrid/index.jsx +++ /dev/null @@ -1,184 +0,0 @@ -import { DataGrid as MuiDataGrid } from "@mui/x-data-grid"; -import { styled } from "@mui/material/styles"; -import { darken, lighten } from "@mui/material/styles"; - -import { colorTint } from "../../lib/utils"; - -const PREFIX = "index"; - -const classes = { - root: `${PREFIX}-root`, -}; - -const Root = styled("div")(({ theme }) => { - const nodeStatusColors = { - available: colorTint(theme.palette.success.main, 20), - down: colorTint(theme.palette.error.main, 15), - locked: colorTint(theme.palette.warning.main, 20), - }; - const statusColors = { - pass: colorTint(theme.palette.success.main, 20), - fail: colorTint(theme.palette.error.main, 15), - running: colorTint(theme.palette.warning.main, 20), - waiting: colorTint(theme.palette.info.main, 20), - dead: colorTint(theme.palette.error.main, 10), - // queued is neutral color - }; - const getSelectedColor = (color) => { - const newColor = - getThemePaletteMode(theme.palette) === "dark" - ? darken(color, 0.3) - : lighten(color, 0.3); - return newColor + " !important"; - }; - const getHoverColor = (color) => { - const newColor = - getThemePaletteMode(theme.palette) === "dark" - ? lighten(color, 0.3) - : darken(color, 0.3); - return newColor + " !important"; - }; - return { - [`& .${classes.root}`]: { - fontSize: 12, - "& .MuiDataGrid-columnHeaderTitle": { - overflow: "visible", - }, - "& .MuiSvgIcon-root": { - width: "0.75em", - height: "0.75em", - }, - "& .MuiDataGrid-row": { - "&.Mui-selected": { - borderColor: theme.palette.secondary.main, - borderStyle: "solid", - borderWidth: "1px", - }, - "&.status-pass": { - backgroundColor: statusColors.pass, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(statusColors.pass), - }, - "&:hover": { - backgroundColor: getHoverColor(statusColors.pass), - }, - }, - "&.status-fail": { - backgroundColor: statusColors.fail, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(statusColors.fail), - }, - "&:hover": { - backgroundColor: getHoverColor(statusColors.fail), - }, - }, - "&.status-dead": { - backgroundColor: statusColors.dead, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(statusColors.dead), - }, - "&:hover": { - backgroundColor: getHoverColor(statusColors.dead), - }, - }, - "&.status-running": { - backgroundColor: statusColors.running, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(statusColors.running), - }, - "&:hover": { - backgroundColor: getHoverColor(statusColors.running), - }, - }, - "&.status-waiting": { - backgroundColor: statusColors.waiting, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(statusColors.waiting), - }, - "&:hover": { - backgroundColor: getHoverColor(statusColors.waiting), - }, - }, - "&.node-down": { - backgroundColor: nodeStatusColors.down, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(nodeStatusColors.down), - }, - "&:hover": { - backgroundColor: getHoverColor(nodeStatusColors.down), - }, - }, - "&.node-locked": { - backgroundColor: nodeStatusColors.locked, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(nodeStatusColors.locked), - }, - "&:hover": { - backgroundColor: getHoverColor(nodeStatusColors.locked), - }, - }, - "&.node-available": { - backgroundColor: nodeStatusColors.available, - color: "black", - "&.Mui-selected": { - backgroundColor: getSelectedColor(nodeStatusColors.available), - }, - "&:hover": { - backgroundColor: getHoverColor(nodeStatusColors.available), - }, - }, - '.MuiDataGrid-booleanCell[data-value="true"]': { - color: "black" - }, - '.MuiDataGrid-booleanCell[data-value="false"]': { - color: "black" - }, - }, - }, - }; -}); - -function getThemePaletteMode(palette) { - return palette.mode; -} - -export default function DataGrid(props) { - return ( - -
-
- -
-
-
- ); -} diff --git a/src/components/JobList/index.tsx b/src/components/JobList/index.tsx index e4230c7..ca69230 100644 --- a/src/components/JobList/index.tsx +++ b/src/components/JobList/index.tsx @@ -1,71 +1,65 @@ import DescriptionIcon from "@mui/icons-material/Description"; import Tooltip from '@mui/material/Tooltip'; -import type { - GridCellParams, - GridFilterModel, - GridRowClassNameParams, - GridValueFormatterParams, - GridValueGetterParams, - GridRenderCellParams, - GridColDef, -} from "@mui/x-data-grid"; -import type { - DecodedValueMap, - QueryParamConfigMap, - SetQuery, -} from "use-query-params"; +import { + useMaterialReactTable, + MaterialReactTable, + type MRT_ColumnDef, +} from 'material-react-table'; import type { UseQueryResult } from "@tanstack/react-query"; +import { type Theme } from "@mui/material/styles"; import { formatDate, formatDuration } from "../../lib/utils"; -import DataGrid from "../../components/DataGrid"; import IconLink from "../../components/IconLink"; -import type { Run, NodeJobs } from "../../lib/paddles.d"; +import Link from "../../components/Link"; +import type { Job, NodeJobs, Run } from "../../lib/paddles.d"; import { dirName } from "../../lib/utils"; +import useDefaultTableOptions from "../../lib/table"; import sentryIcon from "./assets/sentry.svg"; -const columns: GridColDef[] = [ +const columns: MRT_ColumnDef[] = [ { - field: "status", - width: 85, - cellClassName: (params: GridCellParams) => `status-${params.value}`, - renderCell: (params: GridRenderCellParams) => { - let failure_reason = params.row.failure_reason || ""; + header: "status", + accessorKey: "status", + size: 120, + filterVariant: "select", + // filterSelectOptions: ["pass", "fail", "dead", "running", "waiting", "unknown"], + Cell: ({ row }) => { + let failure_reason = row.original.failure_reason || ""; const max_length = 800; const ellipsis = "..."; if ( failure_reason.length > max_length ) { failure_reason = failure_reason.substring(0, max_length - ellipsis.length) + ellipsis; } return ( -
-

{params.value}

+ {row.original.status}
-
); } }, { - field: "links", - width: 75, - valueGetter: (params: GridValueGetterParams) => { - return { - log: dirName(params.row.log_href), - sentry: params.row.sentry_event, - }; - }, - renderCell: (params: GridRenderCellParams) => { + header: "links", + id: "links", + size: 75, + Cell: ({ row }) => { + const log_url = row.original.log_href; + const sentry_url = row.original.sentry_event; return (
- {params.value.log ? ( - - + {log_url? ( + + ) : null} - {params.value.sentry ? ( - - Sentry icon + {sentry_url ? ( + + Sentry icon ) : null}
@@ -73,141 +67,156 @@ const columns: GridColDef[] = [ }, }, { - field: "job_id", - headerName: "job ID", - renderCell: (params: GridRenderCellParams) => { + header: "job ID", + accessorKey: "job_id", + size: 110, + Cell: ({ row }) => { return ( - - {params.value} - + + {row.original.job_id} + ); }, }, - // links { - field: "posted", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 125, + header: "posted", + id: "posted", + accessorFn: (row: Job) => formatDate(row.posted), + filterVariant: 'date', + sortingFn: "datetime", + size: 150, }, { - field: "started", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 125, + header: "updated", + id: "updated", + accessorFn: (row: Job) => formatDate(row.updated), + filterVariant: 'date', + sortingFn: "datetime", + size: 150, }, { - field: "updated", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 125, + header: "started", + id: "started", + accessorFn: (row: Job) => formatDate(row.started), + filterVariant: 'date', + sortingFn: "datetime", + size: 150, }, { - field: "runtime", - valueGetter: (params: GridValueGetterParams) => { - const start = Date.parse(params.row.started); - const end = Date.parse(params.row.updated); + header: "runtime", + id: "runtime", + size: 110, + accessorFn: (row: Job) => { + const start = Date.parse(row.started); + const end = Date.parse(row.updated); if (!end || !start) return null; - return Math.round((end - start) / 1000); + return formatDuration(Math.round((end - start) / 1000)); }, - valueFormatter: (row: GridValueFormatterParams) => - formatDuration(row.value), + enableColumnFilter: false, }, { - field: "duration", - valueFormatter: (row: GridValueFormatterParams) => - formatDuration(row.value), + header: "duration", + id: "duration", + size: 120, + accessorFn: (row: Job) => + formatDuration(row.duration), + enableColumnFilter: false, }, { - field: "waiting", - headerName: "in waiting", - valueGetter: (params: GridValueGetterParams) => { - const start = Date.parse(params.row.started); - const end = Date.parse(params.row.updated); - if (!end || !start || !params.row.duration) return null; - return Math.round((end - start) / 1000 - params.row.duration); + header: "in waiting", + id: "waiting", + size: 100, + accessorFn: (row: Job) => { + const start = Date.parse(row.started); + const end = Date.parse(row.updated); + if (!end || !start || !row.duration) return null; + return formatDuration(Math.round((end - start) / 1000 - row.duration)); }, - valueFormatter: (row: GridValueFormatterParams) => - formatDuration(row.value), + enableColumnFilter: false, }, { - field: "machine_type", - headerName: "machine type", + header: "machine type", + accessorKey: "machine_type", + filterVariant: "select", }, { - field: "os_type", - headerName: "OS type", - width: 85, + header: "OS type", + size: 85, + accessorFn: (row: Job) => row.os_type + "", + filterVariant: "select", }, { - field: "os_version", - headerName: "OS version", - width: 85, + header: "OS version", + accessorFn: (row: Job) => row.os_version + "", + size: 85, + filterVariant: "select", }, { - field: "nodes", - headerName: "nodes", - valueGetter: (params: GridValueGetterParams) => { - return Object.keys(params.row.targets || {}).length || null; + header: "nodes", + accessorKey: "nodes", + accessorFn: (row: Job) => { + return Object.keys(row.targets || {}).length || 0; }, - width: 85, + size: 85, }, ]; -interface JobListProps { +function jobStatusToThemeCategory(status: string): keyof Theme["palette"] { + switch (status) { + case "dead": return "error"; + case "fail": return "error"; + case "finished fail": return "error"; + case "pass": return "success"; + case "finished pass": return "success"; + case "running": return "warning"; + default: return "info"; + } +}; + + +type JobListProps = { query: UseQueryResult | UseQueryResult; - params: DecodedValueMap; - setter: SetQuery; - pagingMode: "client" | "server"; } -export default function JobList({ query, params, setter, pagingMode }: JobListProps) { - if (query.isError) return null; - let extraProps: Record = {"paginationMode": pagingMode} - if (pagingMode === "client") { - extraProps["rowCount"] = query.data?.jobs?.length || 999; - } - let filterModel: GridFilterModel = { items: [] }; - if (params.status) { - filterModel = { - items: [ +export default function JobList({ query }: JobListProps) { + const options = useDefaultTableOptions(); + const data = query.data?.jobs || []; + const table = useMaterialReactTable({ + ...options, + columns, + data: data, + rowCount: data.length, + enableFacetedValues: true, + initialState: { + ...options.initialState, + columnVisibility: { + posted: false, + updated: false, + runtime: false, + waiting: false, + }, + pagination: { + pageIndex: 0, + pageSize: 25, + }, + sorting: [ { - field: "status", - value: params.status, - operator: "contains", + id: "job_id", + desc: false, }, ], - }; - } - const onFilterModelChange = (model: GridFilterModel) => { - setter({ status: model.items[0].value || null }); - }; - return ( - { - return `status-${params.row.status}`; - }} - {...extraProps} - /> - ); + }, + state: { + isLoading: query.isLoading || query.isFetching, + }, + const category = jobStatusToThemeCategory(row.original.status); + if ( category ) return { className: category }; + return {}; + }, + }); + if (query.isError) return null; + return } diff --git a/src/components/Link/index.jsx b/src/components/Link/index.jsx index ea15242..cdd1cb4 100644 --- a/src/components/Link/index.jsx +++ b/src/components/Link/index.jsx @@ -5,6 +5,7 @@ export default function Link(props) { {props.children} diff --git a/src/components/NodeList/index.tsx b/src/components/NodeList/index.tsx index 386a69a..0a3b007 100644 --- a/src/components/NodeList/index.tsx +++ b/src/components/NodeList/index.tsx @@ -1,128 +1,127 @@ import type { UseQueryResult } from "@tanstack/react-query"; -import type { - DecodedValueMap, - QueryParamConfigMap, - SetQuery, -} from "use-query-params"; -import type { - GridFilterModel, - GridRowClassNameParams, - GridValueFormatterParams, - GridColDef, - GridRenderCellParams, -} from "@mui/x-data-grid"; +import { + useMaterialReactTable, + MaterialReactTable, + type MRT_ColumnDef, +} from 'material-react-table'; import Link from '@mui/material/Link'; import type { Node } from "../../lib/paddles.d"; import { formatDate } from "../../lib/utils"; -import DataGrid from "../DataGrid"; +import useDefaultTableOptions from "../../lib/table"; -export const columns: GridColDef[] = [ +export const columns: MRT_ColumnDef[] = [ { - field: "name", - width: 100, - renderCell: (params: GridRenderCellParams) => { - return {params.value?.split(".")[0]}; + header: "name", + accessorKey: "name", + size: 100, + Cell: ( { row } ) => { + const name = row.original.name; + return {name?.split(".")[0]}; }, }, { - field: "machine_type", - width: 90, + header: "machine_type", + accessorKey: "machine_type", + size: 90, + maxSize: 90, }, { - field: "up", - type: "boolean", - width: 70, + header: "up", + accessorFn: (row: Node) => row.up?.toLocaleString(), + size: 70, + filterVariant: "select", }, { - field: "locked", - type: "boolean", - width: 70, + header: "locked", + accessorFn: (row: Node) => row.locked?.toLocaleString(), + size: 70, + filterVariant: "select", }, { - headerName: "locked since", - field: "locked_since", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 150, + header: "locked since", + filterVariant: 'date', + sortingFn: "datetime", + accessorFn: (row: Node) => formatDate(row.locked_since), + size: 150, + enableColumnFilter: false, }, { - headerName: "locked by", - field: "locked_by", - width: 175, + header: "locked by", + accessorKey: "locked_by", + size: 175, + filterVariant: "select", }, { - headerName: "OS type", - field: "os_type", - width: 90, + header: "OS type", + accessorFn: (row) => row.os_type || "none", + size: 90, + filterVariant: "select", }, { - headerName: "OS version", - field: "os_version", - width: 90, + header: "OS version", + accessorFn: (row) => row.os_version || "none", + size: 90, + filterVariant: "select", }, { - field: "arch", - width: 60, + header: "arch", + accessorKey: "arch", + size: 60, + filterVariant: "select", }, { - field: "description", - width: 500, + header: "description", + accessorKey: "description", + size: 500, }, ]; interface NodeListProps { query: UseQueryResult; - params: DecodedValueMap; - setter: SetQuery; } -export function nodeRowClass(params: GridRowClassNameParams) { - const isUp = params.row.up; - const isLocked = params.row.locked; - if (!isUp) { - return 'node-down'; - } - return isLocked ? 'node-locked' : 'node-available'; -} - -export default function NodeList({ query, params, setter }:NodeListProps) { - let filterModel: GridFilterModel = { items: [] }; - if (params.machine_type) { - filterModel = { - items: [ +export default function NodeList({ query }: NodeListProps) { + const options = useDefaultTableOptions(); + const data = query.data || []; + const table = useMaterialReactTable({ + ...options, + columns, + data: data, + rowCount: data.length, + enableFacetedValues: true, + initialState: { + ...options.initialState, + columnVisibility: { + posted: false, + updated: false, + }, + pagination: { + pageIndex: 0, + pageSize: 25, + }, + sorting: [ { - field: "machine_type", - value: params.machine_type, - operator: "contains", + id: "machine_type", + desc: false, }, - ], - }; - } - const onFilterModelChange = (model: GridFilterModel) => { - setter({ machine_type: model.items[0].value || null }); - }; - return ( - - ); + ], + }, + state: { + isLoading: query.isLoading || query.isFetching, + }, + muiTableBodyRowProps: ({row}) => { + let className = "info"; + if ( row.original.up === false ) className = "error"; + else if ( row.original.locked === true ) className = "warning"; + else if ( row.original.locked === false ) className = "success"; + return {className}; + }, + }); + return } diff --git a/src/components/RunList/index.tsx b/src/components/RunList/index.tsx index ac1d9a9..3ceaa4c 100644 --- a/src/components/RunList/index.tsx +++ b/src/components/RunList/index.tsx @@ -5,187 +5,258 @@ import type { SetQuery, } from "use-query-params"; import { useDebounce } from "usehooks-ts"; -import type { - GridFilterModel, - GridRowClassNameParams, - GridValueFormatterParams, - GridValueGetterParams, - GridRenderCellParams, - GridColDef, -} from "@mui/x-data-grid"; +import { + useMaterialReactTable, + MaterialReactTable, + type MRT_ColumnDef, + type MRT_PaginationState, + type MRT_Updater, + type MRT_ColumnFiltersState, +} from 'material-react-table'; +import { type Theme } from "@mui/material/styles"; +import { parse } from "date-fns"; import { useRuns } from "../../lib/paddles"; -import { formatDate, formatDuration } from "../../lib/utils"; -import DataGrid from "../DataGrid"; +import { formatDate, formatDay, formatDuration } from "../../lib/utils"; import IconLink from "../../components/IconLink"; +import type { Run } from "../../lib/paddles.d"; +import { RunStatuses } from "../../lib/paddles.d"; +import useDefaultTableOptions from "../../lib/table"; -function resultsGetter(params: GridValueGetterParams) { - return params.row.results[params.field]; -} -const columns: GridColDef[] = [ +const DEFAULT_PAGE_SIZE = 25; +const NON_FILTER_PARAMS = [ + "page", + "pageSize", +]; + +const columns: MRT_ColumnDef[] = [ { - field: "user", + header: "status", + accessorKey: "status", + filterVariant: "select", + Cell: ({ row }) => { + return row.original.status.replace("finished ", ""); + }, + filterSelectOptions: Object.values(RunStatuses), + }, + { + accessorKey: "user", + header: "user", + size: 60, + enableColumnFilter: false, }, { - field: "name", - headerName: "link", - width: 60, - renderCell: (params: GridRenderCellParams) => { + accessorKey: "name", + header: "link", + size: 30, + enableColumnFilter: false, + Cell: ({ row }) => { return ( - - + + ); }, }, { - field: "scheduled", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 125, + id: "scheduled", + header: "scheduled", + accessorFn: (row: Run) => formatDate(row.scheduled), + filterVariant: 'date', + sortingFn: "datetime", + size: 125, }, { - field: "started", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 125, + id: "started", + header: "started", + accessorFn: (row: Run) => formatDate(row.started), + enableColumnFilter: false, + sortingFn: "datetime", + size: 125, }, { - headerName: "updated", - field: "posted", - type: "date", - valueFormatter: (row: GridValueFormatterParams) => formatDate(row.value), - width: 125, + id: "posted", + header: "updated", + accessorFn: (row: Run) => formatDate(row.posted), + enableColumnFilter: false, + sortingFn: "datetime", + size: 125, }, { - headerName: "runtime", - field: "", - valueGetter: (params: GridValueGetterParams) => { - const start = Date.parse(params.row.started); - const end = Date.parse(params.row.updated); + id: "runtime", + header: "runtime", + accessorFn: (row: Run) => { + const start = Date.parse(row.started); + const end = Date.parse(row.updated); if (!end || !start) return null; - return Math.round((end - start) / 1000); + return formatDuration(Math.round((end - start) / 1000)); }, - valueFormatter: (row: GridValueFormatterParams) => formatDuration(row.value), - width: 70, + enableColumnFilter: false, + sortingFn: "datetime", + size: 70, }, { - field: "suite", + accessorKey: "suite", + header: "suite", + size: 70, }, { - field: "branch", + accessorKey: "branch", + header: "branch", + maxSize: 75, }, { - field: "machine_type", - width: 90, + accessorKey: "machine_type", + header: "machine_type", + size: 30, }, { - field: "sha1", - headerName: "hash", - width: 75, + accessorKey: "sha1", + header: "hash", + size: 30, + maxSize: 50, }, { - field: "queued", - valueGetter: resultsGetter, - width: 60, + accessorKey: "results.queued", + header: "queued", + size: 30, + enableColumnFilter: false, }, { - field: "pass", - valueGetter: resultsGetter, - width: 60, + accessorKey: "results.pass", + header: "pass", + size: 30, + enableColumnFilter: false, }, { - field: "fail", - valueGetter: resultsGetter, - width: 60, + accessorKey: "results.fail", + header: "fail", + size: 40, + enableColumnFilter: false, }, { - field: "dead", - valueGetter: resultsGetter, - width: 60, + accessorKey: "results.dead", + header: "dead", + size: 40, + enableColumnFilter: false, }, { - field: "running", - valueGetter: resultsGetter, - width: 60, + accessorKey: "results.running", + header: "running", + size: 40, + enableColumnFilter: false, }, { - field: "waiting", - valueGetter: resultsGetter, - width: 60, + accessorKey: "results.waiting", + header: "waiting", + size: 40, + enableColumnFilter: false, }, ]; -interface RunListProps { +function runStatusToThemeCategory(status: string): keyof Theme["palette"] { + switch (status) { + case "finished dead": return "error"; + case "finished fail": return "error"; + case "finished pass": return "success"; + case "running": return "warning"; + default: return "info"; + } +}; + +type RunListParams = { + [key: string]: number|string; +} + +type RunListProps = { params: DecodedValueMap; setter: SetQuery; } -export default function RunList({ params, setter }:RunListProps) { - const paddlesParams = {...params}; - delete paddlesParams.user; +export default function RunList(props: RunListProps) { + const { params, setter } = props; + const options = useDefaultTableOptions(); const debouncedParams = useDebounce(params, 500); - const query = useRuns(debouncedParams); - if (query.isError) return null; - /* If we want to automatically size the branch column: - const columns = [..._columns]; - if (query.isSuccess) { - const branchLength = Math.max( - ...query.data.map((item) => item.branch.length) - ); - columns.forEach((item) => { - if (item.field === "branch") { - item.width = Math.max(100, branchLength * 7); + const columnFilters: MRT_ColumnFiltersState = []; + Object.entries(debouncedParams).forEach(param => { + const [id, value] = param; + if ( NON_FILTER_PARAMS.includes(id) ) return; + if ( id === "date" && !!value ) { + columnFilters.push({ + id: "scheduled", + value: parse(value, "yyyy-MM-dd", new Date()) + }) + } else { + columnFilters.push({id, value}) + } + }); + let pagination = { + pageIndex: params.page || 0, + pageSize: params.pageSize || DEFAULT_PAGE_SIZE, + }; + const onColumnFiltersChange = (updater: MRT_Updater) => { + if ( ! ( updater instanceof Function ) ) return; + const result: RunListParams = {pageSize: pagination.pageSize}; + const updated = updater(columnFilters); + updated.forEach(item => { + if ( ! item.id ) return; + if ( item.value instanceof Date ) { + result.date = formatDay(item.value); + } else if ( typeof item.value === "string" || typeof item.value === "number" ) { + result[item.id] = item.value } }); - } - */ - let filterModel: GridFilterModel = { items: [] }; - if (params.user) { - filterModel = { - items: [ - { - field: "user", - value: params.user, - operator: "contains", - }, - ], + setter(result); + }; + const onPaginationChange = (updater: MRT_Updater) => { + if ( ! ( updater instanceof Function ) ) return; + pagination = updater(pagination); + const result: Partial = { + ...params, + page: pagination.pageIndex, }; - } - const onFilterModelChange = (model: GridFilterModel) => { - const params: QueryParamConfigMap = {}; - model.items.forEach((item) => { - params[item.field] = item.value || null; - }); - setter(params); + if ( pagination.pageSize != DEFAULT_PAGE_SIZE ) result.pageSize = pagination.pageSize; + setter(result); }; - return ( - { - const status = params.row.status.split(" ").pop(); - return `status-${status}`; - }} - /> - ); + ], + }, + state: { + columnFilters, + pagination, + isLoading: query.isLoading || query.isFetching, + }, + muiTableBodyRowProps: ({row}) => { + const category = runStatusToThemeCategory(row.original.status); + if ( category ) return { className: category }; + return {}; + }, + }); + if (query.isError) return null; + return } diff --git a/src/index.tsx b/src/index.tsx index 25246cf..b7048e7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -14,6 +14,8 @@ import { ThemeProvider, StyledEngineProvider, } from "@mui/material/styles"; +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import * as Sentry from "@sentry/react"; import { BrowserTracing } from "@sentry/tracing"; @@ -87,17 +89,19 @@ export default function Root() { - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/src/lib/paddles.d.ts b/src/lib/paddles.d.ts index afad206..35ce4d0 100644 --- a/src/lib/paddles.d.ts +++ b/src/lib/paddles.d.ts @@ -17,6 +17,12 @@ type NodeParams = { name: string; }; +export const JobStatuses = [ + "queued", "waiting", "running", "dead", "fail", "pass", "unknown", +] as const; + +export type JobStatus = (typeof JobStatuses)[number]; + export type Job = { id?: string; job_id: number; @@ -24,19 +30,49 @@ export type Job = { suite: string; branch: string; scheduled: string; + posted: string; + updated: string; + started: string; + log_href: string; + sentry_event: string; + duration: number; + status: JobStatus; + failure_reason: string; + targets: Node[]; + os_type: string; + os_version: string; }; export type RunResults = { - [key: string]: number | string; + queued: number; + pass: number; + fail: number; + dead: number; + running: number; } +export const RunStatuses = [ + "queued", "waiting", "running", "finished dead", "finished fail", "finished pass" +] as const; + +export type RunStatus = (typeof RunStatuses)[number]; + export type Run = { name: string; branch: string; suite: string; jobs: Job[]; scheduled: string; + user: string; + started: string; + posted: string; + updated: string; + started: string; + runtime: string; + sha1: string; results: RunResults; + machine_type: string; + status: RunStatus; }; export type Node = { diff --git a/src/lib/paddles.ts b/src/lib/paddles.ts index 2b22b1b..d8c1b57 100644 --- a/src/lib/paddles.ts +++ b/src/lib/paddles.ts @@ -9,6 +9,7 @@ import type { StatsJobsResponse, } from "./paddles.d"; + const PADDLES_SERVER = import.meta.env.VITE_PADDLES_SERVER || "https://paddles.front.sepia.ceph.com"; @@ -39,6 +40,8 @@ function getURL(endpoint: string, params?: GetURLParams) { uri += "queued/"; delete params_[key]; break; + case "machine_type": + break; default: uri += `${key}/${value}/`; delete params_[key]; @@ -71,7 +74,7 @@ function useRun(name: string): UseQueryResult { const query = useQuery(["run", { url }], { select: (data: Run) => { data.jobs.forEach((item) => { - item.id = item.job_id; + item.id = item.job_id + ""; }); return data; }, @@ -107,7 +110,7 @@ function useNodeJobs(name: string, params: GetURLParams): UseQueryResult { data.forEach((item) => { - item.id = item.job_id; + item.id = item.job_id + ""; }); const resp: NodeJobs = { 'jobs': data } return resp; @@ -127,8 +130,8 @@ function useNode(name: string): UseQueryResult { return query; } -function useNodes(): UseQueryResult { - const url = new URL("nodes/", PADDLES_SERVER).href +function useNodes(machine_type: string): UseQueryResult { + const url = getURL(`/nodes/`, {machine_type}); const query = useQuery(["nodes", { url }], { select: (data: Node[]) => data.map((item) => { diff --git a/src/lib/table.ts b/src/lib/table.ts new file mode 100644 index 0000000..623fef9 --- /dev/null +++ b/src/lib/table.ts @@ -0,0 +1,61 @@ +import { MRT_RowData, type MRT_TableOptions } from 'material-react-table'; +import { useTheme } from "@mui/material/styles"; + + +export default function useDefaultTableOptions(): Partial> { + const theme = useTheme(); + const palette = theme.palette; + return { + layoutMode: "grid", + defaultColumn: { + minSize: 20, + maxSize: 200, + size: 75, + }, + // enableColumnResizing: true, + enableDensityToggle: false, + enableFullScreenToggle: false, + enableGlobalFilter: false, + // enableRowSelection: true, + initialState: { + density: "compact", + showColumnFilters: true, + }, + mrtTheme: { + baseBackgroundColor: theme.palette.background.default, + }, + muiTableBodyCellProps: { + sx: { + color: "black", + fontSize: "0.75rem", + } + }, + muiTableBodyProps: { + sx: { + 'tr td': {padding: '2px'}, + 'tr td:nth-of-type(1)': {paddingLeft: '8px'}, + 'tr.error td': {backgroundColor: palette.error.main}, + 'tr.warning td': {backgroundColor: palette.warning.main}, + 'tr.info td': {backgroundColor: palette.info.main}, + 'tr.success td': {backgroundColor: palette.success.main}, + '@media (prefers-color-scheme: dark)': { + 'tr:hover td': {filter: "brightness(85%)"}, + }, + '@media (prefers-color-scheme: light)': { + 'tr:hover td': {filter: "brightness(115%)"}, + }, + }, + }, + muiTableBodyRowProps: ({row}) => { + const category = statusToThemeCategory(row.original.status); + return { + className: category, + } + }, + muiTablePaperProps: { + sx: { + border: "1px solid " + theme.palette.grey[800], + }, + }, + } +} diff --git a/src/lib/utils.js b/src/lib/utils.js index fd0cb6f..2b7cab8 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -2,10 +2,15 @@ import { format } from "date-fns"; import convert from "color-convert"; function formatDate(orig) { - if (!orig) return; + if (!orig) return ""; return format(new Date(orig), "yyyy-MM-dd HH:mm:ss"); } +function formatDay(orig) { + if (!orig) return ""; + return format(new Date(orig), "yyyy-MM-dd"); +} + function pad(num) { return `${num}`.padStart(2, "0"); } @@ -44,4 +49,4 @@ function dirName(path) { return array.join("/"); } -export { formatDate, getDuration, formatDuration, colorTint, dirName }; +export { formatDate, formatDay, getDuration, formatDuration, colorTint, dirName }; diff --git a/src/pages/Node/index.tsx b/src/pages/Node/index.tsx index 6b37cdb..bfd7f0f 100644 --- a/src/pages/Node/index.tsx +++ b/src/pages/Node/index.tsx @@ -10,7 +10,7 @@ import type { NodeParams } from "../../lib/paddles.d"; import { useNode, useNodeJobs } from "../../lib/paddles"; export default function Node() { - const [params, setParams] = useQueryParams({ + const [params, _] = useQueryParams({ page: NumberParam, pageSize: NumberParam, }); @@ -35,8 +35,8 @@ export default function Node() {
- - + +
diff --git a/src/pages/Nodes/index.tsx b/src/pages/Nodes/index.tsx index a8ddb9f..f528878 100644 --- a/src/pages/Nodes/index.tsx +++ b/src/pages/Nodes/index.tsx @@ -8,15 +8,16 @@ import NodeList from "../../components/NodeList"; import { useMachineTypes } from "../../lib/paddles"; import { useNodes } from "../../lib/paddles"; + export default function Nodes() { const [params, setParams] = useQueryParams({ machine_type: StringParam, }); const { machine_type } = params; - const query = useNodes(); + const query = useNodes(machine_type || ""); if (query.isError) return null; - + return (
@@ -41,7 +42,7 @@ export default function Nodes() { />
- + ); } diff --git a/src/pages/Run/index.tsx b/src/pages/Run/index.tsx index eec797c..a71382c 100644 --- a/src/pages/Run/index.tsx +++ b/src/pages/Run/index.tsx @@ -3,12 +3,10 @@ import { useQueryParams, StringParam, NumberParam } from "use-query-params"; import { styled } from "@mui/material/styles"; import { useParams } from "react-router-dom"; import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; -import ButtonGroup from "@mui/material/ButtonGroup"; import { format } from "date-fns"; import { Helmet } from "react-helmet"; -import type { Run, RunParams } from "../../lib/paddles.d"; +import type { Run as Run_, RunParams } from "../../lib/paddles.d"; import { useRun } from "../../lib/paddles"; import JobList from "../../components/JobList"; @@ -38,10 +36,6 @@ const FilterLink = (props: PropsWithChildren) => ( ); -type StatusLabels = { - [key: string]: string; -} - export default function Run() { const [params, setParams] = useQueryParams({ status: StringParam, @@ -52,15 +46,9 @@ export default function Run() { const query = useRun(name === undefined ? "" : name); if (query === null) return 404; if (query.isError) return null; - const data: Run | undefined = query.data; + const data: Run_ | undefined = query.data; const suite = data?.suite; const branch = query.data?.branch; - const statuses = ["pass", "fail", "dead", "running", "waiting", "queued"]; - const statusLabels: StatusLabels = {}; - statuses.forEach(item => { - statusLabels[item] = item.charAt(0).toUpperCase() + item.slice(1); - if ( data?.results[item] ) statusLabels[item] += ` (${data.results[item]})`; - }); const date = query.data?.scheduled ? format(new Date(query.data.scheduled), "yyyy-MM-dd") : null; @@ -84,28 +72,7 @@ export default function Run() { date - - - {statuses.filter(item => data?.results[item]).map((item) => ( - - ))} - - + ); } diff --git a/src/pages/Runs/index.tsx b/src/pages/Runs/index.tsx index 8f0d00f..ffe298b 100644 --- a/src/pages/Runs/index.tsx +++ b/src/pages/Runs/index.tsx @@ -1,40 +1,24 @@ -import { useCallback, ChangeEvent } from "react"; -import { useQueryParams, StringParam, NumberParam } from "use-query-params"; +import { useQueryParams, StringParam, NumberParam, withDefault } from "use-query-params"; import Typography from "@mui/material/Typography"; -import TextField from "@mui/material/TextField"; import { Helmet } from "react-helmet"; -import FilterMenu from "../../components/FilterMenu"; import RunList from "../../components/RunList"; -import { - useBranches, - useMachineTypes, - useSuites, - useStatuses, -} from "../../lib/paddles"; - export default function Runs() { const [params, setParams] = useQueryParams({ branch: StringParam, date: StringParam, machine_type: StringParam, - page: NumberParam, + page: withDefault(NumberParam, 0), pageSize: NumberParam, sha1: StringParam, status: StringParam, suite: StringParam, user: StringParam, + }, { + removeDefaultsFromUrl: true, + updateType: "push", }); - const { branch, date, machine_type, sha1, status, suite } = params; - const onSha1Change = (evt: ChangeEvent) => { - const newValue = (evt.target as HTMLTextAreaElement).value; - setParams({sha1: newValue || null}); - }; - const onDateChange = useCallback((evt: ChangeEvent) => { - const newValue = (evt.target as HTMLTextAreaElement).value; - setParams({ date: newValue || null }); - }, []); return (
@@ -43,59 +27,6 @@ export default function Runs() { Runs -
-
-
- - Filter by: - -
- - - - - - -
-
); diff --git a/src/pages/StatsNodesJobs/index.tsx b/src/pages/StatsNodesJobs/index.tsx index d9c04c0..98b31e0 100644 --- a/src/pages/StatsNodesJobs/index.tsx +++ b/src/pages/StatsNodesJobs/index.tsx @@ -1,104 +1,128 @@ import { useQueryParams, StringParam, NumberParam } from "use-query-params"; import Typography from "@mui/material/Typography"; import { Helmet } from "react-helmet"; -import type { GridColDef, GridRenderCellParams } from "@mui/x-data-grid"; import Link from '@mui/material/Link'; +import { + useMaterialReactTable, + MaterialReactTable, + type MRT_ColumnDef, +} from 'material-react-table'; -import DataGrid from "../../components/DataGrid"; import FilterMenu from "../../components/FilterMenu"; +import useDefaultTableOptions from "../../lib/table"; -import { useStatsNodeJobs } from "../../lib/paddles"; -import { useMachineTypes } from "../../lib/paddles"; +import { useMachineTypes, useStatsNodeJobs } from "../../lib/paddles"; +import { type StatsJobsResponse } from "../../lib/paddles.d"; -export const columns: GridColDef[] = [ - { - field: "name", - width: 200, - renderCell: (params: GridRenderCellParams) => { - return {params.value?.split(".")[0]}; - }, - }, - { - field: "pass", - width: 125, - }, - { - field: "fail", - width: 125, - }, - { - field: "dead", - width: 125, - }, - { - field: "unknown", - width: 125, - }, - { - field: "running", - width: 125, - }, - { - field: "total", - width: 125, +export const columns: MRT_ColumnDef[] = [ + { + header: "name", + accessorKey: "name", + size: 200, + Cell: ({ row }) => { + const name = row.original.name; + return {name.split(".")[0]}; }, + }, + { + header: "pass", + accessorKey: "pass", + size: 125, + }, + { + header: "fail", + accessorKey: "fail", + size: 125, + }, + { + header: "dead", + accessorKey: "dead", + size: 125, + }, + { + header: "unknown", + accessorKey: "unknown", + size: 125, + }, + { + header: "running", + accessorKey: "running", + size: 125, + }, + { + header: "total", + accessorKey: "total", + size: 125, + }, ] export default function StatsNodesJobs() { - const [params, setParams] = useQueryParams({ - machine_type: StringParam, - since_days: NumberParam, - }); - const machine_type = params["machine_type"]; - const since_days = params["since_days"]; - const query = useStatsNodeJobs(params); + const [params, setParams] = useQueryParams({ + machine_type: StringParam, + since_days: NumberParam, + }); + const machine_type = params["machine_type"]; + const since_days = params["since_days"]; + const query = useStatsNodeJobs(params); + const options = useDefaultTableOptions(); - if (query === null) return 404; - if (query.isError) return null; + const data = query.data || []; + const table = useMaterialReactTable({ + ...options, + columns, + data: data, + rowCount: data.length, + initialState: { + ...options.initialState, + columnVisibility: { + posted: false, + updated: false, + }, + pagination: { + pageIndex: 0, + pageSize: 25, + }, + sorting: [ + { + id: "name", + desc: false, + }, + ], + }, + state: { + isLoading: query.isLoading || query.isFetching, + }, + }); + if (query === null) return 404; + if (query.isError) return null; + return ( +
+ + Stats Nodes Jobs - Pulpito + + + {since_days || 14}-day stats for {machine_type || "all"} nodes + - return ( -
- - Stats Nodes Jobs - Pulpito - - - {since_days || 14}-day stats for {machine_type || "all"} nodes +
+
+
+ + Filter by: - -
-
-
- - Filter by: - -
- -
-
- +
+
- ); +
+ +
+ ); } diff --git a/src/pages/StatsNodesLock/index.tsx b/src/pages/StatsNodesLock/index.tsx index 8a2ab37..2a8713a 100644 --- a/src/pages/StatsNodesLock/index.tsx +++ b/src/pages/StatsNodesLock/index.tsx @@ -1,83 +1,83 @@ import { useQueryParams, StringParam } from "use-query-params"; import Typography from "@mui/material/Typography"; import { Helmet } from "react-helmet"; -import type { GridColDef } from "@mui/x-data-grid"; +import { + useMaterialReactTable, + MaterialReactTable, + type MRT_ColumnDef, +} from 'material-react-table'; -import DataGrid from "../../components/DataGrid"; -import FilterMenu from "../../components/FilterMenu"; +import useDefaultTableOptions from "../../lib/table"; import { useStatsNodeLocks } from "../../lib/paddles"; -import { useMachineTypes } from "../../lib/paddles"; +import { type StatsLocksResponse } from "../../lib/paddles.d"; -export const columns: GridColDef[] = [ - { - field: "owner", - width: 200, - }, - { - headerName: "machine type", - field: "machine_type", - width: 200, - }, - { - field: "count", - width: 125, - }, +export const columns: MRT_ColumnDef[] = [ + { + header: "owner", + accessorKey: "owner", + size: 200, + }, + { + header: "machine type", + accessorKey: "machine_type", + size: 200, + }, + { + header: "count", + accessorKey: "count", + size: 125, + }, ] export default function StatsNodesLock() { - const [params, setParams] = useQueryParams({ - machine_type: StringParam, - }); - const machine_type = params["machine_type"]; - const query = useStatsNodeLocks(params); - - if (query === null) return 404; - if (query.isError) return null; - - return ( -
- - Stats Nodes Lock - Pulpito - - - Machine usage for up {machine_type || ""} nodes - - -
-
-
- - Filter by: - -
- -
-
- -
- ); + // Avoid "warning '_' is assigned a value but never used" by ignoring the setter + const params = useQueryParams({ + machine_type: StringParam, + })[0]; + const machine_type = params.machine_type; + const query = useStatsNodeLocks(params); + const options = useDefaultTableOptions(); + const data = query.data || []; + const table = useMaterialReactTable({ + ...options, + columns, + data: data, + rowCount: data.length, + initialState: { + ...options.initialState, + columnVisibility: { + posted: false, + updated: false, + }, + pagination: { + pageIndex: 0, + pageSize: 25, + }, + sorting: [ + { + id: "owner", + desc: false, + }, + ], + }, + state: { + isLoading: query.isLoading || query.isFetching, + }, + }); + if (query === null) return 404; + if (query.isError) return null; + return ( +
+ + Stats Nodes Lock - Pulpito + + + Machine usage for up {machine_type || ""} nodes + + +
+ ); } From ea584d168a04d55d0db20c81504a2781dffafe55 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Mon, 11 Dec 2023 17:52:23 -0700 Subject: [PATCH 03/21] npm uninstall @mui/x-data-grid Signed-off-by: Zack Cerza --- package-lock.json | 31 ------------------------------- package.json | 3 +-- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59cf172..250e339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.13.7", "@mui/material": "^5.13.7", - "@mui/x-data-grid": "^6.9.1", "@sentry/react": "^7.73.0", "@sentry/tracing": "^7.73.0", "@sentry/vite-plugin": "^2.8.0", @@ -2533,31 +2532,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, - "node_modules/@mui/x-data-grid": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.9.1.tgz", - "integrity": "sha512-nJjvyGHRh8fWSF+VZYtN/gwDRRv/0V3AvbwlIMKRtEiXNUxPxGZ8G0J8dpq9zu6VSDU2AbQ/Prl1yfrnHiPQog==", - "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/utils": "^5.13.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "reselect": "^4.1.8" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@mui/material": "^5.4.1", - "@mui/system": "^5.4.1", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, "node_modules/@mui/x-date-pickers": { "version": "6.18.2", "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.18.2.tgz", @@ -7695,11 +7669,6 @@ "node": ">=0.10.0" } }, - "node_modules/reselect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", - "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" - }, "node_modules/resolve": { "version": "1.22.2", "license": "MIT", diff --git a/package.json b/package.json index a8888a0..ab1dcfd 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.13.7", "@mui/material": "^5.13.7", - "@mui/x-data-grid": "^6.9.1", "@sentry/react": "^7.73.0", "@sentry/tracing": "^7.73.0", "@sentry/vite-plugin": "^2.8.0", @@ -76,4 +75,4 @@ "typescript": "^5.1.6", "vite": "^4.4.1" } -} \ No newline at end of file +} From 4c05030befee1a0018e4cb9e50f312545f27e52b Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Mon, 11 Dec 2023 13:59:25 -0700 Subject: [PATCH 04/21] Move theme definition to lib/ This adds our former DataGrid theming into the theme itself. Signed-off-by: Zack Cerza --- src/index.tsx | 12 ++++------- src/lib/theme.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/lib/theme.ts diff --git a/src/index.tsx b/src/index.tsx index b7048e7..f6a493c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -24,6 +24,7 @@ import App from "./App"; import reportWebVitals from "./reportWebVitals"; import type { QueryKey } from "./lib/paddles.d"; +import getThemeOptions from "./lib/theme"; Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, @@ -75,16 +76,11 @@ export default function Root() { }; const theme = React.useMemo(() => { const paletteType = darkMode ? "dark" : "light"; - const theme = createTheme({ palette: { mode: paletteType } }); - if (darkMode) { - theme.palette.background.default = "#181818"; - theme.palette.background.paper = "#303030"; - } + const theme = createTheme({ + ...getThemeOptions(paletteType), + }); return theme; }, [darkMode]); - if (darkMode === undefined) { - return null; - } return ( diff --git a/src/lib/theme.ts b/src/lib/theme.ts new file mode 100644 index 0000000..19f2a76 --- /dev/null +++ b/src/lib/theme.ts @@ -0,0 +1,52 @@ +import { green, lightBlue, orange, red } from "@mui/material/colors"; +import { + type ThemeOptions, +} from "@mui/material/styles"; + +export default function getThemeOptions(mode: "dark" | "light"): ThemeOptions { + const darkShade = 200; + const lightShade = 300; + return mode === "dark"? { + palette: { + mode: "dark", + background: { + default: "#181818", + paper: "#303030", + }, + primary: { + main: lightBlue[700], + }, + error: { + main: red[darkShade], + }, + info: { + main: lightBlue[darkShade], + }, + success: { + main: green[darkShade], + }, + warning: { + main: orange[darkShade], + }, + }, + } : { + palette: { + mode: "light", + primary: { + main: lightBlue[600], + }, + error: { + main: red[lightShade], + }, + info: { + main: lightBlue[lightShade], + }, + success: { + main: green[lightShade], + }, + warning: { + main: orange[lightShade], + }, + }, + }; +} From e54d51ac605e5af717414ec373521cdc2488a428 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Mon, 11 Dec 2023 14:29:10 -0700 Subject: [PATCH 05/21] Fix dark mode toggle and persist setting Signed-off-by: Zack Cerza --- index.html | 11 ++++++----- src/index.tsx | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 5c584e2..305f64a 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,6 @@ - @@ -27,10 +26,12 @@