diff --git a/components/frontend/src/App.css b/components/frontend/src/App.css
index 610c057fed..20fbbcd8aa 100644
--- a/components/frontend/src/App.css
+++ b/components/frontend/src/App.css
@@ -1,21 +1,3 @@
-.MainContainer {
- flex: 1;
- margin-top: 6em;
- padding-left: 1em;
- padding-right: 1em;
-}
-
-@media print {
- .MainContainer {
- margin-top: 0em;
- }
-}
-
html {
scroll-padding-top: 163px; /* height of sticky header */
}
-
-:root {
- --inverted-menu-background-color: #1b1c1d;
- --selection-color: #2185d0;
-}
diff --git a/components/frontend/src/App.js b/components/frontend/src/App.js
index 6ee06731d5..1a8673c914 100644
--- a/components/frontend/src/App.js
+++ b/components/frontend/src/App.js
@@ -1,6 +1,7 @@
import "./App.css"
-import { createTheme, ThemeProvider } from "@mui/material/styles"
+import { grey, orange } from "@mui/material/colors"
+import { createTheme, responsiveFontSizes, ThemeProvider } from "@mui/material/styles"
import { Action } from "history"
import history from "history/browser"
import { Component } from "react"
@@ -14,13 +15,57 @@ import { registeredURLSearchParams } from "./hooks/url_search_query"
import { isValidDate_YYYYMMDD, toISODateStringInCurrentTZ } from "./utils"
import { showConnectionMessage, showMessage } from "./widgets/toast"
-const theme = createTheme({
+let theme = createTheme({
colorSchemes: {
dark: true, // Add a dark theme (light theme is available by default)
},
- components: { MuiTooltip: { defaultProps: { arrow: true }, styleOverrides: { tooltip: { fontSize: "1em" } } } },
+ components: {
+ MuiTooltip: {
+ defaultProps: { arrow: true },
+ styleOverrides: { tooltip: { fontSize: "1em" } },
+ },
+ },
+})
+
+theme = createTheme(theme, {
+ palette: {
+ todo: theme.palette.augmentColor({ color: { main: grey[600] }, name: "todo" }),
+ doing: theme.palette.augmentColor({ color: { main: theme.palette.info.main }, name: "doing" }),
+ done: theme.palette.augmentColor({ color: { main: theme.palette.success.main }, name: "done" }),
+ target_not_met: theme.palette.augmentColor({
+ color: { main: theme.palette.error.main },
+ name: "target_not_met",
+ }),
+ target_met: theme.palette.augmentColor({ color: { main: theme.palette.success.main }, name: "target_met" }),
+ near_target_met: theme.palette.augmentColor({ color: { main: orange[300] }, name: "near_target_met" }),
+ debt_target_met: theme.palette.augmentColor({ color: { main: grey[500] }, name: "debt_target_met" }),
+ informative: theme.palette.augmentColor({ color: { main: theme.palette.info.main }, name: "informative" }),
+ unknown: theme.palette.augmentColor({ color: { main: grey[300] }, name: "unknown" }),
+ total: theme.palette.augmentColor({ color: { main: grey[800] }, name: "total" }),
+ },
+ typography: {
+ h1: {
+ fontSize: theme.typography.h4.fontSize,
+ fontWeight: 700,
+ },
+ h2: {
+ fontSize: theme.typography.h5.fontSize,
+ fontWeight: 600,
+ },
+ h3: {
+ fontSize: theme.typography.h6.fontSize,
+ },
+ h4: {
+ fontSize: theme.typography.subtitle1.fontSize,
+ },
+ h5: {
+ fontSize: theme.typography.subtitle2.fontSize,
+ },
+ },
})
+theme = responsiveFontSizes(theme)
+
class App extends Component {
constructor(props) {
super(props)
diff --git a/components/frontend/src/AppUI.js b/components/frontend/src/AppUI.js
index 4b35645ba8..7494a249df 100644
--- a/components/frontend/src/AppUI.js
+++ b/components/frontend/src/AppUI.js
@@ -66,65 +66,55 @@ export function AppUI({
}
const darkMode = userPrefersDarkMode(mode)
- const backgroundColor = darkMode ? "rgb(40, 40, 40)" : "white"
return (
-
-
-
-
-
- }
- settings={settings}
- setUIMode={setMode}
- uiMode={mode}
- />
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ }
+ settings={settings}
+ setUIMode={setMode}
+ uiMode={mode}
+ />
+
+
+
+
+
+
+
+
+
)
}
AppUI.propTypes = {
diff --git a/components/frontend/src/PageContent.js b/components/frontend/src/PageContent.js
index 87c998c4ef..5407e1bc39 100644
--- a/components/frontend/src/PageContent.js
+++ b/components/frontend/src/PageContent.js
@@ -1,11 +1,11 @@
+import { Box, Container } from "@mui/material"
+import CircularProgress from "@mui/material/CircularProgress"
import { bool, func, number, string } from "prop-types"
import { useEffect, useState } from "react"
-import { Container, Loader } from "semantic-ui-react"
import { get_measurements } from "./api/measurement"
import { Report } from "./report/Report"
import { ReportsOverview } from "./report/ReportsOverview"
-import { Segment } from "./semantic_ui_react_wrappers"
import {
datePropType,
optionalDatePropType,
@@ -72,9 +72,17 @@ export function PageContent({
let content
if (loading) {
content = (
-
-
-
+
+
+
)
} else {
const commonProps = {
@@ -108,7 +116,19 @@ export function PageContent({
}
}
return (
-
+
{content}
)
diff --git a/components/frontend/src/PageContent.test.js b/components/frontend/src/PageContent.test.js
index 7f1c28c37d..1b7007f209 100644
--- a/components/frontend/src/PageContent.test.js
+++ b/components/frontend/src/PageContent.test.js
@@ -63,7 +63,7 @@ it("shows that the report was missing", async () => {
it("shows the loading spinner", async () => {
await renderPageContent({ loading: true })
- expect(screen.getAllByLabelText(/Loading/).length).toBe(1)
+ expect(screen.getAllByRole("progressbar").length).toBe(1)
})
function expectMeasurementsCall(date, offset = 0) {
diff --git a/components/frontend/src/dashboard/ExportCard.css b/components/frontend/src/dashboard/ExportCard.css
deleted file mode 100644
index 7aeabb7464..0000000000
--- a/components/frontend/src/dashboard/ExportCard.css
+++ /dev/null
@@ -1,35 +0,0 @@
-.ui.card.export-data-card {
- display: none;
-}
-
-@media print {
- .reportHeader {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-right: -13px;
- }
-
- .ui.card.export-data-card {
- display: block;
- min-width: 270px;
- flex-shrink: 0;
- }
-
- .ui.card.export-data-card.list .item {
- display: flex;
- overflow: hidden;
- white-space: normal;
- padding: 2px;
- line-height: 1.4em;
- }
-
- .ui.card.export-data-card .header {
- overflow: hidden;
- white-space: nowrap;
- }
-
- .ui.card.export-data-card .list {
- margin-top: 0.5em;
- }
-}
diff --git a/components/frontend/src/dashboard/ExportCard.js b/components/frontend/src/dashboard/ExportCard.js
deleted file mode 100644
index 3ff8f30416..0000000000
--- a/components/frontend/src/dashboard/ExportCard.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import "./ExportCard.css"
-
-import { bool, string } from "prop-types"
-import { Card, List } from "semantic-ui-react"
-
-import { childrenPropType, datePropType, reportPropType } from "../sharedPropTypes"
-
-function ExportCardItem({ children, url }) {
- const item = children
- return url ? (
-
- {item}
-
- ) : (
- {item}
- )
-}
-ExportCardItem.propTypes = {
- children: childrenPropType,
- url: string,
-}
-
-export function ExportCard({ lastUpdate, report, reportDate, isOverview = false }) {
- const reportURL = new URLSearchParams(window.location.search).get("report_url") ?? window.location.href
- const title = isOverview ? "About these reports" : "About this report"
- const listItems = [
-
-
- {report.title}
-
- ,
-
-
- {"Report date: " + formatDate(reportDate ?? new Date())}
-
- ,
-
-
-
- {"Generated: " + formatDate(lastUpdate) + ", " + formatTime(lastUpdate)}
-
-
- ,
-
-
-
- Quality-time v{process.env.REACT_APP_VERSION}
-
-
- ,
- ]
- return (
-
-
-
- {title}
-
- {listItems}
-
-
- )
-}
-ExportCard.propTypes = {
- isOverview: bool,
- lastUpdate: datePropType,
- report: reportPropType,
- reportDate: datePropType,
-}
-
-// Hard code en-GB to get European style dates and times. See https://github.com/ICTU/quality-time/issues/8381.
-
-function formatDate(date) {
- return date.toLocaleDateString("en-GB", { year: "numeric", month: "2-digit", day: "2-digit" }).replace(/\//g, "-")
-}
-
-function formatTime(date) {
- return date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })
-}
diff --git a/components/frontend/src/dashboard/FilterCardWithTable.js b/components/frontend/src/dashboard/FilterCardWithTable.js
index 12bde1f378..c294f5cb5b 100644
--- a/components/frontend/src/dashboard/FilterCardWithTable.js
+++ b/components/frontend/src/dashboard/FilterCardWithTable.js
@@ -1,14 +1,14 @@
+import { Table, TableBody } from "@mui/material"
import { bool, func, string } from "prop-types"
-import { Table } from "../semantic_ui_react_wrappers"
import { childrenPropType } from "../sharedPropTypes"
import { DashboardCard } from "./DashboardCard"
export function FilterCardWithTable({ children, onClick, selected, title }) {
return (
-
- {children}
+
)
diff --git a/components/frontend/src/dashboard/IssuesCard.js b/components/frontend/src/dashboard/IssuesCard.js
index 20576ce725..e8124c9045 100644
--- a/components/frontend/src/dashboard/IssuesCard.js
+++ b/components/frontend/src/dashboard/IssuesCard.js
@@ -1,9 +1,8 @@
-import { Chip } from "@mui/material"
+import { Chip, TableCell, TableRow } from "@mui/material"
import { bool, func } from "prop-types"
-import { Table } from "../semantic_ui_react_wrappers"
import { reportPropType } from "../sharedPropTypes"
-import { capitalize, ISSUE_STATUS_THEME_COLORS } from "../utils"
+import { capitalize } from "../utils"
import { FilterCardWithTable } from "./FilterCardWithTable"
function issueStatuses(report) {
@@ -33,18 +32,12 @@ issueStatuses.propTypes = {
function tableRows(report) {
const statuses = issueStatuses(report)
return Object.keys(statuses).map((status) => (
-
- {capitalize(status)}
-
-
-
-
+
+ {capitalize(status)}
+
+
+
+
))
}
tableRows.propTypes = {
diff --git a/components/frontend/src/dashboard/LegendCard.js b/components/frontend/src/dashboard/LegendCard.js
index 3b57f71028..7d1a11fa28 100644
--- a/components/frontend/src/dashboard/LegendCard.js
+++ b/components/frontend/src/dashboard/LegendCard.js
@@ -9,7 +9,7 @@ export function LegendCard() {
-
+
))
diff --git a/components/frontend/src/dashboard/MetricsRequiringActionCard.js b/components/frontend/src/dashboard/MetricsRequiringActionCard.js
index b51abde1cf..ecaaf6a0c3 100644
--- a/components/frontend/src/dashboard/MetricsRequiringActionCard.js
+++ b/components/frontend/src/dashboard/MetricsRequiringActionCard.js
@@ -1,7 +1,7 @@
+import { Chip, TableCell, TableRow } from "@mui/material"
import { bool, func } from "prop-types"
-import { STATUS_COLORS, STATUS_NAME, STATUSES_REQUIRING_ACTION } from "../metric/status"
-import { Label, Table } from "../semantic_ui_react_wrappers"
+import { STATUS_NAME, STATUSES_REQUIRING_ACTION } from "../metric/status"
import { reportsPropType } from "../sharedPropTypes"
import { getMetricStatus, sum } from "../utils"
import { FilterCardWithTable } from "./FilterCardWithTable"
@@ -30,26 +30,22 @@ metricStatuses.propTypes = {
function tableRows(reports) {
const statuses = metricStatuses(reports)
const rows = Object.keys(statuses).map((status) => (
-
- {STATUS_NAME[status]}
-
-
- {statuses[status]}
-
-
-
+
+ {STATUS_NAME[status]}
+
+
+
+
))
rows.push(
-
-
+
+
Total
-
-
-
- {sum(Object.values(statuses))}
-
-
- ,
+
+
+
+
+ ,
)
return rows
}
diff --git a/components/frontend/src/dashboard/PageHeader.js b/components/frontend/src/dashboard/PageHeader.js
new file mode 100644
index 0000000000..7b0fae2992
--- /dev/null
+++ b/components/frontend/src/dashboard/PageHeader.js
@@ -0,0 +1,43 @@
+import { Stack, Typography } from "@mui/material"
+
+import { datePropType, reportPropType } from "../sharedPropTypes"
+import { HyperLink } from "../widgets/HyperLink"
+
+export function PageHeader({ lastUpdate, report, reportDate }) {
+ const reportURL = new URLSearchParams(window.location.search).get("report_url") ?? window.location.href
+ const title = report?.title ?? "Reports overview"
+ const changelogURL = `https://quality-time.readthedocs.io/en/v${process.env.REACT_APP_VERSION}/changelog.html`
+ return (
+
+
+ {title}
+
+ {"Report date: " + formatDate(reportDate ?? new Date())}
+
+ {"Generated: " + formatDate(lastUpdate) + ", " + formatTime(lastUpdate)}
+
+
+ Quality-time v{process.env.REACT_APP_VERSION}
+
+
+ )
+}
+PageHeader.propTypes = {
+ lastUpdate: datePropType,
+ report: reportPropType,
+ reportDate: datePropType,
+}
+
+// Hard code en-GB to get European style dates and times. See https://github.com/ICTU/quality-time/issues/8381.
+
+function formatDate(date) {
+ return date.toLocaleDateString("en-GB", { year: "numeric", month: "2-digit", day: "2-digit" }).replace(/\//g, "-")
+}
+
+function formatTime(date) {
+ return date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })
+}
diff --git a/components/frontend/src/dashboard/ExportCard.test.js b/components/frontend/src/dashboard/PageHeader.test.js
similarity index 65%
rename from components/frontend/src/dashboard/ExportCard.test.js
rename to components/frontend/src/dashboard/PageHeader.test.js
index 69c1e09bdf..f3e019e44d 100644
--- a/components/frontend/src/dashboard/ExportCard.test.js
+++ b/components/frontend/src/dashboard/PageHeader.test.js
@@ -1,7 +1,7 @@
import { render, screen } from "@testing-library/react"
-import { ExportCard } from "./ExportCard"
import { mockGetAnimations } from "./MockAnimations"
+import { PageHeader } from "./PageHeader"
beforeEach(() => mockGetAnimations())
@@ -15,6 +15,7 @@ const mockDateOfToday = new Date()
const report = {
report_uuid: "report_uuid",
+ title: "Title",
subjects: {
subject_uuid: {
type: "subject_type",
@@ -32,37 +33,38 @@ const report = {
},
}
-function renderExportCard({ isOverview = false, lastUpdate = new Date(), report = null, reportDate = null } = {}) {
- render( )
+function renderPageHeader({ lastUpdate = new Date(), report = null, reportDate = null } = {}) {
+ render( )
}
-it("displays correct title for an overview report", () => {
- renderExportCard({ isOverview: true, report: report })
- expect(screen.getByText(/About these reports/)).toBeInTheDocument()
+it("displays correct title for the reports overview", () => {
+ renderPageHeader({})
+ expect(screen.getByText(/Reports overview/)).toBeInTheDocument()
})
-it("displays correct title for a detailed report", () => {
- renderExportCard({ report: report })
- expect(screen.getByText(/About this report/)).toBeInTheDocument()
+it("displays correct title for a report", () => {
+ window.location.search = "?report_url=https://report/"
+ renderPageHeader({ report: report })
+ expect(screen.getByText(/Title/)).toBeInTheDocument()
})
it("displays dates in en-GB format", () => {
- renderExportCard({ lastUpdate: mockLastUpdate, report: report, reportDate: mockReportDate })
+ renderPageHeader({ lastUpdate: mockLastUpdate, report: report, reportDate: mockReportDate })
expect(screen.getByText(/Report date: 24-03-2024/)).toBeInTheDocument()
expect(screen.getByText(/Generated: 26-03-2024, 12:34/)).toBeInTheDocument()
})
it("displays report URL", () => {
- renderExportCard({ report: report })
+ renderPageHeader({ report: report })
expect(screen.getByTestId("reportUrl")).toBeInTheDocument()
})
it("displays version link", () => {
- renderExportCard({ lastUpdate: mockLastUpdate, report: report })
+ renderPageHeader({ lastUpdate: mockLastUpdate, report: report })
expect(screen.getByTestId("version")).toBeInTheDocument()
})
it("displays today as report date if no report date is provided", () => {
- renderExportCard({ lastUpdate: mockLastUpdate, report: report })
+ renderPageHeader({ lastUpdate: mockLastUpdate, report: report })
expect(screen.getByText(`Report date: ${mockDateOfToday}`)).toBeInTheDocument()
})
diff --git a/components/frontend/src/errorMessage.js b/components/frontend/src/errorMessage.js
index 39a8e16017..1f9e02e741 100644
--- a/components/frontend/src/errorMessage.js
+++ b/components/frontend/src/errorMessage.js
@@ -1,22 +1,19 @@
+import Grid from "@mui/material/Grid2"
import { bool, object, oneOfType, string } from "prop-types"
-import { Grid } from "semantic-ui-react"
-import { Message } from "./semantic_ui_react_wrappers"
+import { WarningMessage } from "./widgets/WarningMessage"
export function ErrorMessage({ formatAsText, message, title }) {
return (
-
-
-
- {title}
- {formatAsText ? (
- message
- ) : (
- {message}
- )}
-
-
-
+
+
+ {formatAsText ? (
+ message
+ ) : (
+ {message}
+ )}
+
+
)
}
ErrorMessage.propTypes = {
diff --git a/components/frontend/src/header_footer/Footer.js b/components/frontend/src/header_footer/Footer.js
index a4a97eec6f..45ead297c6 100644
--- a/components/frontend/src/header_footer/Footer.js
+++ b/components/frontend/src/header_footer/Footer.js
@@ -153,7 +153,7 @@ function QuoteColumn() {
export function Footer({ lastUpdate, report }) {
return (
-
+
}
sx={{ textTransform: "none" }}
>
- Quality-time
+ Quality-time
diff --git a/components/frontend/src/issue/IssueStatus.js b/components/frontend/src/issue/IssueStatus.js
index a31ec09b40..fbdea36756 100644
--- a/components/frontend/src/issue/IssueStatus.js
+++ b/components/frontend/src/issue/IssueStatus.js
@@ -1,21 +1,36 @@
+import { Card, CardActionArea, CardContent, List, ListItem, Tooltip, Typography } from "@mui/material"
import { bool, string } from "prop-types"
import TimeAgo from "react-timeago"
-import { Label, Popup } from "../semantic_ui_react_wrappers"
import { issueStatusPropType, metricPropType, settingsPropType, stringsPropType } from "../sharedPropTypes"
-import { getMetricIssueIds, ISSUE_STATUS_COLORS } from "../utils"
-import { HyperLink } from "../widgets/HyperLink"
+import { getMetricIssueIds } from "../utils"
import { TimeAgoWithDate } from "../widgets/TimeAgoWithDate"
function IssueWithoutTracker({ issueId }) {
return (
-
+ No issue tracker configured
+
+ Please configure an issue tracker by expanding the report title, selecting the ‘Issue
+ tracker’ tab, and configuring an issue tracker.
+
+ >
}
- header={"No issue tracker configured"}
- trigger={{issueId} }
- />
+ >
+
+
+
+
+
+ {issueId} - ?
+
+
+
+
+
+
)
}
IssueWithoutTracker.propTypes = {
@@ -35,41 +50,47 @@ IssuesWithoutTracker.propTypes = {
issueIds: stringsPropType,
}
-function labelDetails(issueStatus, settings) {
- let details = [{issueStatus.name || "?"} ]
+function cardDetails(issueStatus, settings) {
+ let details = []
if (issueStatus.summary && settings.showIssueSummary.value) {
- details.push({issueStatus.summary} )
+ details.push({issueStatus.summary} )
}
if (issueStatus.created && settings.showIssueCreationDate.value) {
details.push(
-
- Created
- ,
+
+
+ Created
+
+ ,
)
}
if (issueStatus.updated && settings.showIssueUpdateDate.value) {
details.push(
-
- Updated
- ,
+
+
+ Updated
+
+ ,
)
}
if (issueStatus.duedate && settings.showIssueDueDate.value) {
details.push(
-
- Due
- ,
+
+
+ Due
+
+ ,
)
}
if (issueStatus.release_name && settings.showIssueRelease.value) {
- details.push(releaseLabel(issueStatus))
+ details.push(release(issueStatus))
}
if (issueStatus.sprint_name && settings.showIssueSprint.value) {
- details.push(sprintLabel(issueStatus))
+ details.push(sprint(issueStatus))
}
- return details
+ return details.length > 0 ? {details}
: null
}
-labelDetails.propTypes = {
+cardDetails.propTypes = {
issueStatus: issueStatusPropType,
settings: settingsPropType,
}
@@ -81,31 +102,31 @@ releaseStatus.propTypes = {
issueStatus: issueStatusPropType,
}
-function releaseLabel(issueStatus) {
+function release(issueStatus) {
const date = issueStatus.release_date ? : null
return (
-
+
{prefixName(issueStatus.release_name, "Release")} {releaseStatus(issueStatus)} {date}
-
+
)
}
-releaseLabel.propTypes = {
+release.propTypes = {
issueStatus: issueStatusPropType,
}
-function sprintLabel(issueStatus) {
+function sprint(issueStatus) {
const sprintEnd = issueStatus.sprint_enddate ? (
<>
ends
>
) : null
return (
-
+
{prefixName(issueStatus.sprint_name, "Sprint")} ({issueStatus.sprint_state}) {sprintEnd}
-
+
)
}
-sprintLabel.propTypes = {
+sprint.propTypes = {
issueStatus: issueStatusPropType,
}
@@ -118,26 +139,24 @@ prefixName.propType = {
prefix: string,
}
-function issueLabel(issueStatus, settings, error) {
+function IssueCard({ issueStatus, settings, error }) {
// The issue status can be unknown when the issue was added recently and the status hasn't been collected yet
- const color = error ? "red" : ISSUE_STATUS_COLORS[issueStatus.status_category ?? "unknown"]
- const label = (
-
- {issueStatus.issue_id}
- {labelDetails(issueStatus, settings)}
-
+ const color = error ? "error" : (issueStatus.status_category ?? "unknown")
+ const onClick = issueStatus.landing_url ? () => window.open(issueStatus.landing_url) : null
+ return (
+
+
+
+
+ {issueStatus.issue_id} - {issueStatus.name || "?"}
+
+ {cardDetails(issueStatus, settings)}
+
+
+
)
- if (issueStatus.landing_url) {
- // Without the span, the popup doesn't work
- return (
-
- {label}
-
- )
- }
- return label
}
-issueLabel.propTypes = {
+IssueCard.propTypes = {
issueStatus: issueStatusPropType,
settings: settingsPropType,
error: string,
@@ -154,15 +173,26 @@ function IssueWithTracker({ issueStatus, settings }) {
popupHeader = "Parse error"
popupContent = "Quality-time could not parse the data received from the issue tracker."
}
- let label = issueLabel(issueStatus, settings, popupHeader)
+ let card =
if (!popupContent && issueStatus.created) {
popupHeader = issueStatus.summary
popupContent = issuePopupContent(issueStatus)
}
if (popupContent) {
- label =
+ card = (
+
+ {popupHeader}
+ {popupContent}
+ >
+ }
+ >
+ {card}
+
+ )
}
- return label
+ return card
}
IssueWithTracker.propTypes = {
issueStatus: issueStatusPropType,
diff --git a/components/frontend/src/issue/IssueStatus.test.js b/components/frontend/src/issue/IssueStatus.test.js
index 4bfdca6da4..fd8515d1bf 100644
--- a/components/frontend/src/issue/IssueStatus.test.js
+++ b/components/frontend/src/issue/IssueStatus.test.js
@@ -1,9 +1,8 @@
-import { render, screen, waitFor } from "@testing-library/react"
+import { fireEvent, render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import history from "history/browser"
import { createTestableSettings } from "../__fixtures__/fixtures"
-import { ISSUE_STATUS_COLORS } from "../utils"
import { IssueStatus } from "./IssueStatus"
function renderIssueStatus({
@@ -62,47 +61,22 @@ beforeEach(() => {
})
it("displays the issue id", () => {
- const { queryByText } = renderIssueStatus()
- expect(queryByText(/123/)).not.toBe(null)
-})
-
-it("displays the status", () => {
- const { queryByText } = renderIssueStatus()
- expect(queryByText(/in progress/)).not.toBe(null)
-})
-
-it("displays the status category doing", () => {
- renderIssueStatus({ statusCategory: "doing" })
- expect(screen.getByText(/123/).className).toContain("blue")
-})
-
-it("displays the status category todo", () => {
- renderIssueStatus({ statusCategory: "todo" })
- expect(screen.getByText(/123/).className).toContain("grey")
-})
-
-it("displays the status category done", () => {
- renderIssueStatus({ statusCategory: "done" })
- expect(screen.getByText(/123/).className).toContain("green")
-})
-
-it("displays a missing status category as unknown", () => {
renderIssueStatus()
- Object.values(ISSUE_STATUS_COLORS)
- .filter((color) => color !== null)
- .forEach((color) => {
- expect(screen.getByText(/123/).className).not.toContain(color)
- })
+ expect(screen.queryByText(/123/)).not.toBe(null)
})
-it("displays the issue landing url", async () => {
+it("opens the issue landing url", async () => {
+ window.open = jest.fn()
const { queryByText } = renderIssueStatus()
- expect(queryByText(/123/).closest("a").href).toBe("https://issue/")
+ fireEvent.click(queryByText(/123/))
+ expect(window.open).toHaveBeenCalledWith("https://issue")
})
-it("does not display an url if the issue has no landing url", async () => {
- const { queryByText } = renderIssueStatus({ landingUrl: null })
- expect(queryByText(/123/).closest("a")).toBe(null)
+it("does not open an url if the issue has no landing url", async () => {
+ window.open = jest.fn()
+ const { queryByText } = renderIssueStatus({ landingUrl: "" })
+ fireEvent.click(queryByText(/123/))
+ expect(window.open).not.toHaveBeenCalled()
})
it("displays a question mark as status if the issue has no status", () => {
diff --git a/components/frontend/src/issue/IssuesRows.js b/components/frontend/src/issue/IssuesRows.js
index 6f2960689d..f740d8a6df 100644
--- a/components/frontend/src/issue/IssuesRows.js
+++ b/components/frontend/src/issue/IssuesRows.js
@@ -1,6 +1,6 @@
+import Grid from "@mui/material/Grid2"
import { bool, func, node, string } from "prop-types"
import { useState } from "react"
-import { Grid } from "semantic-ui-react"
import { add_metric_issue, set_metric_attribute } from "../api/metric"
import { get_report_issue_tracker_suggestions } from "../api/report"
@@ -116,64 +116,56 @@ export function IssuesRows({ metric, metric_uuid, reload, report, target }) {
}
return (
<>
-
-
+
+
+
+ }
+ editableComponent={
+ <>
+
+
+
+
-
- }
- editableComponent={
- <>
-
-
-
-
-
-
- >
- }
- />
-
+
+ >
+ }
+ />
{getMetricIssueIds(metric).length > 0 && !issueTrackerConfigured && (
-
-
-
-
-
+
+
+
)}
{(metric.issue_status ?? [])
.filter((issue_status) => issue_status.connection_error)
.map((issue_status) => (
-
-
-
-
-
+
+
+
))}
{(metric.issue_status ?? [])
.filter((issue_status) => issue_status.parse_error)
.map((issue_status) => (
-
-
-
-
-
+
+
+
))}
>
)
diff --git a/components/frontend/src/measurement/MeasurementSources.js b/components/frontend/src/measurement/MeasurementSources.js
index 049ff10b0c..692554ff76 100644
--- a/components/frontend/src/measurement/MeasurementSources.js
+++ b/components/frontend/src/measurement/MeasurementSources.js
@@ -2,8 +2,7 @@ import { SourceStatus } from "./SourceStatus"
export function MeasurementSources({ metric }) {
const sources = metric.latest_measurement?.sources ?? []
- return sources.map((source, index) => [
- index > 0 && ", ",
+ return sources.map((source) => [
,
])
}
diff --git a/components/frontend/src/measurement/MeasurementSources.test.js b/components/frontend/src/measurement/MeasurementSources.test.js
index 8cebc97468..1f4b135565 100644
--- a/components/frontend/src/measurement/MeasurementSources.test.js
+++ b/components/frontend/src/measurement/MeasurementSources.test.js
@@ -37,5 +37,6 @@ it("renders multiple measurement sources", () => {
/>
,
)
- expect(screen.getAllByText(/Source name 1, Source name 2/).length).toBe(1)
+ expect(screen.getAllByText(/Source name 1/).length).toBe(1)
+ expect(screen.getAllByText(/Source name 2/).length).toBe(1)
})
diff --git a/components/frontend/src/measurement/MeasurementTarget.js b/components/frontend/src/measurement/MeasurementTarget.js
index 134e21d7dc..17bd0629e2 100644
--- a/components/frontend/src/measurement/MeasurementTarget.js
+++ b/components/frontend/src/measurement/MeasurementTarget.js
@@ -1,7 +1,7 @@
+import { Tooltip } from "@mui/material"
import { useContext } from "react"
import { DataModel } from "../context/DataModel"
-import { Label, Popup } from "../semantic_ui_react_wrappers"
import { metricPropType } from "../sharedPropTypes"
import {
formatMetricDirection,
@@ -12,6 +12,7 @@ import {
getMetricTarget,
isValidDate_YYYYMMDD,
} from "../utils"
+import { Label } from "../widgets/Label"
function popupText(metric, debtEndDateInThePast, allIssuesDone, dataModel) {
const unit = formatMetricScaleAndUnit(metric, dataModel)
@@ -59,11 +60,11 @@ export function MeasurementTarget({ metric }) {
const today = new Date()
debtEndDateInThePast = endDate.toISOString().split("T")[0] < today.toISOString().split("T")[0]
}
- const label = allIssuesDone || debtEndDateInThePast ? {target} : {target}
+ const label = allIssuesDone || debtEndDateInThePast ? {target} : target
return (
-
- {popupText(metric, debtEndDateInThePast, allIssuesDone, dataModel)}
-
+ {popupText(metric, debtEndDateInThePast, allIssuesDone, dataModel)}}>
+ {label}
+
)
}
MeasurementTarget.propTypes = {
diff --git a/components/frontend/src/measurement/MeasurementValue.js b/components/frontend/src/measurement/MeasurementValue.js
index f9d7e58c63..73348294db 100644
--- a/components/frontend/src/measurement/MeasurementValue.js
+++ b/components/frontend/src/measurement/MeasurementValue.js
@@ -1,10 +1,10 @@
import "./MeasurementValue.css"
+import { Alert, Tooltip, Typography } from "@mui/material"
import { bool, string } from "prop-types"
import { useContext } from "react"
import { DataModel } from "../context/DataModel"
-import { Label, Message, Popup } from "../semantic_ui_react_wrappers"
import { datePropType, measurementPropType, metricPropType } from "../sharedPropTypes"
import { IGNORABLE_SOURCE_ENTITY_STATUSES, SOURCE_ENTITY_STATUS_NAME } from "../source/source_entity_status"
import {
@@ -18,6 +18,7 @@ import {
sum,
} from "../utils"
import { IgnoreIcon, LoadingIcon } from "../widgets/icons"
+import { Label } from "../widgets/Label"
import { TimeAgoWithDate } from "../widgets/TimeAgoWithDate"
import { WarningMessage } from "../widgets/WarningMessage"
@@ -30,14 +31,20 @@ function measurementValueLabel(hasIgnoredEntities, stale, updating, value) {
value
)
if (stale) {
- return {measurementValue}
+ return (
+
+ {measurementValue}
+
+ )
}
if (updating) {
return (
-
-
- {measurementValue}
-
+
+
+
+ {measurementValue}
+
+
)
}
return {measurementValue}
@@ -101,49 +108,43 @@ export function MeasurementValue({ metric, reportDate }) {
const requested = isMeasurementRequested(metric)
const hasIgnoredEntities = sum(ignoredEntitiesCount(metric.latest_measurement)) > 0
return (
-
+
+ This may indicate a problem with Quality-time itself. Please contact a system administrator.
+
+
+ The source configuration of this metric was changed after the latest measurement.
+
+
+ An update of the latest measurement was requested by a user.
+
+ {hasIgnoredEntities && (
+
+
+ {`Ignored ${unit}`}
+
+ {ignoredEntitiesMessage(metric.latest_measurement, unit)}
+
+ )}
+ {metric.latest_measurement && (
+ <>
+
+ {metric.status ? "The metric was last measured" : "Last measurement attempt"}
+
+
+
+ {metric.status ? "The current value was first measured" : "The value is unknown since"}
+
+ >
+ )}
+
+ }
>
-
-
-
- {hasIgnoredEntities && (
-
- {`Ignored ${unit}`}
-
- }
- content={ignoredEntitiesMessage(metric.latest_measurement, unit)}
- />
- )}
- {metric.latest_measurement && (
- <>
-
- {metric.status ? "The metric was last measured" : "Last measurement attempt"}
-
-
-
- {metric.status ? "The current value was first measured" : "The value is unknown since"}
-
- >
- )}
-
+ {measurementValueLabel(hasIgnoredEntities, stale, outdated || requested, value)}
+
)
}
MeasurementValue.propTypes = {
diff --git a/components/frontend/src/measurement/MeasurementValue.test.js b/components/frontend/src/measurement/MeasurementValue.test.js
index 1af6c4721e..62f5e67c9d 100644
--- a/components/frontend/src/measurement/MeasurementValue.test.js
+++ b/components/frontend/src/measurement/MeasurementValue.test.js
@@ -55,7 +55,6 @@ it("renders an outdated value", async () => {
},
})
const measurementValue = screen.getByText(/1/)
- expect(measurementValue.className).toContain("yellow")
expect(screen.getAllByTestId("LoopIcon").length).toBe(1)
await userEvent.hover(measurementValue)
await waitFor(() => {
@@ -73,7 +72,6 @@ it("renders a value for which a measurement was requested", async () => {
measurement_requested: now,
})
const measurementValue = screen.getByText(/1/)
- expect(measurementValue.className).toContain("yellow")
expect(screen.getAllByTestId("LoopIcon").length).toBe(1)
await userEvent.hover(measurementValue)
await waitFor(() => {
@@ -89,7 +87,6 @@ it("renders a value for which a measurement was requested, but which is now up t
measurement_requested: "2024-01-01T00:00:00",
})
const measurementValue = screen.getByText(/1/)
- expect(measurementValue.className).not.toContain("yellow")
expect(screen.queryAllByTestId("LoopIcon").length).toBe(0)
await userEvent.hover(measurementValue)
await waitFor(() => {
diff --git a/components/frontend/src/measurement/Overrun.js b/components/frontend/src/measurement/Overrun.js
index 4f29c6016d..a7d46fe9d7 100644
--- a/components/frontend/src/measurement/Overrun.js
+++ b/components/frontend/src/measurement/Overrun.js
@@ -1,8 +1,18 @@
+import {
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Tooltip,
+ Typography,
+} from "@mui/material"
import { string } from "prop-types"
import { useContext } from "react"
import { DataModel } from "../context/DataModel"
-import { Header, Popup, Table } from "../semantic_ui_react_wrappers"
import { datesPropType, measurementsPropType, metricPropType, reportPropType } from "../sharedPropTypes"
import { getMetricResponseOverrun, pluralize } from "../utils"
import { StatusIcon } from "./StatusIcon"
@@ -23,59 +33,60 @@ export function Overrun({ metric_uuid, metric, report, measurements, dates }) {
const period = `${sortedDates.at(0).toLocaleDateString()} - ${sortedDates.at(-1).toLocaleDateString()}`
const content = (
<>
-
-
- Metric reaction time overruns
- In the period {period}
-
-
-
-
-
-
- When did the metric need action?
-
-
- How long did it take to react?
-
-
-
- Status
- Start
- End
- Actual
- Desired
- Overrun
-
-
-
- {overruns.map((overrun) => (
-
-
-
-
- {overrun.start.split("T")[0]}
- {overrun.end.split("T")[0]}
- {formatDays(overrun.actual_response_time)}
- {formatDays(overrun.desired_response_time)}
- {formatDays(overrun.overrun)}
-
- ))}
-
-
-
-
- Total
-
-
- {triggerText}
-
-
-
-
+ Metric reaction time overruns in the period {period}
+
+
+
+
+
+ When did the metric need action?
+
+
+ How long did it take to react?
+
+
+
+ Status
+ Start
+ End
+ Actual
+ Desired
+ Overrun
+
+
+
+ {overruns.map((overrun) => (
+
+
+
+
+ {overrun.start.split("T")[0]}
+ {overrun.end.split("T")[0]}
+ {formatDays(overrun.actual_response_time)}
+ {formatDays(overrun.desired_response_time)}
+ {formatDays(overrun.overrun)}
+
+ ))}
+
+
+
+
+ Total
+
+
+ {triggerText}
+
+
+
+
+
>
)
- return
+ return (
+
+ {trigger}
+
+ )
}
Overrun.propTypes = {
dates: datesPropType,
diff --git a/components/frontend/src/measurement/SourceStatus.js b/components/frontend/src/measurement/SourceStatus.js
index 48ac30f5a4..99cfb83187 100644
--- a/components/frontend/src/measurement/SourceStatus.js
+++ b/components/frontend/src/measurement/SourceStatus.js
@@ -1,10 +1,11 @@
+import { Tooltip } from "@mui/material"
import { useContext } from "react"
import { DataModel } from "../context/DataModel"
-import { Label, Popup } from "../semantic_ui_react_wrappers"
import { measurementSourcePropType, metricPropType } from "../sharedPropTypes"
import { getMetricName, getSourceName } from "../utils"
import { HyperLink } from "../widgets/HyperLink"
+import { Label } from "../widgets/Label"
export function SourceStatus({ metric, measurement_source }) {
const dataModel = useContext(DataModel)
@@ -36,13 +37,18 @@ export function SourceStatus({ metric, measurement_source }) {
header = "Parse error"
}
return (
- {source_label()}}
- />
+
+ {header}
+ {content}
+ >
+ }
+ >
+
+ {source_label()}
+
+
)
} else {
return source_label()
diff --git a/components/frontend/src/measurement/StatusIcon.js b/components/frontend/src/measurement/StatusIcon.js
index fd73ed86b3..5a44aa0003 100644
--- a/components/frontend/src/measurement/StatusIcon.js
+++ b/components/frontend/src/measurement/StatusIcon.js
@@ -1,7 +1,7 @@
import { Avatar, Tooltip } from "@mui/material"
import { instanceOf, oneOfType, string } from "prop-types"
-import { STATUS_COLORS_MUI, STATUS_ICONS, STATUS_SHORT_NAME, statusPropType } from "../metric/status"
+import { STATUS_ICONS, STATUS_SHORT_NAME, statusPropType } from "../metric/status"
import { TimeAgoWithDate } from "../widgets/TimeAgoWithDate"
export function StatusIcon({ status, statusStart, size }) {
@@ -9,7 +9,7 @@ export function StatusIcon({ status, statusStart, size }) {
const sizes = { small: 20, undefined: 32 }
const statusName = STATUS_SHORT_NAME[status]
// Use Avatar to create a round inverted icon:
- const iconStyle = { width: sizes[size], height: sizes[size], bgcolor: STATUS_COLORS_MUI[status] }
+ const iconStyle = { width: sizes[size], height: sizes[size], bgcolor: `${status}.main` }
const icon = (
{STATUS_ICONS[status]}
diff --git a/components/frontend/src/measurement/TimeLeft.js b/components/frontend/src/measurement/TimeLeft.js
index d89e8ae5d4..31e5c448f3 100644
--- a/components/frontend/src/measurement/TimeLeft.js
+++ b/components/frontend/src/measurement/TimeLeft.js
@@ -1,6 +1,8 @@
-import { Label, Popup } from "../semantic_ui_react_wrappers"
+import { Tooltip } from "@mui/material"
+
import { metricPropType, reportPropType } from "../sharedPropTypes"
import { days, getMetricResponseDeadline, getMetricResponseTimeLeft, pluralize } from "../utils"
+import { Label } from "../widgets/Label"
import { TimeAgoWithDate } from "../widgets/TimeAgoWithDate"
export function TimeLeft({ metric, report }) {
@@ -12,15 +14,15 @@ export function TimeLeft({ metric, report }) {
const daysLeft = days(Math.max(0, timeLeft))
const triggerText = `${daysLeft} ${pluralize("day", daysLeft)}`
let deadlineLabel = "Deadline to address this metric was"
- let trigger = {triggerText}
+ let trigger = {triggerText}
if (timeLeft >= 0) {
deadlineLabel = "Time left to address this metric is"
- trigger = {triggerText}
+ trigger = triggerText
}
return (
-
- {deadlineLabel} .
-
+ {deadlineLabel}}>
+ {trigger}
+
)
}
TimeLeft.propTypes = {
diff --git a/components/frontend/src/metric/MetricConfigurationParameters.js b/components/frontend/src/metric/MetricConfigurationParameters.js
index abbdb6ac36..1ee06db407 100644
--- a/components/frontend/src/metric/MetricConfigurationParameters.js
+++ b/components/frontend/src/metric/MetricConfigurationParameters.js
@@ -1,6 +1,6 @@
+import Grid from "@mui/material/Grid2"
import { func, string } from "prop-types"
import { useContext } from "react"
-import { Grid, Header } from "semantic-ui-react"
import { set_metric_attribute } from "../api/metric"
import { DataModel } from "../context/DataModel"
@@ -17,6 +17,7 @@ import {
getMetricTags,
getReportTags,
} from "../utils"
+import { Header } from "../widgets/Header"
import { LabelWithHelp } from "../widgets/LabelWithHelp"
import { MetricType } from "./MetricType"
import { Target } from "./Target"
@@ -27,7 +28,7 @@ function metric_scale_options(metric_scales, dataModel) {
let scale_name = dataModel.scales ? dataModel.scales[scale].name : "Count"
let scale_description = dataModel.scales ? dataModel.scales[scale].description : ""
scale_options.push({
- content: ,
+ content: ,
key: scale,
text: scale_name,
value: scale,
@@ -191,61 +192,51 @@ export function MetricConfigurationParameters({ metric, metric_uuid, reload, rep
const dataModel = useContext(DataModel)
const metricScale = getMetricScale(metric, dataModel)
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {metricScale !== "version_number" && (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {metricScale !== "version_number" && }
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/components/frontend/src/metric/MetricDebtParameters.js b/components/frontend/src/metric/MetricDebtParameters.js
index a473eaabc7..84fa4cf92d 100644
--- a/components/frontend/src/metric/MetricDebtParameters.js
+++ b/components/frontend/src/metric/MetricDebtParameters.js
@@ -1,5 +1,5 @@
+import Grid from "@mui/material/Grid2"
import { func, string } from "prop-types"
-import { Grid } from "semantic-ui-react"
import { set_metric_attribute, set_metric_debt } from "../api/metric"
import { EDIT_REPORT_PERMISSION } from "../context/Permissions"
@@ -94,35 +94,30 @@ TechnicalDebtEndDate.propTypes = {
export function MetricDebtParameters({ metric, metric_uuid, reload, report }) {
return (
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
- set_metric_attribute(metric_uuid, "comment", value, reload)}
- value={metric.comment}
- />
-
-
+
+ set_metric_attribute(metric_uuid, "comment", value, reload)}
+ value={metric.comment}
+ />
+
)
}
diff --git a/components/frontend/src/metric/MetricDetails.test.js b/components/frontend/src/metric/MetricDetails.test.js
index 2e1de91a2c..3be9cb6d61 100644
--- a/components/frontend/src/metric/MetricDetails.test.js
+++ b/components/frontend/src/metric/MetricDetails.test.js
@@ -164,12 +164,12 @@ it("removes the existing hashtag from the URL to share", async () => {
it("displays whether sources have errors", async () => {
await renderMetricDetails(null, "Connection error")
- expect(screen.getByText(/Sources/)).toHaveClass("red label")
+ expect(screen.getByText(/Sources/)).toHaveClass("error")
})
it("displays whether sources have warnings", async () => {
await renderMetricDetails()
- expect(screen.getByText(/Sources/)).toHaveClass("yellow label")
+ expect(screen.getByText(/Sources/)).toHaveClass("warning")
})
it("moves the metric", async () => {
diff --git a/components/frontend/src/metric/MetricTypeHeader.js b/components/frontend/src/metric/MetricTypeHeader.js
index bc91a5c021..19f5e428b3 100644
--- a/components/frontend/src/metric/MetricTypeHeader.js
+++ b/components/frontend/src/metric/MetricTypeHeader.js
@@ -1,6 +1,6 @@
-import { Header } from "../semantic_ui_react_wrappers"
import { metricTypePropType } from "../sharedPropTypes"
import { slugify } from "../utils"
+import { Header } from "../widgets/Header"
import { ReadTheDocsLink } from "../widgets/ReadTheDocsLink"
export function MetricTypeHeader({ metricType }) {
@@ -9,15 +9,15 @@ export function MetricTypeHeader({ metricType }) {
? " for specific information on how to configure this metric type."
: ""
return (
-
-
- {metricType.name}
-
- {metricType.description}
- {howToConfigure}
-
-
-
+
+ {metricType.description} {howToConfigure}
+ >
+ }
+ />
)
}
MetricTypeHeader.propTypes = {
diff --git a/components/frontend/src/metric/Target.js b/components/frontend/src/metric/Target.js
index fdaf4b49a7..d17b301e7f 100644
--- a/components/frontend/src/metric/Target.js
+++ b/components/frontend/src/metric/Target.js
@@ -1,5 +1,5 @@
import HelpIcon from "@mui/icons-material/Help"
-import { Box, Stack, Typography } from "@mui/material"
+import { Box, Stack, Tooltip, Typography } from "@mui/material"
import { bool, func, oneOf, string } from "prop-types"
import { useContext } from "react"
@@ -9,7 +9,6 @@ import { EDIT_REPORT_PERMISSION } from "../context/Permissions"
import { IntegerInput } from "../fields/IntegerInput"
import { StringInput } from "../fields/StringInput"
import { StatusIcon } from "../measurement/StatusIcon"
-import { Popup } from "../semantic_ui_react_wrappers"
import { childrenPropType, labelPropType, metricPropType, scalePropType } from "../sharedPropTypes"
import {
capitalize,
@@ -18,7 +17,7 @@ import {
formatMetricValue,
getMetricScale,
} from "../utils"
-import { STATUS_COLORS_MUI, STATUS_SHORT_NAME, statusPropType } from "./status"
+import { STATUS_SHORT_NAME, statusPropType } from "./status"
function smallerThan(target1, target2) {
const t1 = target1 ?? `${Number.POSITIVE_INFINITY}`
@@ -52,7 +51,7 @@ function ColoredSegment({ children, color, show, status }) {
return null
}
return (
-
+
{STATUS_SHORT_NAME[status]}
@@ -232,7 +231,7 @@ TargetVisualiser.propTypes = {
metric: metricPropType,
}
-function TargetLabel({ label, metric, position, targetType }) {
+function TargetLabel({ label, metric, targetType }) {
const dataModel = useContext(DataModel)
const metricType = dataModel.metrics[metric.type]
const defaultTarget = metricType[targetType]
@@ -245,32 +244,33 @@ function TargetLabel({ label, metric, position, targetType }) {
return (
{label + defaultTargetLabel}{" "}
- }
- flowing
- header="How measurement values are evaluated"
- hoverable
- on={["hover", "focus"]}
- position={position}
- trigger={ }
- />
+
+ How measurement values are evaluated
+
+ >
+ }
+ >
+
+
)
}
TargetLabel.propTypes = {
label: labelPropType,
metric: metricPropType,
- position: string,
targetType: string,
}
-export function Target({ label, labelPosition, metric, metric_uuid, reload, target_type }) {
+export function Target({ label, metric, metric_uuid, reload, target_type }) {
const dataModel = useContext(DataModel)
const metricScale = getMetricScale(metric, dataModel)
const metricDirectionPrefix = formatMetricDirection(metric, dataModel)
const targetValue = metric[target_type]
const unit = formatMetricScaleAndUnit(metric, dataModel)
- const targetLabel =
+ const targetLabel =
if (metricScale === "version_number") {
return (
{
it("shows help", async () => {
renderMetricTarget({ type: "violations", target: "10", near_target: "15" })
- await userEvent.tab()
+ await userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expect(screen.queryAllByText(/How measurement values are evaluated/).length).toBe(1)
})
@@ -100,7 +100,7 @@ function expectNotVisible(...matchers) {
it("shows help for evaluated metric without tech debt", async () => {
renderMetricTarget({ type: "violations", target: "10", near_target: "15" })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target met/,
@@ -122,7 +122,7 @@ it("shows help for evaluated metric with tech debt", async () => {
near_target: "20",
accept_debt: true,
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target met/,
@@ -139,7 +139,7 @@ it("shows help for evaluated metric with tech debt", async () => {
it("shows help for evaluated metric with tech debt if debt target is missing", async () => {
renderMetricTarget({ type: "violations", target: "10", near_target: "20", accept_debt: true })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target met/,
@@ -162,7 +162,7 @@ it("shows help for evaluated metric with tech debt with end date", async () => {
accept_debt: true,
debt_end_date: "3000-01-01",
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target met/,
@@ -186,7 +186,7 @@ it("shows help for evaluated metric with tech debt with end date in the past", a
accept_debt: true,
debt_end_date: "2000-01-01",
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target met/,
@@ -208,7 +208,7 @@ it("shows help for evaluated metric with tech debt completely overlapping near t
near_target: "20",
accept_debt: true,
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target met/,
@@ -224,7 +224,7 @@ it("shows help for evaluated metric with tech debt completely overlapping near t
it("shows help for evaluated metric without tech debt and target completely overlapping near target", async () => {
renderMetricTarget({ type: "violations", target: "10", near_target: "10" })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(/Target met/, /≦ 10 violations/, /Target not met/, /> 10 violations/)
expectNotVisible(/Debt target met/, /Near target met/)
@@ -233,7 +233,7 @@ it("shows help for evaluated metric without tech debt and target completely over
it("shows help for evaluated more-is-better metric without tech debt", async () => {
renderMetricTarget({ type: "violations", target: "15", near_target: "10", direction: ">" })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target not met/,
@@ -256,7 +256,7 @@ it("shows help for evaluated more-is-better metric with tech debt", async () =>
accept_debt: true,
direction: ">",
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target not met/,
@@ -279,7 +279,7 @@ it("shows help for evaluated more-is-better metric with tech debt and missing de
accept_debt: true,
direction: ">",
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target not met/,
@@ -302,7 +302,7 @@ it("shows help for evaluated more-is-better metric with tech debt completely ove
accept_debt: true,
direction: ">",
})
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(
/Target not met/,
@@ -318,7 +318,7 @@ it("shows help for evaluated more-is-better metric with tech debt completely ove
it("shows help for evaluated more-is-better metric without tech debt and target completely overlapping near target", async () => {
renderMetricTarget({ type: "violations", target: "15", near_target: "15", direction: ">" })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(/Target not met/, /< 15 violations/, /Target met/, /≧ 15 violations/)
expectNotVisible(/Near target met/, /Debt target met/)
@@ -327,7 +327,7 @@ it("shows help for evaluated more-is-better metric without tech debt and target
it("shows help for evaluated metric without tech debt and zero target completely overlapping near target", async () => {
renderMetricTarget({ type: "violations", target: "0", near_target: "0", direction: ">" })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(/Target met/, /≧ 0 violations/)
expectNotVisible(/Debt target met/, /Near target met/, /Target not met/)
@@ -336,7 +336,7 @@ it("shows help for evaluated metric without tech debt and zero target completely
it("shows help for informative metric", async () => {
renderMetricTarget({ type: "violations", evaluate_targets: false })
- await userEvent.tab()
+ userEvent.hover(screen.getByTestId("HelpIcon"))
await waitFor(() => {
expectVisible(/Informative/, /violations are not evaluated/)
expectNotVisible(/Target met/, /Debt target met/, /Near target met/, /Target not met/)
diff --git a/components/frontend/src/metric/TrendGraph.js b/components/frontend/src/metric/TrendGraph.js
index ca509a5201..6b804d0966 100644
--- a/components/frontend/src/metric/TrendGraph.js
+++ b/components/frontend/src/metric/TrendGraph.js
@@ -1,5 +1,4 @@
import { useContext } from "react"
-import { Message } from "semantic-ui-react"
import { VictoryAxis, VictoryChart, VictoryLabel, VictoryLine, VictoryTheme } from "victory"
import { DarkMode } from "../context/DarkMode"
@@ -7,7 +6,7 @@ import { DataModel } from "../context/DataModel"
import { loadingPropType, measurementsPropType, metricPropType } from "../sharedPropTypes"
import { capitalize, formatMetricScaleAndUnit, getMetricName, getMetricScale, niceNumber, scaledNumber } from "../utils"
import { LoadingPlaceHolder } from "../widgets/Placeholder"
-import { FailedToLoadMeasurementsWarningMessage, WarningMessage } from "../widgets/WarningMessage"
+import { FailedToLoadMeasurementsWarningMessage, InfoMessage, WarningMessage } from "../widgets/WarningMessage"
function measurementAttributeAsNumber(metric, measurement, field, dataModel) {
const scale = getMetricScale(metric, dataModel)
@@ -22,10 +21,9 @@ export function TrendGraph({ metric, measurements, loading }) {
const estimatedTotalChartHeight = chartHeight + 200 // Estimate of the height including title and axis
if (getMetricScale(metric, dataModel) === "version_number") {
return (
-
+
+ Trend graphs are not supported for metrics with a version number scale.
+
)
}
if (loading === "failed") {
@@ -36,10 +34,9 @@ export function TrendGraph({ metric, measurements, loading }) {
}
if (measurements.length === 0) {
return (
-
+
+ A trend graph can not be displayed until this metric has measurements.
+
)
}
const metricName = getMetricName(metric, dataModel)
diff --git a/components/frontend/src/metric/status.js b/components/frontend/src/metric/status.js
index 3ef465e9f7..f6b5990e8d 100644
--- a/components/frontend/src/metric/status.js
+++ b/components/frontend/src/metric/status.js
@@ -1,7 +1,6 @@
// Metric status constants
import { Bolt, Check, Money, QuestionMark, Warning } from "@mui/icons-material"
-import { blue, green, grey, orange, red } from "@mui/material/colors"
import { oneOf } from "prop-types"
import { HyperLink } from "../widgets/HyperLink"
@@ -25,14 +24,6 @@ export const STATUS_COLORS_RGB = {
informative: "rgb(0,165,255)",
unknown: "rgb(245,245,245)",
}
-export const STATUS_COLORS_MUI = {
- target_not_met: red[700],
- target_met: green[600],
- near_target_met: orange[300],
- debt_target_met: grey[500],
- informative: blue[500],
- unknown: grey[300],
-}
export const STATUS_ICONS = {
target_met: ,
near_target_met: ,
diff --git a/components/frontend/src/notification/NotificationDestinations.js b/components/frontend/src/notification/NotificationDestinations.js
index 28ce09fc8e..cc257b7f66 100644
--- a/components/frontend/src/notification/NotificationDestinations.js
+++ b/components/frontend/src/notification/NotificationDestinations.js
@@ -1,6 +1,6 @@
import { Stack } from "@mui/material"
+import Grid from "@mui/material/Grid2"
import { func, objectOf, string } from "prop-types"
-import { Grid } from "semantic-ui-react"
import {
add_notification_destination,
@@ -9,13 +9,13 @@ import {
} from "../api/notification"
import { EDIT_REPORT_PERMISSION, ReadOnlyOrEditable } from "../context/Permissions"
import { StringInput } from "../fields/StringInput"
-import { Message } from "../semantic_ui_react_wrappers"
import { destinationPropType } from "../sharedPropTypes"
import { ButtonRow } from "../widgets/ButtonRow"
import { AddButton } from "../widgets/buttons/AddButton"
import { DeleteButton } from "../widgets/buttons/DeleteButton"
import { HyperLink } from "../widgets/HyperLink"
import { LabelWithHelp } from "../widgets/LabelWithHelp"
+import { InfoMessage } from "../widgets/WarningMessage"
function NotificationDestination({ destination, destination_uuid, reload, report_uuid }) {
const help_url =
@@ -23,47 +23,41 @@ function NotificationDestination({ destination, destination_uuid, reload, report
const teams_hyperlink = Microsoft Teams
return (
-
-
-
- {
- set_notification_destination_attributes(
- report_uuid,
- destination_uuid,
- { name: value },
- reload,
- )
- }}
- value={destination.name}
- />
-
-
- Paste a {teams_hyperlink} webhook URL here.>}
- hoverable
- />
- }
- placeholder="https://example.webhook.office.com/webhook..."
- set_value={(value) => {
- set_notification_destination_attributes(
- report_uuid,
- destination_uuid,
- { webhook: value, url: window.location.href },
- reload,
- )
- }}
- value={destination.webhook}
- />
-
-
+
+
+ {
+ set_notification_destination_attributes(
+ report_uuid,
+ destination_uuid,
+ { name: value },
+ reload,
+ )
+ }}
+ value={destination.name}
+ />
+
+
+ Paste a {teams_hyperlink} webhook URL here.>} />
+ }
+ placeholder="https://example.webhook.office.com/webhook..."
+ set_value={(value) => {
+ set_notification_destination_attributes(
+ report_uuid,
+ destination_uuid,
+ { webhook: value, url: window.location.href },
+ reload,
+ )
+ }}
+ value={destination.webhook}
+ />
+
+
{notification_destinations.length === 0 ? (
-
- No notification destinations
- No notification destinations have been configured yet.
-
+
+ No notification destinations have been configured yet.
+
) : (
notification_destinations
)}
@@ -121,7 +114,7 @@ export function NotificationDestinations({ destinations, reload, report_uuid })
/>
}
/>
- >
+
)
}
NotificationDestinations.propTypes = {
diff --git a/components/frontend/src/report/IssueTracker.js b/components/frontend/src/report/IssueTracker.js
index 6c8a8c9169..bf0d0fae60 100644
--- a/components/frontend/src/report/IssueTracker.js
+++ b/components/frontend/src/report/IssueTracker.js
@@ -1,6 +1,7 @@
+import { Stack } from "@mui/material"
+import Grid from "@mui/material/Grid2"
import { func } from "prop-types"
import { useContext, useEffect, useState } from "react"
-import { Grid, Header } from "semantic-ui-react"
import { get_report_issue_tracker_options, set_report_issue_tracker_attribute } from "../api/report"
import { DataModel } from "../context/DataModel"
@@ -11,6 +12,7 @@ import { SingleChoiceInput } from "../fields/SingleChoiceInput"
import { StringInput } from "../fields/StringInput"
import { reportPropType } from "../sharedPropTypes"
import { Logo } from "../source/Logo"
+import { Header } from "../widgets/Header"
import { LabelWithHelp } from "../widgets/LabelWithHelp"
import { LabelWithHyperLink } from "../widgets/LabelWithHyperLink"
import { showMessage } from "../widgets/toast"
@@ -20,11 +22,7 @@ const NONE_OPTION = {
key: null,
text: "None",
value: null,
- content: (
-
- ),
+ content: ,
}
export function IssueTracker({ report, reload }) {
@@ -80,13 +78,16 @@ export function IssueTracker({ report, reload }) {
text: source_type.name,
value: source_name,
content: (
-
-
-
- {source_type.name}
- {source_type.description}
-
-
+
+
+ {source_type.name}
+ >
+ }
+ level="h4"
+ subheader={source_type.description}
+ />
),
}
})
@@ -104,111 +105,96 @@ export function IssueTracker({ report, reload }) {
const epic_link = report.issue_tracker?.parameters?.epic_link
return (
-
-
-
- set_report_issue_tracker_attribute(report_uuid, "type", value, reload)}
- value={report.issue_tracker?.type}
- />
-
-
- set_report_issue_tracker_attribute(report_uuid, "url", value, reload)}
- value={report.issue_tracker?.parameters?.url}
- />
-
-
-
-
-
- set_report_issue_tracker_attribute(report_uuid, "username", value, reload)
- }
- value={report.issue_tracker?.parameters?.username}
- />
-
-
-
- set_report_issue_tracker_attribute(report_uuid, "password", value, reload)
- }
- value={report.issue_tracker?.parameters?.password}
- />
-
-
-
-
-
- set_report_issue_tracker_attribute(report_uuid, "private_token", value, reload)
- }
- value={report.issue_tracker?.parameters?.private_token}
- />
-
-
-
-
-
- }
- options={projectOptions}
- placeholder="None"
- set_value={(value) =>
- set_report_issue_tracker_attribute(report_uuid, "project_key", value, reload)
- }
- value={project_key}
- />
-
-
-
- }
- options={issueTypeOptions}
- placeholder="None"
- set_value={(value) =>
- set_report_issue_tracker_attribute(report_uuid, "issue_type", value, reload)
- }
- value={issue_type}
- />
-
-
-
-
+
+
+ set_report_issue_tracker_attribute(report_uuid, "type", value, reload)}
+ value={report.issue_tracker?.type}
+ />
+
+
+ set_report_issue_tracker_attribute(report_uuid, "url", value, reload)}
+ value={report.issue_tracker?.parameters?.url}
+ />
+
+
+ set_report_issue_tracker_attribute(report_uuid, "username", value, reload)}
+ value={report.issue_tracker?.parameters?.username}
+ />
+
+
+ set_report_issue_tracker_attribute(report_uuid, "password", value, reload)}
+ value={report.issue_tracker?.parameters?.password}
+ />
+
+
+
+ set_report_issue_tracker_attribute(report_uuid, "private_token", value, reload)
+ }
+ value={report.issue_tracker?.parameters?.private_token}
+ />
+
+
+
+
+ }
+ options={projectOptions}
+ placeholder="None"
+ set_value={(value) => set_report_issue_tracker_attribute(report_uuid, "project_key", value, reload)}
+ value={project_key}
+ />
+
+
+
+ }
+ options={issueTypeOptions}
+ placeholder="None"
+ set_value={(value) => set_report_issue_tracker_attribute(report_uuid, "issue_type", value, reload)}
+ value={issue_type}
+ />
+
+
+
-
-
+ title="Epic links not supported"
+ >
+ {`The issue type '${issue_type}' in project '${project_key}' does not support adding epic links when creating issues, so no epic link will be added to new issues.`}
+
+
+
+
+
-
-
+ title="Labels not supported"
+ >
+ {`The issue type '${issue_type}' in project '${project_key}' does not support adding labels when creating issues, so no labels will be added to new issues.`}
+
+
+
)
}
diff --git a/components/frontend/src/report/Report.js b/components/frontend/src/report/Report.js
index 05db2beb3d..fffc0ff9f6 100644
--- a/components/frontend/src/report/Report.js
+++ b/components/frontend/src/report/Report.js
@@ -1,6 +1,7 @@
+import { Divider, Paper } from "@mui/material"
import { func } from "prop-types"
-import { ExportCard } from "../dashboard/ExportCard"
+import { PageHeader } from "../dashboard/PageHeader"
import {
datePropType,
datesPropType,
@@ -15,8 +16,8 @@ import { Subjects } from "../subject/Subjects"
import { SubjectsButtonRow } from "../subject/SubjectsButtonRow"
import { getReportTags } from "../utils"
import { CommentSegment } from "../widgets/CommentSegment"
+import { WarningMessage } from "../widgets/WarningMessage"
import { ReportDashboard } from "./ReportDashboard"
-import { ReportErrorMessage } from "./ReportErrorMessage"
import { ReportTitle } from "./ReportTitle"
export function Report({
@@ -42,13 +43,18 @@ export function Report({
}
if (!report) {
- return
+ return (
+
+ {report_date ? `Sorry, this report didn't exist at ${report_date}` : "Sorry, this report doesn't exist"}
+
+ )
}
// Sort measurements in reverse order so that if there multiple measurements on a day, we find the most recent one:
const reversedMeasurements = measurements.slice().sort((m1, m2) => (m1.start < m2.start ? 1 : -1))
return (
-
-
-
navigate_to_subject(e, s)}
- onClickTag={(tag) => {
- // If there are hidden tags (hiddenTags.length > 0), show the hidden tags.
- // Otherwise, hide all tags in this report except the one clicked on.
- const tagsToToggle =
- settings.hiddenTags.value.length > 0 ? settings.hiddenTags.value : getReportTags(report)
- settings.hiddenTags.toggle(...tagsToToggle.filter((visibleTag) => visibleTag !== tag))
- }}
- report={report}
- reload={reload}
- settings={settings}
- />
+
+
+ navigate_to_subject(e, s)}
+ onClickTag={(tag) => {
+ // If there are hidden tags (hiddenTags.length > 0), show the hidden tags.
+ // Otherwise, hide all tags in this report except the one clicked on.
+ const tagsToToggle =
+ settings.hiddenTags.value.length > 0 ? settings.hiddenTags.value : getReportTags(report)
+ settings.hiddenTags.toggle(...tagsToToggle.filter((visibleTag) => visibleTag !== tag))
+ }}
+ report={report}
+ reload={reload}
+ settings={settings}
+ />
+
- {children}
-
- )
-}
-ErrorMessage.propTypes = {
- children: string,
-}
-
-export function ReportErrorMessage({ reportDate }) {
- return (
-
- {reportDate ? `Sorry, this report didn't exist at ${reportDate}` : "Sorry, this report doesn't exist"}
-
- )
-}
-ReportErrorMessage.propTypes = {
- reportDate: optionalDatePropType,
-}
-
-export function ReportsOverviewErrorMessage({ reportDate }) {
- return {`Sorry, no reports existed at ${reportDate}`}
-}
-ReportsOverviewErrorMessage.propTypes = {
- reportDate: datePropType,
-}
diff --git a/components/frontend/src/report/ReportTitle.js b/components/frontend/src/report/ReportTitle.js
index 8d4dfdbfa8..7bd77835a9 100644
--- a/components/frontend/src/report/ReportTitle.js
+++ b/components/frontend/src/report/ReportTitle.js
@@ -1,5 +1,6 @@
+import { Typography } from "@mui/material"
+import Grid from "@mui/material/Grid2"
import { bool, func, oneOfType, string } from "prop-types"
-import { Grid } from "semantic-ui-react"
import { delete_report, set_report_attribute } from "../api/report"
import { activeTabIndex, tabChangeHandler } from "../app_ui_settings"
@@ -10,7 +11,7 @@ import { IntegerInput } from "../fields/IntegerInput"
import { StringInput } from "../fields/StringInput"
import { STATUS_DESCRIPTION, STATUS_NAME, statusPropType } from "../metric/status"
import { NotificationDestinations } from "../notification/NotificationDestinations"
-import { Label, Segment, Tab } from "../semantic_ui_react_wrappers"
+import { Tab } from "../semantic_ui_react_wrappers"
import { entityStatusPropType, reportPropType, settingsPropType } from "../sharedPropTypes"
import { SOURCE_ENTITY_STATUS_DESCRIPTION, SOURCE_ENTITY_STATUS_NAME } from "../source/source_entity_status"
import { getDesiredResponseTime } from "../utils"
@@ -25,36 +26,32 @@ import { IssueTracker } from "./IssueTracker"
function ReportConfiguration({ reload, report }) {
return (
-
-
-
- set_report_attribute(report.report_uuid, "title", value, reload)}
- value={report.title}
- />
-
-
- set_report_attribute(report.report_uuid, "subtitle", value, reload)}
- value={report.subtitle}
- />
-
-
-
-
-
-
+
+
+ set_report_attribute(report.report_uuid, "title", value, reload)}
+ value={report.title}
+ />
+
+
+ set_report_attribute(report.report_uuid, "subtitle", value, reload)}
+ value={report.subtitle}
+ />
+
+
+
)
}
@@ -92,50 +89,40 @@ DesiredResponseTimeInput.propTypes = {
function ReactionTimes(props) {
return (
- <>
-
-
- Desired metric response times
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Desired metric response times
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Desired time after which to review measurement entities (violations, warnings, issues, etc.)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
}
ReactionTimes.propTypes = {
diff --git a/components/frontend/src/report/ReportTitle.test.js b/components/frontend/src/report/ReportTitle.test.js
index 64494f9548..84197a402c 100644
--- a/components/frontend/src/report/ReportTitle.test.js
+++ b/components/frontend/src/report/ReportTitle.test.js
@@ -84,7 +84,7 @@ it("sets the unknown status reaction time", async () => {
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/Unknown/), "4{Enter}}", {
+ await userEvent.type(screen.getByLabelText("Unknown"), "4{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 1,
})
@@ -102,7 +102,7 @@ it("sets the target not met status reaction time", async () => {
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/Target not met/), "5{Enter}}", {
+ await userEvent.type(screen.getByLabelText("Target not met"), "5{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 1,
})
@@ -120,7 +120,7 @@ it("sets the near target met status reaction time", async () => {
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/Near target met/), "6{Enter}}", {
+ await userEvent.type(screen.getByLabelText("Near target met"), "6{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 2,
})
@@ -156,7 +156,7 @@ it("sets the confirmed measurement entity status reaction time", async () => {
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/Confirmed/), "60{Enter}}", {
+ await userEvent.type(screen.getByLabelText("Confirmed"), "60{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 3,
})
@@ -174,7 +174,7 @@ it("sets the false positive measurement entity status reaction time", async () =
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/False positive/), "70{Enter}}", {
+ await userEvent.type(screen.getByLabelText("False positive"), "70{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 3,
})
@@ -192,7 +192,7 @@ it("sets the fixed measurement entity status reaction time", async () => {
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/Fixed/), "80{Enter}}", {
+ await userEvent.type(screen.getByLabelText("Fixed"), "80{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 3,
})
@@ -210,7 +210,7 @@ it("sets the won't fixed measurement entity status reaction time", async () => {
await act(async () => {
fireEvent.click(screen.getByText(/reaction times/))
})
- await userEvent.type(screen.getByLabelText(/Won't fix/), "90{Enter}}", {
+ await userEvent.type(screen.getByLabelText("Won't fix"), "90{Enter}}", {
initialSelectionStart: 0,
initialSelectionEnd: 3,
})
diff --git a/components/frontend/src/report/ReportsOverview.js b/components/frontend/src/report/ReportsOverview.js
index ff25b1ef57..82aaf5642a 100644
--- a/components/frontend/src/report/ReportsOverview.js
+++ b/components/frontend/src/report/ReportsOverview.js
@@ -3,7 +3,7 @@ import { func } from "prop-types"
import { add_report, copy_report } from "../api/report"
import { EDIT_REPORT_PERMISSION, ReadOnlyOrEditable } from "../context/Permissions"
-import { ExportCard } from "../dashboard/ExportCard"
+import { PageHeader } from "../dashboard/PageHeader"
import {
datePropType,
datesPropType,
@@ -21,7 +21,7 @@ import { AddButton } from "../widgets/buttons/AddButton"
import { CopyButton } from "../widgets/buttons/CopyButton"
import { CommentSegment } from "../widgets/CommentSegment"
import { report_options } from "../widgets/menu_options"
-import { ReportsOverviewErrorMessage } from "./ReportErrorMessage"
+import { WarningMessage } from "../widgets/WarningMessage"
import { ReportsOverviewDashboard } from "./ReportsOverviewDashboard"
import { ReportsOverviewTitle } from "./ReportsOverviewTitle"
@@ -30,7 +30,7 @@ function ReportsOverviewButtonRow({ reload, reports }) {
+
add_report(reload)} />
+ return {`Sorry, no reports existed at ${report_date}`}
}
// Sort measurements in reverse order so that if there multiple measurements on a day, we find the most recent one:
const reversedMeasurements = measurements.slice().sort((m1, m2) => (m1.start < m2.start ? 1 : -1))
return (
-
-
-
-
+
+
{
const reports = [{ report_uuid: "report_uuid", subjects: {} }]
const reportsOverview = { title: "Overview", permissions: {} }
renderReportsOverview({ reports: reports, reportsOverview: reportsOverview })
- expect(screen.getAllByText(/Overview/).length).toBe(2)
+ expect(screen.getAllByText(/Overview/).length).toBe(1)
})
it("shows the comment", async () => {
diff --git a/components/frontend/src/report/ReportsOverviewTitle.js b/components/frontend/src/report/ReportsOverviewTitle.js
index b5f383e1ee..a22f69108c 100644
--- a/components/frontend/src/report/ReportsOverviewTitle.js
+++ b/components/frontend/src/report/ReportsOverviewTitle.js
@@ -1,5 +1,5 @@
+import Grid from "@mui/material/Grid2"
import { func, shape } from "prop-types"
-import { Grid } from "semantic-ui-react"
import { set_reports_attribute } from "../api/report"
import { activeTabIndex, tabChangeHandler } from "../app_ui_settings"
@@ -17,36 +17,32 @@ import { setDocumentTitle } from "./document_title"
function ReportsOverviewConfiguration({ reports_overview, reload }) {
return (
-
-
-
- set_reports_attribute("title", value, reload)}
- value={reports_overview.title}
- />
-
-
- set_reports_attribute("subtitle", value, reload)}
- value={reports_overview.subtitle}
- />
-
-
-
-
-
-
+
+
+ set_reports_attribute("title", value, reload)}
+ value={reports_overview.title}
+ />
+
+
+ set_reports_attribute("subtitle", value, reload)}
+ value={reports_overview.subtitle}
+ />
+
+
+
)
}
@@ -62,35 +58,31 @@ function setPermissions(permissions, permission, value, reload) {
function Permissions({ permissions, reload }) {
return (
-
-
-
- setPermissions(permissions, EDIT_REPORT_PERMISSION, value, reload)}
- value={permissions[EDIT_REPORT_PERMISSION]}
- />
-
-
-
-
- setPermissions(permissions, EDIT_ENTITY_PERMISSION, value, reload)}
- value={permissions[EDIT_ENTITY_PERMISSION]}
- />
-
-
+
+
+ setPermissions(permissions, EDIT_REPORT_PERMISSION, value, reload)}
+ value={permissions[EDIT_REPORT_PERMISSION]}
+ />
+
+
+ setPermissions(permissions, EDIT_ENTITY_PERMISSION, value, reload)}
+ value={permissions[EDIT_ENTITY_PERMISSION]}
+ />
+
)
}
diff --git a/components/frontend/src/semantic_ui_react_wrappers.js b/components/frontend/src/semantic_ui_react_wrappers.js
index 53c7959114..0cbc602602 100644
--- a/components/frontend/src/semantic_ui_react_wrappers.js
+++ b/components/frontend/src/semantic_ui_react_wrappers.js
@@ -1,10 +1,3 @@
-export { Card } from "./semantic_ui_react_wrappers/Card"
-export { Dropdown } from "./semantic_ui_react_wrappers/Dropdown"
export { Form } from "./semantic_ui_react_wrappers/Form"
-export { Header } from "./semantic_ui_react_wrappers/Header"
export { Label } from "./semantic_ui_react_wrappers/Label"
-export { Message } from "./semantic_ui_react_wrappers/Message"
-export { Popup } from "./semantic_ui_react_wrappers/Popup"
-export { Segment } from "./semantic_ui_react_wrappers/Segment"
export { Tab } from "./semantic_ui_react_wrappers/Tab"
-export { Table } from "./semantic_ui_react_wrappers/Table"
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Card.css b/components/frontend/src/semantic_ui_react_wrappers/Card.css
deleted file mode 100644
index a98355e504..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Card.css
+++ /dev/null
@@ -1,11 +0,0 @@
-.ui.inverted.card {
- background: rgba(50, 50, 50, 0.8);
-}
-
-.ui.inverted.card:hover {
- background: rgba(30, 30, 30, 0.8);
-}
-
-.ui.inverted.card > .content > .header {
- color: rgba(255, 255, 255, 0.87);
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Card.js b/components/frontend/src/semantic_ui_react_wrappers/Card.js
deleted file mode 100644
index 8ad57a5d80..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Card.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import "./Card.css"
-
-import { useContext } from "react"
-import { Card as SemanticUICard } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-import { addInvertedClassNameWhenInDarkMode } from "./dark_mode"
-
-export function Card(props) {
- return
-}
-
-Card.Content = SemanticUICard.Content
-Card.Description = SemanticUICard.Description
-Card.Group = SemanticUICard.Group
-Card.Header = SemanticUICard.Header
-Card.Meta = SemanticUICard.Meta
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Dropdown.css b/components/frontend/src/semantic_ui_react_wrappers/Dropdown.css
deleted file mode 100644
index 4f51f41ff1..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Dropdown.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.ui.dropdown.inline.inverted {
- background-color: black !important;
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Dropdown.js b/components/frontend/src/semantic_ui_react_wrappers/Dropdown.js
deleted file mode 100644
index 4a7c58c4a0..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Dropdown.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useContext } from "react"
-import { Dropdown as SemanticUIDropdown } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-import { addInvertedClassNameWhenInDarkMode } from "./dark_mode"
-
-export function Dropdown(props) {
- return
-}
-
-Dropdown.Divider = SemanticUIDropdown.Divider
-Dropdown.Header = SemanticUIDropdown.Header
-Dropdown.Item = SemanticUIDropdown.Item
-Dropdown.Menu = SemanticUIDropdown.Menu
-Dropdown.SearchInput = SemanticUIDropdown.SearchInput
-Dropdown.Text = SemanticUIDropdown.Text
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Header.css b/components/frontend/src/semantic_ui_react_wrappers/Header.css
deleted file mode 100644
index 1a4485e873..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Header.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.ui.inverted.header {
- color: rgba(255, 255, 255, 0.87);
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Header.js b/components/frontend/src/semantic_ui_react_wrappers/Header.js
deleted file mode 100644
index 6767e44e7d..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Header.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import "./Header.css"
-
-import { useContext } from "react"
-import { Header as SemanticUIHeader } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-
-export function Header(props) {
- return
-}
-
-Header.Content = SemanticUIHeader.Content
-Header.Subheader = SemanticUIHeader.Subheader
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Message.js b/components/frontend/src/semantic_ui_react_wrappers/Message.js
deleted file mode 100644
index dfdad0232a..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Message.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useContext } from "react"
-import { Message as SemanticUIMessage } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-import { addInvertedClassNameWhenInDarkMode } from "./dark_mode"
-
-export function Message(props) {
- return
-}
-
-Message.Content = SemanticUIMessage.Content
-Message.Header = SemanticUIMessage.Header
-Message.Item = SemanticUIMessage.Item
-Message.List = SemanticUIMessage.List
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Popup.css b/components/frontend/src/semantic_ui_react_wrappers/Popup.css
deleted file mode 100644
index 750287c6ec..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Popup.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.ui.inverted.popup {
- background-color: rgba(60, 65, 70);
- box-shadow:
- 0 2px 4px 0 rgba(255, 255, 255, 0.1),
- 0 2px 8px 0 rgba(255, 255, 255, 0.15);
-}
-
-.ui.inverted.popup .negative.message .header {
- color: #912d2b; /* For some reason the header color is white within an inverted popup. Override. */
-}
-
-.ui.inverted.popup:before {
- background-color: rgba(60, 65, 70) !important;
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Popup.js b/components/frontend/src/semantic_ui_react_wrappers/Popup.js
deleted file mode 100644
index e5cdcacbbc..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Popup.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import "./Popup.css"
-
-import { useContext } from "react"
-import { Popup as SemanticUIPopup } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-
-export function Popup(props) {
- return
-}
-
-Popup.Content = SemanticUIPopup.Content
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Segment.css b/components/frontend/src/semantic_ui_react_wrappers/Segment.css
deleted file mode 100644
index c973062943..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Segment.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.ui.inverted.segment,
-.ui.inverted.segments .segment,
-.ui.primary.inverted.segment {
- background-color: rgba(40, 40, 40);
-}
-
-.ui.inverted.segment > .ui.header,
-.ui.inverted.segment > .ui.header .sub.header,
-.ui.inverted.segments .segment > .ui.header,
-.ui.inverted.segments .segment > .ui.header .sub.header {
- color: rgba(255, 255, 255, 0.87);
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Segment.js b/components/frontend/src/semantic_ui_react_wrappers/Segment.js
deleted file mode 100644
index c22bcfd22e..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Segment.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import "./Segment.css"
-
-import { useContext } from "react"
-import { Segment as SemanticUISegment } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-
-export function Segment(props) {
- return
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Table.css b/components/frontend/src/semantic_ui_react_wrappers/Table.css
deleted file mode 100644
index b7868f829a..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Table.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.ui.inverted.table,
-.ui.ui.inverted.table > tbody > tr > th,
-.ui.ui.inverted.table > tfoot > tr > td,
-.ui.ui.inverted.table > tfoot > tr > th,
-.ui.ui.inverted.table > thead > tr > th,
-.ui.ui.inverted.table > tr > th {
- color: rgba(255, 255, 255, 0.87);
-}
-
-.ui.sortable.table:not(.basic) thead th.sorted {
- background-color: rgba(242, 242, 242, 1) !important;
-}
-
-.ui.sortable.table:not(.basic):not(.inverted) thead th.sorted:hover {
- background-color: rgba(232, 232, 232, 1) !important;
-}
-
-.ui.sortable.table.inverted:not(.basic) thead th.sorted:hover {
- background-color: rgba(140, 140, 140, 1) !important;
-}
diff --git a/components/frontend/src/semantic_ui_react_wrappers/Table.js b/components/frontend/src/semantic_ui_react_wrappers/Table.js
deleted file mode 100644
index a8fff4973c..0000000000
--- a/components/frontend/src/semantic_ui_react_wrappers/Table.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import "./Table.css"
-
-import { useContext } from "react"
-import { Table as SemanticUITable } from "semantic-ui-react"
-
-import { DarkMode } from "../context/DarkMode"
-
-export function Table(props) {
- return
-}
-
-Table.Body = SemanticUITable.Body
-Table.Cell = SemanticUITable.Cell
-Table.Footer = SemanticUITable.Footer
-Table.Header = SemanticUITable.Header
-Table.HeaderCell = SemanticUITable.HeaderCell
-Table.Row = SemanticUITable.Row
diff --git a/components/frontend/src/source/Source.js b/components/frontend/src/source/Source.js
index 9bee36716a..998bf5b813 100644
--- a/components/frontend/src/source/Source.js
+++ b/components/frontend/src/source/Source.js
@@ -1,6 +1,6 @@
+import Grid from "@mui/material/Grid2"
import { bool, func, object, oneOfType, string } from "prop-types"
import { useContext } from "react"
-import { Grid } from "semantic-ui-react"
import { delete_source, set_source_attribute } from "../api/source"
import { ChangeLog } from "../changelog/ChangeLog"
@@ -73,39 +73,35 @@ function Parameters({
const dataModel = useContext(DataModel)
const source_type = dataModel.sources[source.type]
return (
-
-
-
- set_source_attribute(source_uuid, a, v, reload)}
- source_uuid={source_uuid}
- source_type={source.type}
- />
-
-
- set_source_attribute(source_uuid, "name", value, reload)}
- value={source.name}
- />
-
-
-
-
-
-
-
+
+
+ set_source_attribute(source_uuid, a, v, reload)}
+ source_uuid={source_uuid}
+ source_type={source.type}
+ />
+
+
+ set_source_attribute(source_uuid, "name", value, reload)}
+ value={source.name}
+ />
+
+
+
+
{connection_error && }
{parse_error && }
{config_error && }
diff --git a/components/frontend/src/source/SourceEntities.css b/components/frontend/src/source/SourceEntities.css
index 005c4c4988..235d4007a6 100644
--- a/components/frontend/src/source/SourceEntities.css
+++ b/components/frontend/src/source/SourceEntities.css
@@ -1,11 +1,3 @@
-.ui.sortable.table.entities.stickyHeader > thead {
- /* Make thead sticky by positioning the th's */
- position: sticky;
- /* Leave room for the menu bar, the subject title, and the subject table header row */
- top: 187px;
- z-index: 1;
-}
-
@media print {
button.ui {
display: none !important;
diff --git a/components/frontend/src/source/SourceEntities.js b/components/frontend/src/source/SourceEntities.js
index 15b44f2447..bb72e1c289 100644
--- a/components/frontend/src/source/SourceEntities.js
+++ b/components/frontend/src/source/SourceEntities.js
@@ -1,13 +1,22 @@
import "./SourceEntities.css"
import HelpIcon from "@mui/icons-material/Help"
-import { IconButton, Tooltip } from "@mui/material"
+import {
+ IconButton,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ TableSortLabel,
+ Tooltip,
+} from "@mui/material"
import { bool, func, object, string } from "prop-types"
import { useContext, useState } from "react"
-import { Message } from "semantic-ui-react"
import { DataModel } from "../context/DataModel"
-import { Popup, Table } from "../semantic_ui_react_wrappers"
import {
alignmentPropType,
childrenPropType,
@@ -25,7 +34,7 @@ import {
import { capitalize } from "../utils"
import { IgnoreIcon, ShowIcon } from "../widgets/icons"
import { LoadingPlaceHolder } from "../widgets/Placeholder"
-import { FailedToLoadMeasurementsWarningMessage } from "../widgets/WarningMessage"
+import { FailedToLoadMeasurementsWarningMessage, InfoMessage } from "../widgets/WarningMessage"
import { SourceEntity } from "./SourceEntity"
function entityStatus(source, entity) {
@@ -99,6 +108,10 @@ sort.propTypes = {
sortDirection: sortDirectionPropType,
}
+function MuiSortDirection(sortDirection) {
+ return sortDirection === "ascending" ? "asc" : "desc"
+}
+
function SortableHeaderCell({
children,
column,
@@ -111,15 +124,17 @@ function SortableHeaderCell({
textAlign,
}) {
return (
-
- sort(column, columnType, setColumnType, setSortColumn, setSortDirection, sortColumn, sortDirection)
- }
- sorted={sorted(column, sortColumn, sortDirection)}
- textAlign={textAlign}
- >
- {children}
-
+
+
+ sort(column, columnType, setColumnType, setSortColumn, setSortDirection, sortColumn, sortDirection)
+ }
+ >
+ {children}
+
+
)
}
SortableHeaderCell.propTypes = {
@@ -144,16 +159,12 @@ function EntityAttributeHeaderCell({ entityAttribute, ...sortProps }) {
>
{entityAttribute.name}
{entityAttribute.help ? (
-
-
-
-
- }
- content={entityAttribute.help}
- />
+
+
+
+
+
+
) : null}
)
@@ -178,8 +189,8 @@ function sourceEntitiesHeaders(
const entityNamePlural = metricEntities.name_plural
const hideIgnoredEntitiesLabel = `${hideIgnoredEntities ? "Show" : "Hide"} ignored ${entityNamePlural}`
return (
-
-
+
+
: }
-
+
{`${capitalize(entityName)} status`}
@@ -204,19 +215,15 @@ function sourceEntitiesHeaders(
{entityAttributes.map((entityAttribute) => (
))}
-
+
)
}
sourceEntitiesHeaders.propTypes = {
entityAttributes: entityAttributesPropType,
hideIgnoredEntities: bool,
metricEntities: object,
- setColumnType: func,
setHideIgnoredEntities: func,
- setSortColumn: func,
- setSortDirection: func,
- sortColumn: string,
- sortDirection: sortDirectionPropType,
+ sortProps: object,
}
function sortedEntities(columnType, sortColumn, sortDirection, source) {
@@ -270,10 +277,9 @@ export function SourceEntities({ loading, measurements, metric, metric_uuid, rel
const unit = dataModel.metrics[metric.type].unit || "entities"
const sourceTypeName = dataModel.sources[sourceType].name
return (
-
+
+ {`Showing individual ${unit} is not supported when using ${sourceTypeName} as source.`}
+
)
}
if (loading === "failed") {
@@ -284,20 +290,18 @@ export function SourceEntities({ loading, measurements, metric, metric_uuid, rel
}
if (measurements.length === 0) {
return (
-
+
+ Measurement details not available because Quality-time has not collected any measurements yet.
+
)
}
const lastMeasurement = measurements[measurements.length - 1]
const source = lastMeasurement.sources.find((source) => source.source_uuid === source_uuid)
if (!Array.isArray(source.entities) || source.entities.length === 0) {
return (
-
+
+ There are currently no measurement details available.
+
)
}
const entityAttributes = metricEntities.attributes.filter((attribute) => attribute?.visible ?? true)
@@ -333,10 +337,12 @@ export function SourceEntities({ loading, measurements, metric, metric_uuid, rel
/>
))
return (
-
+
+
+
)
}
SourceEntities.propTypes = {
diff --git a/components/frontend/src/source/SourceEntities.test.js b/components/frontend/src/source/SourceEntities.test.js
index 98cf824b89..844d0b14fb 100644
--- a/components/frontend/src/source/SourceEntities.test.js
+++ b/components/frontend/src/source/SourceEntities.test.js
@@ -131,7 +131,7 @@ it("renders a message if the metric does not support measurement entities", () =
).toBe(1)
})
-it("renders a message if the metric does not support measurement entities andhas no unit", () => {
+it("renders a message if the metric does not support measurement entities and has no unit", () => {
renderSourceEntities({
metric: {
type: "metric_type_without_unit",
diff --git a/components/frontend/src/source/SourceEntity.js b/components/frontend/src/source/SourceEntity.js
index 923ecb9a31..4b262568d4 100644
--- a/components/frontend/src/source/SourceEntity.js
+++ b/components/frontend/src/source/SourceEntity.js
@@ -1,8 +1,8 @@
import "./SourceEntity.css"
+import { TableCell } from "@mui/material"
import { bool, func, string } from "prop-types"
import { useState } from "react"
-import { Table } from "semantic-ui-react"
import { entityAttributesPropType, entityPropType, entityStatusPropType, reportPropType } from "../sharedPropTypes"
import { DivWithHTML } from "../widgets/DivWithHTML"
@@ -77,22 +77,22 @@ export function SourceEntity({
onExpand={setExpanded}
style={{ maxHeight: "100px", overflow: "auto" }}
>
- {SOURCE_ENTITY_STATUS_NAME[status]}
- {status === "unconfirmed" ? "" : status_end_date}
-
+ {SOURCE_ENTITY_STATUS_NAME[status]}
+ {status === "unconfirmed" ? "" : status_end_date}
+
{rationale}
-
-
+
+
{entity.first_seen ? : ""}
-
+
{entity_attributes.map((entity_attribute) => (
-
-
+
))}
)
diff --git a/components/frontend/src/source/SourceEntity.test.js b/components/frontend/src/source/SourceEntity.test.js
index bc4d7c75b7..5cb4f6f201 100644
--- a/components/frontend/src/source/SourceEntity.test.js
+++ b/components/frontend/src/source/SourceEntity.test.js
@@ -1,5 +1,5 @@
+import { Table, TableBody } from "@mui/material"
import { fireEvent, render, screen } from "@testing-library/react"
-import { Table } from "semantic-ui-react"
import { SourceEntity } from "./SourceEntity"
@@ -12,7 +12,7 @@ function renderSourceEntity({
}) {
return render(
,
)
}
diff --git a/components/frontend/src/source/SourceEntityDetails.js b/components/frontend/src/source/SourceEntityDetails.js
index 632babc659..69e5aaa66d 100644
--- a/components/frontend/src/source/SourceEntityDetails.js
+++ b/components/frontend/src/source/SourceEntityDetails.js
@@ -1,5 +1,5 @@
+import Grid from "@mui/material/Grid2"
import { func, node, oneOf, string } from "prop-types"
-import { Grid, Header } from "semantic-ui-react"
import { set_source_entity_attribute } from "../api/source"
import { EDIT_ENTITY_PERMISSION } from "../context/Permissions"
@@ -8,6 +8,7 @@ import { SingleChoiceInput } from "../fields/SingleChoiceInput"
import { TextInput } from "../fields/TextInput"
import { entityPropType, entityStatusPropType, reportPropType } from "../sharedPropTypes"
import { capitalize, getDesiredResponseTime } from "../utils"
+import { Header } from "../widgets/Header"
import { LabelWithDate } from "../widgets/LabelWithDate"
import { SOURCE_ENTITY_STATUS_ACTION, SOURCE_ENTITY_STATUS_NAME } from "./source_entity_status"
@@ -16,7 +17,7 @@ function entityStatusOption(status, subheader) {
key: status,
text: SOURCE_ENTITY_STATUS_NAME[status],
value: status,
- content: ,
+ content: ,
}
}
entityStatusOption.propTypes = {
@@ -77,65 +78,56 @@ export function SourceEntityDetails({
source_uuid,
}) {
return (
-
-
-
-
- set_source_entity_attribute(metric_uuid, source_uuid, entity.key, "status", value, reload)
- }
- value={status}
- sort={false}
- />
-
-
-
- }
- placeholder="YYYY-MM-DD"
- set_value={(value) =>
- set_source_entity_attribute(
- metric_uuid,
- source_uuid,
- entity.key,
- "status_end_date",
- value,
- reload,
- )
- }
- value={status_end_date}
- />
-
-
-
- set_source_entity_attribute(
- metric_uuid,
- source_uuid,
- entity.key,
- "rationale",
- value,
- reload,
- )
- }
- value={rationale}
- />
-
-
+
+
+
+ set_source_entity_attribute(metric_uuid, source_uuid, entity.key, "status", value, reload)
+ }
+ value={status}
+ sort={false}
+ />
+
+
+
+ }
+ placeholder="YYYY-MM-DD"
+ set_value={(value) =>
+ set_source_entity_attribute(
+ metric_uuid,
+ source_uuid,
+ entity.key,
+ "status_end_date",
+ value,
+ reload,
+ )
+ }
+ value={status_end_date}
+ />
+
+
+
+ set_source_entity_attribute(metric_uuid, source_uuid, entity.key, "rationale", value, reload)
+ }
+ value={rationale}
+ />
+
)
}
diff --git a/components/frontend/src/source/SourceParameter.js b/components/frontend/src/source/SourceParameter.js
index 35ae091a25..db532f9ad1 100644
--- a/components/frontend/src/source/SourceParameter.js
+++ b/components/frontend/src/source/SourceParameter.js
@@ -22,58 +22,43 @@ import { LabelWithDropdown } from "../widgets/LabelWithDropdown"
import { LabelWithHelp } from "../widgets/LabelWithHelp"
import { LabelWithHyperLink } from "../widgets/LabelWithHyperLink"
-function SourceParameterLabel({ edit_scope, index, label, parameter_short_name, setEditScope, source_type_name }) {
+function SourceParameterLabel({ edit_scope, label, setEditScope }) {
const scope_options = [
{
key: "source",
value: "source",
text: "Apply change to source",
- description: `Change the ${parameter_short_name} of this ${source_type_name} source only`,
- label: { color: "grey", empty: true, circular: true },
+ color: "grey",
},
{
key: "metric",
value: "metric",
text: "Apply change to metric",
- description: `Change the ${parameter_short_name} of ${source_type_name} sources in this metric that have the same ${parameter_short_name}`,
- label: { color: "black", empty: true, circular: true },
+ color: "text.primary",
},
{
key: "subject",
value: "subject",
text: "Apply change to subject",
- description: `Change the ${parameter_short_name} of ${source_type_name} sources in this subject that have the same ${parameter_short_name}`,
- label: { color: "yellow", empty: true, circular: true },
+ color: "gold",
},
{
key: "report",
value: "report",
text: "Apply change to report",
- description: `Change the ${parameter_short_name} of ${source_type_name} sources in this report that have the same ${parameter_short_name}`,
- label: { color: "orange", empty: true, circular: true },
+ color: "orange",
},
{
key: "reports",
value: "reports",
text: "Apply change to all reports",
- description: `Change the ${parameter_short_name} of ${source_type_name} sources in all reports that have the same ${parameter_short_name}`,
- label: { color: "red", empty: true, circular: true },
+ color: "red",
},
]
return (
setEditScope(data.value)}
+ onChange={(value) => setEditScope(value)}
options={scope_options}
value={edit_scope}
/>
@@ -81,11 +66,8 @@ function SourceParameterLabel({ edit_scope, index, label, parameter_short_name,
}
SourceParameterLabel.propTypes = {
edit_scope: string,
- index: number,
label: labelPropType,
- parameter_short_name: string,
setEditScope: func,
- source_type_name: string,
}
function sources(report) {
@@ -128,11 +110,9 @@ parameterValues.propTypes = {
export function SourceParameter({
help,
help_url,
- index,
parameter_key,
parameter_type,
parameter_name,
- parameter_short_name,
parameter_unit,
parameter_min,
parameter_max,
@@ -144,7 +124,6 @@ export function SourceParameter({
required,
requiredPermissions,
source,
- source_type_name,
source_uuid,
warning,
}) {
@@ -167,16 +146,7 @@ export function SourceParameter({
}
let parameter_props = {
requiredPermissions: requiredPermissions,
- editableLabel: (
-
- ),
+ editableLabel: ,
label: label,
placeholder: placeholder,
required: required,
@@ -216,11 +186,9 @@ export function SourceParameter({
SourceParameter.propTypes = {
help: popupContentPropType,
help_url: string,
- index: number,
parameter_key: string,
parameter_type: string,
parameter_name: string,
- parameter_short_name: string,
parameter_unit: string,
parameter_min: number,
parameter_max: number,
@@ -232,7 +200,6 @@ SourceParameter.propTypes = {
required: bool,
requiredPermissions: permissionsPropType,
source: sourcePropType,
- source_type_name: string,
source_uuid: string,
warning: bool,
}
diff --git a/components/frontend/src/source/SourceParameter.test.js b/components/frontend/src/source/SourceParameter.test.js
index 0ea0323b5f..3274091b84 100644
--- a/components/frontend/src/source/SourceParameter.test.js
+++ b/components/frontend/src/source/SourceParameter.test.js
@@ -36,7 +36,6 @@ const report = {
function renderSourceParameter({
help = null,
help_url = null,
- index = 0,
parameter_key = "key1",
parameter_name = "URL",
parameter_type = "url",
@@ -50,7 +49,6 @@ function renderSourceParameter({
{
})
it("renders an url parameter with warning", () => {
- renderSourceParameter({ warning: true, index: 1 })
+ renderSourceParameter({ warning: true })
expect(screen.queryAllByText(/URL/).length).toBe(1)
expect(screen.queryAllByText(/placeholder/).length).toBe(1)
- expect(screen.getByRole("combobox")).toBeInvalid()
})
it("renders a string parameter", () => {
@@ -171,6 +168,7 @@ it("changes the value", async () => {
it("changes the value via mass edit", async () => {
fetch_server_api.fetch_server_api = jest.fn().mockResolvedValue({ ok: true })
renderSourceParameter({})
+ await userEvent.click(screen.queryByText(/Apply change to source/))
await userEvent.click(screen.queryByText(/Apply change to subject/))
await userEvent.type(screen.queryByText(/test/), "/new{Enter}")
expect(fetch_server_api.fetch_server_api).toHaveBeenLastCalledWith("post", "source/source_uuid/parameter/key1", {
diff --git a/components/frontend/src/source/SourceParameters.js b/components/frontend/src/source/SourceParameters.js
index fc3ac78908..40d80bb0bf 100644
--- a/components/frontend/src/source/SourceParameters.js
+++ b/components/frontend/src/source/SourceParameters.js
@@ -1,10 +1,10 @@
+import { Paper, Typography } from "@mui/material"
+import Grid from "@mui/material/Grid2"
import { func, string } from "prop-types"
import { useContext } from "react"
-import { Grid } from "semantic-ui-react"
import { DataModel } from "../context/DataModel"
import { EDIT_REPORT_PERMISSION } from "../context/Permissions"
-import { Header, Segment } from "../semantic_ui_react_wrappers"
import { metricPropType, reportPropType, sourcePropType, stringsPropType } from "../sharedPropTypes"
import { formatMetricScaleAndUnit } from "../utils"
import { SourceParameter } from "./SourceParameter"
@@ -44,17 +44,15 @@ export function SourceParameters({ changed_param_keys, metric, reload, report, s
if (parameterKeys.length === 0) {
return null
}
- const parameters = parameterKeys.map((parameterKey, index) => (
+ const parameters = parameterKeys.map((parameterKey) => (
))
return (
-
-
-
- {parameterGroup.name}
-
+
+
+ {parameterGroup.name}
{parameters}
-
-
+
+
)
})
return (
-
- {groups}
+
+ {groups}
)
}
diff --git a/components/frontend/src/source/SourceTypeHeader.js b/components/frontend/src/source/SourceTypeHeader.js
index ca1b62f8ab..0f19521c51 100644
--- a/components/frontend/src/source/SourceTypeHeader.js
+++ b/components/frontend/src/source/SourceTypeHeader.js
@@ -1,9 +1,9 @@
import { Chip } from "@mui/material"
import { string } from "prop-types"
-import { Header } from "../semantic_ui_react_wrappers"
import { sourceTypePropType } from "../sharedPropTypes"
import { slugify } from "../utils"
+import { Header } from "../widgets/Header"
import { ReadTheDocsLink } from "../widgets/ReadTheDocsLink"
import { Logo } from "./Logo"
import { sourceTypeDescription } from "./SourceType"
@@ -15,18 +15,22 @@ export function SourceTypeHeader({ metricTypeId, sourceTypeId, sourceType }) {
}
const url = `https://quality-time.readthedocs.io/en/v${process.env.REACT_APP_VERSION}/reference.html${slugify(sourceType.name)}`
return (
-
-
-
- {sourceType.name}
- {sourceType.deprecated && }
-
+
+ {sourceType.name}
+ {sourceType.deprecated && }
+ >
+ }
+ level="h4"
+ subheader={
+ <>
{`${sourceTypeDescription(sourceType)} `}
{howToConfigure}
-
-
-
+ >
+ }
+ />
)
}
SourceTypeHeader.propTypes = {
diff --git a/components/frontend/src/source/Sources.js b/components/frontend/src/source/Sources.js
index 15a6a3b9a3..8b1a7b0520 100644
--- a/components/frontend/src/source/Sources.js
+++ b/components/frontend/src/source/Sources.js
@@ -1,10 +1,10 @@
+import { Paper } from "@mui/material"
import { func, number, string } from "prop-types"
import { useContext } from "react"
import { add_source, copy_source, move_source } from "../api/source"
import { DataModel } from "../context/DataModel"
import { EDIT_REPORT_PERMISSION, ReadOnlyOrEditable } from "../context/Permissions"
-import { Message, Segment } from "../semantic_ui_react_wrappers"
import {
measurementPropType,
measurementSourcePropType,
@@ -20,6 +20,7 @@ import { CopyButton } from "../widgets/buttons/CopyButton"
import { MoveButton } from "../widgets/buttons/MoveButton"
import { source_options } from "../widgets/menu_options"
import { showMessage } from "../widgets/toast"
+import { InfoMessage } from "../widgets/WarningMessage"
import { Source } from "./Source"
import { sourceTypeOptions } from "./SourceType"
@@ -59,7 +60,7 @@ ButtonSegment.propTypes = {
function SourceSegment({ changed_fields, index, last_index, measurement_source, metric, reload, report, sourceUuid }) {
return (
-
+
-
+
)
}
SourceSegment.propTypes = {
@@ -118,10 +119,7 @@ export function Sources({ reports, report, metric, metric_uuid, measurement, cha
return (
<>
{sourceSegments.length === 0 ? (
-
- No sources
- No sources have been configured yet.
-
+ No sources have been configured yet.
) : (
sourceSegments
)}
diff --git a/components/frontend/src/source/Sources.test.js b/components/frontend/src/source/Sources.test.js
index 3e1f1de5b0..715200f0ef 100644
--- a/components/frontend/src/source/Sources.test.js
+++ b/components/frontend/src/source/Sources.test.js
@@ -168,10 +168,9 @@ it("updates a parameter of a source", async () => {
it("mass updates a parameter of a source", async () => {
fetch_server_api.fetch_server_api = jest.fn().mockResolvedValue({ ok: true, nr_sources_mass_edited: 2 })
renderSources()
- await act(async () => {
- fireEvent.click(screen.getByText(/Apply change to subject/))
- })
- expect(screen.getAllByText(/Apply change to subject/).length).toBe(2)
+ await userEvent.click(screen.getByText(/Apply change to source/))
+ await userEvent.click(screen.getByText(/Apply change to subject/))
+ expect(screen.getAllByText(/Apply change to subject/).length).toBe(1)
await userEvent.type(screen.getByDisplayValue(/https:\/\/test.nl/), "https://other{Enter}", {
initialSelectionStart: 0,
initialSelectionEnd: 15,
@@ -185,5 +184,5 @@ it("mass updates a parameter of a source", async () => {
url: "https://other",
})
expect(toast.showMessage).toHaveBeenCalledTimes(1)
- expect(screen.getAllByText(/Apply change to subject/).length).toBe(1)
+ expect(screen.getAllByText(/Apply change to source/).length).toBe(1)
})
diff --git a/components/frontend/src/subject/Subject.css b/components/frontend/src/subject/Subject.css
index 1957375822..b17fe6a6ea 100644
--- a/components/frontend/src/subject/Subject.css
+++ b/components/frontend/src/subject/Subject.css
@@ -1,5 +1,5 @@
div.sticky {
position: sticky; /* Make the div sticky */
- top: 15px; /* The menu bar is about 60px high, move the top margin under it */
+ top: 60px; /* The menu bar is about 60px high, move the top margin under it */
z-index: 3;
}
diff --git a/components/frontend/src/subject/Subject.js b/components/frontend/src/subject/Subject.js
index 2e22900287..ec831c9a5d 100644
--- a/components/frontend/src/subject/Subject.js
+++ b/components/frontend/src/subject/Subject.js
@@ -1,5 +1,6 @@
import "./Subject.css"
+import { Divider, Paper } from "@mui/material"
import { bool, func, string } from "prop-types"
import { useContext } from "react"
@@ -164,7 +165,7 @@ export function Subject({
}
return (
-
+
)
}
Subject.propTypes = {
diff --git a/components/frontend/src/subject/SubjectParameters.js b/components/frontend/src/subject/SubjectParameters.js
index ec40812d73..e923df7481 100644
--- a/components/frontend/src/subject/SubjectParameters.js
+++ b/components/frontend/src/subject/SubjectParameters.js
@@ -1,5 +1,5 @@
+import Grid from "@mui/material/Grid2"
import { func, string } from "prop-types"
-import { Grid } from "semantic-ui-react"
import { set_subject_attribute } from "../api/subject"
import { EDIT_REPORT_PERMISSION } from "../context/Permissions"
@@ -10,44 +10,40 @@ import { SubjectType } from "./SubjectType"
export function SubjectParameters({ subject, subject_uuid, subject_name, reload }) {
return (
-
-
-
- set_subject_attribute(subject_uuid, "type", value, reload)}
- subjectType={subject.type}
- />
-
-
- set_subject_attribute(subject_uuid, "name", value, reload)}
- value={subject.name}
- />
-
-
- set_subject_attribute(subject_uuid, "subtitle", value, reload)}
- value={subject.subtitle}
- />
-
-
-
-
-
-
+
+
+ set_subject_attribute(subject_uuid, "type", value, reload)}
+ subjectType={subject.type}
+ />
+
+
+ set_subject_attribute(subject_uuid, "name", value, reload)}
+ value={subject.name}
+ />
+
+
+ set_subject_attribute(subject_uuid, "subtitle", value, reload)}
+ value={subject.subtitle}
+ />
+
+
+
)
}
diff --git a/components/frontend/src/subject/SubjectTable.css b/components/frontend/src/subject/SubjectTable.css
index 925a569965..c94a5de627 100644
--- a/components/frontend/src/subject/SubjectTable.css
+++ b/components/frontend/src/subject/SubjectTable.css
@@ -1,53 +1,11 @@
-.ui.sortable.table.stickyHeader > thead {
+table.stickyHeader > thead {
/* Make thead sticky by positioning the th's */
position: sticky;
/* Leave room for the menu bar and the subject title */
- top: 143px;
+ top: 120px;
z-index: 2;
}
-.ui.sortable.table.stickyHeader > thead > tr > th {
- /* Apply the top table border to the th as the table border scrolls out of view */
- border-top: 1px solid rgba(34, 36, 38, 0.15);
-}
-
-.ui.sortable.table.stickyHeader {
- /* The top table border is applied to the th's because the top table border scrolls out of view */
- border-top: 0px;
-}
-
-/* Remove opacity from the th background, otherwise the rows underneath are visible. */
-
-.ui.inverted.sortable.table > thead > tr > th {
- background-color: rgba(50, 50, 50, 1);
-}
-
-.ui.sortable.table > thead > tr > th.sorted {
- background-color: rgba(242, 242, 242, 1);
-}
-
-.ui.inverted.sortable.table > thead > tr > th.sorted {
- background-color: rgba(80, 80, 80, 1) !important;
-}
-
-.ui.sortable.table > thead > tr > th:not(.unsortable):hover {
- background-color: rgba(242, 242, 242, 1);
-}
-
-.ui.inverted.sortable.table > thead > tr > th:not(.unsortable):hover {
- background-color: rgba(100, 100, 100, 1);
-}
-
-.ui.sortable.table > thead > tr > th.unsortable:hover {
- /* Don't highlight unsortable columns */
- background-color: rgba(249, 250, 251, 1);
-}
-
-.ui.inverted.sortable.table > thead > tr > th.unsortable:hover {
- /* Don't highlight unsortable columns */
- background-color: rgba(50, 50, 50, 1);
-}
-
tr.target_met,
td.target_met {
background-color: rgb(30, 148, 78, 0.15) !important;
@@ -103,21 +61,7 @@ td.unknown {
background-color: rgb(245, 245, 245, 0.15) !important;
}
-.ui.table.inverted > tbody > tr.unknown:hover,
-.ui.table.inverted > tbody > tr > td.unknown:hover {
- background-color: rgb(245, 245, 245, 0.25) !important;
-}
-
tr.unknown:hover,
td.unknown:hover {
- background-color: rgb(245, 245, 245, 0.65) !important;
-}
-
-td > a {
- color: rgb(0, 88, 176) !important;
-}
-
-.ui.sortable.table thead th.unsortable:hover {
- /* Allow for specifying that some columns in a sortable table aren't sortable */
- background: #f9fafb;
+ background-color: rgb(245, 245, 245, 0.2) !important;
}
diff --git a/components/frontend/src/subject/SubjectTable.js b/components/frontend/src/subject/SubjectTable.js
index 48457c3402..7e4440c232 100644
--- a/components/frontend/src/subject/SubjectTable.js
+++ b/components/frontend/src/subject/SubjectTable.js
@@ -1,8 +1,8 @@
import "./SubjectTable.css"
+import { Table, TableContainer } from "@mui/material"
import { array, func, object, string } from "prop-types"
-import { Table } from "../semantic_ui_react_wrappers"
import {
datesPropType,
measurementsPropType,
@@ -33,34 +33,36 @@ export function SubjectTable({
// Sort measurements in reverse order so that if there multiple measurements on a day, we find the most recent one:
const reversedMeasurements = measurements.slice().sort((m1, m2) => (m1.start < m2.start ? 1 : -1))
return (
-
-
-
- {
- handleSort(null)
- settings.hiddenTags.reset()
- settings.metricsToHide.reset()
- }}
- />
-
+
+
+
+
+ {
+ handleSort(null)
+ settings.hiddenTags.reset()
+ settings.metricsToHide.reset()
+ }}
+ />
+
+
)
}
SubjectTable.propTypes = {
diff --git a/components/frontend/src/subject/SubjectTable.test.js b/components/frontend/src/subject/SubjectTable.test.js
index 3057af4959..4ca63587a0 100644
--- a/components/frontend/src/subject/SubjectTable.test.js
+++ b/components/frontend/src/subject/SubjectTable.test.js
@@ -202,7 +202,7 @@ it("hides the tags column", () => {
it("expands the details via the button", () => {
const expandedItems = renderHook(() => useExpandedItemsSearchQuery())
renderSubjectTable({ expandedItems: expandedItems.result.current })
- const expand = screen.getAllByRole("button")[0]
+ const expand = screen.getAllByRole("button", { name: "Expand/collapse" })[0]
fireEvent.click(expand)
expandedItems.rerender()
expect(expandedItems.result.current.value).toStrictEqual(["1:0"])
@@ -212,7 +212,7 @@ it("collapses the details via the button", async () => {
history.push("?expanded=1:0")
const expandedItems = renderHook(() => useExpandedItemsSearchQuery())
renderSubjectTable({ expandedItems: expandedItems.result.current })
- const expand = screen.getAllByRole("button")[0]
+ const expand = screen.getAllByRole("button", { name: "Expand/collapse" })[0]
await act(async () => fireEvent.click(expand))
expandedItems.rerender()
expect(expandedItems.result.current.value).toStrictEqual([])
diff --git a/components/frontend/src/subject/SubjectTableBody.js b/components/frontend/src/subject/SubjectTableBody.js
index 28d187c3b6..4132f12c4c 100644
--- a/components/frontend/src/subject/SubjectTableBody.js
+++ b/components/frontend/src/subject/SubjectTableBody.js
@@ -1,6 +1,6 @@
+import { TableBody } from "@mui/material"
import { array, func, string } from "prop-types"
-import { Table } from "../semantic_ui_react_wrappers"
import {
datesPropType,
measurementsPropType,
@@ -28,7 +28,7 @@ export function SubjectTableBody({
}) {
const lastIndex = metricEntries.length - 1
return (
-
+
{metricEntries.map(([metric_uuid, metric], index) => {
return (
)
})}
-
+
)
}
SubjectTableBody.propTypes = {
diff --git a/components/frontend/src/subject/SubjectTableFooter.js b/components/frontend/src/subject/SubjectTableFooter.js
index ca66d032d1..fbda961b44 100644
--- a/components/frontend/src/subject/SubjectTableFooter.js
+++ b/components/frontend/src/subject/SubjectTableFooter.js
@@ -1,6 +1,6 @@
+import { TableCell, TableFooter, TableRow } from "@mui/material"
import { func, string } from "prop-types"
import { useContext } from "react"
-import { Table } from "semantic-ui-react"
import { add_metric, copy_metric, move_metric } from "../api/metric"
import { DataModel } from "../context/DataModel"
@@ -16,8 +16,8 @@ import { metric_options } from "../widgets/menu_options"
function SubjectTableFooterButtonRow({ subject, subjectUuid, reload, reports, stopFilteringAndSorting }) {
const dataModel = useContext(DataModel)
return (
-
-
+
+
metric_options(reports, dataModel, subject.type, subjectUuid)}
/>
-
-
+
+
)
}
SubjectTableFooterButtonRow.propTypes = {
@@ -63,9 +63,9 @@ export function SubjectTableFooter(props) {
+
-
+
}
/>
)
diff --git a/components/frontend/src/subject/SubjectTableFooter.test.js b/components/frontend/src/subject/SubjectTableFooter.test.js
index 72de24050c..62f2293724 100644
--- a/components/frontend/src/subject/SubjectTableFooter.test.js
+++ b/components/frontend/src/subject/SubjectTableFooter.test.js
@@ -1,5 +1,5 @@
+import { Table } from "@mui/material"
import { act, fireEvent, render, screen } from "@testing-library/react"
-import { Table } from "semantic-ui-react"
import { dataModel, report } from "../__fixtures__/fixtures"
import * as fetch_server_api from "../api/fetch_server_api"
diff --git a/components/frontend/src/subject/SubjectTableHeader.js b/components/frontend/src/subject/SubjectTableHeader.js
index c30cd58081..f6b5ff43c8 100644
--- a/components/frontend/src/subject/SubjectTableHeader.js
+++ b/components/frontend/src/subject/SubjectTableHeader.js
@@ -1,10 +1,8 @@
-import { List, ListItem, ListItemIcon, ListItemText } from "@mui/material"
+import { Chip, List, ListItem, ListItemIcon, ListItemText, Paper, TableHead, TableRow, Typography } from "@mui/material"
import { bool, func, string } from "prop-types"
-import { Table } from "semantic-ui-react"
import { StatusIcon } from "../measurement/StatusIcon"
import { STATUS_DESCRIPTION, STATUSES } from "../metric/status"
-import { Label } from "../semantic_ui_react_wrappers"
import { datesPropType, settingsPropType } from "../sharedPropTypes"
import { HyperLink } from "../widgets/HyperLink"
import { IgnoreIcon, TriangleRightIcon } from "../widgets/icons"
@@ -78,9 +76,9 @@ const measurementHelp = (
If the measurement value has a{" "}
-
+
red background
-
+
, the metric has not been measured recently. This indicates a problem with Quality-time itself, and
a system administrator should be notified.
@@ -99,9 +97,9 @@ const targetHelp = (
The value against which measurements are evaluated to determine whether a metric needs action.
The target value has a{" "}
-
+
grey background
- {" "}
+ {" "}
if the metric has accepted technical debt that is not applied because the technical debt end date is in the
past or all issues linked to the metric have been resolved.
@@ -172,9 +170,9 @@ const sourcesHelp = (
The tools and reports accessed to collect the measurement data. One metric can have multiple sources.
If a source has a{" "}
-
+
red background
-
+
, the source could not be accessed or the data could not be parsed. metric and navigate to
the source to see the error details.
@@ -194,9 +192,9 @@ const issuesHelp = (
If an issue has a{" "}
-
+
red background
-
+
, the issue tracker could not be accessed or the data could not be parsed. metric and
navigate to the technical debt tab to see the error details.
@@ -222,6 +220,18 @@ const tagsHelp = (
>
)
+function InlineChip({ color, label }) {
+ return (
+
+
+
+ )
+}
+InlineChip.propTypes = {
+ color: string,
+ label: string,
+}
+
function MeasurementHeaderCells({ columnDates, showDeltaColumns }) {
const cells = []
columnDates.forEach((date, index) => {
@@ -236,29 +246,16 @@ function MeasurementHeaderCells({ columnDates, showDeltaColumns }) {
and next date.
- A plus sign{" "}
-
- +
- {" "}
- indicates that the newer value is higher. A minus sign{" "}
-
- -
- {" "}
- indicates that the newer value is lower.
+ A plus sign indicates that the newer value is
+ higher. A minus sign indicates that the newer
+ value is lower.
- A{" "}
-
- green outline
- {" "}
+ A
indicates that the newer value is better. A{" "}
-
- red outline
- {" "}
+
indicates that the newer value is worse. A{" "}
-
- blue outline
- {" "}
+
is used for metrics that are informative.
@@ -289,8 +286,8 @@ export function SubjectTableHeader({ columnDates, handleSort, settings }) {
}
const nrDates = columnDates.length
return (
-
-
+
+
{nrDates > 1 && (
)}
-
-
+
+
)
}
SubjectTableHeader.propTypes = {
diff --git a/components/frontend/src/subject/SubjectTableHeader.test.js b/components/frontend/src/subject/SubjectTableHeader.test.js
index a6b6f3eceb..79a98545f7 100644
--- a/components/frontend/src/subject/SubjectTableHeader.test.js
+++ b/components/frontend/src/subject/SubjectTableHeader.test.js
@@ -1,7 +1,7 @@
+import { Table } from "@mui/material"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import history from "history/browser"
-import { Table } from "semantic-ui-react"
import { createTestableSettings } from "../__fixtures__/fixtures"
import { SubjectTableHeader } from "./SubjectTableHeader"
diff --git a/components/frontend/src/subject/SubjectTableRow.js b/components/frontend/src/subject/SubjectTableRow.js
index d338f4a430..972ec45e4d 100644
--- a/components/frontend/src/subject/SubjectTableRow.js
+++ b/components/frontend/src/subject/SubjectTableRow.js
@@ -1,7 +1,7 @@
+import { Chip, TableCell, Tooltip } from "@mui/material"
import { bool, func, number, object, string } from "prop-types"
import { useContext } from "react"
-import { DarkMode } from "../context/DarkMode"
import { DataModel } from "../context/DataModel"
import { IssueStatus } from "../issue/IssueStatus"
import { MeasurementSources } from "../measurement/MeasurementSources"
@@ -12,7 +12,6 @@ import { StatusIcon } from "../measurement/StatusIcon"
import { TimeLeft } from "../measurement/TimeLeft"
import { TrendSparkline } from "../measurement/TrendSparkline"
import { MetricDetails } from "../metric/MetricDetails"
-import { Label, Popup, Table } from "../semantic_ui_react_wrappers"
import {
dataModelPropType,
datePropType,
@@ -68,9 +67,9 @@ didValueImprove.propTypes = {
function deltaColor(metric, improved) {
const evaluateTarget = metric.evaluate_targets ?? true
if (evaluateTarget) {
- return improved ? "green" : "red"
+ return improved ? "success" : "error"
}
- return "blue"
+ return "info"
}
deltaColor.propTypes = {
metric: metricPropType,
@@ -131,20 +130,15 @@ function DeltaCell({ dateOrderAscending, index, metric, metricValue, previousVal
const description = deltaDescription(dataModel, metric, scale, delta, improved, oldValue, newValue)
const color = deltaColor(metric, improved)
label = (
-
- {delta}
-
- }
- />
+
+
+
)
}
return (
-
+
{label}
-
+
)
}
DeltaCell.propTypes = {
@@ -208,10 +202,10 @@ function MeasurementCells({ dates, metric, metric_uuid, measurements, settings }
)
}
cells.push(
-
+
{formatMetricValue(scale, metricValue)}
{formatMetricScale(metric, dataModel)}
- ,
+ ,
)
previousValue = metricValue === "?" ? previousValue : metricValue
})
@@ -252,12 +246,10 @@ export function SubjectTableRow({
subject_uuid,
}) {
const dataModel = useContext(DataModel)
- const darkMode = useContext(DarkMode)
const metricName = getMetricName(metric, dataModel)
const scale = getMetricScale(metric, dataModel)
const unit = getMetricUnit(metric, dataModel)
const nrDates = dates.length
- const style = nrDates > 1 ? { background: darkMode ? "rgba(60, 60, 60, 1)" : "#f9fafb" } : {}
return (
item?.startsWith(metric_uuid)).length > 0}
id={metric_uuid}
onExpand={(expand) => expandOrCollapseItem(expand, metric_uuid, settings.expandedItems)}
- style={style}
>
- {metricName}
+ {metricName}
{nrDates > 1 && (
)}
{nrDates === 1 && settings.hiddenColumns.excludes("trend") && (
-
+
-
+
)}
{nrDates === 1 && settings.hiddenColumns.excludes("status") && (
-
+
-
+
)}
{nrDates === 1 && settings.hiddenColumns.excludes("measurement") && (
-
+
-
+
)}
{nrDates === 1 && settings.hiddenColumns.excludes("target") && (
-
+
-
+
)}
- {settings.hiddenColumns.excludes("unit") && {unit} }
+ {settings.hiddenColumns.excludes("unit") && {unit} }
{settings.hiddenColumns.excludes("source") && (
-
+
-
+
)}
{settings.hiddenColumns.excludes("time_left") && (
-
+
-
+
)}
{nrDates > 1 && settings.hiddenColumns.excludes("overrun") && (
-
+
-
+
)}
{settings.hiddenColumns.excludes("comment") && (
-
+
{metric.comment}
-
+
)}
{settings.hiddenColumns.excludes("issues") && (
-
+
-
+
)}
{settings.hiddenColumns.excludes("tags") && (
-
+
{getMetricTags(metric).map((tag) => (
))}
-
+
)}
)
diff --git a/components/frontend/src/subject/SubjectTableRow.test.js b/components/frontend/src/subject/SubjectTableRow.test.js
index 1d254d2470..8cbc517666 100644
--- a/components/frontend/src/subject/SubjectTableRow.test.js
+++ b/components/frontend/src/subject/SubjectTableRow.test.js
@@ -1,9 +1,9 @@
+import { Table, TableBody } from "@mui/material"
import { render, screen } from "@testing-library/react"
import history from "history/browser"
import { createTestableSettings, dataModel, report } from "../__fixtures__/fixtures"
import { DataModel } from "../context/DataModel"
-import { Table } from "../semantic_ui_react_wrappers"
import { SubjectTableRow } from "./SubjectTableRow"
beforeEach(() => {
@@ -47,7 +47,7 @@ function renderSubjectTableRow({
render(
,
)
diff --git a/components/frontend/src/subject/SubjectTitle.js b/components/frontend/src/subject/SubjectTitle.js
index a5370644cc..89ac7bec0c 100644
--- a/components/frontend/src/subject/SubjectTitle.js
+++ b/components/frontend/src/subject/SubjectTitle.js
@@ -6,13 +6,14 @@ import { activeTabIndex, tabChangeHandler } from "../app_ui_settings"
import { ChangeLog } from "../changelog/ChangeLog"
import { DataModel } from "../context/DataModel"
import { EDIT_REPORT_PERMISSION, ReadOnlyOrEditable } from "../context/Permissions"
-import { Header, Tab } from "../semantic_ui_react_wrappers"
+import { Tab } from "../semantic_ui_react_wrappers"
import { reportPropType, settingsPropType } from "../sharedPropTypes"
import { getSubjectType, slugify } from "../utils"
import { ButtonRow } from "../widgets/ButtonRow"
import { DeleteButton } from "../widgets/buttons/DeleteButton"
import { PermLinkButton } from "../widgets/buttons/PermLinkButton"
import { ReorderButtonGroup } from "../widgets/buttons/ReorderButtonGroup"
+import { Header } from "../widgets/Header"
import { HeaderWithDetails } from "../widgets/HeaderWithDetails"
import { ReadTheDocsLink } from "../widgets/ReadTheDocsLink"
import { changelogTabPane, configurationTabPane } from "../widgets/TabPane"
@@ -21,14 +22,15 @@ import { SubjectParameters } from "./SubjectParameters"
function SubjectHeader({ subjectType }) {
const url = `https://quality-time.readthedocs.io/en/v${process.env.REACT_APP_VERSION}/reference.html${slugify(subjectType.name)}`
return (
-
-
- {subjectType.name}
-
+
{subjectType.description}
-
-
-
+ >
+ }
+ />
)
}
SubjectHeader.propTypes = {
@@ -94,15 +96,10 @@ export function SubjectTitle({
return (
diff --git a/components/frontend/src/subject/SubjectsButtonRow.js b/components/frontend/src/subject/SubjectsButtonRow.js
index 450169b2a7..37fe01c417 100644
--- a/components/frontend/src/subject/SubjectsButtonRow.js
+++ b/components/frontend/src/subject/SubjectsButtonRow.js
@@ -23,7 +23,7 @@ export function SubjectsButtonRow({ reload, report, reports, settings }) {
+
-
-
+
+
+
)
}
return null
diff --git a/components/frontend/src/widgets/Header.js b/components/frontend/src/widgets/Header.js
new file mode 100644
index 0000000000..a48ae577cc
--- /dev/null
+++ b/components/frontend/src/widgets/Header.js
@@ -0,0 +1,18 @@
+import { Typography } from "@mui/material"
+import { element, oneOfType, string } from "prop-types"
+
+export function Header({ header, level, subheader }) {
+ return (
+
+ {header}
+
+ {subheader}
+
+
+ )
+}
+Header.propTypes = {
+ header: oneOfType([element, string]),
+ level: string,
+ subheader: oneOfType([element, string]),
+}
diff --git a/components/frontend/src/widgets/HeaderWithDetails.css b/components/frontend/src/widgets/HeaderWithDetails.css
deleted file mode 100644
index f0f4f35cd9..0000000000
--- a/components/frontend/src/widgets/HeaderWithDetails.css
+++ /dev/null
@@ -1,9 +0,0 @@
-@media print {
- .Caret {
- display: none !important;
- }
-}
-
-div.sticky {
- background-color: white;
-}
diff --git a/components/frontend/src/widgets/HeaderWithDetails.js b/components/frontend/src/widgets/HeaderWithDetails.js
index 5d287a780d..fbf8d6bcdd 100644
--- a/components/frontend/src/widgets/HeaderWithDetails.js
+++ b/components/frontend/src/widgets/HeaderWithDetails.js
@@ -1,43 +1,52 @@
-import "./HeaderWithDetails.css"
+import { Accordion, AccordionDetails, AccordionSummary } from "@mui/material"
+import { accordionSummaryClasses } from "@mui/material/AccordionSummary"
+import { string } from "prop-types"
-import { node, object, string } from "prop-types"
-
-import { Header, Segment } from "../semantic_ui_react_wrappers"
import { childrenPropType, settingsPropType } from "../sharedPropTypes"
-import { ExpandButton } from "./buttons/ExpandButton"
+import { Header } from "./Header"
+import { CaretRight } from "./icons"
-export function HeaderWithDetails({ children, className, header, item_uuid, level, style, settings, subheader }) {
- const showDetails = settings.expandedItems.includes(item_uuid)
- const segmentStyle = { paddingLeft: "0px", paddingRight: "0px" }
+export function HeaderWithDetails({ children, header, item_uuid, level, settings, subheader }) {
+ const showDetails = Boolean(settings.expandedItems.includes(item_uuid))
return (
-
- settings.expandedItems.toggle(item_uuid)}
- onKeyPress={(event) => {
- event.preventDefault()
- settings.expandedItems.toggle(item_uuid)
+ settings.expandedItems.toggle(item_uuid)}
+ slotProps={{ transition: { unmountOnExit: true } }} // Make testing for (dis)appearance of contents easier
+ sx={{
+ "&:before": {
+ display: "none", // Remove top border
+ },
+ }}
+ >
+ }
+ id={`accordion-header-${item_uuid}`}
+ sx={{
+ border: "0",
+ flexDirection: "row-reverse",
+ height: "60px",
+ padding: "0px",
+ [`& .${accordionSummaryClasses.expandIconWrapper}.${accordionSummaryClasses.expanded}`]: {
+ transform: "rotate(90deg)",
+ },
+ color: "primary.main",
}}
- style={style}
- tabIndex="0"
>
-
-
- {header}
- {subheader}
-
-
- {showDetails && {children} }
-
+
+
+ {children}
+
)
}
HeaderWithDetails.propTypes = {
children: childrenPropType,
- className: string,
- header: node,
+ header: string,
item_uuid: string,
level: string,
settings: settingsPropType,
- style: object,
subheader: string,
}
diff --git a/components/frontend/src/widgets/HeaderWithDetails.test.js b/components/frontend/src/widgets/HeaderWithDetails.test.js
index 78f8ea0e6e..8cc13e9502 100644
--- a/components/frontend/src/widgets/HeaderWithDetails.test.js
+++ b/components/frontend/src/widgets/HeaderWithDetails.test.js
@@ -11,22 +11,20 @@ beforeEach(() => {
it("expands the details on click", () => {
render(
-
+
Hello
,
)
- expect(screen.queryAllByText("Hello").length).toBe(0)
- fireEvent.click(screen.getByTitle("expand"))
+ fireEvent.click(screen.getByText("Expand"))
expect(history.location.search).toBe("?expanded=uuid")
})
it("expands the details on space", async () => {
render(
-
+
Hello
,
)
- expect(screen.queryAllByText("Hello").length).toBe(0)
await userEvent.tab()
await userEvent.keyboard(" ")
expect(history.location.search).toBe("?expanded=uuid")
@@ -35,7 +33,7 @@ it("expands the details on space", async () => {
it("is expanded on load when listed in the query string", () => {
history.push("?expanded=uuid")
render(
-
+
Hello
,
)
diff --git a/components/frontend/src/widgets/HyperLink.js b/components/frontend/src/widgets/HyperLink.js
index f63eb294de..dc08102421 100644
--- a/components/frontend/src/widgets/HyperLink.js
+++ b/components/frontend/src/widgets/HyperLink.js
@@ -13,6 +13,7 @@ export function HyperLink({ url, children }) {
target="_blank"
title="Opens new window or tab"
underline="always"
+ variant="inherit"
>
{children}
diff --git a/components/frontend/src/widgets/Label.js b/components/frontend/src/widgets/Label.js
new file mode 100644
index 0000000000..554c7e9204
--- /dev/null
+++ b/components/frontend/src/widgets/Label.js
@@ -0,0 +1,28 @@
+import { Box } from "@mui/material"
+import { string } from "prop-types"
+
+import { childrenPropType } from "../sharedPropTypes"
+
+export function Label({ color, children }) {
+ const bgcolor = `${color}.main`
+ const fgcolor = `${color}.contrastText`
+ return (
+
+ {children}
+
+ )
+}
+Label.propTypes = {
+ color: string,
+ children: childrenPropType,
+}
diff --git a/components/frontend/src/widgets/LabelWithDropdown.js b/components/frontend/src/widgets/LabelWithDropdown.js
index 52e16c3d01..04f79d6a92 100644
--- a/components/frontend/src/widgets/LabelWithDropdown.js
+++ b/components/frontend/src/widgets/LabelWithDropdown.js
@@ -1,29 +1,31 @@
+import { MenuItem, Select } from "@mui/material"
import { array, func, string } from "prop-types"
-import { Dropdown } from "../semantic_ui_react_wrappers"
-import { alignmentPropType, labelPropType } from "../sharedPropTypes"
+import { labelPropType } from "../sharedPropTypes"
-export function LabelWithDropdown({ color, direction, label, onChange, options, value }) {
+export function LabelWithDropdown({ label, onChange, options, value }) {
return (
{label}
-
-
-
+ onChange(event.target.value)}
+ value={value}
+ inputProps={{ sx: { paddingBottom: "2px", paddingTop: "2px" } }}
+ sx={{
+ color: options.find((option) => option.value === value).color,
+ marginLeft: "6px",
+ }}
+ >
+ {options.map((option) => (
+
+ {option.text}
+
+ ))}
+
)
}
LabelWithDropdown.propTypes = {
- color: string,
- direction: alignmentPropType,
label: labelPropType,
onChange: func,
options: array,
diff --git a/components/frontend/src/widgets/LabelWithDropdown.test.js b/components/frontend/src/widgets/LabelWithDropdown.test.js
index 6d38d0771b..6929dd4b07 100644
--- a/components/frontend/src/widgets/LabelWithDropdown.test.js
+++ b/components/frontend/src/widgets/LabelWithDropdown.test.js
@@ -1,80 +1,32 @@
-import { fireEvent, render, screen } from "@testing-library/react"
+import { render, screen } from "@testing-library/react"
+import userEvent from "@testing-library/user-event"
import { LabelWithDropdown } from "./LabelWithDropdown"
-it("shows the label", () => {
- render( )
- expect(screen.getByText(/Hello/)).not.toBe(null)
-})
-
-it("can be colored", () => {
- render(
- ,
- )
- expect(screen.getByRole("listbox")).toHaveAttribute("color", "red")
-})
-
-it("has default color black", () => {
- render(
- ,
- )
- expect(screen.getByRole("listbox")).not.toHaveAttribute("color")
-})
-
-it("changes the option", () => {
+function renderLabelWithDropdown() {
const mockCallback = jest.fn()
render(
,
)
- fireEvent.click(screen.getByText(/Option 2/))
- expect(mockCallback).toHaveBeenCalled()
+ return mockCallback
+}
+
+it("shows the label", () => {
+ renderLabelWithDropdown()
+ expect(screen.getByText(/Hello/)).not.toBe(null)
})
-it("opens the dropdown when clicking the current option", () => {
- const mockCallback = jest.fn()
- render(
- ,
- )
- expect(screen.getByRole("listbox")).toHaveAttribute("aria-expanded", "false")
- fireEvent.click(screen.getAllByText(/Option 1/)[0])
- expect(screen.getByRole("listbox")).toHaveAttribute("aria-expanded", "true")
+it("changes the option", async () => {
+ const mockCallback = renderLabelWithDropdown()
+ await userEvent.click(screen.getByText(/Option 1/))
+ await userEvent.click(screen.getByText(/Option 2/))
+ expect(mockCallback).toHaveBeenCalledWith("2")
})
diff --git a/components/frontend/src/widgets/LabelWithHelp.js b/components/frontend/src/widgets/LabelWithHelp.js
index 1d744eb063..48febb721e 100644
--- a/components/frontend/src/widgets/LabelWithHelp.js
+++ b/components/frontend/src/widgets/LabelWithHelp.js
@@ -1,20 +1,16 @@
import HelpIcon from "@mui/icons-material/Help"
-import { bool, string } from "prop-types"
+import { Tooltip } from "@mui/material"
+import { string } from "prop-types"
-import { Popup } from "../semantic_ui_react_wrappers"
import { labelPropType, popupContentPropType } from "../sharedPropTypes"
-export function LabelWithHelp({ labelId, labelFor, label, help, hoverable }) {
+export function LabelWithHelp({ labelId, labelFor, label, help }) {
return (
{label}{" "}
- }
- wide
- />
+
+
+
)
}
@@ -23,5 +19,4 @@ LabelWithHelp.propTypes = {
labelFor: string,
label: labelPropType,
help: popupContentPropType,
- hoverable: bool,
}
diff --git a/components/frontend/src/widgets/LabelWithHyperLink.js b/components/frontend/src/widgets/LabelWithHyperLink.js
index 954149b09f..b0ea07ffeb 100644
--- a/components/frontend/src/widgets/LabelWithHyperLink.js
+++ b/components/frontend/src/widgets/LabelWithHyperLink.js
@@ -9,7 +9,7 @@ export function LabelWithHyperLink({ labelId, label, url }) {
{label}{" "}
-
+
)
diff --git a/components/frontend/src/widgets/ReadTheDocsLink.js b/components/frontend/src/widgets/ReadTheDocsLink.js
index 87b6dde7b6..f0e8ecda5f 100644
--- a/components/frontend/src/widgets/ReadTheDocsLink.js
+++ b/components/frontend/src/widgets/ReadTheDocsLink.js
@@ -1,14 +1,9 @@
-import HelpIcon from "@mui/icons-material/Help"
import { string } from "prop-types"
import { HyperLink } from "./HyperLink"
export function ReadTheDocsLink({ url }) {
- return (
-
- Read the Docs
-
- )
+ return Read the Docs
}
ReadTheDocsLink.propTypes = {
url: string,
diff --git a/components/frontend/src/widgets/TabPane.js b/components/frontend/src/widgets/TabPane.js
index 5d578bd426..d45f03002e 100644
--- a/components/frontend/src/widgets/TabPane.js
+++ b/components/frontend/src/widgets/TabPane.js
@@ -7,13 +7,14 @@ import { useContext } from "react"
import { Menu } from "semantic-ui-react"
import { DarkMode } from "../context/DarkMode"
-import { Label, Tab } from "../semantic_ui_react_wrappers"
+import { Tab } from "../semantic_ui_react_wrappers"
+import { Label } from "./Label"
function FocusableTab({ error, icon, image, label, warning }) {
const className = useContext(DarkMode) ? "tabbutton inverted" : "tabbutton"
let tabLabel = label
if (error || warning) {
- const color = error ? "red" : "yellow"
+ const color = error ? "error" : "warning"
tabLabel = {label}
}
return (
diff --git a/components/frontend/src/widgets/TabPane.test.js b/components/frontend/src/widgets/TabPane.test.js
index 8587d2c9bd..74274890f7 100644
--- a/components/frontend/src/widgets/TabPane.test.js
+++ b/components/frontend/src/widgets/TabPane.test.js
@@ -21,12 +21,12 @@ it("is inverted in dark mode", () => {
it("shows the tab red when there is an error", () => {
render(Pane, { error: true })]} />)
- expect(screen.getByText("Tab").className).toEqual(expect.stringContaining("red"))
+ expect(screen.getByText("Tab").className).toEqual(expect.stringContaining("error"))
})
it("shows the tab yellow when there is a warning", () => {
render(Pane, { warning: true })]} />)
- expect(screen.getByText("Tab").className).toEqual(expect.stringContaining("yellow"))
+ expect(screen.getByText("Tab").className).toEqual(expect.stringContaining("warning"))
})
it("shows an icon", () => {
diff --git a/components/frontend/src/widgets/TableHeaderCell.js b/components/frontend/src/widgets/TableHeaderCell.js
index f861f89f97..aa2fd776a5 100644
--- a/components/frontend/src/widgets/TableHeaderCell.js
+++ b/components/frontend/src/widgets/TableHeaderCell.js
@@ -1,7 +1,6 @@
-import { Tooltip } from "@mui/material"
+import { TableCell, TableSortLabel, Tooltip } from "@mui/material"
import { func, string } from "prop-types"
-import { Table } from "../semantic_ui_react_wrappers"
import {
alignmentPropType,
labelPropType,
@@ -13,7 +12,7 @@ import {
function TableHeaderCellContents({ help, label }) {
return help ? (
- {label}
+ {label}
) : (
label
@@ -24,6 +23,10 @@ TableHeaderCellContents.propTypes = {
label: labelPropType,
}
+function MuiSortDirection(sortDirection) {
+ return sortDirection === "ascending" ? "asc" : "desc"
+}
+
export function SortableTableHeaderCell({
colSpan,
column,
@@ -34,16 +37,17 @@ export function SortableTableHeaderCell({
textAlign,
help,
}) {
- const sorted = sortColumn.value === column ? sortDirection.value : null
+ const sorted = sortColumn.value === column ? MuiSortDirection(sortDirection.value) : null
return (
- handleSort(column)}
- sorted={sorted}
- textAlign={textAlign || "left"}
- >
-
-
+
+ handleSort(column)}
+ >
+
+
+
)
}
SortableTableHeaderCell.propTypes = {
@@ -59,9 +63,9 @@ SortableTableHeaderCell.propTypes = {
export function UnsortableTableHeaderCell({ help, label, textAlign, width }) {
return (
-
+
-
+
)
}
UnsortableTableHeaderCell.propTypes = {
diff --git a/components/frontend/src/widgets/TableHeaderCell.test.js b/components/frontend/src/widgets/TableHeaderCell.test.js
index 40b05e8df2..72e793885f 100644
--- a/components/frontend/src/widgets/TableHeaderCell.test.js
+++ b/components/frontend/src/widgets/TableHeaderCell.test.js
@@ -1,6 +1,6 @@
+import { Table, TableHead, TableRow } from "@mui/material"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
-import { Table } from "semantic-ui-react"
import { createTestableSettings } from "../__fixtures__/fixtures"
import { SortableTableHeaderCell, UnsortableTableHeaderCell } from "./TableHeaderCell"
@@ -9,16 +9,16 @@ function renderSortableTableHeaderCell(help) {
const settings = createTestableSettings()
render(
,
)
}
@@ -39,11 +39,11 @@ it("shows the help of the sortable header", async () => {
function renderUnsortableTableHeaderCell(help) {
render(
,
)
}
diff --git a/components/frontend/src/widgets/TableRowWithDetails.js b/components/frontend/src/widgets/TableRowWithDetails.js
index c805f59a48..de0a799922 100644
--- a/components/frontend/src/widgets/TableRowWithDetails.js
+++ b/components/frontend/src/widgets/TableRowWithDetails.js
@@ -1,23 +1,23 @@
-import { bool, func, object } from "prop-types"
+import { TableCell, TableRow } from "@mui/material"
+import { bool, func } from "prop-types"
-import { Table } from "../semantic_ui_react_wrappers"
import { childrenPropType } from "../sharedPropTypes"
import { ExpandButton } from "./buttons/ExpandButton"
export function TableRowWithDetails(props) {
- const { children, details, expanded, onExpand, style, ...otherProps } = props
+ const { children, details, expanded, onExpand, ...otherProps } = props
return (
<>
-
-
+
+
onExpand(!expanded)} size="1.5em" />
-
+
{children}
-
+
{expanded && (
-
- {details}
-
+
+ {details}
+
)}
>
)
@@ -27,5 +27,4 @@ TableRowWithDetails.propTypes = {
details: childrenPropType,
expanded: bool,
onExpand: func,
- style: object,
}
diff --git a/components/frontend/src/widgets/TableRowWithDetails.test.js b/components/frontend/src/widgets/TableRowWithDetails.test.js
index 5bcac28cb3..72da7d5376 100644
--- a/components/frontend/src/widgets/TableRowWithDetails.test.js
+++ b/components/frontend/src/widgets/TableRowWithDetails.test.js
@@ -1,15 +1,15 @@
+import { Table, TableBody } from "@mui/material"
import { fireEvent, render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
-import { Table } from "semantic-ui-react"
import { TableRowWithDetails } from "./TableRowWithDetails"
function renderTableRowWithDetails(expanded, onExpand) {
render(
,
)
}
diff --git a/components/frontend/src/widgets/WarningMessage.js b/components/frontend/src/widgets/WarningMessage.js
index 86dea71c07..18de1cfe31 100644
--- a/components/frontend/src/widgets/WarningMessage.js
+++ b/components/frontend/src/widgets/WarningMessage.js
@@ -1,21 +1,40 @@
-import { bool } from "prop-types"
+import { Alert, AlertTitle } from "@mui/material"
+import { bool, string } from "prop-types"
-import { Message } from "../semantic_ui_react_wrappers"
+import { childrenPropType } from "../sharedPropTypes"
-export function WarningMessage(props) {
+export function WarningMessage({ children, title, showIf }) {
// Show a warning message if showIf is true or undefined
- const { showIf, ...messageProps } = props
- return (showIf ?? true) ? : null
+ return (showIf ?? true) ? (
+
+ {title}
+ {children}
+
+ ) : null
}
WarningMessage.propTypes = {
+ children: childrenPropType,
showIf: bool,
+ title: string,
}
export function FailedToLoadMeasurementsWarningMessage() {
return (
-
+
+ Loading the measurements from the API-server failed.
+
)
}
+
+export function InfoMessage({ children, title }) {
+ return (
+
+ {title}
+ {children}
+
+ )
+}
+InfoMessage.propTypes = {
+ children: childrenPropType,
+ title: string,
+}
diff --git a/components/frontend/src/widgets/WarningMessage.test.js b/components/frontend/src/widgets/WarningMessage.test.js
index bc859c756f..cfc5fb9ab7 100644
--- a/components/frontend/src/widgets/WarningMessage.test.js
+++ b/components/frontend/src/widgets/WarningMessage.test.js
@@ -3,16 +3,16 @@ import { render, screen } from "@testing-library/react"
import { WarningMessage } from "./WarningMessage"
it("shows a warning message if showIf is true", () => {
- render( )
+ render(Warning )
expect(screen.getAllByText("Warning").length).toBe(1)
})
it("does not show a warning message if showIf is false", () => {
- render( )
+ render(Warning )
expect(screen.queryAllByText("Warning").length).toBe(0)
})
it("shows a warning message if showIf is undefined", () => {
- render( )
+ render(Warning )
expect(screen.getAllByText("Warning").length).toBe(1)
})
diff --git a/components/frontend/src/widgets/icons.js b/components/frontend/src/widgets/icons.js
index 3d5fcb39cf..55fa0fe45f 100644
--- a/components/frontend/src/widgets/icons.js
+++ b/components/frontend/src/widgets/icons.js
@@ -42,7 +42,7 @@ export function DeleteItemIcon() {
}
export function IgnoreIcon() {
- return
+ return
}
export function MoveItemIcon() {
diff --git a/tests/application_tests/src/test_report.py b/tests/application_tests/src/test_report.py
index 35466abc4f..f39aeaba57 100644
--- a/tests/application_tests/src/test_report.py
+++ b/tests/application_tests/src/test_report.py
@@ -45,6 +45,7 @@ class OpenReportTest(unittest.TestCase):
# Class names of MUI-components used in the tests
DASHBOARD_CARD_CLASS_NAME = "MuiCard-root"
DASHBOARD_CARD_HEADER_CONTENT_CLASS_NAME = "MuiCardHeader-content"
+ REPORT_HEADER_CLASS_NAME = "MuiAccordionSummary-content"
def setUp(self):
"""Override to setup the driver."""
@@ -84,9 +85,8 @@ def test_open_report(self):
report = self.dashboard_cards()[-1] # The last card is a report
report_title = report.find_element(By.CLASS_NAME, self.DASHBOARD_CARD_HEADER_CONTENT_CLASS_NAME)
report.click()
- self.assertTrue(
- expect.text_to_be_present_in_element(self.driver.find_element(By.CLASS_NAME, "header"), report_title)
- )
+ report_header = self.driver.find_element(By.CLASS_NAME, self.REPORT_HEADER_CLASS_NAME)
+ self.assertTrue(expect.text_to_be_present_in_element(report_header, report_title))
def test_login_and_logout(self):
"""Test that a user can login and logout."""