grafana/public/app/core/components/RolePicker/TeamRolePicker.tsx
Yudintsev George (Egor) e124ee79f4
Chore: RBAC: Migrate role picker to rtkq (#116571)
What is this feature?

This PR introduce refactoring for role picker for teams and users. It uses RTK Query.
The related task: grafana/identity-access-team#1821

The previous PR with related logic: #113783

Why do we need this feature?

This refactoring is a cleaner way for handing refetching.
2026-02-03 13:46:41 +00:00

108 lines
3.2 KiB
TypeScript

import { skipToken } from '@reduxjs/toolkit/query';
import { useMemo } from 'react';
import { useListTeamRolesQuery, useSetTeamRolesMutation } from 'app/api/clients/roles';
import { contextSrv } from 'app/core/services/context_srv';
import { AccessControlAction, Role } from 'app/types/accessControl';
import { RolePicker } from './RolePicker';
export interface Props {
teamId: number;
orgId?: number;
roleOptions: Role[];
disabled?: boolean;
roles?: Role[];
onApplyRoles?: (newRoles: Role[]) => void;
pendingRoles?: Role[];
/**
* Set whether the component should send a request with the new roles to the
* backend in TeamRolePicker.onRolesChange (apply=false), or call {@link onApplyRoles}
* with the updated list of roles (apply=true).
*
* Besides it sets the RolePickerMenu's Button title to
* * `Update` in case apply equals false
* * `Apply` in case apply equals true
*
* @default false
*/
apply?: boolean;
maxWidth?: string | number;
width?: string | number;
isLoading?: boolean;
}
export const TeamRolePicker = ({
teamId,
orgId,
roleOptions,
disabled,
roles,
onApplyRoles,
pendingRoles,
apply = false,
maxWidth,
width,
isLoading,
}: Props) => {
const hasPermission = contextSrv.hasPermission(AccessControlAction.ActionTeamsRolesList) && teamId > 0;
// In non-apply mode, always fetch to ensure we have fresh data after mutations
// In apply mode, only skip fetch if we have pendingRoles
const shouldFetch = apply ? !Boolean(pendingRoles?.length) && hasPermission : hasPermission;
const { data: fetchedRoles, isLoading: isFetching } = useListTeamRolesQuery(
shouldFetch ? { teamId, targetOrgId: orgId } : skipToken
);
const [updateTeamRoles, { isLoading: isUpdating }] = useSetTeamRolesMutation();
const appliedRoles =
useMemo(() => {
if (apply && Boolean(pendingRoles?.length)) {
return pendingRoles;
}
// Otherwise prefer fetched data (which is always fresh due to cache invalidation)
// Fall back to roles prop if fetched data is not available yet
return fetchedRoles || roles || [];
}, [roles, pendingRoles, fetchedRoles, apply]) || [];
const onRolesChange = async (newRoles: Role[]) => {
if (!apply) {
try {
const roleUids = newRoles.map((role) => role.uid);
await updateTeamRoles({
teamId,
targetOrgId: orgId,
setTeamRolesCommand: {
roleUids,
},
}).unwrap();
} catch (error) {
console.error('Error updating team roles', error);
}
} else if (onApplyRoles) {
onApplyRoles(newRoles);
}
};
const canUpdateRoles =
contextSrv.hasPermission(AccessControlAction.ActionTeamsRolesAdd) &&
contextSrv.hasPermission(AccessControlAction.ActionTeamsRolesRemove);
return (
<RolePicker
pickerId={`team-picker-${teamId}`}
apply={apply}
onRolesChange={onRolesChange}
roleOptions={roleOptions}
appliedRoles={appliedRoles}
isLoading={isFetching || isUpdating || isLoading}
disabled={disabled}
basicRoleDisabled={true}
canUpdateRoles={canUpdateRoles}
maxWidth={maxWidth}
width={width}
/>
);
};