+ 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} - ?
+
+
+
+
+
+
)
}
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 = (
-
+ 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..18b05dabf6 100644
--- a/components/frontend/src/measurement/MeasurementSources.test.js
+++ b/components/frontend/src/measurement/MeasurementSources.test.js
@@ -37,5 +37,5 @@ it("renders multiple measurement sources", () => {
/>
,
)
- expect(screen.getAllByText(/Source name 1, Source name 2/).length).toBe(1)
+ expect(screen.getAllByText(/Source name 1.*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}
+ 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..0566fdfb52 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
+ 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/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..e3d1d0f756 100644
--- a/components/frontend/src/measurement/SourceStatus.js
+++ b/components/frontend/src/measurement/SourceStatus.js
@@ -1,10 +1,11 @@
+import { Tooltip, Typography } 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)
@@ -17,7 +18,9 @@ export function SourceStatus({ metric, measurement_source }) {
const configError = !dataModel.metrics[metric.type].sources.includes(source.type)
function source_label() {
return measurement_source.landing_url ? (
- {source_name}
+
+ {source_name}
+
) : (
source_name
)
@@ -36,13 +39,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/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 =
+ 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..884cc8c861 100644
--- a/components/frontend/src/metric/MetricConfigurationParameters.js
+++ b/components/frontend/src/metric/MetricConfigurationParameters.js
@@ -1,6 +1,7 @@
+import Grid from "@mui/material/Grid2"
import { func, string } from "prop-types"
import { useContext } from "react"
-import { Grid, Header } from "semantic-ui-react"
+import { Header } from "semantic-ui-react"
import { set_metric_attribute } from "../api/metric"
import { DataModel } from "../context/DataModel"
@@ -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/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 (
)
}
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..c91e17e9a4 100644
--- a/components/frontend/src/report/IssueTracker.js
+++ b/components/frontend/src/report/IssueTracker.js
@@ -1,6 +1,8 @@
+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 { Header } from "semantic-ui-react"
import { get_report_issue_tracker_options, set_report_issue_tracker_attribute } from "../api/report"
import { DataModel } from "../context/DataModel"
@@ -104,111 +106,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..dc61a8c791 100644
--- a/components/frontend/src/report/Report.js
+++ b/components/frontend/src/report/Report.js
@@ -1,6 +1,6 @@
import { func } from "prop-types"
-import { ExportCard } from "../dashboard/ExportCard"
+import { PageHeader } from "../dashboard/PageHeader"
import {
datePropType,
datesPropType,
@@ -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,12 +42,17 @@ 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 (
+
-
- {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 (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
}
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..dd38230501 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"
@@ -63,20 +63,15 @@ 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))
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..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/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.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/SourceEntityDetails.js b/components/frontend/src/source/SourceEntityDetails.js
index 632babc659..f90240114d 100644
--- a/components/frontend/src/source/SourceEntityDetails.js
+++ b/components/frontend/src/source/SourceEntityDetails.js
@@ -1,5 +1,6 @@
+import Grid from "@mui/material/Grid2"
import { func, node, oneOf, string } from "prop-types"
-import { Grid, Header } from "semantic-ui-react"
+import { Header } from "semantic-ui-react"
import { set_source_entity_attribute } from "../api/source"
import { EDIT_ENTITY_PERMISSION } from "../context/Permissions"
@@ -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/SourceParameters.js b/components/frontend/src/source/SourceParameters.js
index fc3ac78908..bdd9892f79 100644
--- a/components/frontend/src/source/SourceParameters.js
+++ b/components/frontend/src/source/SourceParameters.js
@@ -1,6 +1,6 @@
+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"
@@ -76,19 +76,19 @@ export function SourceParameters({ changed_param_keys, metric, reload, report, s
))
return (
-
+
{parameters}
-
+
)
})
return (
-
- {groups}
+
+ {groups}
)
}
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/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/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/utils.js b/components/frontend/src/utils.js
index c70554c64c..ef1cb76eb3 100644
--- a/components/frontend/src/utils.js
+++ b/components/frontend/src/utils.js
@@ -19,7 +19,6 @@ export const MILLISECONDS_PER_HOUR = 60 * 60 * 1000
const MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR
export const ISSUE_STATUS_COLORS = { todo: "grey", doing: "blue", done: "green", unknown: null }
-export const ISSUE_STATUS_THEME_COLORS = { todo: "grey", doing: "info", done: "success", unknown: "" }
export function getMetricDirection(metric, dataModel) {
// Old versions of the data model may contain the unicode version of the direction, be prepared:
diff --git a/components/frontend/src/widgets/CommentSegment.js b/components/frontend/src/widgets/CommentSegment.js
index 45526d3137..58a1dd8fd6 100644
--- a/components/frontend/src/widgets/CommentSegment.js
+++ b/components/frontend/src/widgets/CommentSegment.js
@@ -1,13 +1,12 @@
+import { Box } from "@mui/material"
import { string } from "prop-types"
-import { Segment } from "../semantic_ui_react_wrappers"
-
export function CommentSegment({ comment }) {
if (comment) {
return (
-
+
-
+
)
}
return null
diff --git a/components/frontend/src/widgets/Label.js b/components/frontend/src/widgets/Label.js
new file mode 100644
index 0000000000..5a65379d4a
--- /dev/null
+++ b/components/frontend/src/widgets/Label.js
@@ -0,0 +1,27 @@
+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/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..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() {