Skip to content
Snippets Groups Projects
Unverified Commit d489654c authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Add additional dashboard info to sidesheet (#48078)

* create dashboard settings sidebar

* only show settings to dashboard editors

* add dashboard details

* update localization

* fix merged type

* fix mixed-up creation and editing
parent c04928d5
No related branches found
No related tags found
No related merge requests found
import cx from "classnames";
import { useState } from "react";
import { c, t } from "ttag";
import { skipToken, useGetUserQuery } from "metabase/api";
import { SidesheetCardSection } from "metabase/common/components/Sidesheet";
import DateTime from "metabase/components/DateTime";
import Link from "metabase/core/components/Link";
import Styles from "metabase/css/core/index.css";
import { getUserName } from "metabase/lib/user";
import { DashboardPublicLinkPopover } from "metabase/sharing/components/PublicLinkPopover";
import { Box, FixedSizeIcon, Flex, Text } from "metabase/ui";
import type { Dashboard } from "metabase-types/api";
import SidebarStyles from "./DashboardInfoSidebar.module.css";
export const DashboardDetails = ({ dashboard }: { dashboard: Dashboard }) => {
const lastEditInfo = dashboard["last-edit-info"];
const createdAt = dashboard.created_at;
// we don't hydrate creator user info on the dashboard object
const { data: creator } = useGetUserQuery(dashboard.creator_id ?? skipToken);
return (
<>
<SidesheetCardSection title={t`Creator and last editor`}>
{creator && (
<Flex gap="sm" align="top">
<FixedSizeIcon name="ai" className={SidebarStyles.IconMargin} />
<Text>
{c(
"Describes when a dashboard was created. {0} is a date/time and {1} is a person's name",
).jt`${(
<DateTime unit="day" value={createdAt} key="date" />
)} by ${getUserName(creator)}`}
</Text>
</Flex>
)}
{lastEditInfo && (
<Flex gap="sm" align="top">
<FixedSizeIcon name="pencil" className={SidebarStyles.IconMargin} />
<Text>
{c(
"Describes when a dashboard was last edited. {0} is a date/time and {1} is a person's name",
).jt`${(
<DateTime
unit="day"
value={lastEditInfo.timestamp}
key="date"
/>
)} by ${getUserName(lastEditInfo)}`}
</Text>
</Flex>
)}
</SidesheetCardSection>
<SidesheetCardSection
title={c(
"This is a heading that appears above the name of a collection - a collection that a dashboard is saved in. Feel free to translate this heading as though it said 'Saved in collection', if you think that would make more sense in your language.",
).t`Saved in`}
>
<Flex gap="sm" align="top">
<FixedSizeIcon
name="folder"
className={SidebarStyles.IconMargin}
color="var(--mb-color-brand)"
/>
<div>
<Text>
<Link
to={`/collection/${dashboard.collection_id}`}
variant="brand"
>
{dashboard.collection?.name}
</Link>
</Text>
</div>
</Flex>
</SidesheetCardSection>
<SharingDisplay dashboard={dashboard} />
</>
);
};
function SharingDisplay({ dashboard }: { dashboard: Dashboard }) {
const publicUUID = dashboard.public_uuid;
const embeddingEnabled = dashboard.enable_embedding;
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
if (!publicUUID && !embeddingEnabled) {
return null;
}
return (
<SidesheetCardSection title={t`Visibility`}>
{publicUUID && (
<Flex gap="sm" align="center">
<FixedSizeIcon name="globe" color="var(--mb-color-brand)" />
<Text>{t`Shared publicly`}</Text>
<DashboardPublicLinkPopover
target={
<FixedSizeIcon
name="link"
onClick={() => setIsPopoverOpen(prev => !prev)}
className={cx(
Styles.cursorPointer,
Styles.textBrandHover,
SidebarStyles.IconMargin,
)}
/>
}
isOpen={isPopoverOpen}
onClose={() => setIsPopoverOpen(false)}
dashboard={dashboard}
/>
</Flex>
)}
{embeddingEnabled && (
<Flex gap="sm" align="center">
<Box className={SidebarStyles.BrandCircle}>
<FixedSizeIcon name="embed" size="14px" />
</Box>
<Text>{t`Embedded`}</Text>
</Flex>
)}
</SidesheetCardSection>
);
}
...@@ -3,3 +3,16 @@ ...@@ -3,3 +3,16 @@
overflow: auto; overflow: auto;
line-height: 1.38rem; /* magic number to keep line-height from changing in edit mode */ line-height: 1.38rem; /* magic number to keep line-height from changing in edit mode */
} }
.BrandCircle {
background-color: var(--mb-color-brand);
color: var(--mb-color-text-white);
border-radius: 50%;
height: 1rem;
width: 1rem;
padding: 1px;
}
.IconMargin {
margin-top: 0.25rem;
}
...@@ -14,6 +14,7 @@ import SidesheetS from "metabase/common/components/Sidesheet/sidesheet.module.cs ...@@ -14,6 +14,7 @@ import SidesheetS from "metabase/common/components/Sidesheet/sidesheet.module.cs
import { Timeline } from "metabase/common/components/Timeline"; import { Timeline } from "metabase/common/components/Timeline";
import { getTimelineEvents } from "metabase/common/components/Timeline/utils"; import { getTimelineEvents } from "metabase/common/components/Timeline/utils";
import { useRevisionListQuery } from "metabase/common/hooks"; import { useRevisionListQuery } from "metabase/common/hooks";
import { EntityIdCard } from "metabase/components/EntityIdCard";
import EditableText from "metabase/core/components/EditableText"; import EditableText from "metabase/core/components/EditableText";
import { revertToRevision, updateDashboard } from "metabase/dashboard/actions"; import { revertToRevision, updateDashboard } from "metabase/dashboard/actions";
import { DASHBOARD_DESCRIPTION_MAX_LENGTH } from "metabase/dashboard/constants"; import { DASHBOARD_DESCRIPTION_MAX_LENGTH } from "metabase/dashboard/constants";
...@@ -22,6 +23,7 @@ import { getUser } from "metabase/selectors/user"; ...@@ -22,6 +23,7 @@ import { getUser } from "metabase/selectors/user";
import { Stack, Tabs, Text } from "metabase/ui"; import { Stack, Tabs, Text } from "metabase/ui";
import type { Dashboard, Revision, User } from "metabase-types/api"; import type { Dashboard, Revision, User } from "metabase-types/api";
import { DashboardDetails } from "./DashboardDetails";
import DashboardInfoSidebarS from "./DashboardInfoSidebar.module.css"; import DashboardInfoSidebarS from "./DashboardInfoSidebar.module.css";
interface DashboardInfoSidebarProps { interface DashboardInfoSidebarProps {
...@@ -174,6 +176,10 @@ const OverviewTab = ({ ...@@ -174,6 +176,10 @@ const OverviewTab = ({
</Text> </Text>
)} )}
</SidesheetCard> </SidesheetCard>
<SidesheetCard>
<DashboardDetails dashboard={dashboard} />
</SidesheetCard>
<EntityIdCard entityId={dashboard.entity_id} />
</Stack> </Stack>
); );
}; };
......
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { screen } from "__support__/ui"; import { screen } from "__support__/ui";
import { createMockDashboard } from "metabase-types/api/mocks"; import {
createMockCollection,
createMockDashboard,
} from "metabase-types/api/mocks";
import { setup } from "./setup"; import { setup } from "./setup";
...@@ -99,4 +102,69 @@ describe("DashboardInfoSidebar", () => { ...@@ -99,4 +102,69 @@ describe("DashboardInfoSidebar", () => {
expect(setDashboardAttribute).toHaveBeenCalledWith("description", ""); expect(setDashboardAttribute).toHaveBeenCalledWith("description", "");
}); });
it("should show last edited info", async () => {
await setup({
dashboard: createMockDashboard({
"last-edit-info": {
timestamp: "1793-09-22T00:00:00",
first_name: "Frodo",
last_name: "Baggins",
email: "dontlikejewelry@example.com",
id: 7,
},
}),
});
expect(screen.getByText("Creator and last editor")).toBeInTheDocument();
expect(screen.getByText("September 22, 1793")).toBeInTheDocument();
expect(screen.getByText("by Frodo Baggins")).toBeInTheDocument();
});
it("should show creator info", async () => {
await setup({
dashboard: createMockDashboard({
creator_id: 1,
"last-edit-info": {
timestamp: "1793-09-22T00:00:00",
first_name: "Frodo",
last_name: "Baggins",
email: "dontlikejewelry@example.com",
id: 7,
},
}),
});
expect(screen.getByText("Creator and last editor")).toBeInTheDocument();
expect(screen.getByText("January 1, 2024")).toBeInTheDocument();
expect(screen.getByText("by Testy Tableton")).toBeInTheDocument();
});
it("should show collection", async () => {
await setup({
dashboard: createMockDashboard({
collection: createMockCollection({
name: "My little collection ",
}),
}),
});
expect(screen.getByText("Saved in")).toBeInTheDocument();
expect(await screen.findByText("My little collection")).toBeInTheDocument();
});
it("should not show Visibility section when not shared publicly", async () => {
await setup();
expect(screen.queryByText("Visibility")).not.toBeInTheDocument();
});
it("should show Visibility section when dashboard has a public link", async () => {
await setup({ dashboard: createMockDashboard({ public_uuid: "123" }) });
expect(screen.getByText("Visibility")).toBeInTheDocument();
});
it("should show visibility section when embedding is enabled", async () => {
await setup({ dashboard: createMockDashboard({ enable_embedding: true }) });
expect(screen.getByText("Visibility")).toBeInTheDocument();
expect(screen.getByText("Embedded")).toBeInTheDocument();
});
}); });
...@@ -26,11 +26,24 @@ export const QuestionDetails = ({ question }: { question: Question }) => { ...@@ -26,11 +26,24 @@ export const QuestionDetails = ({ question }: { question: Question }) => {
return ( return (
<> <>
<SidesheetCardSection title={t`Creator and last editor`}> <SidesheetCardSection title={t`Creator and last editor`}>
<Flex gap="sm" align="top">
<Icon name="ai" className={SidebarStyles.IconMargin} />
<Text>
{c(
"Describes when a question was created. {0} is a date/time and {1} is a person's name",
).jt`${(
<DateTime unit="day" value={createdAt} key="date" />
)} by ${getUserName(createdBy)}`}
</Text>
</Flex>
{lastEditInfo && ( {lastEditInfo && (
<Flex gap="sm" align="top"> <Flex gap="sm" align="top">
<Icon name="ai" className={SidebarStyles.IconMargin} /> <Icon name="pencil" className={SidebarStyles.IconMargin} />
<Text> <Text>
{c("{0} is a date/time and {1} is a person's name").jt`${( {c(
"Describes when a question was last edited. {0} is a date/time and {1} is a person's name",
).jt`${(
<DateTime <DateTime
unit="day" unit="day"
value={lastEditInfo.timestamp} value={lastEditInfo.timestamp}
...@@ -40,15 +53,6 @@ export const QuestionDetails = ({ question }: { question: Question }) => { ...@@ -40,15 +53,6 @@ export const QuestionDetails = ({ question }: { question: Question }) => {
</Text> </Text>
</Flex> </Flex>
)} )}
<Flex gap="sm" align="top">
<Icon name="pencil" className={SidebarStyles.IconMargin} />
<Text>
{c("{0} is a date/time and {1} is a person's name").jt`${(
<DateTime unit="day" value={createdAt} key="date" />
)} by ${getUserName(createdBy)}`}
</Text>
</Flex>
</SidesheetCardSection> </SidesheetCardSection>
<SidesheetCardSection title={t`Saved in`}> <SidesheetCardSection title={t`Saved in`}>
<Flex gap="sm" align="top" color="var(--mb-color-brand)"> <Flex gap="sm" align="top" color="var(--mb-color-brand)">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment