From 036b7fe1bd8a17ee7b10e2b5a1aeac892d311dc3 Mon Sep 17 00:00:00 2001 From: Jake Wagoner Date: Mon, 28 Oct 2024 11:26:46 -0600 Subject: [PATCH 1/6] Add userEditPerms to manage user's plotinformation edit permissions --- packages/app/src/components/Body.tsx | 21 ++++++- .../src/atoms/config/userEditPermsAtoms.ts | 6 ++ .../upset/src/components/AltTextSidebar.tsx | 58 ++++++++++++------- packages/upset/src/components/Root.tsx | 12 +++- packages/upset/src/components/Upset.tsx | 5 +- .../src/components/custom/PlotInformation.tsx | 35 ++++++----- packages/upset/src/types.ts | 5 ++ 7 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 packages/upset/src/atoms/config/userEditPermsAtoms.ts diff --git a/packages/app/src/components/Body.tsx b/packages/app/src/components/Body.tsx index 548b26d1..49275a87 100644 --- a/packages/app/src/components/Body.tsx +++ b/packages/app/src/components/Body.tsx @@ -5,7 +5,7 @@ import { encodedDataAtom } from '../atoms/dataAtom'; import { doesHaveSavedQueryParam, queryParamAtom, saveQueryParam } from '../atoms/queryParamAtom'; import { ErrorModal } from './ErrorModal'; import { ProvenanceContext } from '../App'; -import { useContext, useEffect } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { provenanceVisAtom } from '../atoms/provenanceVisAtom'; import { elementSidebarAtom } from '../atoms/elementSidebarAtom'; import { altTextSidebarAtom } from '../atoms/altTextSidebarAtom'; @@ -13,6 +13,7 @@ import { loadingAtom } from '../atoms/loadingAtom'; import { Backdrop, CircularProgress } from '@mui/material'; import { updateMultinetSession } from '../api/session'; import { generateAltText } from '../api/generateAltText'; +import { api } from '../api/api'; import { rowsSelector } from '../atoms/selectors'; type Props = { @@ -51,6 +52,23 @@ export const Body = ({ data, config }: Props) => { }) }, [provObject.provenance, sessionId, workspace]); + // Check if the user has permissions to edit the plot + const [permissions, setPermissions] = useState(false); + + useEffect(() => { + const fetchPermissions = async () => { + try { + const r = await api.getCurrentUserWorkspacePermissions(workspace || ''); + // https://api.multinet.app/swagger/?format=openapi#/definitions/PermissionsReturn for possible permissions returns + return r.permission_label === 'owner' || r.permission_label === 'maintainer'; + } catch (e) { + return false; + } + }; + + fetchPermissions().then(setPermissions); + }, [workspace]); + /** * Generates alt text for a plot based on the current state and configuration. * If an error occurs during the generation, an error message is returned. @@ -109,6 +127,7 @@ export const Body = ({ data, config }: Props) => { ({ + key: 'userEditPermsAtom', + default: false, +}); diff --git a/packages/upset/src/components/AltTextSidebar.tsx b/packages/upset/src/components/AltTextSidebar.tsx index 8e603fa0..1ae3119e 100644 --- a/packages/upset/src/components/AltTextSidebar.tsx +++ b/packages/upset/src/components/AltTextSidebar.tsx @@ -24,6 +24,7 @@ import '../index.css'; import { PlotInformation } from './custom/PlotInformation'; import { UpsetActions } from '../provenance'; import { plotInformationSelector } from '../atoms/config/plotInformationAtom'; +import { userEditPermsAtom } from '../atoms/config/userEditPermsAtoms'; /** * Props for the AltTextSidebar component. @@ -66,6 +67,7 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { const { actions }: {actions: UpsetActions} = useContext(ProvenanceContext); const currState = useRecoilValue(upsetConfigAtom); + const userEditPerms = useRecoilValue(userEditPermsAtom); const [altText, setAltText] = useState(null); const [textGenErr, setTextGenErr] = useState(false); const [textEditing, setTextEditing] = useState(false); @@ -83,6 +85,10 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { * Handler for when the save button is clicked */ const saveButtonClick: () => void = useCallback(() => { + // if the user doesn't have edit permissions, don't allow saving + // The user shouldn't be able to edit in this case, but this is a failsafe + if (!userEditPerms) return; + setTextEditing(false); if (!currState.useUserAlt) actions.setUseUserAltText(true); if (currState.userAltText?.shortDescription !== userShortText @@ -93,6 +99,10 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { * Sets text editing to true and sets default user alttexts if necessary */ const enableTextEditing: () => void = useCallback(() => { + // if the user doesn't have edit permissions, don't allow editing + // The button should be hidden in this case, but this is a failsafe + if (!userEditPerms) return; + setTextEditing(true); if (!currState.userAltText?.shortDescription) setUserShortText(altText?.shortDescription); if (!currState.userAltText?.longDescription) setUserLongText(altText?.longDescription); @@ -206,12 +216,15 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { setEditing={setPlotInfoEditing} /> ) : ( - + // only show "Add Plot Information" if the user has edit permissions + userEditPerms ? ( + + ) : null )} {displayPlotInfo && ( <> @@ -268,21 +281,24 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { }} tabIndex={3} > - + {userEditPerms && ( + // Only show the edit button if the user has edit permissions + + )} diff --git a/packages/upset/src/components/Root.tsx b/packages/upset/src/components/Root.tsx index 6ae1ad8d..2b9bb6f3 100644 --- a/packages/upset/src/components/Root.tsx +++ b/packages/upset/src/components/Root.tsx @@ -14,6 +14,7 @@ import { dataAtom } from '../atoms/dataAtom'; import { allowAttributeRemovalAtom } from '../atoms/config/allowAttributeRemovalAtom'; import { contextMenuAtom } from '../atoms/contextMenuAtom'; import { upsetConfigAtom } from '../atoms/config/upsetConfigAtoms'; +import { userEditPermsAtom } from '../atoms/config/userEditPermsAtoms'; import { getActions, initializeProvenanceTracking, UpsetActions, UpsetProvenance, } from '../provenance'; @@ -41,6 +42,7 @@ type Props = { config: UpsetConfig; allowAttributeRemoval?: boolean; hideSettings?: boolean; + userEditPerms?: boolean; extProvenance?: { provenance: UpsetProvenance; actions: UpsetActions; @@ -61,13 +63,13 @@ type Props = { }; export const Root: FC = ({ - data, config, allowAttributeRemoval, hideSettings, extProvenance, provVis, elementSidebar, altTextSidebar, generateAltText, + data, config, allowAttributeRemoval, hideSettings, userEditPerms, extProvenance, provVis, elementSidebar, altTextSidebar, generateAltText, }) => { // Get setter for recoil config atom const setState = useSetRecoilState(upsetConfigAtom); - const [sets, setSets] = useRecoilState(setsAtom); const [items, setItems] = useRecoilState(itemsAtom); + const setUserEditPerms = useSetRecoilState(userEditPermsAtom); const setAttributeColumns = useSetRecoilState(attributeAtom); const setAllColumns = useSetRecoilState(columnsAtom); const setData = useSetRecoilState(dataAtom); @@ -79,6 +81,12 @@ export const Root: FC = ({ setData(data); }, []); + useEffect(() => { + if (userEditPerms !== undefined) { + setUserEditPerms(userEditPerms); + } + }, [userEditPerms]); + // Initialize Provenance and pass it setter to connect const { provenance, actions } = useMemo(() => { if (extProvenance) { diff --git a/packages/upset/src/components/Upset.tsx b/packages/upset/src/components/Upset.tsx index f5f4a68c..fc3a6c7a 100644 --- a/packages/upset/src/components/Upset.tsx +++ b/packages/upset/src/components/Upset.tsx @@ -20,12 +20,13 @@ const defaultVisibleSets = 6; * @param {boolean} [allowAttributeRemoval=false] - Whether or not to allow the user to remove attribute columns. This should be enabled only if there is an option within the parent application which allows for attributes to be added after removal. Default attribute removal behavior in UpSet 2.0 is done via context menu on attribute headers. Defaults to `false`. * @param {boolean} [hideSettings] - Hide the aggregations/filter settings sidebar. * @param {boolean} [parentHasHeight=false] - Indicates if the parent component has a fixed height. If this is set to `false`, the plot will occupy the full viewport height. When set to `true`, the plot will fit entirely within the parent component. Defaults to `false`. + * @param {boolean} [userEditPerms=false] - Whether or not the user has plot information edit permissions. * @param {Object} [extProvenance] - External provenance actions and [TrrackJS](https://github.com/Trrack/trrackjs) object for provenance history tracking and actions. This should only be used if your tool is using TrrackJS and has all the actions used by UpSet 2.0. Provenance is still tracked if nothing is provided. See [App.tsx](https://github.com/visdesignlab/upset2/blob/main/packages/app/src/App.tsx) to see how UpSet 2.0 and Multinet use an external Trrack object. Note that [initializeProvenanceTracking](https://github.com/visdesignlab/upset2/blob/main/packages/upset/src/provenance/index.ts#L300) and [getActions](https://github.com/visdesignlab/upset2/blob/main/packages/upset/src/provenance/index.ts#L322) are used to ensure that the provided provenance object is compatible. * @param {SidebarProps} [provVis] - The provenance visualization sidebar options. * @param {SidebarProps} [elementSidebar] - The element sidebar options. This sidebar is used for element queries, element selection datatable, and supplimental plot generation. * @param {SidebarProps} [altTextSidebar] - The alternative text sidebar options. This sidebar is used to display the generated text descriptions for an Upset 2.0 plot, given that the `generateAltText` function is provided. * @param {() => Promise} [generateAltText] - The function to generate alternative text. - * @returns {JSX.Element} The rendered Upset component. +* @returns {JSX.Element} The rendered Upset component. */ export const Upset: FC = ({ data, @@ -34,6 +35,7 @@ export const Upset: FC = ({ visualizeDatasetAttributes, visualizeUpsetAttributes = false, allowAttributeRemoval = false, + userEditPerms = false, hideSettings, extProvenance, provVis, @@ -107,6 +109,7 @@ export const Upset: FC = ({ data={processData} config={combinedConfig} allowAttributeRemoval={allowAttributeRemoval} + userEditPerms={userEditPerms} hideSettings={hideSettings} extProvenance={extProvenance} provVis={provVis} diff --git a/packages/upset/src/components/custom/PlotInformation.tsx b/packages/upset/src/components/custom/PlotInformation.tsx index f2a23377..f000cce0 100644 --- a/packages/upset/src/components/custom/PlotInformation.tsx +++ b/packages/upset/src/components/custom/PlotInformation.tsx @@ -12,6 +12,7 @@ import { useContext, useState, useCallback, } from 'react'; import { plotInformationSelector } from '../../atoms/config/plotInformationAtom'; +import { userEditPermsAtom } from '../../atoms/config/userEditPermsAtoms'; import { ProvenanceContext } from '../Root'; /** @@ -85,6 +86,7 @@ export const PlotInformation = ({ */ const plotInformationState = useRecoilValue(plotInformationSelector); + const userEditPerms = useRecoilValue(userEditPermsAtom); const [plotInformation, setPlotInformation] = useState(plotInformationState); const { actions } = useContext(ProvenanceContext); @@ -142,21 +144,24 @@ export const PlotInformation = ({ }} >
- + {userEditPerms && ( + // Hide the edit button if the user does not have permissions + + )} {plotInformation.caption}

diff --git a/packages/upset/src/types.ts b/packages/upset/src/types.ts index 60a2b55b..bd832111 100644 --- a/packages/upset/src/types.ts +++ b/packages/upset/src/types.ts @@ -127,6 +127,11 @@ export interface UpsetProps { */ allowAttributeRemoval?: boolean; + /** + * Whether or not the user has plot information edit permissions. + */ + userEditPerms?: boolean; + /** * Hide the aggregations/filter settings sidebar. */ From 4814a88c07ee40b2b79eb89a5da6f1e60df521d8 Mon Sep 17 00:00:00 2001 From: Jake Wagoner Date: Mon, 28 Oct 2024 11:38:08 -0600 Subject: [PATCH 2/6] Add mock user permissions api callback to playwrite tests --- e2e-tests/common.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e-tests/common.ts b/e2e-tests/common.ts index fb4ff738..0d456a1c 100644 --- a/e2e-tests/common.ts +++ b/e2e-tests/common.ts @@ -34,6 +34,9 @@ export async function beforeTest({ page }: {page: Page}) { await route.fulfill({ json }); } else if (url.includes('workspaces/Upset%20Examples/sessions/table/193/')) { await route.fulfill({ status: 200 }); + } else if (url.includes('workspaces/Upset%20Examples/permissions/me')) { + // User has owner permissions, this will allow plot information editing + await route.fulfill({ status: 200, json: { permission_label: 'owner' } }); } else { await route.continue(); } From 502647f0316838ebcd39b803d99d3c2e6bbd794d Mon Sep 17 00:00:00 2001 From: Jake Wagoner Date: Mon, 28 Oct 2024 11:40:43 -0600 Subject: [PATCH 3/6] Update upset component props to include usereditperms --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f7b3c2b9..467eb1a7 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,7 @@ const main = () => { - `visualizeAttributes` (optional)(`string[]`): List of attribute names (strings) which should be visualized. Defaults to the first 3 if no value is provided. If an empty list is provided, displays no attributes. - `visualizeUpsetAttributes` (optional)(`boolean`): Whether or not to visualize UpSet generated attributes (`degree` and `deviation`). Defaults to `false`. - `allowAttributeRemoval` (optional)(`boolean`): Whether or not to allow the user to remove attribute columns. This should be enabled only if there is an option within the parent application which allows for attributes to be added after removal. Default attribute removal behavior in UpSet 2.0 is done via context menu on attribute headers. Defaults to `false`. +- `userEditPerms` (optional)(`boolean`): Whether or not the user has plot information edit permissions. - `hideSettings` (optional)(`boolean`): Hide the aggregations/filter settings sidebar. - `parentHasHeight` (optional)(`boolean`): Indicates if the parent component has a fixed height. If this is set to `false`, the plot will occupy the full viewport height. When set to `true`, the plot will fit entirely within the parent component. Defaults to `false`. - `extProvenance` (optional): External provenance actions and [TrrackJS](https://github.com/Trrack/trrackjs) object for provenance history tracking and actions. This should only be used if your tool is using TrrackJS and the Trrack object you provide has all the actions used by UpSet 2.0. Provenance is still tracked if nothing is provided. See [App.tsx](https://github.com/visdesignlab/upset2/blob/main/packages/app/src/App.tsx) to see how UpSet 2.0 and Multinet use an external Trrack object. Note that [initializeProvenanceTracking](https://github.com/visdesignlab/upset2/blob/main/packages/upset/src/provenance/index.ts#L300) and [getActions](https://github.com/visdesignlab/upset2/blob/main/packages/upset/src/provenance/index.ts#L322) are used to ensure that the provided provenance object is compatible. The provided provenance object must have a type compatible with the [extProvenance](https://vdl.sci.utah.edu/upset2/interfaces/_visdesignlab_upset2_react.UpsetProps.html#extProvenance) UpSet 2.0 prop type. From 40790662251f63631b84648efbd588c4c62cc25b Mon Sep 17 00:00:00 2001 From: Jake Wagoner Date: Mon, 28 Oct 2024 12:40:34 -0600 Subject: [PATCH 4/6] Small style changes from feedback --- packages/app/src/components/Body.tsx | 7 ++++--- packages/upset/src/components/Upset.tsx | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/Body.tsx b/packages/app/src/components/Body.tsx index 49275a87..5fe11954 100644 --- a/packages/app/src/components/Body.tsx +++ b/packages/app/src/components/Body.tsx @@ -60,13 +60,14 @@ export const Body = ({ data, config }: Props) => { try { const r = await api.getCurrentUserWorkspacePermissions(workspace || ''); // https://api.multinet.app/swagger/?format=openapi#/definitions/PermissionsReturn for possible permissions returns - return r.permission_label === 'owner' || r.permission_label === 'maintainer'; + setPermissions(r.permission_label === 'owner' || r.permission_label === 'maintainer'); } catch (e) { - return false; + setPermissions(false) + return; } }; - fetchPermissions().then(setPermissions); + fetchPermissions(); }, [workspace]); /** diff --git a/packages/upset/src/components/Upset.tsx b/packages/upset/src/components/Upset.tsx index fc3a6c7a..a6249f1b 100644 --- a/packages/upset/src/components/Upset.tsx +++ b/packages/upset/src/components/Upset.tsx @@ -26,7 +26,7 @@ const defaultVisibleSets = 6; * @param {SidebarProps} [elementSidebar] - The element sidebar options. This sidebar is used for element queries, element selection datatable, and supplimental plot generation. * @param {SidebarProps} [altTextSidebar] - The alternative text sidebar options. This sidebar is used to display the generated text descriptions for an Upset 2.0 plot, given that the `generateAltText` function is provided. * @param {() => Promise} [generateAltText] - The function to generate alternative text. -* @returns {JSX.Element} The rendered Upset component. + * @returns {JSX.Element} The rendered Upset component. */ export const Upset: FC = ({ data, From 9ecb9eb6f1347bc338aa81b224dcddee605c048f Mon Sep 17 00:00:00 2001 From: Jake Wagoner Date: Wed, 30 Oct 2024 11:11:55 -0600 Subject: [PATCH 5/6] Rename userEditPerms to canEditPlotInformation --- README.md | 2 +- packages/app/src/components/Body.tsx | 2 +- .../src/atoms/config/canEditPlotInformationAtom.ts | 6 ++++++ .../upset/src/atoms/config/userEditPermsAtoms.ts | 6 ------ packages/upset/src/components/AltTextSidebar.tsx | 12 ++++++------ packages/upset/src/components/Root.tsx | 14 +++++++------- packages/upset/src/components/Upset.tsx | 6 +++--- .../src/components/custom/PlotInformation.tsx | 6 +++--- packages/upset/src/types.ts | 2 +- 9 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 packages/upset/src/atoms/config/canEditPlotInformationAtom.ts delete mode 100644 packages/upset/src/atoms/config/userEditPermsAtoms.ts diff --git a/README.md b/README.md index 467eb1a7..798025cd 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ const main = () => { - `visualizeAttributes` (optional)(`string[]`): List of attribute names (strings) which should be visualized. Defaults to the first 3 if no value is provided. If an empty list is provided, displays no attributes. - `visualizeUpsetAttributes` (optional)(`boolean`): Whether or not to visualize UpSet generated attributes (`degree` and `deviation`). Defaults to `false`. - `allowAttributeRemoval` (optional)(`boolean`): Whether or not to allow the user to remove attribute columns. This should be enabled only if there is an option within the parent application which allows for attributes to be added after removal. Default attribute removal behavior in UpSet 2.0 is done via context menu on attribute headers. Defaults to `false`. -- `userEditPerms` (optional)(`boolean`): Whether or not the user has plot information edit permissions. +- `canEditPlotInformation` (optional)(`boolean`): Whether or not the user can edit the plot information in the text descriptions sidebar. - `hideSettings` (optional)(`boolean`): Hide the aggregations/filter settings sidebar. - `parentHasHeight` (optional)(`boolean`): Indicates if the parent component has a fixed height. If this is set to `false`, the plot will occupy the full viewport height. When set to `true`, the plot will fit entirely within the parent component. Defaults to `false`. - `extProvenance` (optional): External provenance actions and [TrrackJS](https://github.com/Trrack/trrackjs) object for provenance history tracking and actions. This should only be used if your tool is using TrrackJS and the Trrack object you provide has all the actions used by UpSet 2.0. Provenance is still tracked if nothing is provided. See [App.tsx](https://github.com/visdesignlab/upset2/blob/main/packages/app/src/App.tsx) to see how UpSet 2.0 and Multinet use an external Trrack object. Note that [initializeProvenanceTracking](https://github.com/visdesignlab/upset2/blob/main/packages/upset/src/provenance/index.ts#L300) and [getActions](https://github.com/visdesignlab/upset2/blob/main/packages/upset/src/provenance/index.ts#L322) are used to ensure that the provided provenance object is compatible. The provided provenance object must have a type compatible with the [extProvenance](https://vdl.sci.utah.edu/upset2/interfaces/_visdesignlab_upset2_react.UpsetProps.html#extProvenance) UpSet 2.0 prop type. diff --git a/packages/app/src/components/Body.tsx b/packages/app/src/components/Body.tsx index 5fe11954..11972041 100644 --- a/packages/app/src/components/Body.tsx +++ b/packages/app/src/components/Body.tsx @@ -128,7 +128,7 @@ export const Body = ({ data, config }: Props) => { ({ + key: 'canEditPlotInformationAtom', + default: false, +}); diff --git a/packages/upset/src/atoms/config/userEditPermsAtoms.ts b/packages/upset/src/atoms/config/userEditPermsAtoms.ts deleted file mode 100644 index 311e4787..00000000 --- a/packages/upset/src/atoms/config/userEditPermsAtoms.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { atom } from 'recoil'; - -export const userEditPermsAtom = atom({ - key: 'userEditPermsAtom', - default: false, -}); diff --git a/packages/upset/src/components/AltTextSidebar.tsx b/packages/upset/src/components/AltTextSidebar.tsx index 1ae3119e..def99d65 100644 --- a/packages/upset/src/components/AltTextSidebar.tsx +++ b/packages/upset/src/components/AltTextSidebar.tsx @@ -24,7 +24,7 @@ import '../index.css'; import { PlotInformation } from './custom/PlotInformation'; import { UpsetActions } from '../provenance'; import { plotInformationSelector } from '../atoms/config/plotInformationAtom'; -import { userEditPermsAtom } from '../atoms/config/userEditPermsAtoms'; +import { canEditPlotInformationAtom } from '../atoms/config/canEditPlotInformationAtoms'; /** * Props for the AltTextSidebar component. @@ -67,7 +67,7 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { const { actions }: {actions: UpsetActions} = useContext(ProvenanceContext); const currState = useRecoilValue(upsetConfigAtom); - const userEditPerms = useRecoilValue(userEditPermsAtom); + const canEditPlotInformation = useRecoilValue(canEditPlotInformationAtom); const [altText, setAltText] = useState(null); const [textGenErr, setTextGenErr] = useState(false); const [textEditing, setTextEditing] = useState(false); @@ -87,7 +87,7 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { const saveButtonClick: () => void = useCallback(() => { // if the user doesn't have edit permissions, don't allow saving // The user shouldn't be able to edit in this case, but this is a failsafe - if (!userEditPerms) return; + if (!canEditPlotInformation) return; setTextEditing(false); if (!currState.useUserAlt) actions.setUseUserAltText(true); @@ -101,7 +101,7 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { const enableTextEditing: () => void = useCallback(() => { // if the user doesn't have edit permissions, don't allow editing // The button should be hidden in this case, but this is a failsafe - if (!userEditPerms) return; + if (!canEditPlotInformation) return; setTextEditing(true); if (!currState.userAltText?.shortDescription) setUserShortText(altText?.shortDescription); @@ -217,7 +217,7 @@ export const AltTextSidebar: FC = ({ open, close, generateAltText }) => { /> ) : ( // only show "Add Plot Information" if the user has edit permissions - userEditPerms ? ( + canEditPlotInformation ? (