diff --git a/components/frontend/src/errorMessage.js b/components/frontend/src/errorMessage.js index 39a8e16017..bc931c900f 100644 --- a/components/frontend/src/errorMessage.js +++ b/components/frontend/src/errorMessage.js @@ -1,20 +1,19 @@ 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}
)} -
+
) diff --git a/components/frontend/src/issue/IssueStatus.js b/components/frontend/src/issue/IssueStatus.js index a31ec09b40..c9bce9fe96 100644 --- a/components/frontend/src/issue/IssueStatus.js +++ b/components/frontend/src/issue/IssueStatus.js @@ -1,7 +1,8 @@ +import { Tooltip } from "@mui/material" import { bool, string } from "prop-types" import TimeAgo from "react-timeago" -import { Label, Popup } from "../semantic_ui_react_wrappers" +import { Label } from "../semantic_ui_react_wrappers" import { issueStatusPropType, metricPropType, settingsPropType, stringsPropType } from "../sharedPropTypes" import { getMetricIssueIds, ISSUE_STATUS_COLORS } from "../utils" import { HyperLink } from "../widgets/HyperLink" @@ -9,13 +10,21 @@ 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={} - /> + > + + + + ) } IssueWithoutTracker.propTypes = { @@ -160,7 +169,18 @@ function IssueWithTracker({ issueStatus, settings }) { popupContent = issuePopupContent(issueStatus) } if (popupContent) { - label = + label = ( + +

{popupHeader}

+

{popupContent}

+ + } + > + {label} +
+ ) } return label } diff --git a/components/frontend/src/measurement/MeasurementTarget.js b/components/frontend/src/measurement/MeasurementTarget.js index 134e21d7dc..160cda74b2 100644 --- a/components/frontend/src/measurement/MeasurementTarget.js +++ b/components/frontend/src/measurement/MeasurementTarget.js @@ -1,7 +1,8 @@ +import { Tooltip } from "@mui/material" import { useContext } from "react" import { DataModel } from "../context/DataModel" -import { Label, Popup } from "../semantic_ui_react_wrappers" +import { Label } from "../semantic_ui_react_wrappers" import { metricPropType } from "../sharedPropTypes" import { formatMetricDirection, @@ -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} + const label = allIssuesDone || debtEndDateInThePast ? : 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..97471816dc 100644 --- a/components/frontend/src/measurement/MeasurementValue.js +++ b/components/frontend/src/measurement/MeasurementValue.js @@ -1,10 +1,11 @@ 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 { Label } 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 { @@ -30,14 +31,20 @@ function measurementValueLabel(hasIgnoredEntities, stale, updating, value) { value ) if (stale) { - return + return ( + + + + ) } if (updating) { return ( - + + + ) } 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/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..178375c5ce 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} + + } + > + + + +
) } else { return source_label() diff --git a/components/frontend/src/measurement/TimeLeft.js b/components/frontend/src/measurement/TimeLeft.js index d89e8ae5d4..e60a565a23 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 = + let trigger = 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..ba4dcb3099 100644 --- a/components/frontend/src/metric/MetricConfigurationParameters.js +++ b/components/frontend/src/metric/MetricConfigurationParameters.js @@ -228,7 +228,6 @@ export function MetricConfigurationParameters({ metric, metric_uuid, reload, rep {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/notification/NotificationDestinations.js b/components/frontend/src/notification/NotificationDestinations.js index 28ce09fc8e..2719af2d1e 100644 --- a/components/frontend/src/notification/NotificationDestinations.js +++ b/components/frontend/src/notification/NotificationDestinations.js @@ -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 = @@ -48,7 +48,6 @@ function NotificationDestination({ destination, destination_uuid, reload, report Paste a {teams_hyperlink} webhook URL here.} - hoverable /> } placeholder="https://example.webhook.office.com/webhook..." @@ -104,10 +103,9 @@ export function NotificationDestinations({ destinations, reload, report_uuid }) return ( <> {notification_destinations.length === 0 ? ( - - No notification destinations -

No notification destinations have been configured yet.

-
+ + No notification destinations have been configured yet. + ) : ( notification_destinations )} diff --git a/components/frontend/src/report/IssueTracker.js b/components/frontend/src/report/IssueTracker.js index 6c8a8c9169..1285521169 100644 --- a/components/frontend/src/report/IssueTracker.js +++ b/components/frontend/src/report/IssueTracker.js @@ -227,9 +227,10 @@ export function IssueTracker({ report, reload }) { /> + 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..7b15cd948b 100644 --- a/components/frontend/src/report/Report.js +++ b/components/frontend/src/report/Report.js @@ -15,8 +15,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,7 +42,11 @@ 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)) diff --git a/components/frontend/src/report/ReportErrorMessage.js b/components/frontend/src/report/ReportErrorMessage.js deleted file mode 100644 index fdc4373277..0000000000 --- a/components/frontend/src/report/ReportErrorMessage.js +++ /dev/null @@ -1,33 +0,0 @@ -import { string } from "prop-types" - -import { Message } from "../semantic_ui_react_wrappers" -import { datePropType, optionalDatePropType } from "../sharedPropTypes" - -function ErrorMessage({ children }) { - return ( - - {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.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..ea96f05aa1 100644 --- a/components/frontend/src/report/ReportsOverview.js +++ b/components/frontend/src/report/ReportsOverview.js @@ -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" @@ -63,7 +63,7 @@ export function ReportsOverview({ settings, }) { if (reports.length === 0 && report_date !== null) { - return + 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)) diff --git a/components/frontend/src/semantic_ui_react_wrappers.js b/components/frontend/src/semantic_ui_react_wrappers.js index 53c7959114..cd42422e4e 100644 --- a/components/frontend/src/semantic_ui_react_wrappers.js +++ b/components/frontend/src/semantic_ui_react_wrappers.js @@ -3,8 +3,6 @@ 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/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/source/SourceEntities.js b/components/frontend/src/source/SourceEntities.js index 15b44f2447..000e99464f 100644 --- a/components/frontend/src/source/SourceEntities.js +++ b/components/frontend/src/source/SourceEntities.js @@ -4,10 +4,9 @@ import HelpIcon from "@mui/icons-material/Help" import { IconButton, 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 { Table } from "../semantic_ui_react_wrappers" import { alignmentPropType, childrenPropType, @@ -25,7 +24,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) { @@ -144,16 +143,12 @@ function EntityAttributeHeaderCell({ entityAttribute, ...sortProps }) { > {entityAttribute.name} {entityAttribute.help ? ( - -   - - - } - content={entityAttribute.help} - /> + + +   + + + ) : null} ) @@ -270,10 +265,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 +278,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) 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/Sources.js b/components/frontend/src/source/Sources.js index 15a6a3b9a3..4e3064e848 100644 --- a/components/frontend/src/source/Sources.js +++ b/components/frontend/src/source/Sources.js @@ -4,7 +4,7 @@ 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 { 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" @@ -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/subject/SubjectTableRow.js b/components/frontend/src/subject/SubjectTableRow.js index d338f4a430..0b22e8b2f0 100644 --- a/components/frontend/src/subject/SubjectTableRow.js +++ b/components/frontend/src/subject/SubjectTableRow.js @@ -1,3 +1,4 @@ +import { Tooltip } from "@mui/material" import { bool, func, number, object, string } from "prop-types" import { useContext } from "react" @@ -12,7 +13,7 @@ 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 { Label, Table } from "../semantic_ui_react_wrappers" import { dataModelPropType, datePropType, @@ -131,14 +132,13 @@ function DeltaCell({ dateOrderAscending, index, metric, metricValue, previousVal const description = deltaDescription(dataModel, metric, scale, delta, improved, oldValue, newValue) const color = deltaColor(metric, improved) label = ( - + + + - } - /> + + ) } return ( diff --git a/components/frontend/src/widgets/Label.js b/components/frontend/src/widgets/Label.js new file mode 100644 index 0000000000..4cdf724227 --- /dev/null +++ b/components/frontend/src/widgets/Label.js @@ -0,0 +1,23 @@ +import { Box } from "@mui/material" +import { bool } from "prop-types" + +import { childrenPropType } from "../sharedPropTypes" + +export function Label({ error, children }) { + return ( + + {children} + + ) +} +Label.propTypes = { + children: childrenPropType, + error: bool, +} 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 ( ) } @@ -23,5 +19,4 @@ LabelWithHelp.propTypes = { labelFor: string, label: labelPropType, help: popupContentPropType, - hoverable: bool, } diff --git a/components/frontend/src/widgets/WarningMessage.js b/components/frontend/src/widgets/WarningMessage.js index 86dea71c07..088f026a5c 100644 --- a/components/frontend/src/widgets/WarningMessage.js +++ b/components/frontend/src/widgets/WarningMessage.js @@ -1,21 +1,41 @@ -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, showIf, title }) { + return (showIf ?? true) ? ( + + {title} + {children} + + ) : null +} +InfoMessage.propTypes = { + children: childrenPropType, + showIf: bool, + 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() {