1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +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:


![image](https://user-images.githubusercontent.com/14320932/205987327-def46ef2-4010-47dd-ba7d-5a264f0b547d.png)


PR also adds support for SWR options in ConditionalSWR and
EnterpriseSWR.
This commit is contained in:
Nuno Góis 2022-12-07 10:22:42 +00:00 committed by GitHub
parent c66daa0ba2
commit 9accbcfa8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 162 additions and 82 deletions

View File

@ -392,7 +392,12 @@ export const ProjectAccessAssign = ({
</StyledAutocompleteWrapper>
<ConditionallyRender
condition={Boolean(role?.id)}
show={<ProjectRoleDescription roleId={role?.id!} />}
show={
<ProjectRoleDescription
roleId={role?.id!}
projectId={projectId}
/>
}
/>
</div>

View File

@ -2,6 +2,9 @@ import { styled, SxProps, Theme } from '@mui/material';
import { ForwardedRef, forwardRef, useMemo, VFC } from 'react';
import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
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', {
shouldForwardProp: prop =>
@ -46,15 +49,24 @@ interface IProjectRoleDescriptionStyleProps {
interface IProjectRoleDescriptionProps
extends IProjectRoleDescriptionStyleProps {
roleId: number;
projectId: string;
}
export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> =
forwardRef(
(
{ roleId, className, sx, ...props }: IProjectRoleDescriptionProps,
{
roleId,
projectId,
className,
sx,
...props
}: IProjectRoleDescriptionProps,
ref: ForwardedRef<HTMLDivElement>
) => {
const { role } = useProjectRole(roleId.toString());
const { access } = useProjectAccess(projectId);
const accessRole = access?.roles.find(role => role.id === roleId);
const environments = useMemo(() => {
const environments = new Set<string>();
@ -80,62 +92,65 @@ export const ProjectRoleDescription: VFC<IProjectRoleDescriptionProps> =
ref={ref}
>
<ConditionallyRender
condition={Boolean(projectPermissions?.length)}
condition={role.permissions?.length > 0}
show={
<>
<StyledDescriptionHeader>
Project permissions
</StyledDescriptionHeader>
<StyledDescriptionBlock>
{role.permissions
?.filter(
(permission: any) =>
!permission.environment
)
.map(
(permission: any) =>
permission.displayName
)
.sort()
.map((permission: any) => (
<p key={permission}>{permission}</p>
))}
</StyledDescriptionBlock>
<ConditionallyRender
condition={Boolean(
projectPermissions?.length
)}
show={
<>
<StyledDescriptionHeader>
Project permissions
</StyledDescriptionHeader>
<StyledDescriptionBlock>
<ProjectRoleDescriptionProjectPermissions
permissions={
role.permissions
}
/>
</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>
))}
</>
}
/>
</>
}
/>
<ConditionallyRender
condition={Boolean(environments.length)}
show={
elseShow={
<>
<StyledDescriptionHeader>
Environment permissions
</StyledDescriptionHeader>
{environments.map((environment: any) => (
<div key={environment}>
<StyledDescriptionSubHeader>
{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>
))}
<StyledDescriptionSubHeader>
{accessRole?.name}
</StyledDescriptionSubHeader>
<StyledDescriptionBlock>
{accessRole?.description}
</StyledDescriptionBlock>
</>
}
/>

View File

@ -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>
))}
</>
);

View File

@ -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>
))}
</>
);

View File

@ -17,12 +17,14 @@ const StyledPopover = styled(Popover)(() => ({
interface IProjectAccessRoleCellProps {
roleId: number;
projectId: string;
value?: string;
emptyText?: string;
}
export const ProjectAccessRoleCell: VFC<IProjectAccessRoleCellProps> = ({
roleId,
projectId,
value,
emptyText,
}) => {
@ -63,7 +65,11 @@ export const ProjectAccessRoleCell: VFC<IProjectAccessRoleCellProps> = ({
horizontal: 'left',
}}
>
<ProjectRoleDescription roleId={roleId} popover />
<ProjectRoleDescription
roleId={roleId}
projectId={projectId}
popover
/>
</StyledPopover>
</>
);

View File

@ -170,6 +170,7 @@ export const ProjectAccessTable: VFC = () => {
Cell: ({ value, row: { original: row } }: any) => (
<ProjectAccessRoleCell
roleId={row.entity.roleId}
projectId={projectId}
value={value}
/>
),

View File

@ -7,9 +7,9 @@ export const useChangeRequestConfig = (projectId: string) => {
const { data, error, mutate } = useEnterpriseSWR<
IChangeRequestEnvironmentConfig[]
>(
[],
formatApiPath(`api/admin/projects/${projectId}/change-requests/config`),
fetcher,
[]
fetcher
);
return {

View File

@ -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;
};

View File

@ -1,33 +1,21 @@
import useSWR, { BareFetcher, Key, SWRResponse } from 'swr';
import { useEffect } from 'react';
import { BareFetcher, Key, SWRConfiguration } from 'swr';
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
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>(
fallback: Data,
key: Key,
fetcher: BareFetcher<Data>,
fallback: Data
options: SWRConfiguration = {}
) => {
const { isEnterprise } = useUiConfig();
const result = useConditionalSWR(
isEnterprise(),
fallback,
key,
(path: string) =>
isEnterprise() ? fetcher(path) : Promise.resolve(fallback),
isEnterprise()
fetcher,
options
);
return result;

View File

@ -11,9 +11,9 @@ const fetcher = (path: string) => {
export const usePendingChangeRequests = (project: string) => {
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
[],
formatApiPath(`api/admin/projects/${project}/change-requests/pending`),
fetcher,
[]
fetcher
);
return {

View File

@ -14,11 +14,11 @@ export const usePendingChangeRequestsForFeature = (
featureName: string
) => {
const { data, error, mutate } = useEnterpriseSWR<IChangeRequest[]>(
[],
formatApiPath(
`api/admin/projects/${project}/change-requests/pending/${featureName}`
),
fetcher,
[]
fetcher
);
return {

View File

@ -1,7 +1,8 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { mutate, SWRConfiguration } from 'swr';
import { useState, useEffect } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR';
const useProjectRole = (id: string, options: SWRConfiguration = {}) => {
const fetcher = () => {
@ -13,7 +14,12 @@ const useProjectRole = (id: string, options: SWRConfiguration = {}) => {
.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 refetch = () => {