Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move the share buttons to the headers. #9016

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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