From b71676164a337394cd3b6dd62e1bf2f54a832283 Mon Sep 17 00:00:00 2001 From: Alexander Polyankin <alexander.polyankin@metabase.com> Date: Mon, 28 Feb 2022 20:35:22 +0300 Subject: [PATCH] Add write checks (#20762) --- frontend/src/metabase-types/api/collection.ts | 1 + .../metabase-types/api/mocks/collection.ts | 1 + .../components/EventCard/EventCard.tsx | 12 +++-- .../TimelineDetailsModal.styled.tsx | 2 +- .../TimelineDetailsModal.tsx | 51 ++++++++++++------- .../TimelineEmptyState/TimelineEmptyState.tsx | 8 +-- .../TimelineListModal/TimelineListModal.tsx | 3 +- frontend/src/metabase/timelines/types.ts | 6 +++ 8 files changed, 58 insertions(+), 26 deletions(-) diff --git a/frontend/src/metabase-types/api/collection.ts b/frontend/src/metabase-types/api/collection.ts index 9ca3e320c93..ea02ed31f5b 100644 --- a/frontend/src/metabase-types/api/collection.ts +++ b/frontend/src/metabase-types/api/collection.ts @@ -3,4 +3,5 @@ export type CollectionId = number | string; export interface Collection { id: CollectionId; name: string; + can_write: boolean; } diff --git a/frontend/src/metabase-types/api/mocks/collection.ts b/frontend/src/metabase-types/api/mocks/collection.ts index 404dee52b0b..46db10223ee 100644 --- a/frontend/src/metabase-types/api/mocks/collection.ts +++ b/frontend/src/metabase-types/api/mocks/collection.ts @@ -5,5 +5,6 @@ export const createMockCollection = ( ): Collection => ({ id: 1, name: "Collection", + can_write: false, ...opts, }); diff --git a/frontend/src/metabase/timelines/components/EventCard/EventCard.tsx b/frontend/src/metabase/timelines/components/EventCard/EventCard.tsx index ddba507d6bd..eb036ebefd4 100644 --- a/frontend/src/metabase/timelines/components/EventCard/EventCard.tsx +++ b/frontend/src/metabase/timelines/components/EventCard/EventCard.tsx @@ -61,9 +61,11 @@ const EventCard = ({ )} <CardCreatorInfo>{creatorMessage}</CardCreatorInfo> </CardBody> - <CardAside> - <EntityMenu items={menuItems} triggerIcon="ellipsis" /> - </CardAside> + {menuItems.length > 0 && ( + <CardAside> + <EntityMenu items={menuItems} triggerIcon="ellipsis" /> + </CardAside> + )} </CardRoot> ); }; @@ -75,7 +77,9 @@ const getMenuItems = ( onArchive?: (event: TimelineEvent) => void, onUnarchive?: (event: TimelineEvent) => void, ) => { - if (!event.archived) { + if (!collection.can_write) { + return []; + } else if (!event.archived) { return [ { title: t`Edit event`, diff --git a/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx b/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx index f21cc45ad52..835c09f45ce 100644 --- a/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx +++ b/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.styled.tsx @@ -15,7 +15,6 @@ export const ModalToolbar = styled.div` export const ModalToolbarInput = styled(TextInput)` flex: 1 1 auto; - margin-right: 1rem; ${TextInput.Input} { height: 2.5rem; @@ -27,6 +26,7 @@ export const ModalToolbarLink = styled(Link)` flex: 0 0 auto; align-items: center; height: 2.5rem; + margin-left: 1rem; `; export const ModalBody = styled.div` diff --git a/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.tsx b/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.tsx index faee2fe09ef..17d8cbb8ce1 100644 --- a/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.tsx +++ b/frontend/src/metabase/timelines/components/TimelineDetailsModal/TimelineDetailsModal.tsx @@ -19,6 +19,7 @@ import { ModalToolbarInput, ModalToolbarLink, } from "./TimelineDetailsModal.styled"; +import { MenuItem } from "../../types"; export interface TimelineDetailsModalProps { timeline: Timeline; @@ -50,16 +51,19 @@ const TimelineDetailsModal = ({ }, [timeline, searchText, isArchive]); const menuItems = useMemo(() => { - return getMenuItems(timeline, collection); - }, [timeline, collection]); + return getMenuItems(timeline, collection, isArchive); + }, [timeline, collection, isArchive]); const isNotEmpty = events.length > 0; const isSearching = searchText.length > 0; + const canWrite = collection.can_write; return ( <ModalRoot> <ModalHeader title={title} onClose={onClose}> - {!isArchive && <EntityMenu items={menuItems} triggerIcon="ellipsis" />} + {menuItems.length > 0 && ( + <EntityMenu items={menuItems} triggerIcon="ellipsis" /> + )} </ModalHeader> {(isNotEmpty || isSearching) && ( <ModalToolbar> @@ -69,7 +73,7 @@ const TimelineDetailsModal = ({ icon={<Icon name="search" />} onChange={setInputText} /> - {!isArchive && ( + {canWrite && !isArchive && ( <ModalToolbarLink className="Button" to={Urls.newEventInCollection(timeline, collection)} @@ -119,21 +123,34 @@ const isEventMatch = (event: TimelineEvent, searchText: string) => { ); }; -const getMenuItems = (timeline: Timeline, collection: Collection) => { - return [ - { - title: t`New timeline`, - link: Urls.newTimelineInCollection(collection), - }, - { - title: t`Edit timeline details`, - link: Urls.editTimelineInCollection(timeline, collection), - }, - { +const getMenuItems = ( + timeline: Timeline, + collection: Collection, + isArchive: boolean, +) => { + const items: MenuItem[] = []; + + if (collection.can_write && !isArchive) { + items.push( + { + title: t`New timeline`, + link: Urls.newTimelineInCollection(collection), + }, + { + title: t`Edit timeline details`, + link: Urls.editTimelineInCollection(timeline, collection), + }, + ); + } + + if (!isArchive) { + items.push({ title: t`View archived events`, link: Urls.timelineArchiveInCollection(timeline, collection), - }, - ]; + }); + } + + return items; }; export default TimelineDetailsModal; diff --git a/frontend/src/metabase/timelines/components/TimelineEmptyState/TimelineEmptyState.tsx b/frontend/src/metabase/timelines/components/TimelineEmptyState/TimelineEmptyState.tsx index 2fda3fbfd50..1ba6972c103 100644 --- a/frontend/src/metabase/timelines/components/TimelineEmptyState/TimelineEmptyState.tsx +++ b/frontend/src/metabase/timelines/components/TimelineEmptyState/TimelineEmptyState.tsx @@ -66,9 +66,11 @@ const TimelineEmptyState = ({ <EmptyStateMessage> {t`Add events to Metabase to open important milestones, launches, or anything else, right alongside your data.`} </EmptyStateMessage> - <Link className="Button Button--primary" to={link}> - {t`Add an event`} - </Link> + {collection.can_write && ( + <Link className="Button Button--primary" to={link}> + {t`Add an event`} + </Link> + )} </EmptyStateBody> </EmptyStateRoot> ); diff --git a/frontend/src/metabase/timelines/components/TimelineListModal/TimelineListModal.tsx b/frontend/src/metabase/timelines/components/TimelineListModal/TimelineListModal.tsx index 5aa3f486ce3..1628ec045a9 100644 --- a/frontend/src/metabase/timelines/components/TimelineListModal/TimelineListModal.tsx +++ b/frontend/src/metabase/timelines/components/TimelineListModal/TimelineListModal.tsx @@ -19,13 +19,14 @@ const TimelineListModal = ({ collection, onClose, }: TimelineListModalProps): JSX.Element => { + const canWrite = collection.can_write; const hasTimelines = timelines.length > 0; const title = hasTimelines ? t`Events` : t`${collection.name} events`; return ( <div> <ModalHeader title={title} onClose={onClose}> - {hasTimelines && <TimelineMenu collection={collection} />} + {canWrite && hasTimelines && <TimelineMenu collection={collection} />} </ModalHeader> <ModalBody> {hasTimelines ? ( diff --git a/frontend/src/metabase/timelines/types.ts b/frontend/src/metabase/timelines/types.ts index 903d8931310..3ca07dee24b 100644 --- a/frontend/src/metabase/timelines/types.ts +++ b/frontend/src/metabase/timelines/types.ts @@ -1,3 +1,9 @@ +export interface MenuItem { + title: string; + link?: string; + action?: () => void; +} + export interface ModalParams { slug: string; timelineId?: string; -- GitLab