diff --git a/components/frontend/src/metric/MetricDetails.js b/components/frontend/src/metric/MetricDetails.js index 9d0a071775..1e1420bf5e 100644 --- a/components/frontend/src/metric/MetricDetails.js +++ b/components/frontend/src/metric/MetricDetails.js @@ -9,7 +9,6 @@ import { ChangeLog } from "../changelog/ChangeLog" import { DataModel } from "../context/DataModel" import { EDIT_REPORT_PERMISSION, ReadOnlyOrEditable } from "../context/Permissions" import { Label, Tab } from "../semantic_ui_react_wrappers" -import { Share } from "../share/Share" import { datePropType, reportPropType, @@ -20,7 +19,7 @@ import { import { SourceEntities } from "../source/SourceEntities" import { Sources } from "../source/Sources" import { getMetricScale, getSourceName } from "../utils" -import { DeleteButton, ReorderButtonGroup } from "../widgets/Button" +import { DeleteButton, PermLinkButton, ReorderButtonGroup } from "../widgets/Button" import { FocusableTab } from "../widgets/FocusableTab" import { showMessage } from "../widgets/toast" import { MetricConfigurationParameters } from "./MetricConfigurationParameters" @@ -28,7 +27,7 @@ import { MetricDebtParameters } from "./MetricDebtParameters" import { MetricTypeHeader } from "./MetricTypeHeader" import { TrendGraph } from "./TrendGraph" -function Buttons({ isFirstMetric, isLastMetric, metric_uuid, reload, stopFilteringAndSorting }) { +function Buttons({ isFirstMetric, isLastMetric, metric_uuid, reload, stopFilteringAndSorting, url }) { return ( + delete_metric(metric_uuid, reload)} /> } @@ -56,6 +56,7 @@ Buttons.propTypes = { metric_uuid: string, reload: func, stopFilteringAndSorting: func, + url: string, } function fetchMeasurements(reportDate, metric_uuid, setMeasurements) { @@ -176,19 +177,6 @@ export function MetricDetails({ ), }, - { - menuItem: ( - - - {"Share"} - - ), - render: () => ( - - - - ), - }, ) if (measurements.length > 0) { if (getMetricScale(metric, dataModel) !== "version_number") { @@ -251,6 +239,7 @@ export function MetricDetails({ isLastMetric={isLastMetric} reload={reload} stopFilteringAndSorting={stopFilteringAndSorting} + url={metricUrl} /> ) diff --git a/components/frontend/src/metric/MetricDetails.test.js b/components/frontend/src/metric/MetricDetails.test.js index b3a556e796..e9e4ef295c 100644 --- a/components/frontend/src/metric/MetricDetails.test.js +++ b/components/frontend/src/metric/MetricDetails.test.js @@ -141,18 +141,15 @@ it("does not show the trend graph tab if the metric scale is version number", as expect(screen.queryAllByText(/Trend graph/).length).toBe(0) }) -it("switches tabs to the share tab", async () => { - await renderMetricDetails() - expect(screen.getAllByText(/Metric name/).length).toBe(1) - fireEvent.click(screen.getByText(/Share/)) - expect(screen.getAllByText(/Metric permanent link/).length).toBe(1) -}) - it("removes the existing hashtag from the URL to share", async () => { history.push("#hash_that_should_be_removed") + Object.assign(window, { isSecureContext: true }) + Object.assign(navigator, { + clipboard: { writeText: jest.fn().mockImplementation(() => Promise.resolve()) }, + }) await renderMetricDetails() fireEvent.click(screen.getByText(/Share/)) - expect(screen.getByTestId("permlink").value).toBe("http://localhost/#metric_uuid") + expect(navigator.clipboard.writeText).toHaveBeenCalledWith("http://localhost/#metric_uuid") }) it("displays whether sources have errors", async () => { diff --git a/components/frontend/src/report/ReportTitle.js b/components/frontend/src/report/ReportTitle.js index 75c8e3f3ce..66ce3d7335 100644 --- a/components/frontend/src/report/ReportTitle.js +++ b/components/frontend/src/report/ReportTitle.js @@ -11,10 +11,9 @@ import { StringInput } from "../fields/StringInput" import { STATUS_DESCRIPTION, STATUS_NAME } from "../metric/status" import { NotificationDestinations } from "../notification/NotificationDestinations" import { Label, Segment, Tab } from "../semantic_ui_react_wrappers" -import { Share } from "../share/Share" import { reportPropType, settingsPropType } from "../sharedPropTypes" import { getDesiredResponseTime } from "../utils" -import { DeleteButton } from "../widgets/Button" +import { DeleteButton, PermLinkButton } from "../widgets/Button" import { FocusableTab } from "../widgets/FocusableTab" import { HeaderWithDetails } from "../widgets/HeaderWithDetails" import { LabelWithHelp } from "../widgets/LabelWithHelp" @@ -284,7 +283,7 @@ ReactionTimes.propTypes = { report: reportPropType, } -function ButtonRow({ report_uuid, openReportsOverview }) { +function ButtonRow({ report_uuid, openReportsOverview, url }) { return ( + delete_report(report_uuid, openReportsOverview)} /> } @@ -305,6 +305,7 @@ function ButtonRow({ report_uuid, openReportsOverview }) { ButtonRow.propTypes = { report_uuid: string, openReportsOverview: func, + url: string, } export function ReportTitle({ report, openReportsOverview, reload, settings }) { @@ -381,19 +382,6 @@ export function ReportTitle({ report, openReportsOverview, reload, settings }) { ), }, - { - menuItem: ( - - - {"Share"} - - ), - render: () => ( - - - - ), - }, ] setDocumentTitle(report.title) @@ -411,7 +399,7 @@ export function ReportTitle({ report, openReportsOverview, reload, settings }) { panes={panes} />
- +
) diff --git a/components/frontend/src/report/ReportTitle.test.js b/components/frontend/src/report/ReportTitle.test.js index 1a5a26e604..64494f9548 100644 --- a/components/frontend/src/report/ReportTitle.test.js +++ b/components/frontend/src/report/ReportTitle.test.js @@ -299,13 +299,6 @@ it("loads the changelog", async () => { expect(changelog_api.get_changelog).toHaveBeenCalledWith(5, { report_uuid: "report_uuid" }) }) -it("shows the share tab", () => { - renderReportTitle() - fireEvent.click(screen.getByTitle(/expand/)) - fireEvent.click(screen.getByText(/Share/)) - expect(screen.getAllByText(/Report permanent link/).length).toBe(1) -}) - it("shows the notification destinations", () => { renderReportTitle() fireEvent.click(screen.getByTitle(/expand/)) diff --git a/components/frontend/src/share/Share.js b/components/frontend/src/share/Share.js deleted file mode 100644 index a51ba43786..0000000000 --- a/components/frontend/src/share/Share.js +++ /dev/null @@ -1,17 +0,0 @@ -import { string } from "prop-types" - -import { Header } from "../semantic_ui_react_wrappers" -import { PermLinkButton } from "../widgets/Button" - -export function Share({ title, url }) { - return ( - <> -
{title}
- - - ) -} -Share.propTypes = { - title: string, - url: string, -} diff --git a/components/frontend/src/subject/SubjectTitle.js b/components/frontend/src/subject/SubjectTitle.js index c3b99686bf..54281f8684 100644 --- a/components/frontend/src/subject/SubjectTitle.js +++ b/components/frontend/src/subject/SubjectTitle.js @@ -8,10 +8,9 @@ import { ChangeLog } from "../changelog/ChangeLog" import { DataModel } from "../context/DataModel" import { EDIT_REPORT_PERMISSION, ReadOnlyOrEditable } from "../context/Permissions" import { Header, Tab } from "../semantic_ui_react_wrappers" -import { Share } from "../share/Share" import { reportPropType, settingsPropType } from "../sharedPropTypes" import { getSubjectType, slugify } from "../utils" -import { DeleteButton, ReorderButtonGroup } from "../widgets/Button" +import { DeleteButton, PermLinkButton, ReorderButtonGroup } from "../widgets/Button" import { FocusableTab } from "../widgets/FocusableTab" import { HeaderWithDetails } from "../widgets/HeaderWithDetails" import { HyperLink } from "../widgets/HyperLink" @@ -37,7 +36,7 @@ SubjectHeader.propTypes = { subjectType: object, } -function ButtonRow({ subject_uuid, firstSubject, lastSubject, reload }) { +function ButtonRow({ subject_uuid, firstSubject, lastSubject, reload, url }) { return ( + delete_subject(subject_uuid, reload)} /> } @@ -62,6 +62,7 @@ ButtonRow.propTypes = { firstSubject: bool, lastSubject: bool, reload: func, + url: string, } export function SubjectTitle({ @@ -112,19 +113,6 @@ export function SubjectTitle({ ), }, - { - menuItem: ( - - - {"Share"} - - ), - render: () => ( - - - - ), - }, ] return ( @@ -149,6 +137,7 @@ export function SubjectTitle({ firstSubject={firstSubject} lastSubject={lastSubject} reload={reload} + url={subjectUrl} /> diff --git a/components/frontend/src/subject/SubjectTitle.test.js b/components/frontend/src/subject/SubjectTitle.test.js index 41ed1b2b73..0110b9db48 100644 --- a/components/frontend/src/subject/SubjectTitle.test.js +++ b/components/frontend/src/subject/SubjectTitle.test.js @@ -105,14 +105,6 @@ it("loads the changelog", async () => { expect(fetch_server_api.fetch_server_api).toHaveBeenCalledWith("get", "changelog/subject/subject_uuid/5") }) -it("shows the share tab", async () => { - await renderSubjectTitle() - await act(async () => { - fireEvent.click(screen.getByText(/Share/)) - }) - expect(screen.getAllByText(/Subject permanent link/).length).toBe(1) -}) - it("moves the subject", async () => { fetch_server_api.fetch_server_api = jest.fn().mockResolvedValue({ ok: true }) await renderSubjectTitle() diff --git a/components/frontend/src/widgets/Button.js b/components/frontend/src/widgets/Button.js index 7d0da853d4..65334eb450 100644 --- a/components/frontend/src/widgets/Button.js +++ b/components/frontend/src/widgets/Button.js @@ -2,7 +2,7 @@ import { array, arrayOf, bool, func, string } from "prop-types" import { useState } from "react" import { Icon, Input } from "semantic-ui-react" -import { Button, Checkbox, Dropdown, Label, Popup } from "../semantic_ui_react_wrappers" +import { Button, Checkbox, Dropdown, Popup } from "../semantic_ui_react_wrappers" import { popupContentPropType } from "../sharedPropTypes" import { showMessage } from "../widgets/toast" import { ItemBreadcrumb } from "./ItemBreadcrumb" @@ -296,7 +296,7 @@ ReorderButton.propTypes = { export function ReorderButtonGroup(props) { return ( - + @@ -364,59 +364,32 @@ export function MoveButton(props) { return } -export function PermLinkButton({ url }) { - if (navigator.clipboard) { +export function PermLinkButton({ itemType, url }) { + if (window.isSecureContext) { // Frontend runs in a secure context (https) so we can use the Clipboard API return ( - - ) - } else { - // Frontend does not run in a secure context (https) so we cannot use the Clipboard API, and have - // to use the deprecated Document.execCommand. As document.exeCommand expects selected text, we also - // cannot use the Label component but have to use a (read only) input element so we can select the URL - // before copying it to the clipboard. - return ( - -