Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -1547,7 +1547,7 @@ export const ShareAgentForm: FunctionComponent<AgentShareFormPropsInterface> = (
>
<Grid xs={ 12 }>
<SelectiveOrgShareWithSelectiveRoles
userId={ agent?.id }
restrictToImmediateChildOrgs={ true }
applicationRolesList={ agentRolesList }
selectedItems={ selectedOrgIds }
setSelectedItems={ setSelectedOrgIds }
Expand Down
2 changes: 1 addition & 1 deletion features/admin.users.v1/components/share-user-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ export const ShareUserForm: FunctionComponent<UserShareFormPropsInterface> = (
>
<Grid xs={ 12 }>
<SelectiveOrgShareWithSelectiveRoles
userId={ user?.id }
restrictToImmediateChildOrgs={ true }
applicationRolesList={ userRolesList }
selectedItems={ selectedOrgIds }
setSelectedItems={ setSelectedOrgIds }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import { TreeViewBaseItem } from "@mui/x-tree-view/models";
import { RichTreeView } from "@mui/x-tree-view/RichTreeView";
import { TreeItemCheckbox } from "@mui/x-tree-view/TreeItem";
import Alert from "@oxygen-ui/react/Alert";
import Autocomplete, {
AutocompleteChangeDetails,
Expand Down Expand Up @@ -66,6 +67,7 @@ import isEmpty from "lodash-es/isEmpty";
import React, {
ChangeEvent,
Dispatch as ReactDispatch,
ReactElement,
ReactNode,
SetStateAction,
SyntheticEvent,
Expand Down Expand Up @@ -102,7 +104,16 @@ interface SelectiveOrgShareWithSelectiveRolesProps extends IdentifiableComponent
clearAdvancedRoleSharing?: boolean;
disableOrgSelection?: boolean;
enableAdminRole?: boolean;
userId?: string;
/**
* When `true`, only immediate child organizations of the current organization can be selected and
* have an editable sharing policy. Deeper descendants are still shown (and expandable for viewing)
* but their checkboxes are disabled and their roles are rendered read-only, since they inherit the
* sharing of their parent.
*
* Used by user and agent sharing, whose backend shares with the immediate child organizations only.
* Application and console sharing leave this `false` (nested organization sharing is supported).
*/
restrictToImmediateChildOrgs?: boolean;
allRolesSharingMessage: string;
shareWithFutureChildOrgsLabel: string;
sharingSettingsLabel: string;
Expand Down Expand Up @@ -163,7 +174,7 @@ const SelectiveOrgShareWithSelectiveRoles = (props: SelectiveOrgShareWithSelecti
clearAdvancedRoleSharing = false,
disableOrgSelection = false,
enableAdminRole = false,
userId,
restrictToImmediateChildOrgs = false,
allRolesSharingMessage,
shareWithFutureChildOrgsLabel,
sharingSettingsLabel,
Expand Down Expand Up @@ -926,6 +937,62 @@ const SelectiveOrgShareWithSelectiveRoles = (props: SelectiveOrgShareWithSelecti
}
};

/**
* Determines whether an organization node should be non-selectable in the tree.
*
* In user and agent sharing the server shares with the immediate child organizations of the
* sharing-initiated organization only; deeper descendants in the request are silently skipped.
* Disabling their checkboxes keeps the tree consistent with what is actually shared and avoids
* the confusion of selecting an organization that never gets shared. To reach deeper
* organizations, the direct sub-organization is selected with sharing extended to its children.
*
* Only the checkbox/selection is disabled (via `isItemSelectionDisabled`); the node can still be
* expanded and clicked, so deeper organizations remain viewable.
*
* This applies only when the sharing flow restricts sharing to immediate child organizations
* (`restrictToImmediateChildOrgs`) — i.e. user and agent sharing. Application and console sharing
* support nested organizations and are unaffected, as is the read-only view (`disableOrgSelection`).
*
* @param orgId - The organization ID of the tree node.
* @returns `true` if the node's selection should be disabled.
*/
const isOrgSelectionDisabled = (orgId: string): boolean => {
if (!restrictToImmediateChildOrgs || disableOrgSelection) {
return false;
}

const parentId: string | undefined = flatOrganizationMap[orgId]?.parentId;

// Immediate children of the current organization have parentId === organizationId.
return !isEmpty(parentId) && parentId !== organizationId;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};

/**
* Custom checkbox slot for the organization tree.
*
* For non-immediate organizations the tree marks the item non-selectable (via
* `isItemSelectionDisabled`), which by default hides the checkbox entirely (the Tree View passes
* `visible: false`, and the default checkbox slot renders `null` in that case). This slot reuses
* the Tree View's own `TreeItemCheckbox` (the same component used for selectable items) but forces
* `visible` to `true`, so deeper organizations show a disabled checkbox — the Tree View already
* sets `disabled: true` on them — instead of an empty gap, while remaining expandable and clickable
* for viewing. Reusing `TreeItemCheckbox` keeps the disabled checkbox visually identical to the
* selectable ones and correctly forwards the ref the Tree View injects. In the read-only view
* (`disableOrgSelection`) no checkboxes are shown.
*
* @param checkboxSlotProps - Props injected by the Tree View for the checkbox slot.
* @returns The rendered checkbox, or `null` when no checkbox should be shown.
*/
const renderSelectionCheckbox = (
checkboxSlotProps: React.HTMLAttributes<HTMLButtonElement> & { visible?: boolean }
): ReactElement | null => {
if (disableOrgSelection) {
return null;
}

return <TreeItemCheckbox { ...checkboxSlotProps } visible={ true } />;
};

const loadMoreOrganizations = (): void => {
const cursorFragments: string[] = nextPageLink?.split("after=");

Expand Down Expand Up @@ -1179,12 +1246,13 @@ const SelectiveOrgShareWithSelectiveRoles = (props: SelectiveOrgShareWithSelecti
);
}

// For user sharing only, non-immediate sub-organizations (whose parent is not the root
// organization) do not have their own sharing policy. Their role assignments are inherited
// and cannot be modified. Show a read-only view of the currently assigned roles instead.
// When sharing is restricted to immediate child organizations (user and agent sharing),
// non-immediate sub-organizations (whose parent is not the root organization) do not have their
// own sharing policy. Their role assignments are inherited and cannot be modified. Show a
// read-only view of the currently assigned roles instead.
const selectedOrgParentId: string | undefined = flatOrganizationMap[selectedOrgId]?.parentId;
const isNonImmediateOrgInUserSharing: boolean =
!isEmpty(userId) &&
restrictToImmediateChildOrgs &&
!isEmpty(selectedOrgId) &&
!isEmpty(selectedOrgParentId) &&
selectedOrgParentId !== organizationId;
Expand Down Expand Up @@ -1621,6 +1689,8 @@ const SelectiveOrgShareWithSelectiveRoles = (props: SelectiveOrgShareWithSelecti
selectedItems={ disableOrgSelection ? selectedOrgId : selectedItems }
checkboxSelection={ !disableOrgSelection }
disableSelection={ disableOrgSelection }
isItemSelectionDisabled={ (item: TreeViewBaseItemWithRoles) =>
isOrgSelectionDisabled(item.id) }
multiSelect={ !disableOrgSelection }
selectionPropagation={ {
descendants: false,
Expand All @@ -1629,6 +1699,13 @@ const SelectiveOrgShareWithSelectiveRoles = (props: SelectiveOrgShareWithSelecti
slots={ {
item: CustomTreeItem
} }
slotProps={ {
item: {
slots: {
checkbox: renderSelectionCheckbox
}
}
} }
/>
</InfiniteScroll>
</Grid>
Expand Down
Loading