Skip to content

Commit

Permalink
Move the share buttons to the headers.
Browse files Browse the repository at this point in the history
Remove the share tabs for reports, subjects, and metrics and move the share button to the report, subject, and metric headers.

Closes #8821.
  • Loading branch information
fniessink committed Jun 19, 2024
1 parent ed14358 commit 9fb7f09
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 169 deletions.
21 changes: 5 additions & 16 deletions components/frontend/src/metric/MetricDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,15 +19,15 @@ 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"
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 (
<ReadOnlyOrEditable
requiredPermissions={[EDIT_REPORT_PERMISSION]}
Expand All @@ -44,6 +43,7 @@ function Buttons({ isFirstMetric, isLastMetric, metric_uuid, reload, stopFilteri
set_metric_attribute(metric_uuid, "position", direction, reload)
}}
/>
<PermLinkButton itemType="metric" url={url} />
<DeleteButton itemType="metric" onClick={() => delete_metric(metric_uuid, reload)} />
</div>
}
Expand All @@ -56,6 +56,7 @@ Buttons.propTypes = {
metric_uuid: string,
reload: func,
stopFilteringAndSorting: func,
url: string,
}

function fetchMeasurements(reportDate, metric_uuid, setMeasurements) {
Expand Down Expand Up @@ -176,19 +177,6 @@ export function MetricDetails({
</Tab.Pane>
),
},
{
menuItem: (
<Menu.Item key="share">
<Icon name="share square" />
<FocusableTab>{"Share"}</FocusableTab>
</Menu.Item>
),
render: () => (
<Tab.Pane>
<Share title="Metric permanent link" url={metricUrl} />
</Tab.Pane>
),
},
)
if (measurements.length > 0) {
if (getMetricScale(metric, dataModel) !== "version_number") {
Expand Down Expand Up @@ -251,6 +239,7 @@ export function MetricDetails({
isLastMetric={isLastMetric}
reload={reload}
stopFilteringAndSorting={stopFilteringAndSorting}
url={metricUrl}
/>
</>
)
Expand Down
13 changes: 5 additions & 8 deletions components/frontend/src/metric/MetricDetails.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
22 changes: 5 additions & 17 deletions components/frontend/src/report/ReportTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -284,7 +283,7 @@ ReactionTimes.propTypes = {
report: reportPropType,
}

function ButtonRow({ report_uuid, openReportsOverview }) {
function ButtonRow({ report_uuid, openReportsOverview, url }) {
return (
<ReadOnlyOrEditable
requiredPermissions={[EDIT_REPORT_PERMISSION]}
Expand All @@ -296,6 +295,7 @@ function ButtonRow({ report_uuid, openReportsOverview }) {
*/
style={{ height: "36px", width: "100%", display: "block" }}
>
<PermLinkButton itemType="report" url={url} />
<DeleteButton itemType="report" onClick={() => delete_report(report_uuid, openReportsOverview)} />
</span>
}
Expand All @@ -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 }) {
Expand Down Expand Up @@ -381,19 +382,6 @@ export function ReportTitle({ report, openReportsOverview, reload, settings }) {
</Tab.Pane>
),
},
{
menuItem: (
<Menu.Item key="share">
<Icon name="share square" />
<FocusableTab>{"Share"}</FocusableTab>
</Menu.Item>
),
render: () => (
<Tab.Pane>
<Share title="Report permanent link" url={reportUrl} />
</Tab.Pane>
),
},
]
setDocumentTitle(report.title)

Expand All @@ -411,7 +399,7 @@ export function ReportTitle({ report, openReportsOverview, reload, settings }) {
panes={panes}
/>
<div style={{ marginTop: "20px" }}>
<ButtonRow report_uuid={report_uuid} openReportsOverview={openReportsOverview} />
<ButtonRow report_uuid={report_uuid} openReportsOverview={openReportsOverview} url={reportUrl} />
</div>
</HeaderWithDetails>
)
Expand Down
7 changes: 0 additions & 7 deletions components/frontend/src/report/ReportTitle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/))
Expand Down
17 changes: 0 additions & 17 deletions components/frontend/src/share/Share.js

This file was deleted.

21 changes: 5 additions & 16 deletions components/frontend/src/subject/SubjectTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -37,7 +36,7 @@ SubjectHeader.propTypes = {
subjectType: object,
}

function ButtonRow({ subject_uuid, firstSubject, lastSubject, reload }) {
function ButtonRow({ subject_uuid, firstSubject, lastSubject, reload, url }) {
return (
<ReadOnlyOrEditable
requiredPermissions={[EDIT_REPORT_PERMISSION]}
Expand All @@ -51,6 +50,7 @@ function ButtonRow({ subject_uuid, firstSubject, lastSubject, reload }) {
set_subject_attribute(subject_uuid, "position", direction, reload)
}}
/>
<PermLinkButton itemType="subject" url={url} />
<DeleteButton itemType="subject" onClick={() => delete_subject(subject_uuid, reload)} />
</>
}
Expand All @@ -62,6 +62,7 @@ ButtonRow.propTypes = {
firstSubject: bool,
lastSubject: bool,
reload: func,
url: string,
}

export function SubjectTitle({
Expand Down Expand Up @@ -112,19 +113,6 @@ export function SubjectTitle({
</Tab.Pane>
),
},
{
menuItem: (
<Menu.Item key="share">
<Icon name="share square" />
<FocusableTab>{"Share"}</FocusableTab>
</Menu.Item>
),
render: () => (
<Tab.Pane>
<Share title="Subject permanent link" url={subjectUrl} />
</Tab.Pane>
),
},
]

return (
Expand All @@ -149,6 +137,7 @@ export function SubjectTitle({
firstSubject={firstSubject}
lastSubject={lastSubject}
reload={reload}
url={subjectUrl}
/>
</div>
</HeaderWithDetails>
Expand Down
8 changes: 0 additions & 8 deletions components/frontend/src/subject/SubjectTitle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
71 changes: 22 additions & 49 deletions components/frontend/src/widgets/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -296,7 +296,7 @@ ReorderButton.propTypes = {

export function ReorderButtonGroup(props) {
return (
<Button.Group style={{ marginTop: "0px" }}>
<Button.Group style={{ marginTop: "0px", marginRight: "5px" }}>
<ReorderButton {...props} direction="first" />
<ReorderButton {...props} direction="previous" />
<ReorderButton {...props} direction="next" />
Expand Down Expand Up @@ -364,59 +364,32 @@ export function MoveButton(props) {
return <ActionAndItemPickerButton {...props} action="Move" icon="shuffle" />
}

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 (
<Button
as="div"
labelPosition="right"
onClick={() =>
navigator.clipboard
.writeText(url)
.then(() => showMessage("success", "Copied URL to clipboard"))
.catch((error) => showMessage("error", "Could not copy URL to clipboard", `${error}`))
<Popup
content={`Copy a permanent link to this ${itemType} to the clipboard`}
trigger={
<Button
basic
content="Share"
icon="share square"
onClick={() =>
navigator.clipboard
.writeText(url)
.then(() => showMessage("success", "Copied URL to clipboard"))
.catch((error) => showMessage("error", "Could not copy URL to clipboard", `${error}`))
}
primary
/>
}
>
<Button basic content="Copy" icon="copy" primary />
<Label as="a" color="blue">
{url}
</Label>
</Button>
)
} 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 (
<Input action actionPosition="left" color="blue" defaultValue={url} fluid readOnly>
<Button
basic
color="blue"
content="Copy"
icon="copy"
onClick={() => {
let urlText = document.querySelector("#permlink")
urlText.select()
document.execCommand("copy")
showMessage("success", "Copied URL to clipboard")
}}
style={{ fontWeight: "bold" }}
/>
<input
data-testid="permlink"
id="permlink"
style={{
border: "1px solid rgb(143, 208, 255)",
color: "rgb(143, 208, 255)",
fontWeight: "bold",
}}
/>
</Input>
/>
)
}
return null
}
PermLinkButton.propTypes = {
itemType: string,
url: string,
}
Loading

0 comments on commit 9fb7f09

Please sign in to comment.