feat: expose the write permission on shared modifications - #1261
feat: expose the write permission on shared modifications#1261flomillot wants to merge 26 commits into
Conversation
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>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 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. Comment |
…-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>
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>
| 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 |
There was a problem hiding this comment.
Unnecessary comment !
revert
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Unnecessary comment !
remove it
There was a problem hiding this comment.
Returning null from a component is not something that is common and should be explained. At least it's legit.
There was a problem hiding this comment.
I update the comments to make it clearer.
| DELETE_DIRECTORY = 'DELETE_DIRECTORY', | ||
| ADD_DIRECTORY = 'ADD_DIRECTORY', | ||
| UPDATE_DIRECTORY = 'UPDATE_DIRECTORY', | ||
| } |
There was a problem hiding this comment.
This isn't the right file for this enum !
There was a problem hiding this comment.
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>
| messageType: string; | ||
| messageValues: string; | ||
| // MODIFICATION_REFERENCE only: uuid of the referenced composite modification | ||
| referenceId?: UUID; |
There was a problem hiding this comment.
referenceId is already declared in ReferenceModificationInfos could that cause conflicts?
There was a problem hiding this comment.
Nope because it's the same field in the back. However ReferenceModificationInfos is bad typed because it should not be optional.
There was a problem hiding this comment.
However, I added this line before the one you mention exists, so I can remove the other one.
|
Here’s my code review of this #1313 |
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>
…difications-permissions
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>
…difications-permissions
| }, []); | ||
|
|
||
| // 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) { |
There was a problem hiding this comment.
and if openDescModificationDialog = true
(component is already mounted)
There was a problem hiding this comment.
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 []; |
There was a problem hiding this comment.
why return [] ? (should be return;)
even if the error is about fetch failure ? (it will be treaten like a real deny)
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
if (aborted || accessibleIds == null) return;
There was a problem hiding this comment.
I switched the order of then and catch, then I don't need this.
| useEffect(() => { | ||
| let aborted = false; | ||
|
|
||
| const referenceIds = getReferenceIds(modifications.filter(isReferenceModification)); |
There was a problem hiding this comment.
use useMemo for referenceIds (outside the useEffect)
There was a problem hiding this comment.
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>
…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>
|



PR Summary
In GridStudy, a 'shared modification' is a
MODIFICATION_REFERENCEcarrying areferenceId, which is also theelementUuidof the composite in directory-server.use-shared-modifications-permissionsresolves the writepermission of every shared modification in a single
getAccessibleElementscall, caches the answers andinvalidates them on
UPDATE_DIRECTORYnotifications. 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 sharedmodifications 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, andthe toolbar's delete, cut, paste and assemble.
isModificationEditLocked— the above, plus the reference row itself, for what targets the sharedmodification: 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
referenceIdin the metadata nothing locks, so thatone has to be merged first.
Requires gridsuite/explore-server#209 and gridsuite/directory-server#267, which provide the batch permission
endpoint this branch calls.
hasElementPermissionis rebuilt on top of it and keeps its signature, so itsother 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.