mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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>
 | 
			
		||||
                        <ConditionallyRender
 | 
			
		||||
                            condition={Boolean(role?.id)}
 | 
			
		||||
                            show={<ProjectRoleDescription roleId={role?.id!} />}
 | 
			
		||||
                            show={
 | 
			
		||||
                                <ProjectRoleDescription
 | 
			
		||||
                                    roleId={role?.id!}
 | 
			
		||||
                                    projectId={projectId}
 | 
			
		||||
                                />
 | 
			
		||||
                            }
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
                            </>
 | 
			
		||||
                        }
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
    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>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -170,6 +170,7 @@ export const ProjectAccessTable: VFC = () => {
 | 
			
		||||
                Cell: ({ value, row: { original: row } }: any) => (
 | 
			
		||||
                    <ProjectAccessRoleCell
 | 
			
		||||
                        roleId={row.entity.roleId}
 | 
			
		||||
                        projectId={projectId}
 | 
			
		||||
                        value={value}
 | 
			
		||||
                    />
 | 
			
		||||
                ),
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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 { 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;
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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 = () => {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user