Skip to content

feat: expose the write permission on shared modifications - #1261

Open
flomillot wants to merge 26 commits into
mainfrom
florent/poc-shared-modifications-permissions
Open

feat: expose the write permission on shared modifications#1261
flomillot wants to merge 26 commits into
mainfrom
florent/poc-shared-modifications-permissions

Conversation

@flomillot

@flomillot flomillot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

PR Summary

In GridStudy, a 'shared modification' is a MODIFICATION_REFERENCE carrying a referenceId, which is also the elementUuid of the composite in directory-server. use-shared-modifications-permissions resolves the write
permission of every shared modification in a single getAccessibleElements call, caches the answers and
invalidates them on UPDATE_DIRECTORY notifications. A permission that hasn't resolved yet reads as denied, and a resolution that failed is asked for again on the next trigger.

Every row unfolded from a reference carries ancestorSharedModificationUuids, the chain of the shared
modifications it sits inside: it is locked as soon as ONE of them can't be written into. Two rules build on that chain:

  • isInLockedSharedModification — what only the content of a shared modification forbids: opening a form, and
    the toolbar's delete, cut, paste and assemble.
  • isModificationEditLocked — the above, plus the reference row itself, for what targets the shared
    modification: renaming, saving a description, and toggling the applicability globally or per root network.

Actions on the shared modification taken as a whole stay allowed: moving it, deleting it, assembling it.

Drag and drop reasons on the same chain: nothing can be taken out of a shared modification the user can't
write into, moved around inside it, nor dropped into it, while the shared modification taken as a whole stays
movable.

Requires gridsuite/network-modification-server#858: without referenceId in the metadata nothing locks, so that
one has to be merged first.

Requires gridsuite/explore-server#209 and gridsuite/directory-server#267, which provide the batch permission
endpoint this branch calls. hasElementPermission is rebuilt on top of it and keeps its signature, so its
other callers are unaffected.

Limitation left as TODO in the code: a permission granted through a group is not invalidated —
user-admin-server emits no notification on group composition change.

flomillot and others added 2 commits July 24, 2026 18:13
A shared modification references a composite whose uuid is also the uuid of the shared element in the
directory, so the permission of the current user can be resolved from it.

useSharedModificationsPermissions resolves that permission for the shared modifications of a node, and the
table derives from it everything nested inside the ones the user can't write into. Both are published through
the table meta, so the cells deny the actions targeting them: applicability, renaming, description saving and
drag and drop. Acting on a shared modification as a whole stays allowed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
A shared modification references a composite whose uuid is also the uuid of the shared element in the
directory, so the permission of the current user can be resolved from it.

useSharedModificationsPermissions resolves that permission for the shared modifications of a node, and the
table derives from it everything nested inside the ones the user can't write into. Both are published through
the table meta, so the cells deny the actions targeting them: applicability, renaming, description saving and
drag and drop. Acting on a shared modification as a whole stays allowed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 68c4f69d-de42-427c-b66b-fc0a6e6fba60


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

flomillot and others added 7 commits July 29, 2026 10:16
…-permissions' into florent/poc-shared-modifications-permissions
…difications-permissions

Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>

# Conflicts:
#	src/features/network-modification-table/renderers/cell-renderers.tsx
#	src/features/network-modification-table/utils.ts
Dragging anything in or out of a referenced modification is already forbidden, whatever the permissions, by the
temporary guard of GRD-4785. The permission check on top of it was dead code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
…difications-permissions

Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
The trigger only fires on a composite that has just been assembled, which can never be a shared modification nor
sit inside one, so the permission check was dead. Clicking the label stays blocked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
The hook needed the UPDATE_DIRECTORY type, which only existed in gridstudy. Moved to commons-ui next to
NotificationsUrlKeys so both sides read the same constant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
gridexplore uses these types as types, not only as values, hence the const assertion and the derived union.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
flomillot and others added 4 commits August 14, 2026 14:49
The sets hold the uuids of the rows sitting in the study, which are reference modifications, while the shared
modification is the one element in the directory they all point at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
Without the write permission and without an existing description, the button only opened a dialog whose
validation was denied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
An enum is both a value and a type, which is what the consumers need, so the const assertion and the derived
union go away. It also matches NotificationsUrlKeys right above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>
@flomillot
flomillot marked this pull request as ready for review August 17, 2026 09:17
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.

setOpenDescModificationDialog(true);
}, []);

// Nothing to read and nothing to write

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 !
remove it

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.

Returning null from a component is not something that is common and should be explained. At least it's legit.

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 update the comments to make it clearer.

