Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d44e112
feat: expose the write permission on shared modifications
flomillot Jul 24, 2026
f7d407a
feat: expose the write permission on shared modifications
flomillot Jul 24, 2026
03b3ddc
Merge remote-tracking branch 'origin/florent/poc-shared-modifications…
flomillot Jul 29, 2026
f7e95c1
Merge remote-tracking branch 'origin/main' into florent/poc-shared-mo…
flomillot Aug 13, 2026
b6d78a8
Drop the shared modification permission guard on drag and drop
flomillot Aug 13, 2026
9ea99ed
Merge remote-tracking branch 'origin/main' into florent/poc-shared-mo…
flomillot Aug 13, 2026
2c3931f
Keep honouring the external rename trigger on a locked row
flomillot Aug 14, 2026
7f75ed1
Share the directory notification types with the apps
flomillot Aug 14, 2026
406d8b7
Add the case export notification type and expose the value type
flomillot Aug 14, 2026
82992de
Tell the reference modifications from the shared ones they point at
flomillot Aug 14, 2026
4c072c4
Clarify comment on replacing hook with Redux for permissions handling
flomillot Aug 14, 2026
68cedce
Hide the description button when there is nothing to read nor write
flomillot Aug 17, 2026
5b79a23
Turn the directory notification types into an enum
flomillot Aug 17, 2026
b1197fb
clarify: update description cell logic to skip rendering when empty a…
flomillot Aug 20, 2026
b62a185
Code review shared modifications permission (#1313)
ghazwarhili Aug 26, 2026
a1922e1
Re-emit the selection when the permissions resolve
flomillot Aug 26, 2026
7d99dd0
Treat an unresolved permission as denied
flomillot Aug 26, 2026
a757f67
Resolve the permissions from the unfolded tree, not from the node's list
flomillot Aug 26, 2026
ad4ca5a
Merge remote-tracking branch 'origin/main' into florent/poc-shared-mo…
flomillot Aug 26, 2026
3165e34
Resolve the shared modification permissions in a single call
flomillot Aug 27, 2026
f16d063
Merge remote-tracking branch 'origin/main' into florent/poc-shared-mo…
flomillot Aug 27, 2026
f264c34
Merge remote-tracking branch 'origin/main' into florent/poc-shared-mo…
flomillot Sep 3, 2026
d947377
Lock drag-and-drop interactions for read-only shared modifications
flomillot Sep 3, 2026
1944832
Retry the shared modification permissions after a failed resolution
flomillot Sep 4, 2026
d87b44e
Merge remote-tracking branch 'origin/main' into florent/poc-shared-mo…
flomillot Sep 7, 2026
fe59ef9
Follow the batch permission endpoint rename
flomillot Sep 9, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface DescriptionModificationDialogProps {
onClose: () => void;
updateElement?: (data: Record<string, string>) => Promise<Response>;
updateForm?: (data: Record<string, string>) => void;
disabledSave?: boolean;
}

const schema = yup.object().shape({
Expand All @@ -37,6 +38,7 @@ export function DescriptionModificationDialog({
onClose,
updateElement,
updateForm,
disabledSave,
}: Readonly<DescriptionModificationDialogProps>) {
const { snackError } = useSnackMessage();

Expand Down Expand Up @@ -79,6 +81,7 @@ export function DescriptionModificationDialog({
onSave={onSubmit}
formContext={{ ...methods, validationSchema: schema, removeOptional: true }}
titleId="description"
disabledSave={disabledSave}
>
<Box paddingTop={1}>
<ExpandingTextField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,32 @@ import { useModificationsDragAndDrop } from './use-modifications-drag-and-drop';
import { useModificationsSelection } from './use-modifications-selection';
import {
collectApplicabilities,
collectReferenceModifications,
fetchSubModificationsForExpandedRows,
findAllLoadedCompositeModifications,
findDepth,
formatToComposedModification,
isCompositeModification,
isInLockedSharedModification,
isReferenceModification,
MAX_COMPOSITE_NESTING_DEPTH,
mergeSubModificationsIntoTree,
removeUuidsFromTree,
} from './utils';
import { ModificationRow } from './row';
import { useSharedModificationsPermissions } from './use-shared-modifications-permissions';

interface NetworkModificationsTableProps extends Omit<NetworkModificationEditorNameHeaderProps, 'modificationCount'> {
modifications: NetworkModificationMetadata[];
handleCellClick: (modification: ComposedModificationMetadata) => void;
isRowDragDisabled?: boolean;
onRowDragStart: () => void;
onRowDragEnd: () => void;
onSelectedRowsChange: (selectedRows: ComposedModificationMetadata[], isAssemblyDepthExceeded: boolean) => void;
onSelectedRowsChange: (
selectedRows: ComposedModificationMetadata[],
isAssemblyDepthExceeded: boolean,
containsLockedModification: boolean
) => void;
columns: ColumnDef<ComposedModificationMetadata>[];
highlightedModificationUuid: UUID | null;
modificationUuidsToReset?: UUID[]; // those modifications are unselected and unexpanded
Expand Down Expand Up @@ -117,6 +124,13 @@ export function NetworkModificationsTable({
composedModificationsRef.current = composedModifications;
}, [composedModifications]);

// Resolved from the whole loaded tree rather than from the node's modifications, so we get all nested references
const referenceModifications = useMemo(
() => collectReferenceModifications(composedModifications),
[composedModifications]
);
const { readOnlySharedModificationUuids } = useSharedModificationsPermissions(referenceModifications);

// refs are kept for the "event" props to prevent retriggering the associated useEffects
const modificationToEditLabelRef = useRef(modificationToEditLabel);
useEffect(() => {
Expand All @@ -137,9 +151,12 @@ export function NetworkModificationsTable({

const handleRowSelected = useCallback(
(selectedRows: ComposedModificationMetadata[]) => {
onSelectedRowsChange(selectedRows, isAssemblyDepthExceeded(selectedRows));
const containsLockedModification = selectedRows.some((row) =>
isInLockedSharedModification(row, readOnlySharedModificationUuids)
);
onSelectedRowsChange(selectedRows, isAssemblyDepthExceeded(selectedRows), containsLockedModification);
},
[onSelectedRowsChange, isAssemblyDepthExceeded]
[onSelectedRowsChange, isAssemblyDepthExceeded, readOnlySharedModificationUuids]
);

const { rowSelection, onRowSelectionChange, lastClickedRowId, emitSelection } = useModificationsSelection({
Expand Down Expand Up @@ -220,6 +237,9 @@ export function NetworkModificationsTable({
isRowDragDisabled,
modificationToEditLabel: modificationToEditLabelRef,
},
permissions: {
readOnlySharedModificationUuids,
},
status: {
isImpactedByNotification,
notificationMessageId,
Expand All @@ -240,6 +260,7 @@ export function NetworkModificationsTable({
handleRowSelected,
modificationToEditLabelRef,
isRowDragDisabled,
readOnlySharedModificationUuids,
isImpactedByNotification,
notificationMessageId,
isFetchingModifications,
Expand Down Expand Up @@ -287,6 +308,7 @@ export function NetworkModificationsTable({
onDragEnd: onRowDragEnd,
studyUuid,
currentNodeUuid: currentNodeId,
readOnlySharedModificationUuids,
});

// unselect and unexpand all network modifications from modificationUuidsToReset and their sub-modifications
Expand Down Expand Up @@ -385,6 +407,10 @@ export function NetworkModificationsTable({
handleCellClick={handleCellClick}
isRowDragDisabled={isRowDragDisabled}
highlightedModificationUuid={highlightedModificationUuid}
isFormOpeningLocked={isInLockedSharedModification(
row.original,
readOnlySharedModificationUuids
)}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { DescriptionCell } from './description-cell';
import { SwitchCell } from './switch-cell';
import { RootNetworkChipCell } from './root-network-chip-cell';
import { createRootNetworkChipCellSx, networkModificationTableStyles } from '../network-modification-table-styles';
import { isModificationEditLocked, isReferenceModification } from '../utils';
import { ComposedModificationMetadata } from '../../../utils';
import { isReferenceModification } from '../utils';
import { ReferenceLinkCell } from './reference-link-cell';

/**
Expand All @@ -35,6 +35,11 @@ import { ReferenceLinkCell } from './reference-link-cell';
type CCtx = CellContext<ComposedModificationMetadata, unknown>;
type HCtx = HeaderContext<ComposedModificationMetadata, unknown>;

/** True when the row can't be edited because of the permissions on the shared modification behind it. */
function isRowEditLocked({ row, table }: CCtx): boolean {
return isModificationEditLocked(row.original, table.options.meta?.permissions.readOnlySharedModificationUuids);
}

export function DragHandleRenderer({ table }: CCtx) {
return <DragHandleCell isRowDragDisabled={table.options.meta?.interaction.isRowDragDisabled ?? false} />;
}
Expand All @@ -60,18 +65,28 @@ export function NameHeaderRenderer({ table }: HCtx) {
);
}

export function NameCellRenderer({ row, table, column }: CCtx) {
return <NameCell row={row} table={table} onChange={column.columnDef.meta?.onChange} />;
export function NameCellRenderer(context: CCtx) {
const { row, table, column } = context;
return (
<NameCell
row={row}
table={table}
onChange={column.columnDef.meta?.onChange}
isRenameDisabled={isRowEditLocked(context)}
/>
);
}

export function DescriptionCellRenderer({ row, table }: CCtx) {
export function DescriptionCellRenderer(context: CCtx) {
const { row, table } = context;
const { meta } = table.options;
return (
<DescriptionCell
data={row.original}
studyUuid={meta?.context.studyUuid ?? null}
currentNodeId={meta?.context.currentNodeId}
isDisabled={meta?.status.isDisabled}
isSaveDisabled={isRowEditLocked(context)}
/>
);
}
Expand All @@ -84,14 +99,15 @@ export function ReferenceCellRenderer({ row, table }: CCtx) {
}
return null;
}
export function SwitchCellRenderer({ row, table }: CCtx) {
export function SwitchCellRenderer(context: CCtx) {
const { row, table } = context;
const { meta } = table.options;
return (
<SwitchCell
data={row.original}
studyUuid={meta?.context.studyUuid ?? null}
currentNodeId={meta?.context.currentNodeId}
isDisabled={meta?.status.isDisabled}
isDisabled={meta?.status.isDisabled || isRowEditLocked(context)}
/>
);
}
Expand All @@ -118,7 +134,8 @@ export function RootNetworkHeaderRenderer({ column, table }: HCtx) {
);
}

export function RootNetworkCellRenderer({ row, column, table }: CCtx) {
export function RootNetworkCellRenderer(context: CCtx) {
const { row, column, table } = context;
const { meta } = table.options;
// `column.id` is the rootNetworkUuid (set in createRootNetworksColumns).
const rootNetwork = meta?.context.rootNetworks?.find((r) => r.rootNetworkUuid === column.id);
Expand All @@ -134,7 +151,7 @@ export function RootNetworkCellRenderer({ row, column, table }: CCtx) {
rootNetwork={rootNetwork}
applicabilities={meta.modifications.applicabilities}
setApplicabilities={meta.modifications.setApplicabilities}
isDisabled={meta?.status.isDisabled}
isDisabled={meta?.status.isDisabled || isRowEditLocked(context)}
/>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ export interface DescriptionCellProps {
studyUuid: UUID | null;
currentNodeId?: UUID;
isDisabled?: boolean;
// the dialog stays reachable to read an existing description, only its validation is denied
isSaveDisabled?: boolean;
}

export function DescriptionCell(props: DescriptionCellProps) {
const { data, studyUuid, currentNodeId, isDisabled = false } = props;
const { data, studyUuid, currentNodeId, isDisabled = false, isSaveDisabled = false } = props;
const [isLoading, setIsLoading] = useState(false);
const [openDescModificationDialog, setOpenDescModificationDialog] = useState(false);

Expand Down Expand Up @@ -52,6 +54,11 @@ export function DescriptionCell(props: DescriptionCellProps) {
setOpenDescModificationDialog(true);
}, []);

// As the description is empty and we can't update it, we don't want to render the cell and its button
if (empty && isSaveDisabled) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and if openDescModificationDialog = true
(component is already mounted)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you want to change exactly. Can you suggest the code here ?

return null;
}

return (
<>
{openDescModificationDialog && modificationUuid && (
Expand All @@ -60,6 +67,7 @@ export function DescriptionCell(props: DescriptionCellProps) {
description={description ?? ''}
onClose={handleDescDialogClose}
updateElement={updateModification}
disabledSave={isSaveDisabled}
/>
)}
<Tooltip title={description ?? <FormattedMessage id="addDescription" />} arrow enterDelay={250}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ interface NameCellProps {
row: Row<ComposedModificationMetadata>;
table: Table<ComposedModificationMetadata>;
onChange?: (modification: ComposedModificationMetadata, newValue: string) => Promise<unknown>;
isRenameDisabled?: boolean;
}

export function NameCell({ row, table, onChange }: Readonly<NameCellProps>) {
export function NameCell({ row, table, onChange, isRenameDisabled = false }: Readonly<NameCellProps>) {
const { meta } = table.options;
const intl = useIntl();
const theme = useTheme();
Expand All @@ -49,6 +50,7 @@ export function NameCell({ row, table, onChange }: Readonly<NameCellProps>) {
const { depth } = row;

const isComposite = isCompositeModification(row.original);
const isCompositeAndRenamable = isComposite && !isRenameDisabled;

const getModificationLabel = useCallback(
(modification: ComposedModificationMetadata, formatBold: boolean = true) => {
Expand Down Expand Up @@ -157,6 +159,7 @@ export function NameCell({ row, table, onChange }: Readonly<NameCellProps>) {
const defaultCompositeName: string = useMemo(() => intl.formatMessage({ id: 'CompositeModification' }), [intl]);

// triggers composite name editing from outside the component
// i.e., when a composite is being created

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary comment !
revert

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is. The only purpose of this use effect is when we create a new composite which was not mentioned.

useEffect(() => {
const modificationToEditLabel = meta?.interaction.modificationToEditLabel.current;
if (isComposite && !isEditingRef.current && modificationToEditLabel === row.original.uuid) {
Expand Down Expand Up @@ -198,7 +201,7 @@ export function NameCell({ row, table, onChange }: Readonly<NameCellProps>) {
<DepthBox key={i} firstLevel={i === 0} displayAsFolder={isComposite && i === depthLevelCount - 1} />
));
};
const compositeReadModeProps = isComposite
const renamableCompositeModeProps = isCompositeAndRenamable
? {
ref: labelRef,
onClick: handleLabelClick,
Expand Down Expand Up @@ -274,11 +277,11 @@ export function NameCell({ row, table, onChange }: Readonly<NameCellProps>) {
/* Read mode */
<CustomTooltip disableFocusListener disableTouchListener title={label}>
<Box
{...compositeReadModeProps}
{...renamableCompositeModeProps}
sx={mergeSx(
networkModificationTableStyles.modificationLabel,
createModificationNameCellStyle(row.original.activated),
compositeReadModeProps.sx
renamableCompositeModeProps.sx
)}
>
{label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { updateModificationStatusByRootNetwork } from '../../../services';
import { useSnackMessage } from '../../../hooks';
import {
ComposedModificationMetadata,
ModificationType,
NetworkModificationApplicabilities,
RootNetworkRowInfo,
snackWithFallback,
} from '../../../utils';
import { isReferenceModificationOrInsideOne } from '../utils';

/**
* A modification is applicable on a root network unless its applicability for it is explicitly false:
Expand Down Expand Up @@ -69,8 +69,7 @@ export function RootNetworkChipCell(props: RootNetworkChipCellProps) {
const { snackError } = useSnackMessage();
const modificationUuid = data.uuid;

const isReferenceModificationOrInsideOne =
data.type === ModificationType.MODIFICATION_REFERENCE || data.childFromShared;
const isSharedContent = isReferenceModificationOrInsideOne(data);

const isModificationApplicable = useMemo(() => {
return isApplicableOn(applicabilities, modificationUuid, rootNetwork.rootNetworkUuid);
Expand Down Expand Up @@ -124,7 +123,7 @@ export function RootNetworkChipCell(props: RootNetworkChipCellProps) {
label={rootNetwork.tag}
tooltipMessage={rootNetwork.name}
isActivated={isModificationApplicable}
isDisabled={isLoading || isDisabled || isReferenceModificationOrInsideOne || rootNetwork.isCreating}
isDisabled={isLoading || isDisabled || isSharedContent || rootNetwork.isCreating}
onClick={handleModificationActivationByRootNetwork}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ interface ModificationRowProps {
handleCellClick?: (modification: ComposedModificationMetadata) => void;
isRowDragDisabled: boolean;
highlightedModificationUuid: string | null;
// TODO temporary before GRD-5139
isFormOpeningLocked?: boolean;
}

export function ModificationRow({
Expand All @@ -36,6 +38,7 @@ export function ModificationRow({
handleCellClick,
isRowDragDisabled,
highlightedModificationUuid,
isFormOpeningLocked = false,
}: Readonly<ModificationRowProps>) {
const isHighlighted = row.original.uuid === highlightedModificationUuid;
const theme = useTheme();
Expand All @@ -44,11 +47,11 @@ export function ModificationRow({

const handleCellClickCallback = useCallback(
(columnId: string) => {
if (columnId === BASE_MODIFICATION_TABLE_COLUMNS.NAME.id) {
if (columnId === BASE_MODIFICATION_TABLE_COLUMNS.NAME.id && !isFormOpeningLocked) {
handleCellClick?.(row.original);
}
},
[handleCellClick, row.original]
[handleCellClick, row.original, isFormOpeningLocked]
);

return (
Expand Down
Loading
Loading