diff --git a/package-lock.json b/package-lock.json index 1943481a..4f157b02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2375,9 +2375,9 @@ "license": "MIT" }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", + "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -5692,13 +5692,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.15.20", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.20.tgz", - "integrity": "sha512-BK8F94AIqSrnaPYXf2KAOjGZJgWfvqAVQ2gVR3EryvQFtuBnG6RwodxrCvd3B48VuMy6Wsk897+lQMUxJyk+6g==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.20", + "@mui/utils": "^5.16.6", "prop-types": "^15.8.1" }, "engines": { @@ -5719,9 +5719,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", - "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -5751,16 +5751,16 @@ } }, "node_modules/@mui/system": { - "version": "5.15.20", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.20.tgz", - "integrity": "sha512-LoMq4IlAAhxzL2VNUDBTQxAb4chnBe8JvRINVNDiMtHE2PiPOoHlhOPutSxEbaL5mkECPVWSv6p8JEV+uykwIA==", + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.20", - "@mui/styled-engine": "^5.15.14", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.20", + "@mui/private-theming": "^5.16.6", + "@mui/styled-engine": "^5.16.6", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.6", "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -5791,12 +5791,12 @@ } }, "node_modules/@mui/types": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", - "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", + "version": "7.2.16", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz", + "integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==", "license": "MIT", "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -5805,15 +5805,17 @@ } }, "node_modules/@mui/utils": { - "version": "5.15.20", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.20.tgz", - "integrity": "sha512-mAbYx0sovrnpAu1zHc3MDIhPqL8RPVC5W5xcO1b7PiSCJPtckIZmBkp8hefamAvUiAV8gpfMOM6Zb+eSisbI2A==", + "version": "5.16.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", + "@mui/types": "^7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^18.3.1" }, "engines": { "node": ">=12.0.0" @@ -5832,6 +5834,71 @@ } } }, + "node_modules/@mui/x-date-pickers": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.14.0.tgz", + "integrity": "sha512-3xI3xYVxqPU4//KfE4FcR+Zs7UT4kkDPvA+IDOcQdRUyVwmcXCjBuJZgKgJMqSCNK/KIJZQQrpmy5XGHOKTbdA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "@mui/system": "^5.16.7", + "@mui/utils": "^5.16.6", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14", + "date-fns": "^2.25.0 || ^3.2.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.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/@next/env": { "version": "14.2.4", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.4.tgz", @@ -9524,9 +9591,9 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", "license": "MIT", "dependencies": { "@types/react": "*" @@ -13603,10 +13670,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", - "dev": true, + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "license": "MIT" }, "node_modules/debug": { @@ -29373,9 +29439,11 @@ "@mui/icons-material": "^5.15.18", "@mui/material": "^5.15.18", "@mui/utils": "^5.15.14", + "@mui/x-date-pickers": "^7.14.0", "@types/node": "20.11.30", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", + "dayjs": "^1.11.13", "jsoncrush": "^1.1.8", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx b/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx index 466a8e81..73ee1f39 100644 --- a/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx +++ b/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx @@ -82,10 +82,31 @@ const renderStatusCell = (status: string) => { * The head cells for the data grid (desktop version) */ const headCells: Column[] = [ - { id: "JobID", label: "Job ID" }, + { id: "JobID", label: "Job ID", type: "number" }, { id: "JobName", label: "Job Name" }, { id: "Site", label: "Site" }, - { id: "Status", label: "Status", render: renderStatusCell }, + { + id: "Status", + label: "Status", + render: renderStatusCell, + type: [ + "Submitting", + "Received", + "Checking", + "Staging", + "Waiting", + "Matched", + "Running", + "Rescheduled", + "Completing", + "Completed", + "Done", + "Failed", + "Stalled", + "Killed", + "Deleted", + ].sort(), + }, { id: "MinorStatus", label: "Minor Status", @@ -93,6 +114,7 @@ const headCells: Column[] = [ { id: "SubmissionTime", label: "Submission Time", + type: "DateTime", }, ]; diff --git a/packages/diracx-web-components/components/shared/DataTable.tsx b/packages/diracx-web-components/components/shared/DataTable.tsx index 15d29d9a..86bc0258 100644 --- a/packages/diracx-web-components/components/shared/DataTable.tsx +++ b/packages/diracx-web-components/components/shared/DataTable.tsx @@ -363,6 +363,7 @@ export function DataTable(props: DataTableProps) { parameter: filter.column, operator: filter.operator, value: filter.value, + values: filter.values, })); setSearchBody({ search: jsonFilters }); setPage(0); @@ -386,18 +387,12 @@ export function DataTable(props: DataTableProps) { if (SectionItem?.data?.filters) { setFilters(SectionItem.data.filters); setAppliedFilters(SectionItem.data.filters); - const jsonFilters = SectionItem.data.filters.map( - (filter: { - id: number; - column: string; - operator: string; - value: string; - }) => ({ - parameter: filter.column, - operator: filter.operator, - value: filter.value, - }), - ); + const jsonFilters = SectionItem.data.filters.map((filter: Filter) => ({ + parameter: filter.column, + operator: filter.operator, + value: filter.value, + values: filter.values, + })); setSearchBody({ search: jsonFilters }); } else { setFilters([]); diff --git a/packages/diracx-web-components/components/shared/FilterForm.tsx b/packages/diracx-web-components/components/shared/FilterForm.tsx index 34e51d69..d2609bd2 100644 --- a/packages/diracx-web-components/components/shared/FilterForm.tsx +++ b/packages/diracx-web-components/components/shared/FilterForm.tsx @@ -14,6 +14,10 @@ import { } from "@mui/material"; import { Filter } from "@/types/Filter"; import { Column } from "@/types/Column"; +import { DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import dayjs from "dayjs"; +import "dayjs/locale/en-gb"; /** * Filter form props @@ -62,13 +66,19 @@ export function FilterForm(props: FilterFormProps) { if (filterIndex !== -1) { setTempFilter(filters[filterIndex]); } else { - setTempFilter({ id: Date.now(), column: "", operator: "eq", value: "" }); + setTempFilter({ + id: Date.now(), + column: "", + operator: "eq", + value: "", + values: [], + }); } }, [filters, filterIndex]); if (!tempFilter) return null; - const onChange = (field: string, value: string) => { + const onChange = (field: string, value: string | string[] | undefined) => { setTempFilter((prevFilter: Filter | null) => { if (prevFilter === null) { return null; // or initialize a new Filter object as appropriate @@ -83,6 +93,13 @@ export function FilterForm(props: FilterFormProps) { }; const applyChanges = () => { + if (tempFilter.operator == "last") { + tempFilter.operator = "gt"; + tempFilter.value = dayjs() + .subtract(1, tempFilter.value as "hour" | "day" | "month" | "year") + .toISOString(); + } + if (filterIndex === -1) { setFilters([...filters, tempFilter]); } else { @@ -91,6 +108,8 @@ export function FilterForm(props: FilterFormProps) { handleFilterMenuClose(); }; + const selectedColumn = columns.find((c) => c.id == tempFilter.column); + return ( @@ -98,7 +117,7 @@ export function FilterForm(props: FilterFormProps) { Edit Filter - + Column onChange("operator", e.target.value)} + onChange={(e) => { + onChange( + ["in", "not in"].includes(e.target.value) + ? "values" + : "value", + [] || "", + ); + onChange( + ["in", "not in"].includes(e.target.value) + ? "value" + : "values", + undefined, + ); + onChange("operator", e.target.value); + }} label="Operator" labelId="operator" data-testid="filter-form-select-operator" sx={{ minWidth: 120 }} > - equals to - not equals to - is greater than - is lower than - like + {selectedColumn?.type != "DateTime" && ( + equals to + )} + {selectedColumn?.type != "DateTime" && ( + not equals to + )} + + {selectedColumn?.type == "DateTime" && ( + in the last + )} + + {typeof selectedColumn?.type != "object" && ( + is greater than + )} + {typeof selectedColumn?.type != "object" && ( + is lower than + )} + {(typeof selectedColumn?.type == "object" || + selectedColumn?.type == "number") && ( + is in + )} + {(typeof selectedColumn?.type == "object" || + selectedColumn?.type == "number") && ( + is not in + )} + {selectedColumn?.type != "DateTime" && ( + like + )} - - onChange("value", e.target.value)} - sx={{ flexGrow: 1 }} - /> - + + + {typeof selectedColumn?.type == "object" && + tempFilter.operator != "like" ? ( + <> + Value + + + ) : selectedColumn?.type == "DateTime" && + tempFilter.operator != "last" ? ( + onChange("value", e?.toISOString() || "")} + views={["year", "day", "hours", "minutes", "seconds"]} + /> + ) : selectedColumn?.type == "DateTime" && + tempFilter.operator == "last" ? ( + <> + Value + + + ) : selectedColumn?.type == "number" && + !["in", "not in", "like"].includes(tempFilter.operator) ? ( + onChange("value", e.target.value)} + type="number" + /> + ) : selectedColumn?.type == "number" && + ["in", "not in"].includes(tempFilter.operator) ? ( + + onChange("values", e.target.value.split(" ")) + } + /> + ) : ( + onChange("value", e.target.value)} + /> + )} + + applyChanges()} color="success"> diff --git a/packages/diracx-web-components/components/shared/FilterToolbar.tsx b/packages/diracx-web-components/components/shared/FilterToolbar.tsx index e5117024..f4e3249f 100644 --- a/packages/diracx-web-components/components/shared/FilterToolbar.tsx +++ b/packages/diracx-web-components/components/shared/FilterToolbar.tsx @@ -205,7 +205,7 @@ export function FilterToolbar(props: FilterToolbarProps) { {filters.map((filter: Filter, index: number) => ( { handleFilterMenuOpen(event); // Open the menu setSelectedFilter(filter); // Set the selected filter diff --git a/packages/diracx-web-components/package.json b/packages/diracx-web-components/package.json index 87db9b0a..25a4d2f0 100644 --- a/packages/diracx-web-components/package.json +++ b/packages/diracx-web-components/package.json @@ -28,9 +28,11 @@ "@mui/icons-material": "^5.15.18", "@mui/material": "^5.15.18", "@mui/utils": "^5.15.14", + "@mui/x-date-pickers": "^7.14.0", "@types/node": "20.11.30", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", + "dayjs": "^1.11.13", "jsoncrush": "^1.1.8", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/packages/diracx-web-components/types/Column.ts b/packages/diracx-web-components/types/Column.ts index efb38fad..3ed686e1 100644 --- a/packages/diracx-web-components/types/Column.ts +++ b/packages/diracx-web-components/types/Column.ts @@ -2,9 +2,12 @@ * Column interface * @property {number | string} id - the id of the cell * @property {string} label - the label of the cell + * @property {((value: any) => JSX.Element) | null} render - an optional render function to display the values + * @property {string | string[]} type - The type of the values or the list of possible values */ export interface Column { id: number | string; label: string; render?: ((value: any) => JSX.Element) | null; + type?: string | string[]; } diff --git a/packages/diracx-web-components/types/Filter.ts b/packages/diracx-web-components/types/Filter.ts index 2323cc0f..3f8eb362 100644 --- a/packages/diracx-web-components/types/Filter.ts +++ b/packages/diracx-web-components/types/Filter.ts @@ -3,10 +3,12 @@ * @property {string} column - the column to filter by * @property {string} operator - the operator to use for the filter * @property {string} value - the value to filter by + * @property {string[]} values - the values to filter by if there are multiple */ export interface Filter { id: number; column: string; operator: string; - value: string; + value?: string; + values?: string[]; }