mirror of
https://github.com/grafana/grafana.git
synced 2026-02-03 20:49:50 -05:00
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.
This commit is contained in:
parent
f937dfdcc6
commit
e124ee79f4
10 changed files with 175 additions and 125 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
|
@ -930,6 +930,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform
|
||||||
/public/app/core/utils/timeRegions* @grafana/dataviz-squad
|
/public/app/core/utils/timeRegions* @grafana/dataviz-squad
|
||||||
/public/app/core/utils/urlToken.ts @grafana/identity-access-team
|
/public/app/core/utils/urlToken.ts @grafana/identity-access-team
|
||||||
/public/app/core/utils/version.ts @grafana/partner-datasources
|
/public/app/core/utils/version.ts @grafana/partner-datasources
|
||||||
|
/public/app/core/utils/roles.ts @grafana/identity-access-team
|
||||||
/public/app/dev-utils.ts @grafana/grafana-frontend-platform
|
/public/app/dev-utils.ts @grafana/grafana-frontend-platform
|
||||||
|
|
||||||
/public/app/features/actions/ @grafana/dataviz-squad
|
/public/app/features/actions/ @grafana/dataviz-squad
|
||||||
|
|
|
||||||
75
public/app/api/clients/roles/index.ts
Normal file
75
public/app/api/clients/roles/index.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { FetchBaseQueryError } from '@reduxjs/toolkit/query';
|
||||||
|
|
||||||
|
import { generatedAPI, RoleDto } from '@grafana/api-clients/rtkq/legacy';
|
||||||
|
import { isFetchError } from '@grafana/runtime';
|
||||||
|
import { addDisplayNameForFixedRole, addFilteredDisplayName } from 'app/core/utils/roles';
|
||||||
|
import { Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
|
const transformRolesResponse = (response: RoleDto[]): Role[] => {
|
||||||
|
if (!response?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return response.map(addFilteredDisplayName).map(addDisplayNameForFixedRole);
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformRolesError = (error: FetchBaseQueryError) => {
|
||||||
|
if (isFetchError(error)) {
|
||||||
|
error.isHandled = true;
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
// rolesAPI is needed to be overriden to add transformResponse and transformErrorResponse for some endpoints.
|
||||||
|
export const rolesAPI = generatedAPI.injectEndpoints({
|
||||||
|
overrideExisting: true,
|
||||||
|
endpoints: (build) => ({
|
||||||
|
listUserRoles: build.query<
|
||||||
|
Role[],
|
||||||
|
{ userId: number; includeHidden?: boolean; includeMapped?: boolean; targetOrgId?: number }
|
||||||
|
>({
|
||||||
|
query: (queryArg) => ({
|
||||||
|
url: `/access-control/users/${queryArg.userId}/roles`,
|
||||||
|
params: {
|
||||||
|
includeHidden: queryArg.includeHidden,
|
||||||
|
includeMapped: queryArg.includeMapped,
|
||||||
|
targetOrgId: queryArg.targetOrgId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
providesTags: ['access_control', 'enterprise'],
|
||||||
|
transformResponse: transformRolesResponse,
|
||||||
|
transformErrorResponse: transformRolesError,
|
||||||
|
}),
|
||||||
|
listTeamRoles: build.query<Role[], { teamId: number; includeHidden?: boolean; targetOrgId?: number }>({
|
||||||
|
query: (queryArg) => ({
|
||||||
|
url: `/access-control/teams/${queryArg.teamId}/roles`,
|
||||||
|
params: {
|
||||||
|
includeHidden: queryArg.includeHidden,
|
||||||
|
targetOrgId: queryArg.targetOrgId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
providesTags: ['access_control', 'enterprise'],
|
||||||
|
transformResponse: transformRolesResponse,
|
||||||
|
transformErrorResponse: transformRolesError,
|
||||||
|
}),
|
||||||
|
listRoles: build.query<Role[], { delegatable?: boolean; includeHidden?: boolean; targetOrgId?: number }>({
|
||||||
|
query: (queryArg) => ({
|
||||||
|
url: '/access-control/roles',
|
||||||
|
params: {
|
||||||
|
delegatable: queryArg.delegatable,
|
||||||
|
includeHidden: queryArg.includeHidden,
|
||||||
|
targetOrgId: queryArg.targetOrgId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
providesTags: ['access_control', 'enterprise'],
|
||||||
|
transformResponse: transformRolesResponse,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useListTeamRolesQuery,
|
||||||
|
useSetTeamRolesMutation,
|
||||||
|
useListUserRolesQuery,
|
||||||
|
useSetUserRolesMutation,
|
||||||
|
useListRolesQuery,
|
||||||
|
} = rolesAPI;
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { forwardRef, useCallback, useState } from 'react';
|
import { forwardRef, useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useStyles2, getSelectStyles, useTheme2 } from '@grafana/ui';
|
import { useStyles2, getSelectStyles, useTheme2 } from '@grafana/ui';
|
||||||
|
import { isNotDelegatable } from 'app/core/utils/roles';
|
||||||
import { Role } from 'app/types/accessControl';
|
import { Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
import { RoleMenuGroupOption } from './RoleMenuGroupOption';
|
import { RoleMenuGroupOption } from './RoleMenuGroupOption';
|
||||||
import { RoleMenuOption } from './RoleMenuOption';
|
import { RoleMenuOption } from './RoleMenuOption';
|
||||||
import { RolePickerSubMenu } from './RolePickerSubMenu';
|
import { RolePickerSubMenu } from './RolePickerSubMenu';
|
||||||
import { getStyles } from './styles';
|
import { getStyles } from './styles';
|
||||||
import { isNotDelegatable } from './utils';
|
|
||||||
|
|
||||||
interface RoleMenuGroupsSectionProps {
|
interface RoleMenuGroupsSectionProps {
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import { FormEvent, useCallback, useEffect, useRef, useState, useSyncExternalSto
|
||||||
|
|
||||||
import { OrgRole } from '@grafana/data';
|
import { OrgRole } from '@grafana/data';
|
||||||
import { ClickOutsideWrapper, Portal, useTheme2 } from '@grafana/ui';
|
import { ClickOutsideWrapper, Portal, useTheme2 } from '@grafana/ui';
|
||||||
import { Role } from 'app/types/accessControl';
|
import { pickerStateStore } from 'app/core/utils/roles';
|
||||||
|
import type { Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
import { RolePickerInput } from './RolePickerInput';
|
import { RolePickerInput } from './RolePickerInput';
|
||||||
import { RolePickerMenu } from './RolePickerMenu';
|
import { RolePickerMenu } from './RolePickerMenu';
|
||||||
|
|
@ -12,7 +13,6 @@ import {
|
||||||
ROLE_PICKER_MENU_MAX_WIDTH,
|
ROLE_PICKER_MENU_MAX_WIDTH,
|
||||||
ROLE_PICKER_WIDTH,
|
ROLE_PICKER_WIDTH,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { pickerStateStore } from './utils';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
basicRole?: OrgRole;
|
basicRole?: OrgRole;
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import type { JSX } from 'react';
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans, t } from '@grafana/i18n';
|
||||||
import { Button, ScrollContainer, Stack, useStyles2, useTheme2 } from '@grafana/ui';
|
import { Button, ScrollContainer, Stack, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
import { getSelectStyles } from '@grafana/ui/internal';
|
import { getSelectStyles } from '@grafana/ui/internal';
|
||||||
|
import { isNotDelegatable } from 'app/core/utils/roles';
|
||||||
import { Role } from 'app/types/accessControl';
|
import { Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
import { RoleMenuOption } from './RoleMenuOption';
|
import { RoleMenuOption } from './RoleMenuOption';
|
||||||
import { MENU_MAX_HEIGHT } from './constants';
|
import { MENU_MAX_HEIGHT } from './constants';
|
||||||
import { getStyles } from './styles';
|
import { getStyles } from './styles';
|
||||||
import { isNotDelegatable } from './utils';
|
|
||||||
|
|
||||||
interface RolePickerSubMenuProps {
|
interface RolePickerSubMenuProps {
|
||||||
options: Role[];
|
options: Role[];
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { useEffect } from 'react';
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useListTeamRolesQuery, useSetTeamRolesMutation } from 'app/api/clients/roles';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { AccessControlAction, Role } from 'app/types/accessControl';
|
import { AccessControlAction, Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
import { RolePicker } from './RolePicker';
|
import { RolePicker } from './RolePicker';
|
||||||
import { fetchTeamRoles, updateTeamRoles } from './api';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
teamId: number;
|
teamId: number;
|
||||||
|
|
@ -34,6 +34,7 @@ export interface Props {
|
||||||
|
|
||||||
export const TeamRolePicker = ({
|
export const TeamRolePicker = ({
|
||||||
teamId,
|
teamId,
|
||||||
|
orgId,
|
||||||
roleOptions,
|
roleOptions,
|
||||||
disabled,
|
disabled,
|
||||||
roles,
|
roles,
|
||||||
|
|
@ -44,36 +45,44 @@ export const TeamRolePicker = ({
|
||||||
width,
|
width,
|
||||||
isLoading,
|
isLoading,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [{ loading, value: appliedRoles = roles || [] }, getTeamRoles] = useAsyncFn(
|
const hasPermission = contextSrv.hasPermission(AccessControlAction.ActionTeamsRolesList) && teamId > 0;
|
||||||
async (force = false) => {
|
|
||||||
try {
|
// In non-apply mode, always fetch to ensure we have fresh data after mutations
|
||||||
if (!force && roles) {
|
// In apply mode, only skip fetch if we have pendingRoles
|
||||||
return roles;
|
const shouldFetch = apply ? !Boolean(pendingRoles?.length) && hasPermission : hasPermission;
|
||||||
}
|
|
||||||
if (!force && apply && Boolean(pendingRoles?.length)) {
|
const { data: fetchedRoles, isLoading: isFetching } = useListTeamRolesQuery(
|
||||||
return pendingRoles;
|
shouldFetch ? { teamId, targetOrgId: orgId } : skipToken
|
||||||
}
|
|
||||||
if (contextSrv.hasPermission(AccessControlAction.ActionTeamsRolesList) && teamId > 0) {
|
|
||||||
return await fetchTeamRoles(teamId);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error fetching roles', e);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
[teamId, pendingRoles, roles]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const [updateTeamRoles, { isLoading: isUpdating }] = useSetTeamRolesMutation();
|
||||||
getTeamRoles();
|
|
||||||
}, [getTeamRoles]);
|
|
||||||
|
|
||||||
const onRolesChange = async (roles: Role[]) => {
|
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) {
|
if (!apply) {
|
||||||
await updateTeamRoles(roles, teamId);
|
try {
|
||||||
await getTeamRoles(true); // Force fetch from backend after update
|
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) {
|
} else if (onApplyRoles) {
|
||||||
onApplyRoles(roles);
|
onApplyRoles(newRoles);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,7 +97,7 @@ export const TeamRolePicker = ({
|
||||||
onRolesChange={onRolesChange}
|
onRolesChange={onRolesChange}
|
||||||
roleOptions={roleOptions}
|
roleOptions={roleOptions}
|
||||||
appliedRoles={appliedRoles}
|
appliedRoles={appliedRoles}
|
||||||
isLoading={loading || isLoading}
|
isLoading={isFetching || isUpdating || isLoading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
basicRoleDisabled={true}
|
basicRoleDisabled={true}
|
||||||
canUpdateRoles={canUpdateRoles}
|
canUpdateRoles={canUpdateRoles}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { useEffect } from 'react';
|
import { skipToken } from '@reduxjs/toolkit/query';
|
||||||
import { useAsyncFn } from 'react-use';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { OrgRole } from '@grafana/data';
|
import { OrgRole } from '@grafana/data';
|
||||||
|
import { useListUserRolesQuery, useSetUserRolesMutation } from 'app/api/clients/roles';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { AccessControlAction, Role } from 'app/types/accessControl';
|
import { AccessControlAction, Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
import { RolePicker } from './RolePicker';
|
import { RolePicker } from './RolePicker';
|
||||||
import { fetchUserRoles, updateUserRoles } from './api';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
basicRole: OrgRole;
|
basicRole: OrgRole;
|
||||||
|
|
@ -54,39 +54,46 @@ export const UserRolePicker = ({
|
||||||
width,
|
width,
|
||||||
isLoading,
|
isLoading,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [{ loading, value: appliedRoles = roles || [] }, getUserRoles] = useAsyncFn(
|
const hasPermission = contextSrv.hasPermission(AccessControlAction.ActionUserRolesList) && userId > 0 && orgId;
|
||||||
async (force = false) => {
|
|
||||||
try {
|
// Determine when to fetch:
|
||||||
if (!force && roles) {
|
// - In apply mode: only fetch if we don't have roles prop AND no pendingRoles (prevents flicker)
|
||||||
return roles;
|
// - In non-apply mode: always fetch to get fresh data after mutations
|
||||||
}
|
const shouldFetch = apply ? !roles && !Boolean(pendingRoles?.length) && hasPermission : hasPermission;
|
||||||
if (!force && apply && Boolean(pendingRoles?.length)) {
|
|
||||||
return pendingRoles;
|
const { data: fetchedRoles, isLoading: isFetching } = useListUserRolesQuery(
|
||||||
}
|
shouldFetch ? { userId, includeHidden: true, includeMapped: true, targetOrgId: orgId } : skipToken
|
||||||
if (contextSrv.hasPermission(AccessControlAction.ActionUserRolesList) && userId > 0) {
|
|
||||||
return await fetchUserRoles(userId, orgId);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error fetching user roles');
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
[orgId, userId, pendingRoles, roles]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const [updateUserRoles, { isLoading: isUpdating }] = useSetUserRolesMutation();
|
||||||
// only load roles when there is an Org selected
|
|
||||||
if (orgId) {
|
|
||||||
getUserRoles();
|
|
||||||
}
|
|
||||||
}, [getUserRoles, orgId]);
|
|
||||||
|
|
||||||
const onRolesChange = async (roles: Role[]) => {
|
const appliedRoles =
|
||||||
|
useMemo(() => {
|
||||||
|
// In apply mode: prioritize pendingRoles, then roles prop (never use fetched data to prevent flicker)
|
||||||
|
if (apply) {
|
||||||
|
return pendingRoles || roles || [];
|
||||||
|
}
|
||||||
|
// In non-apply mode: prefer fetched data (fresh from cache) over roles prop
|
||||||
|
return fetchedRoles || roles || [];
|
||||||
|
}, [roles, pendingRoles, fetchedRoles, apply]) || [];
|
||||||
|
|
||||||
|
const onRolesChange = async (newRoles: Role[]) => {
|
||||||
if (!apply) {
|
if (!apply) {
|
||||||
await updateUserRoles(roles, userId, orgId);
|
try {
|
||||||
await getUserRoles(true); // Force fetch from backend after update
|
const filteredRoles = newRoles.filter((role) => !role.mapped);
|
||||||
|
const roleUids = filteredRoles.map((role) => role.uid);
|
||||||
|
await updateUserRoles({
|
||||||
|
userId,
|
||||||
|
targetOrgId: orgId,
|
||||||
|
setUserRolesCommand: {
|
||||||
|
roleUids,
|
||||||
|
},
|
||||||
|
}).unwrap();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating user roles', error);
|
||||||
|
}
|
||||||
} else if (onApplyRoles) {
|
} else if (onApplyRoles) {
|
||||||
onApplyRoles(roles, userId, orgId);
|
onApplyRoles(newRoles, userId, orgId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -102,7 +109,7 @@ export const UserRolePicker = ({
|
||||||
onRolesChange={onRolesChange}
|
onRolesChange={onRolesChange}
|
||||||
onBasicRoleChange={onBasicRoleChange}
|
onBasicRoleChange={onBasicRoleChange}
|
||||||
roleOptions={roleOptions}
|
roleOptions={roleOptions}
|
||||||
isLoading={loading || isLoading}
|
isLoading={isFetching || isUpdating || isLoading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
basicRoleDisabled={basicRoleDisabled}
|
basicRoleDisabled={basicRoleDisabled}
|
||||||
basicRoleDisabledMessage={basicRoleDisabledMessage}
|
basicRoleDisabledMessage={basicRoleDisabledMessage}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
/**
|
||||||
import { Role } from 'app/types/accessControl';
|
* @deprecated These functions are legacy API calls. For new code, use the RTK Query API from:
|
||||||
|
* `app/api/clients/roles` which provides:
|
||||||
|
* - useListTeamRolesQuery
|
||||||
|
* - useSetTeamRolesMutation
|
||||||
|
* - useListUserRolesQuery
|
||||||
|
* - useSetUserRolesMutation
|
||||||
|
* - useListRolesQuery
|
||||||
|
*/
|
||||||
|
|
||||||
import { addDisplayNameForFixedRole, addFilteredDisplayName } from './utils';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { addDisplayNameForFixedRole, addFilteredDisplayName } from 'app/core/utils/roles';
|
||||||
|
import { Role } from 'app/types/accessControl';
|
||||||
|
|
||||||
export const fetchRoleOptions = async (orgId?: number): Promise<Role[]> => {
|
export const fetchRoleOptions = async (orgId?: number): Promise<Role[]> => {
|
||||||
let rolesUrl = '/api/access-control/roles?delegatable=true';
|
let rolesUrl = '/api/access-control/roles?delegatable=true';
|
||||||
|
|
@ -15,25 +24,6 @@ export const fetchRoleOptions = async (orgId?: number): Promise<Role[]> => {
|
||||||
return roles.map(addDisplayNameForFixedRole).map(addFilteredDisplayName);
|
return roles.map(addDisplayNameForFixedRole).map(addFilteredDisplayName);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchUserRoles = async (userId: number, orgId?: number): Promise<Role[]> => {
|
|
||||||
let userRolesUrl = `/api/access-control/users/${userId}/roles?includeMapped=true&includeHidden=true`;
|
|
||||||
if (orgId) {
|
|
||||||
userRolesUrl += `&targetOrgId=${orgId}`;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const roles = await getBackendSrv().get(userRolesUrl);
|
|
||||||
if (!roles || !roles.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return roles.map(addDisplayNameForFixedRole).map(addFilteredDisplayName);
|
|
||||||
} catch (error) {
|
|
||||||
if (isFetchError(error)) {
|
|
||||||
error.isHandled = true;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateUserRoles = (roles: Role[], userId: number, orgId?: number) => {
|
export const updateUserRoles = (roles: Role[], userId: number, orgId?: number) => {
|
||||||
let userRolesUrl = `/api/access-control/users/${userId}/roles`;
|
let userRolesUrl = `/api/access-control/users/${userId}/roles`;
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
|
|
@ -46,35 +36,3 @@ export const updateUserRoles = (roles: Role[], userId: number, orgId?: number) =
|
||||||
roleUids,
|
roleUids,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTeamRoles = async (teamId: number, orgId?: number): Promise<Role[]> => {
|
|
||||||
let teamRolesUrl = `/api/access-control/teams/${teamId}/roles`;
|
|
||||||
if (orgId) {
|
|
||||||
teamRolesUrl += `?targetOrgId=${orgId}`;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const roles = await getBackendSrv().get(teamRolesUrl);
|
|
||||||
if (!roles || !roles.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return roles.map(addDisplayNameForFixedRole).map(addFilteredDisplayName);
|
|
||||||
} catch (error) {
|
|
||||||
if (isFetchError(error)) {
|
|
||||||
error.isHandled = true;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateTeamRoles = (roles: Role[], teamId: number, orgId?: number) => {
|
|
||||||
let teamRolesUrl = `/api/access-control/teams/${teamId}/roles`;
|
|
||||||
if (orgId) {
|
|
||||||
teamRolesUrl += `?targetOrgId=${orgId}`;
|
|
||||||
}
|
|
||||||
const roleUids = roles.flatMap((x) => x.uid);
|
|
||||||
|
|
||||||
return getBackendSrv().put(teamRolesUrl, {
|
|
||||||
orgId,
|
|
||||||
roleUids,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@ import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { useCreateFolder } from 'app/api/clients/folder/v1beta1/hooks';
|
import { useCreateFolder } from 'app/api/clients/folder/v1beta1/hooks';
|
||||||
import {
|
import {
|
||||||
useSearchTeamsQuery as useLegacySearchTeamsQuery,
|
CreateTeamCommand,
|
||||||
|
UpdateTeamCommand,
|
||||||
useCreateTeamMutation,
|
useCreateTeamMutation,
|
||||||
useDeleteTeamByIdMutation,
|
useDeleteTeamByIdMutation,
|
||||||
useListTeamsRolesQuery,
|
|
||||||
CreateTeamCommand,
|
|
||||||
useSetTeamRolesMutation,
|
|
||||||
useGetTeamByIdQuery,
|
useGetTeamByIdQuery,
|
||||||
|
useSearchTeamsQuery as useLegacySearchTeamsQuery,
|
||||||
|
useListTeamsRolesQuery,
|
||||||
|
useSetTeamRolesMutation,
|
||||||
useUpdateTeamMutation,
|
useUpdateTeamMutation,
|
||||||
UpdateTeamCommand,
|
|
||||||
} from 'app/api/clients/legacy';
|
} from 'app/api/clients/legacy';
|
||||||
import { addFilteredDisplayName } from 'app/core/components/RolePicker/utils';
|
|
||||||
import { updateNavIndex } from 'app/core/reducers/navModel';
|
import { updateNavIndex } from 'app/core/reducers/navModel';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
|
import { addFilteredDisplayName } from 'app/core/utils/roles';
|
||||||
import { AccessControlAction, Role } from 'app/types/accessControl';
|
import { AccessControlAction, Role } from 'app/types/accessControl';
|
||||||
import { useDispatch } from 'app/types/store';
|
import { useDispatch } from 'app/types/store';
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue