From 10f455c0639a4cde339cf1df7bb031641024abfa Mon Sep 17 00:00:00 2001 From: Frank Niessink Date: Tue, 21 Jan 2025 22:35:17 +0100 Subject: [PATCH] Migration to Material UI fixes: "Some buttons do not show their cursor state". Fixes #6443. --- .../frontend/src/source/SourceEntities.js | 116 +++++++----------- .../src/source/SourceEntities.test.js | 88 ++++--------- .../src/subject/SubjectTableHeader.js | 2 +- .../frontend/src/widgets/TableHeaderCell.js | 41 +++++-- .../src/widgets/TableHeaderCell.test.js | 4 +- docs/src/changelog.md | 2 +- 6 files changed, 98 insertions(+), 155 deletions(-) diff --git a/components/frontend/src/source/SourceEntities.js b/components/frontend/src/source/SourceEntities.js index a6fb76373f..31281f37b8 100644 --- a/components/frontend/src/source/SourceEntities.js +++ b/components/frontend/src/source/SourceEntities.js @@ -10,7 +10,6 @@ import { TableContainer, TableHead, TableRow, - TableSortLabel, Tooltip, } from "@mui/material" import { bool, func, object, string } from "prop-types" @@ -19,7 +18,6 @@ import { useContext, useState } from "react" import { DataModel } from "../context/DataModel" import { alignmentPropType, - childrenPropType, entityAttributePropType, entityAttributesPropType, entityAttributeTypePropType, @@ -34,6 +32,7 @@ import { import { capitalize } from "../utils" import { IgnoreIcon, ShowIcon } from "../widgets/icons" import { LoadingPlaceHolder } from "../widgets/Placeholder" +import { SortableTableHeaderCell } from "../widgets/TableHeaderCell" import { FailedToLoadMeasurementsWarningMessage, InfoMessage } from "../widgets/WarningMessage" import { SourceEntity } from "./SourceEntity" @@ -90,70 +89,19 @@ sorted.propTypes = { sortDirection: sortDirectionPropType, } -function sort(column, columnType, setColumnType, setSortColumn, setSortDirection, sortColumn, sortDirection) { - setColumnType(columnType) - if (column === sortColumn) { - setSortDirection(sortDirection === "ascending" ? "descending" : "ascending") - } else { - setSortColumn(column) - } -} -sort.propTypes = { - column: string, - columnType: entityAttributeTypePropType, - setColumnType: func, - setSortColumn: func, - setSortDirection: func, - sortColumn: string, - sortDirection: sortDirectionPropType, -} - -function MuiSortDirection(sortDirection) { - return sortDirection === "ascending" ? "asc" : "desc" -} - -function SortableHeaderCell({ - children, - column, - columnType, - setColumnType, - setSortColumn, - setSortDirection, - sortColumn, - sortDirection, - textAlign, -}) { - return ( - - - sort(column, columnType, setColumnType, setSortColumn, setSortDirection, sortColumn, sortDirection) - } - > - {children} - - - ) -} -SortableHeaderCell.propTypes = { - children: childrenPropType, - column: string, - columnType: entityAttributeTypePropType, - setColumnType: func, - setSortColumn: func, - setSortDirection: func, - sortColumn: string, - sortDirection: sortDirectionPropType, - textAlign: alignmentPropType, -} - function EntityAttributeHeaderCell({ entityAttribute, ...sortProps }) { + function handleSort(column) { + sortProps.setColumnType(entityAttribute.type || "text") + if (column === sortProps.sortColumn) { + sortProps.setSortDirection(sortProps.sortDirection === "ascending" ? "descending" : "ascending") + } else { + sortProps.setSortColumn(column) + } + } return ( - @@ -166,7 +114,7 @@ function EntityAttributeHeaderCell({ entityAttribute, ...sortProps }) { ) : null} - + ) } EntityAttributeHeaderCell.propTypes = { @@ -188,6 +136,14 @@ function sourceEntitiesHeaders( const entityName = metricEntities.name const entityNamePlural = metricEntities.name_plural const hideIgnoredEntitiesLabel = `${hideIgnoredEntities ? "Show" : "Hide"} ignored ${entityNamePlural}` + function handleSort(column, columnType) { + sortProps.setColumnType(columnType) + if (column === sortProps.sortColumn) { + sortProps.setSortDirection(sortProps.sortDirection === "ascending" ? "descending" : "ascending") + } else { + sortProps.setSortColumn(column) + } + } return ( @@ -200,18 +156,34 @@ function sourceEntitiesHeaders( - + handleSort(column, "text")} + {...sortProps} + > {`${capitalize(entityName)} status`} - - + + handleSort(column, "date")} + {...sortProps} + > Status end date - - + + handleSort(column, "text")} + {...sortProps} + > Status rationale - - + + handleSort(column, "datetime")} + {...sortProps} + > {capitalize(entityName)} first seen - + {entityAttributes.map((entityAttribute) => ( ))} diff --git a/components/frontend/src/source/SourceEntities.test.js b/components/frontend/src/source/SourceEntities.test.js index 844d0b14fb..10f1e91e18 100644 --- a/components/frontend/src/source/SourceEntities.test.js +++ b/components/frontend/src/source/SourceEntities.test.js @@ -180,103 +180,59 @@ it("shows the show ignored entities button", async () => { expect(hideEntitiesButton).toHaveAttribute("aria-label", "Show ignored entities") }) -it("sorts the entities by status", async () => { +async function expectColumnIsSortedCorrectly(header, ascending) { renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/Entity name status/)) - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/Entity name status/)) - expectOrder(["A", "B", "C"]) + expectOrder(["C", "B", "A"]) // Initial order + await userEvent.click(screen.getByText(header)) + expectOrder(ascending) + await userEvent.click(screen.getByText(header)) + expectOrder(ascending.toReversed()) + await userEvent.click(screen.getByText(header)) + expectOrder(ascending) +} + +it("sorts the entities by status", async () => { + await expectColumnIsSortedCorrectly(/Entity name status/, ["C", "B", "A"]) }) it("sorts the entities by status end date", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/Status end date/)) - expectOrder(["A", "C", "B"]) - await userEvent.click(screen.getByText(/Status end date/)) - expectOrder(["B", "C", "A"]) + await expectColumnIsSortedCorrectly(/Status end date/, ["A", "C", "B"]) }) it("sorts the entities by status rationale", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/Status rationale/)) - expectOrder(["A", "C", "B"]) - await userEvent.click(screen.getByText(/Status rationale/)) - expectOrder(["B", "C", "A"]) + await expectColumnIsSortedCorrectly(/Status rationale/, ["A", "C", "B"]) }) it("sorts the entities by first seen date", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/first seen/)) - expectOrder(["C", "A", "B"]) - await userEvent.click(screen.getByText(/first seen/)) - expectOrder(["B", "A", "C"]) + await expectColumnIsSortedCorrectly(/first seen/, ["C", "A", "B"]) }) it("sorts the entities by integer", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/integer/)) - expectOrder(["C", "A", "B"]) - await userEvent.click(screen.getByText(/integer/)) - expectOrder(["B", "A", "C"]) + await expectColumnIsSortedCorrectly(/integer/, ["C", "A", "B"]) }) it("sorts the entities by integer percentage", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/int percentage/)) - expectOrder(["C", "A", "B"]) - await userEvent.click(screen.getByText(/int percentage/)) - expectOrder(["B", "A", "C"]) + await expectColumnIsSortedCorrectly(/int percentage/, ["C", "A", "B"]) }) it("sorts the entities by float", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/float/)) - expectOrder(["A", "B", "C"]) - await userEvent.click(screen.getByText(/float/)) - expectOrder(["C", "B", "A"]) + await expectColumnIsSortedCorrectly(/float/, ["A", "B", "C"]) }) it("sorts the entities by text", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/text/)) - expectOrder(["A", "B", "C"]) - await userEvent.click(screen.getByText(/text/)) - expectOrder(["C", "B", "A"]) + await expectColumnIsSortedCorrectly(/text/, ["A", "B", "C"]) }) it("sorts the entities by date", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/date only/)) - expectOrder(["C", "A", "B"]) - await userEvent.click(screen.getByText(/date only/)) - expectOrder(["B", "A", "C"]) + await expectColumnIsSortedCorrectly(/date only/, ["C", "A", "B"]) }) it("sorts the entities by datetime", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/datetime/)) - expectOrder(["C", "A", "B"]) - await userEvent.click(screen.getByText(/datetime/)) - expectOrder(["B", "A", "C"]) + await expectColumnIsSortedCorrectly(/datetime/, ["C", "A", "B"]) }) it("sorts the entities by minutes", async () => { - renderSourceEntities() - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/minutes/)) - expectOrder(["C", "B", "A"]) - await userEvent.click(screen.getByText(/minutes/)) - expectOrder(["A", "B", "C"]) + await expectColumnIsSortedCorrectly(/minutes/, ["C", "B", "A"]) }) it("shows help", async () => { diff --git a/components/frontend/src/subject/SubjectTableHeader.js b/components/frontend/src/subject/SubjectTableHeader.js index 8a53f9be4a..88e0799fd6 100644 --- a/components/frontend/src/subject/SubjectTableHeader.js +++ b/components/frontend/src/subject/SubjectTableHeader.js @@ -291,7 +291,7 @@ MeasurementHeaderCells.propTypes = { export function SubjectTableHeader({ columnDates, handleSort, settings }) { const sortProps = { - sortColumn: settings.sortColumn, + sortColumn: settings.sortColumn.value, sortDirection: settings.sortDirection, handleSort: handleSort, } diff --git a/components/frontend/src/widgets/TableHeaderCell.js b/components/frontend/src/widgets/TableHeaderCell.js index 566a7060bf..618824b0f7 100644 --- a/components/frontend/src/widgets/TableHeaderCell.js +++ b/components/frontend/src/widgets/TableHeaderCell.js @@ -1,12 +1,12 @@ -import { TableCell, TableSortLabel, Tooltip } from "@mui/material" +import { ButtonBase, TableCell, TableSortLabel, Tooltip } from "@mui/material" import { func, string } from "prop-types" import { alignmentPropType, + childrenPropType, labelPropType, popupContentPropType, - sortDirectionURLSearchQueryPropType, - stringURLSearchQueryPropType, + sortDirectionPropType, } from "../sharedPropTypes" function TableHeaderCellContents({ help, label }) { @@ -28,6 +28,7 @@ function MuiSortDirection(sortDirection) { } export function SortableTableHeaderCell({ + children, colSpan, column, sortColumn, @@ -37,27 +38,41 @@ export function SortableTableHeaderCell({ textAlign, help, }) { - const sorted = sortColumn.value === column ? MuiSortDirection(sortDirection.value) : null + const sorted = sortColumn === column ? MuiSortDirection(sortDirection) : null + const align = textAlign || "left" return ( - - handleSort(column)} + + - - + handleSort(column)} + > + {children || } + + ) } SortableTableHeaderCell.propTypes = { + children: childrenPropType, colSpan: string, column: string, handleSort: func, help: popupContentPropType, label: labelPropType, - sortColumn: stringURLSearchQueryPropType, - sortDirection: sortDirectionURLSearchQueryPropType, + sortColumn: string, + sortDirection: sortDirectionPropType, textAlign: alignmentPropType, } diff --git a/components/frontend/src/widgets/TableHeaderCell.test.js b/components/frontend/src/widgets/TableHeaderCell.test.js index 72e793885f..03cb2cfb7d 100644 --- a/components/frontend/src/widgets/TableHeaderCell.test.js +++ b/components/frontend/src/widgets/TableHeaderCell.test.js @@ -14,8 +14,8 @@ function renderSortableTableHeaderCell(help) { diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 77039ebf27..be781432bd 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -22,7 +22,7 @@ If your currently installed *Quality-time* version is not the latest version, pl ### Changed -- Completed the replacement of Semantic UI React with Material UI as frontend component library. Fixes [#5180] (https://github.com/ICTU/quality-time/issues/5180), [#9904](https://github.com/ICTU/quality-time/issues/9904) and [#10159](https://github.com/ICTU/quality-time/issues/10159). Closes [#9796](https://github.com/ICTU/quality-time/issues/9796). +- Completed the replacement of Semantic UI React with Material UI as frontend component library. Fixes [#5180](https://github.com/ICTU/quality-time/issues/5180), [#6443](https://github.com/ICTU/quality-time/issues/6443), [#9904](https://github.com/ICTU/quality-time/issues/9904) and [#10159](https://github.com/ICTU/quality-time/issues/10159). Closes [#9796](https://github.com/ICTU/quality-time/issues/9796). ## v5.22.0 - 2025-01-16