From 7b149798a68beff9873f4961ec1cbdc35e4bf21d Mon Sep 17 00:00:00 2001 From: Loxeris <30194187+Loxeris@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:41:08 +0200 Subject: [PATCH 1/3] feat(datatable): adapt input fields for filter values --- package-lock.json | 134 +++++++++---- .../components/JobMonitor/JobDataService.ts | 28 +++ .../components/JobMonitor/JobDataTable.tsx | 26 ++- .../components/shared/DataTable.tsx | 20 +- .../components/shared/FilterForm.tsx | 186 ++++++++++++++++-- .../components/shared/FilterToolbar.tsx | 2 +- packages/diracx-web-components/package.json | 2 + .../diracx-web-components/types/Column.ts | 3 + .../diracx-web-components/types/Filter.ts | 4 +- 9 files changed, 335 insertions(+), 70 deletions(-) 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/JobDataService.ts b/packages/diracx-web-components/components/JobMonitor/JobDataService.ts index 8ba967da..caa4b18c 100644 --- a/packages/diracx-web-components/components/JobMonitor/JobDataService.ts +++ b/packages/diracx-web-components/components/JobMonitor/JobDataService.ts @@ -1,5 +1,7 @@ import useSWR, { mutate } from "swr"; import { fetcher } from "@/hooks/utils"; +import { Filter } from "@/types"; +import dayjs from "dayjs"; /** * Custom hook for fetching jobs data. @@ -16,6 +18,19 @@ export const useJobs = ( page: number, rowsPerPage: number, ) => { + searchBody.search = searchBody.search?.map((filter: any) => { + if (filter.operator == "last") { + return { + column: filter.column, + operator: "gt", + value: dayjs() + .subtract(1, filter.value as "hour" | "day" | "month" | "year") + .toISOString(), + values: filter.values, + }; + } + return filter; + }); const urlGetJobs = `/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`; return useSWR([urlGetJobs, accessToken, "POST", searchBody], fetcher); }; @@ -34,6 +49,19 @@ export const refreshJobs = ( page: number, rowsPerPage: number, ) => { + searchBody.search = searchBody.search?.map((filter: any) => { + if (filter.operator == "last") { + return { + column: filter.column, + operator: "gt", + value: dayjs() + .subtract(1, filter.value as "hour" | "day" | "month" | "year") + .toISOString(), + values: filter.values, + }; + } + return filter; + }); const urlGetJobs = `/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`; mutate([urlGetJobs, accessToken, "POST", searchBody]); }; 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..cb66eee8 100644 --- a/packages/diracx-web-components/components/shared/DataTable.tsx +++ b/packages/diracx-web-components/components/shared/DataTable.tsx @@ -32,6 +32,7 @@ import { Filter } from "@/types/Filter"; import { Column } from "@/types/Column"; import { useSearchParamsUtils } from "@/hooks/searchParamsUtils"; import { ApplicationsContext } from "@/contexts/ApplicationsProvider"; +import dayjs from "dayjs"; import { FilterToolbar } from "./FilterToolbar"; /** @@ -363,6 +364,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 +388,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..9dd13514 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,18 @@ 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: "", + }); } }, [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 @@ -91,6 +100,8 @@ export function FilterForm(props: FilterFormProps) { handleFilterMenuClose(); }; + const selectedColumn = columns.find((c) => c.id == tempFilter.column); + return ( @@ -98,11 +109,23 @@ export function FilterForm(props: FilterFormProps) { Edit Filter - + Column - + Operator - - 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[]; } From bb4ba865fe9f3bccc720f7322845e7efb2903aaf Mon Sep 17 00:00:00 2001 From: Loxeris <30194187+Loxeris@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:49:39 +0200 Subject: [PATCH 2/3] style: clean the code --- .../components/JobMonitor/JobDataService.ts | 45 +-- .../components/JobMonitor/JobDataTable.tsx | 54 +-- .../components/shared/DataTable.tsx | 1 - .../components/shared/FilterForm.tsx | 351 ++++++++++-------- 4 files changed, 229 insertions(+), 222 deletions(-) diff --git a/packages/diracx-web-components/components/JobMonitor/JobDataService.ts b/packages/diracx-web-components/components/JobMonitor/JobDataService.ts index caa4b18c..623be8fd 100644 --- a/packages/diracx-web-components/components/JobMonitor/JobDataService.ts +++ b/packages/diracx-web-components/components/JobMonitor/JobDataService.ts @@ -1,8 +1,23 @@ import useSWR, { mutate } from "swr"; import { fetcher } from "@/hooks/utils"; -import { Filter } from "@/types"; import dayjs from "dayjs"; +function processSearchBody(searchBody: any) { + searchBody.search = searchBody.search?.map((filter: any) => { + if (filter.operator == "last") { + return { + parameter: filter.parameter, + operator: "gt", + value: dayjs() + .subtract(1, filter.value as "hour" | "day" | "month" | "year") + .toISOString(), + values: filter.values, + }; + } + return filter; + }); +} + /** * Custom hook for fetching jobs data. * @@ -18,19 +33,7 @@ export const useJobs = ( page: number, rowsPerPage: number, ) => { - searchBody.search = searchBody.search?.map((filter: any) => { - if (filter.operator == "last") { - return { - column: filter.column, - operator: "gt", - value: dayjs() - .subtract(1, filter.value as "hour" | "day" | "month" | "year") - .toISOString(), - values: filter.values, - }; - } - return filter; - }); + processSearchBody(searchBody); const urlGetJobs = `/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`; return useSWR([urlGetJobs, accessToken, "POST", searchBody], fetcher); }; @@ -49,19 +52,7 @@ export const refreshJobs = ( page: number, rowsPerPage: number, ) => { - searchBody.search = searchBody.search?.map((filter: any) => { - if (filter.operator == "last") { - return { - column: filter.column, - operator: "gt", - value: dayjs() - .subtract(1, filter.value as "hour" | "day" | "month" | "year") - .toISOString(), - values: filter.values, - }; - } - return filter; - }); + processSearchBody(searchBody); const urlGetJobs = `/api/jobs/search?page=${page + 1}&per_page=${rowsPerPage}`; mutate([urlGetJobs, accessToken, "POST", searchBody]); }; diff --git a/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx b/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx index 73ee1f39..04e22993 100644 --- a/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx +++ b/packages/diracx-web-components/components/JobMonitor/JobDataTable.tsx @@ -40,28 +40,28 @@ import { useJobs, } from "./JobDataService"; +const statusColors: { [key: string]: string } = { + Submitting: purple[500], + Received: blueGrey[500], + Checking: teal[500], + Staging: lightBlue[500], + Waiting: amber[600], + Matched: blue[300], + Running: blue[900], + Rescheduled: lime[700], + Completing: orange[500], + Completed: green[300], + Done: green[500], + Failed: red[500], + Stalled: amber[900], + Killed: red[900], + Deleted: grey[500], +}; + /** * Renders the status cell with colors */ const renderStatusCell = (status: string) => { - const statusColors: { [key: string]: string } = { - Submitting: purple[500], - Received: blueGrey[500], - Checking: teal[500], - Staging: lightBlue[500], - Waiting: amber[600], - Matched: blue[300], - Running: blue[900], - Rescheduled: lime[700], - Completing: orange[500], - Completed: green[300], - Done: green[500], - Failed: red[500], - Stalled: amber[900], - Killed: red[900], - Deleted: grey[500], - }; - return ( c.id == tempFilter.column); + const types: { + [type: string]: { operators: string[]; defaultOperator: string }; + } = { + DateTime: { operators: ["last", "gt", "lt"], defaultOperator: "last" }, + category: { + operators: ["eq", "neq", "in", "not in", "like"], + defaultOperator: "eq", + }, + number: { + operators: ["eq", "neq", "gt", "lt", "like"], + defaultOperator: "eq", + }, + default: { + operators: ["eq", "neq", "gt", "lt", "like"], + defaultOperator: "eq", + }, + }; + + const operatorText: { [operator: string]: string } = { + eq: "equals to", + neq: "not equals to", + last: "in the last", + gt: "is greater than", + lt: "is lower than", + in: "is in", + "not in": "is not in", + like: "like", + }; + + function operatorSelector() { + if (tempFilter) + return ( + + Operator + + + ); + } + + function valueSelector() { + if (!tempFilter) return null; + if (selectedColumn?.type == "DateTime") { + if (tempFilter.operator != "last") + return ( + + + onChange("value", e?.toISOString() || "")} + views={["year", "day", "hours", "minutes", "seconds"]} + /> + + + ); + else + return ( + + Value + + + ); + } + + if ( + typeof selectedColumn?.type == "object" && + tempFilter.operator != "like" + ) + return ( + + Value + + + ); + if (selectedColumn?.type == "number") { + if (!["in", "not in", "like"].includes(tempFilter.operator)) + return ( + + onChange("value", e.target.value)} + type="number" + /> + + ); + else if (["in", "not in"].includes(tempFilter.operator)) + return ( + + onChange("values", e.target.value.split(" "))} + /> + + ); + } + return ( + + onChange("value", e.target.value)} + /> + + ); + } + return ( @@ -115,16 +294,16 @@ export function FilterForm(props: FilterFormProps) { value={tempFilter.column} onChange={(e) => { onChange("column", e.target.value); - console.log(columns.find((c) => c.id == e.target.value)?.type); - if ( - columns.find((c) => c.id == e.target.value)?.type == - "DateTime" - ) - onChange("operator", "last"); - else if (tempFilter.operator == "last") { - onChange("operator", "eq"); - onChange("value", ""); - } + onChange( + "operator", + types[ + typeof columns.find((v) => v.id == e.target.value)?.type == + "object" + ? "category" + : (columns.find((v) => v.id == e.target.value) + ?.type as string) || "default" + ]?.defaultOperator, + ); }} label="Column" labelId="column" @@ -139,155 +318,9 @@ export function FilterForm(props: FilterFormProps) { - - Operator - - + {operatorSelector()} - - - {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)} - /> - )} - - + {valueSelector()} applyChanges()} color="success"> From fe4420868efbda401eb139b70cdcd2c55f124bb5 Mon Sep 17 00:00:00 2001 From: Loxeris <30194187+Loxeris@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:26:42 +0200 Subject: [PATCH 3/3] test(FilterForm): add tests for new features --- .../test/unit-tests/FilterForm.test.tsx | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/diracx-web-components/test/unit-tests/FilterForm.test.tsx b/packages/diracx-web-components/test/unit-tests/FilterForm.test.tsx index 4b114a65..7aa86983 100644 --- a/packages/diracx-web-components/test/unit-tests/FilterForm.test.tsx +++ b/packages/diracx-web-components/test/unit-tests/FilterForm.test.tsx @@ -1,12 +1,15 @@ import React from "react"; import { render, screen, fireEvent, within } from "@testing-library/react"; import { FilterForm } from "@/components/shared/FilterForm"; +import { Column } from "@/types/Column"; describe("FilterForm", () => { - const columns = [ + const columns: Column[] = [ { id: "column1", label: "Column 1" }, { id: "column2", label: "Column 2" }, { id: "column3", label: "Column 3" }, + { id: "column4", label: "Column 4", type: ["1", "2", "3"] }, + { id: "column5", label: "Column 5", type: "DateTime" }, ]; const filters = [ { id: 1, column: "column1", operator: "eq", value: "value1" }, @@ -160,4 +163,76 @@ describe("FilterForm", () => { }); expect(handleFilterMenuClose).toHaveBeenCalled(); }); + + it("renders the correct input for DateTime column type", () => { + render( + , + ); + + const columnSelect = screen.getByTestId("filter-form-select-column"); + const columnButton = within(columnSelect).getByRole("combobox"); + fireEvent.mouseDown(columnButton); + const columnOption = screen.getByText("Column 5"); + fireEvent.click(columnOption); + + const operatorSelect = screen.getByTestId("filter-form-select-operator"); + expect(operatorSelect).toHaveTextContent("in the last"); + + const dateTimeInput = screen.getByLabelText("Value"); + + expect(dateTimeInput).toHaveRole("combobox"); + + // Simulate a click event on the operator Select element + const operatorButton = within(operatorSelect).getByRole("combobox"); + fireEvent.mouseDown(operatorButton); + + // Select the desired option from the dropdown list + const operatorOption = screen.getByText("is greater than"); + fireEvent.click(operatorOption); + + expect(screen.getByTestId("CalendarIcon")).toBeInTheDocument(); + }); + + it("handles 'in' and 'not in' operators for category columns", () => { + render( + , + ); + + const columnSelect = screen.getByTestId("filter-form-select-column"); + const columnButton = within(columnSelect).getByRole("combobox"); + fireEvent.mouseDown(columnButton); + const columnOption = screen.getByText("Column 4"); + fireEvent.click(columnOption); + + const operatorSelect = screen.getByTestId("filter-form-select-operator"); + const operatorButton = within(operatorSelect).getByRole("combobox"); + fireEvent.mouseDown(operatorButton); + const operatorOption = screen.getByText("is in"); + fireEvent.click(operatorOption); + + const valueSelect = screen.getByLabelText("Value"); + expect(valueSelect).toHaveRole("combobox"); + fireEvent.mouseDown(valueSelect); + + const valueOption1 = screen.getByText("1"); + fireEvent.click(valueOption1); + const valueOption2 = screen.getByText("2"); + fireEvent.click(valueOption2); + + expect(valueSelect).toHaveTextContent("1, 2"); + }); });