Skip to content

Commit

Permalink
Fix pro project role descriptions (#2612)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nunogois committed Dec 7, 2022
1 parent c66daa0 commit 9accbcf
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 82 deletions.
Expand Up @@ -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>

Expand Down
Expand Up @@ -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 =>
Expand Down Expand Up @@ -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>();
Expand All @@ -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>
</>
}
/>
Expand Down
@@ -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>
))}
</>
);
Expand Up @@ -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,
}) => {
Expand Down Expand Up @@ -63,7 +65,11 @@ export const ProjectAccessRoleCell: VFC<IProjectAccessRoleCellProps> = ({
horizontal: 'left',
}}
>
<ProjectRoleDescription roleId={roleId} popover />
<ProjectRoleDescription
roleId={roleId}
projectId={projectId}
popover
/>
</StyledPopover>
</>
);
Expand Down
Expand Up @@ -170,6 +170,7 @@ export const ProjectAccessTable: VFC = () => {
Cell: ({ value, row: { original: row } }: any) => (
<ProjectAccessRoleCell
roleId={row.entity.roleId}
projectId={projectId}
value={value}
/>
),
Expand Down
Expand Up @@ -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 {
Expand Down
@@ -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;
Expand Down
Expand Up @@ -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 {
Expand Down
Expand Up @@ -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 {
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/hooks/api/getters/useProjectRole/useProjectRole.ts
@@ -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 = () => {
Expand All @@ -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 = () => {
Expand Down

1 comment on commit 9accbcf

@vercel
Copy link

@vercel vercel bot commented on 9accbcf Dec 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.