mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
Fix pro project role descriptions (#2612)
https://linear.app/unleash/issue/2-485/bug-pro-access-page-the-tooltip-on-the-roles-is-not-working Fixes an issue where we would not show anything for project role permissions since we're not serving that specific endpoint below Enterprise level. This way we fallback to the access role descriptions, which are also nice and informative:  PR also adds support for SWR options in ConditionalSWR and EnterpriseSWR.
This commit is contained in:
parent
c66daa0ba2
commit
9accbcfa8b
@ -392,7 +392,12 @@ export const ProjectAccessAssign = ({
|
|||||||
</StyledAutocompleteWrapper>
|
</StyledAutocompleteWrapper>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(role?.id)}
|
condition={Boolean(role?.id)}
|
||||||
show={<ProjectRoleDescription roleId={role?.id!} />}
|
show={
|
||||||
|
<ProjectRoleDescription
|
||||||
|
roleId={role?.id!}
|
||||||
|
projectId={projectId}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -2,6 +2,9 @@ import { styled, SxProps, Theme } from '@mui/material';
|
|||||||
import { ForwardedRef, forwardRef, useMemo, VFC } from 'react';
|
import { ForwardedRef, forwardRef, useMemo, VFC } from 'react';
|
||||||
import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
|
import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import useProjectAccess from 'hooks/api/getters/useProjectAccess/useProjectAccess';
|
||||||
|
import { ProjectRoleDescriptionProjectPermissions } from './ProjectRoleDescriptionProjectPermissions/ProjectRoleDescriptionProjectPermissions';
|
||||||
|
import { ProjectRoleDescriptionEnvironmentPermissions } from './ProjectRoleDescriptionEnvironmentPermissions/ProjectRoleDescriptionEnvironmentPermissions';
|
||||||
|
|
||||||
const StyledDescription = styled('div', {
|
const StyledDescription = styled('div', {
|
||||||
shouldForwardProp: prop =>
|
shouldForwardProp: prop =>
|
||||||
@ -46,15 +49,24 @@ interface IProjectRoleDescriptionStyleProps {
|
|||||||
interface IProjectRoleDescriptionProps
|
interface IProjectRoleDescriptionProps
|
||||||
extends IProjectRoleDescriptionStyleProps {
|
extends IProjectRoleDescriptionStyleProps {
|
||||||
roleId: number;
|
roleId: number;
|
||||||
|
projectId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> =
|
export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> =
|
||||||
forwardRef(
|
forwardRef(
|
||||||
(
|
(
|
||||||
{ roleId, className, sx, ...props }: IProjectRoleDescriptionProps,
|
{
|
||||||
|
roleId,
|
||||||
|
projectId,
|
||||||
|
className,
|
||||||
|
sx,
|
||||||
|
...props
|
||||||
|
}: IProjectRoleDescriptionProps,
|
||||||
ref: ForwardedRef<HTMLDivElement>
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
) => {
|
) => {
|
||||||
const { role } = useProjectRole(roleId.toString());
|
const { role } = useProjectRole(roleId.toString());
|
||||||
|
const { access } = useProjectAccess(projectId);
|
||||||
|
const accessRole = access?.roles.find(role => role.id === roleId);
|
||||||
|
|
||||||
const environments = useMemo(() => {
|
const environments = useMemo(() => {
|
||||||
const environments = new Set<string>();
|
const environments = new Set<string>();
|
||||||
@ -80,62 +92,65 @@ export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> =
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(projectPermissions?.length)}
|
condition={role.permissions?.length > 0}
|
||||||
show={
|
show={
|
||||||
<>
|
<>
|
||||||
<StyledDescriptionHeader>
|
<ConditionallyRender
|
||||||
Project permissions
|
condition={Boolean(
|
||||||
</StyledDescriptionHeader>
|
projectPermissions?.length
|
||||||
<StyledDescriptionBlock>
|
)}
|
||||||
{role.permissions
|
show={
|
||||||
?.filter(
|
<>
|
||||||
(permission: any) =>
|
<StyledDescriptionHeader>
|
||||||
!permission.environment
|
Project permissions
|
||||||
)
|
</StyledDescriptionHeader>
|
||||||
.map(
|
<StyledDescriptionBlock>
|
||||||
(permission: any) =>
|
<ProjectRoleDescriptionProjectPermissions
|
||||||
permission.displayName
|
permissions={
|
||||||
)
|
role.permissions
|
||||||
.sort()
|
}
|
||||||
.map((permission: any) => (
|
/>
|
||||||
<p key={permission}>{permission}</p>
|
</StyledDescriptionBlock>
|
||||||
))}
|
</>
|
||||||
</StyledDescriptionBlock>
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(environments.length)}
|
||||||
|
show={
|
||||||
|
<>
|
||||||
|
<StyledDescriptionHeader>
|
||||||
|
Environment permissions
|
||||||
|
</StyledDescriptionHeader>
|
||||||
|
{environments.map(environment => (
|
||||||
|
<div key={environment}>
|
||||||
|
<StyledDescriptionSubHeader>
|
||||||
|
{environment}
|
||||||
|
</StyledDescriptionSubHeader>
|
||||||
|
<StyledDescriptionBlock>
|
||||||
|
<ProjectRoleDescriptionEnvironmentPermissions
|
||||||
|
environment={
|
||||||
|
environment
|
||||||
|
}
|
||||||
|
permissions={
|
||||||
|
role.permissions
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledDescriptionBlock>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
elseShow={
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(environments.length)}
|
|
||||||
show={
|
|
||||||
<>
|
<>
|
||||||
<StyledDescriptionHeader>
|
<StyledDescriptionSubHeader>
|
||||||
Environment permissions
|
{accessRole?.name}
|
||||||
</StyledDescriptionHeader>
|
</StyledDescriptionSubHeader>
|
||||||
{environments.map((environment: any) => (
|
<StyledDescriptionBlock>
|
||||||
<div key={environment}>
|
{accessRole?.description}
|
||||||
<StyledDescriptionSubHeader>
|
</StyledDescriptionBlock>
|
||||||
{environment}
|
|
||||||
</StyledDescriptionSubHeader>
|
|
||||||
<StyledDescriptionBlock>
|
|
||||||
{role.permissions
|
|
||||||
.filter(
|
|
||||||
(permission: any) =>
|
|
||||||
permission.environment ===
|
|
||||||
environment
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(permission: any) =>
|
|
||||||
permission.displayName
|
|
||||||
)
|
|
||||||
.sort()
|
|
||||||
.map((permission: any) => (
|
|
||||||
<p key={permission}>
|
|
||||||
{permission}
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</StyledDescriptionBlock>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
interface IProjectRoleDescriptionEnvironmentPermissionsProps {
|
||||||
|
environment: string;
|
||||||
|
permissions: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectRoleDescriptionEnvironmentPermissions = ({
|
||||||
|
environment,
|
||||||
|
permissions,
|
||||||
|
}: IProjectRoleDescriptionEnvironmentPermissionsProps) => (
|
||||||
|
<>
|
||||||
|
{permissions
|
||||||
|
.filter((permission: any) => permission.environment === environment)
|
||||||
|
.map((permission: any) => permission.displayName)
|
||||||
|
.sort()
|
||||||
|
.map((permission: any) => (
|
||||||
|
<p key={permission}>{permission}</p>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
@ -0,0 +1,17 @@
|
|||||||
|
interface IProjectRoleDescriptionProjectPermissionsProps {
|
||||||
|
permissions: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectRoleDescriptionProjectPermissions = ({
|
||||||
|
permissions,
|
||||||
|
}: IProjectRoleDescriptionProjectPermissionsProps) => (
|
||||||
|
<>
|
||||||
|
{permissions
|
||||||
|
?.filter((permission: any) => !permission.environment)
|
||||||
|
.map((permission: any) => permission.displayName)
|
||||||
|
.sort()
|
||||||
|
.map((permission: any) => (
|
||||||
|
<p key={permission}>{permission}</p>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
@ -17,12 +17,14 @@ const StyledPopover = styled(Popover)(() => ({
|
|||||||
|
|
||||||
interface IProjectAccessRoleCellProps {
|
interface IProjectAccessRoleCellProps {
|
||||||
roleId: number;
|
roleId: number;
|
||||||
|
projectId: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
emptyText?: string;
|
emptyText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectAccessRoleCell: VFC<IProjectAccessRoleCellProps> = ({
|
export const ProjectAccessRoleCell: VFC<IProjectAccessRoleCellProps> = ({
|
||||||
roleId,
|
roleId,
|
||||||
|
projectId,
|
||||||
value,
|
value,
|
||||||
emptyText,
|
emptyText,
|
||||||
}) => {
|
}) => {
|
||||||
@ -63,7 +65,11 @@ export const ProjectAccessRoleCell: VFC<IProjectAccessRoleCellProps> = ({
|
|||||||
horizontal: 'left',
|
horizontal: 'left',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProjectRoleDescription roleId={roleId} popover />
|
<ProjectRoleDescription
|
||||||
|
roleId={roleId}
|
||||||
|
projectId={projectId}
|
||||||
|
popover
|
||||||
|
/>
|
||||||
</StyledPopover>
|
</StyledPopover>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -170,6 +170,7 @@ export const ProjectAccessTable: VFC = () => {
|
|||||||
Cell: ({ value, row: { original: row } }: any) => (
|
Cell: ({ value, row: { original: row } }: any) => (
|
||||||
<ProjectAccessRoleCell
|
<ProjectAccessRoleCell
|
||||||
roleId={row.entity.roleId}
|
roleId={row.entity.roleId}
|
||||||
|
projectId={projectId}
|
||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -7,9 +7,9 @@ export const useChangeRequestConfig = (projectId: string) => {
|
|||||||
const { data, error, mutate } = useEnterpriseSWR<
|
const { data, error, mutate } = useEnterpriseSWR<
|
||||||
IChangeRequestEnvironmentConfig[]
|
IChangeRequestEnvironmentConfig[]
|
||||||
>(
|
>(
|
||||||
|
[],
|
||||||
formatApiPath(`api/admin/projects/${projectId}/change-requests/config`),
|
formatApiPath(`api/admin/projects/${projectId}/change-requests/config`),
|
||||||
fetcher,
|
fetcher
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import useSWR, { BareFetcher, Key, SWRConfiguration, SWRResponse } from 'swr';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useConditionalSWR = <Data = any, Error = any, T = boolean>(
|
||||||
|
condition: T,
|
||||||
|
fallback: Data,
|
||||||
|
key: Key,
|
||||||
|
fetcher: BareFetcher<Data>,
|
||||||
|
options: SWRConfiguration = {}
|
||||||
|
): SWRResponse<Data, Error> => {
|
||||||
|
const result = useSWR(
|
||||||
|
key,
|
||||||
|
(path: string) =>
|
||||||
|
condition ? fetcher(path) : Promise.resolve(fallback),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
result.mutate();
|
||||||
|
}, [condition]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
@ -1,33 +1,21 @@
|
|||||||
import useSWR, { BareFetcher, Key, SWRResponse } from 'swr';
|
import { BareFetcher, Key, SWRConfiguration } from 'swr';
|
||||||
import { useEffect } from 'react';
|
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
|
||||||
import useUiConfig from '../useUiConfig/useUiConfig';
|
import useUiConfig from '../useUiConfig/useUiConfig';
|
||||||
|
|
||||||
export const useConditionalSWR = <Data = any, Error = any, T = boolean>(
|
|
||||||
key: Key,
|
|
||||||
fetcher: BareFetcher<Data>,
|
|
||||||
condition: T
|
|
||||||
): SWRResponse<Data, Error> => {
|
|
||||||
const result = useSWR(key, fetcher);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
result.mutate();
|
|
||||||
}, [condition]);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useEnterpriseSWR = <Data = any, Error = any>(
|
export const useEnterpriseSWR = <Data = any, Error = any>(
|
||||||
|
fallback: Data,
|
||||||
key: Key,
|
key: Key,
|
||||||
fetcher: BareFetcher<Data>,
|
fetcher: BareFetcher<Data>,
|
||||||
fallback: Data
|
options: SWRConfiguration = {}
|
||||||
) => {
|
) => {
|
||||||
const { isEnterprise } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
|
|
||||||
const result = useConditionalSWR(
|
const result = useConditionalSWR(
|
||||||
|
isEnterprise(),
|
||||||
|
fallback,
|
||||||
key,
|
key,
|
||||||
(path: string) =>
|
fetcher,
|
||||||
isEnterprise() ? fetcher(path) : Promise.resolve(fallback),
|
options
|
||||||
isEnterprise()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -11,9 +11,9 @@ const fetcher = (path: string) => {
|
|||||||
|
|
||||||
export const usePendingChangeRequests = (project: string) => {
|
export const usePendingChangeRequests = (project: string) => {
|
||||||
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
||||||
|
[],
|
||||||
formatApiPath(`api/admin/projects/${project}/change-requests/pending`),
|
formatApiPath(`api/admin/projects/${project}/change-requests/pending`),
|
||||||
fetcher,
|
fetcher
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -14,11 +14,11 @@ export const usePendingChangeRequestsForFeature = (
|
|||||||
featureName: string
|
featureName: string
|
||||||
) => {
|
) => {
|
||||||
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
|
||||||
|
[],
|
||||||
formatApiPath(
|
formatApiPath(
|
||||||
`api/admin/projects/${project}/change-requests/pending/${featureName}`
|
`api/admin/projects/${project}/change-requests/pending/${featureName}`
|
||||||
),
|
),
|
||||||
fetcher,
|
fetcher
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
import { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
|
||||||
|
|
||||||
const useProjectRole = (id: string, options: SWRConfiguration = {}) => {
|
const useProjectRole = (id: string, options: SWRConfiguration = {}) => {
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
@ -13,7 +14,12 @@ const useProjectRole = (id: string, options: SWRConfiguration = {}) => {
|
|||||||
.then(res => res.json());
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data, error } = useSWR(`api/admin/roles/${id}`, fetcher, options);
|
const { data, error } = useEnterpriseSWR(
|
||||||
|
{},
|
||||||
|
`api/admin/roles/${id}`,
|
||||||
|
fetcher,
|
||||||
|
options
|
||||||
|
);
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
const [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetch = () => {
|
const refetch = () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user