DELETE_DIRECTORY = 'DELETE_DIRECTORY',
ADD_DIRECTORY = 'ADD_DIRECTORY',
UPDATE_DIRECTORY = 'UPDATE_DIRECTORY',
}

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.

This isn't the right file for this enum !

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 agree the file does not fit totally, but I didn't find an accurate file. Where is it ?

…nd save is disabled

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
@flomillot
flomillot requested a review from ghazwarhili August 20, 2026 14:02
messageType: string;
messageValues: string;
// MODIFICATION_REFERENCE only: uuid of the referenced composite modification
referenceId?: UUID;

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.

referenceId is already declared in ReferenceModificationInfos could that cause conflicts?

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.

Nope because it's the same field in the back. However ReferenceModificationInfos is bad typed because it should not be optional.

@flomillot flomillot Aug 26, 2026

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.

However, I added this line before the one you mention exists, so I can remove the other one.

@ghazwarhili

Copy link
Copy Markdown
Contributor

Here’s my code review of this #1313

ghazwarhili and others added 4 commits August 26, 2026 12:04
handleRowSelected read the permission set through a ref and was not recreated
when it changed, while the effect re-emitting the selection only depends on the
selection and the row model. Permissions resolve asynchronously, so a selection
emitted before the answer arrived kept reporting that nothing was locked, and
delete, cut, paste and assemble stayed enabled on locked content.

The set is referentially stable already - replaceIfChanged keeps the previous
one when the content is unchanged - so it can simply be a dependency, and the
ref goes away.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
The set was only published once every permission had an answer, so nothing was
locked while the first fetch was in flight: forms opened and the activation
switch and description dialog were live on content the user may not write into.
It is now published before the fetch starts and refined by the cache update it
triggers, buildReadOnlySharedModificationUuids already keeping the ids the cache
has no answer for.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
useSharedModificationsPermissions was given the node's flat modification list,
so only top-level references were submitted to hasElementPermission. A reference
nested inside a composite was never queried, and the uuid stamped into its
children's ancestorSharedModificationUuids could not be in the resulting set:
its content stayed editable however the permissions on it.

The table now calls the hook itself, over the reference modifications of the
whole loaded tree, so a reference is resolved as soon as the composite holding
it is unfolded. The readOnlySharedModificationUuids prop is gone, and so is the
hook from the public API - nothing outside the table needs it.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
hasElementPermission was called once per shared modification, which grew
with the number of references loaded in the tree. getAccessibleElements
resolves them all in one request to explore-server, and
hasElementPermission is now built on top of it, keeping its signature.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
}, []);

// 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 ?

.catch((error) => {
console.error('Failed to resolve the permissions on the shared modifications', error);
// Denying them all is what the cache already reports while they are unresolved
return [];

@ghazwarhili ghazwarhili Aug 27, 2026

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.

why return [] ? (should be return;)
even if the error is about fetch failure ? (it will be treaten like a real deny)

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.

Actually, it was to avoid allowing permission if we don't know if the user has the rights.
But it's not necessary anymore and it removes them from missing IDs which is wrong.
I change it.

return [];
})
.then((accessibleIds) => {
if (aborted) {

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.

if (aborted || accessibleIds == null) return;

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 switched the order of then and catch, then I don't need this.

useEffect(() => {
let aborted = false;

const referenceIds = getReferenceIds(modifications.filter(isReferenceModification));

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.

use useMemo for referenceIds (outside the useEffect)

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.

Use memo is useful when there is a heavy compute or when you don't want to trigger re-render.
It's not the case here.

…difications-permissions

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>

# Conflicts:
#	src/features/network-modification-table/utils.ts
Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
Caching a denial on failure dropped the id from the ones left to resolve,
turning a transient error into a lock lasting until the cache is dropped.
Leaving the cache untouched keeps them denied, since an id it doesn't
answer for already counts as read-only, and asks for them again on the
next trigger.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
flomillot and others added 2 commits September 7, 2026 11:32
…difications-permissions

Signed-off-by: Florent MILLOT <florent.millot_externe@rte-france.com>

# Conflicts:
#	src/features/network-modification-table/network-modifications-table.tsx
#	src/features/network-modification-table/renderers/cell-renderers.tsx
#	src/features/network-modification-table/renderers/root-network-chip-cell.tsx
#	src/features/network-modification-table/utils.ts
#	src/utils/types/network-modification-metadata.ts
explore-server now exposes it as /explore/elements/accessible.

Signed-off-by: Florent MILLOT <75525996+flomillot@users.noreply.github.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants