1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +02:00

[Gitar] Cleaning up stale flag: projectListImprovements with value true ()

Co-authored-by: Gitar <noreply@gitar.co>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
gitar-bot[bot] 2024-09-23 13:07:59 +02:00 committed by GitHub
parent 9f5e909436
commit 5dd0fb9f44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 15 additions and 421 deletions
frontend/src
component/project
interfaces
src

View File

@ -1,88 +0,0 @@
import type { FC } from 'react';
import { styled } from '@mui/material';
import type { ProjectSchema, ProjectSchemaOwners } from 'openapi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { AvatarGroup } from 'component/common/AvatarGroup/AvatarGroup';
export interface IProjectOwnersProps {
owners?: ProjectSchema['owners'];
}
const useOwnersMap = () => {
const { uiConfig } = useUiConfig();
return (
owner: ProjectSchemaOwners[0],
): {
name: string;
imageUrl?: string;
email?: string;
} => {
if (owner.ownerType === 'user') {
return {
name: owner.name,
imageUrl: owner.imageUrl || undefined,
email: owner.email || undefined,
};
}
if (owner.ownerType === 'group') {
return {
name: owner.name,
};
}
return {
name: 'System',
imageUrl: `${uiConfig.unleashUrl}/logo-unleash.png`,
};
};
};
const StyledUserName = styled('p')(({ theme }) => ({
fontSize: theme.typography.body1.fontSize,
margin: theme.spacing(0, 0, 0.5, 0),
overflowX: 'hidden',
textOverflow: 'ellipsis',
textWrap: 'nowrap',
alignSelf: 'end',
}));
const StyledContainer = styled('div')(() => ({
display: 'flex',
flexDirection: 'column',
}));
const StyledHeader = styled('h3')(({ theme }) => ({
margin: theme.spacing(0, 0, 1),
fontSize: theme.typography.caption.fontSize,
color: theme.palette.text.primary,
fontWeight: theme.typography.fontWeightRegular,
}));
const StyledWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(1.5, 0, 2.5, 3),
display: 'flex',
alignItems: 'center',
minWidth: 0,
}));
export const ProjectOwners: FC<IProjectOwnersProps> = ({ owners = [] }) => {
const ownersMap = useOwnersMap();
const users = owners.map(ownersMap);
return (
<StyledWrapper>
<StyledContainer>
<StyledHeader>
{owners.length === 1 ? 'Owner' : 'Owners'}
</StyledHeader>
<AvatarGroup users={users} avatarLimit={8} />
</StyledContainer>
<ConditionallyRender
condition={owners.length === 1}
show={<StyledUserName>{users[0]?.name}</StyledUserName>}
elseShow={<div />}
/>
</StyledWrapper>
);
};

View File

@ -2,12 +2,9 @@ import type React from 'react';
import type { FC } from 'react';
import { Box, styled } from '@mui/material';
import {
ProjectOwners,
type IProjectOwnersProps,
ProjectOwners as LegacyProjectOwners,
} from '../LegacyProjectOwners/LegacyProjectOwners';
import { ProjectOwners } from './ProjectOwners/ProjectOwners';
import { useUiFlag } from 'hooks/useUiFlag';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
} from './ProjectOwners/ProjectOwners';
interface IProjectCardFooterProps {
id?: string;
@ -31,20 +28,13 @@ const StyledFooter = styled(Box)<{ disabled: boolean }>(
);
export const ProjectCardFooter: FC<IProjectCardFooterProps> = ({
id,
children,
owners,
disabled = false,
}) => {
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
return (
<StyledFooter disabled={disabled}>
<ConditionallyRender
condition={Boolean(projectListImprovementsEnabled)}
show={<ProjectOwners owners={owners} />}
elseShow={<LegacyProjectOwners owners={owners} />}
/>
<ProjectOwners owners={owners} />
{children}
</StyledFooter>
);

View File

@ -1,11 +1,7 @@
import type { FC } from 'react';
import LockIcon from '@mui/icons-material/Lock';
import ProtectedProjectIcon from '@mui/icons-material/LockOutlined';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import PrivateProjectIcon from '@mui/icons-material/VisibilityOffOutlined';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { Badge } from 'component/common/Badge/Badge';
import { useUiFlag } from 'hooks/useUiFlag';
import { styled } from '@mui/material';
interface IProjectModeBadgeProps {
@ -19,50 +15,28 @@ const StyledIcon = styled('div')(({ theme }) => ({
}));
export const ProjectModeBadge: FC<IProjectModeBadgeProps> = ({ mode }) => {
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
if (mode === 'private') {
if (projectListImprovementsEnabled) {
return (
<HtmlTooltip
title="This project's collaboration mode is set to private. The project and associated feature flags can only be seen by members of the project."
arrow
>
<StyledIcon>
<PrivateProjectIcon fontSize='inherit' />
</StyledIcon>
</HtmlTooltip>
);
}
return (
<HtmlTooltip
title="This project's collaboration mode is set to private. The project and associated feature flags can only be seen by members of the project."
arrow
>
<Badge color='warning' icon={<VisibilityOffIcon />} />
<StyledIcon>
<PrivateProjectIcon fontSize='inherit' />
</StyledIcon>
</HtmlTooltip>
);
}
if (mode === 'protected') {
if (projectListImprovementsEnabled) {
return (
<HtmlTooltip
title="This project's collaboration mode is set to protected. Only admins and project members can submit change requests."
arrow
>
<StyledIcon>
<ProtectedProjectIcon fontSize='inherit' />
</StyledIcon>
</HtmlTooltip>
);
}
return (
<HtmlTooltip
title="This project's collaboration mode is set to protected. Only admins and project members can submit change requests."
arrow
>
<Badge color='warning' icon={<LockIcon />} />
<StyledIcon>
<ProtectedProjectIcon fontSize='inherit' />
</StyledIcon>
</HtmlTooltip>
);
}

View File

@ -140,7 +140,6 @@ export const ArchiveProjectList: FC = () => {
<SearchHighlightProvider value={searchValue}>
<ProjectGroup
loading={loading}
searchValue={searchValue}
projects={filteredProjects}
placeholder='No archived projects found'
ProjectCardComponent={ProjectCard}

View File

@ -1,249 +0,0 @@
import { type FC, useContext, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { ProjectSchema } from 'openapi';
import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton';
import { CREATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import Add from '@mui/icons-material/Add';
import ApiError from 'component/common/ApiError/ApiError';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { Link, styled, useMediaQuery } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import theme from 'themes/theme';
import { Search } from 'component/common/Search/Search';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import type { ITooltipResolverProps } from 'component/common/TooltipResolver/TooltipResolver';
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg';
import { safeRegExp } from '@server/util/escape-regex';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
import { groupProjects } from './group-projects';
import { ProjectGroup } from './ProjectGroup';
import { CreateProjectDialog } from '../Project/CreateProject/NewCreateProjectForm/CreateProjectDialog';
const StyledApiError = styled(ApiError)(({ theme }) => ({
maxWidth: '500px',
marginBottom: theme.spacing(2),
}));
const StyledContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(4),
}));
type PageQueryType = Partial<Record<'search', string>>;
interface ICreateButtonData {
disabled: boolean;
tooltip?: Omit<ITooltipResolverProps, 'children'>;
endIcon?: React.ReactNode;
}
const NAVIGATE_TO_CREATE_PROJECT = 'NAVIGATE_TO_CREATE_PROJECT';
function resolveCreateButtonData(
isOss: boolean,
hasAccess: boolean,
): ICreateButtonData {
if (isOss) {
return {
disabled: true,
tooltip: {
titleComponent: (
<PremiumFeature feature='adding-new-projects' tooltip />
),
sx: { maxWidth: '320px' },
variant: 'custom',
},
endIcon: (
<ThemeMode
darkmode={<ProPlanIconLight />}
lightmode={<ProPlanIcon />}
/>
),
};
} else if (!hasAccess) {
return {
tooltip: {
title: 'You do not have permission to create new projects',
},
disabled: true,
};
} else {
return {
tooltip: { title: 'Click to create a new project' },
disabled: false,
};
}
}
const ProjectCreationButton: FC = () => {
const [searchParams] = useSearchParams();
const showCreateDialog = Boolean(searchParams.get('create'));
const [openCreateDialog, setOpenCreateDialog] = useState(showCreateDialog);
const { hasAccess } = useContext(AccessContext);
const { isOss, loading } = useUiConfig();
const createButtonData = resolveCreateButtonData(
isOss(),
hasAccess(CREATE_PROJECT),
);
return (
<>
<ResponsiveButton
Icon={Add}
endIcon={createButtonData.endIcon}
onClick={() => setOpenCreateDialog(true)}
maxWidth='700px'
permission={CREATE_PROJECT}
disabled={createButtonData.disabled || loading}
tooltipProps={createButtonData.tooltip}
data-testid={NAVIGATE_TO_CREATE_PROJECT}
>
New project
</ResponsiveButton>
<CreateProjectDialog
open={openCreateDialog}
onClose={() => setOpenCreateDialog(false)}
/>
</>
);
};
export const ProjectList = () => {
const { projects, loading, error, refetch } = useProjects();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [searchParams, setSearchParams] = useSearchParams();
const [searchValue, setSearchValue] = useState(
searchParams.get('search') || '',
);
const myProjects = new Set(useProfile().profile?.projects || []);
useEffect(() => {
const tableState: PageQueryType = {};
if (searchValue) {
tableState.search = searchValue;
}
setSearchParams(tableState, {
replace: true,
});
}, [searchValue, setSearchParams]);
const filteredProjects = useMemo(() => {
const regExp = safeRegExp(searchValue, 'i');
return (
searchValue
? projects.filter((project) => regExp.test(project.name))
: projects
).sort((a, b) => {
if (a?.favorite && !b?.favorite) {
return -1;
}
if (!a?.favorite && b?.favorite) {
return 1;
}
return 0;
});
}, [projects, searchValue]);
const groupedProjects = useMemo(() => {
return groupProjects(myProjects, filteredProjects);
}, [filteredProjects, myProjects]);
const projectCount =
filteredProjects.length < projects.length
? `${filteredProjects.length} of ${projects.length}`
: projects.length;
const ProjectGroupComponent = (props: {
sectionTitle?: string;
projects: ProjectSchema[];
}) => {
return (
<ProjectGroup
loading={loading}
searchValue={searchValue}
{...props}
/>
);
};
return (
<PageContent
isLoading={loading}
header={
<PageHeader
title={`Projects (${projectCount})`}
actions={
<>
<ConditionallyRender
condition={!isSmallScreen}
show={
<>
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
<PageHeader.Divider />
</>
}
/>
<>
<Link
component={RouterLink}
to='/projects-archive'
>
Archived projects
</Link>
<PageHeader.Divider />
</>
<ProjectCreationButton />
</>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
/>
}
/>
</PageHeader>
}
>
<StyledContainer>
<ConditionallyRender
condition={error}
show={() => (
<StyledApiError
onClick={refetch}
text='Error fetching projects'
/>
)}
/>
<ProjectGroupComponent
sectionTitle='My projects'
projects={groupedProjects.myProjects}
/>
<ProjectGroupComponent
sectionTitle='Other projects'
projects={groupedProjects.otherProjects}
/>
</StyledContainer>
</PageContent>
);
};

View File

@ -1,13 +1,11 @@
import type { ComponentType, ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ProjectCard as LegacyProjectCard } from '../ProjectCard/LegacyProjectCard';
import { ProjectCard as NewProjectCard } from '../ProjectCard/ProjectCard';
import type { ProjectSchema } from 'openapi';
import loadingData from './loadingData';
import { TablePlaceholder } from 'component/common/Table';
import { styled, Typography } from '@mui/material';
import { useUiFlag } from 'hooks/useUiFlag';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { flexColumn } from 'themes/themeStyles';
@ -52,10 +50,6 @@ type ProjectGroupProps = {
HeaderActions?: ReactNode;
projects: ProjectSchema[];
loading: boolean;
/**
* @deprecated remove with projectListImprovements
*/
searchValue?: string;
placeholder?: string;
ProjectCardComponent?: ComponentType<ProjectSchema & any>;
link?: boolean;
@ -67,15 +61,11 @@ export const ProjectGroup = ({
HeaderActions,
projects,
loading,
searchValue,
placeholder = 'No projects available.',
ProjectCardComponent,
link = true,
}: ProjectGroupProps) => {
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
const ProjectCard =
ProjectCardComponent ??
(projectListImprovementsEnabled ? NewProjectCard : LegacyProjectCard);
const ProjectCard = ProjectCardComponent ?? NewProjectCard;
const { searchQuery } = useSearchHighlightContext();
return (
@ -91,10 +81,7 @@ export const ProjectGroup = ({
}
/>
<ConditionallyRender
condition={
Boolean(sectionSubtitle) &&
projectListImprovementsEnabled
}
condition={Boolean(sectionSubtitle)}
show={
<Typography variant='body2' color='text.secondary'>
{sectionSubtitle}
@ -108,11 +95,11 @@ export const ProjectGroup = ({
condition={projects.length < 1 && !loading}
show={
<ConditionallyRender
condition={(searchValue || searchQuery)?.length > 0}
condition={searchQuery?.length > 0}
show={
<TablePlaceholder>
No projects found matching &ldquo;
{searchValue || searchQuery}
{searchQuery}
&rdquo;
</TablePlaceholder>
}

View File

@ -1,4 +1,4 @@
import { type FC, useCallback } from 'react';
import { useCallback } from 'react';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
@ -9,11 +9,9 @@ import theme from 'themes/theme';
import { Search } from 'component/common/Search/Search';
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
import { ProjectGroup } from './ProjectGroup';
import { useUiFlag } from 'hooks/useUiFlag';
import { ProjectsListSort } from './ProjectsListSort/ProjectsListSort';
import { useProjectsListState } from './hooks/useProjectsListState';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { ProjectList as LegacyProjectList } from './LegacyProjectList';
import { ProjectCreationButton } from './ProjectCreationButton/ProjectCreationButton';
import { useGroupedProjects } from './hooks/useGroupedProjects';
import { useProjectsSearchAndSort } from './hooks/useProjectsSearchAndSort';
@ -30,7 +28,7 @@ const StyledContainer = styled('div')(({ theme }) => ({
gap: theme.spacing(6),
}));
const NewProjectList = () => {
export const ProjectList = () => {
const { projects, loading, error, refetch } = useProjects();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
@ -140,13 +138,3 @@ const NewProjectList = () => {
</PageContent>
);
};
export const ProjectList: FC = () => {
const projectListImprovementsEnabled = useUiFlag('projectListImprovements');
if (projectListImprovementsEnabled) {
return <NewProjectList />;
}
return <LegacyProjectList />;
};

View File

@ -86,7 +86,6 @@ export type UiFlags = {
enableLegacyVariants?: boolean;
navigationSidebar?: boolean;
flagCreator?: boolean;
projectListImprovements?: boolean;
onboardingUI?: boolean;
eventTimeline?: boolean;
personalDashboardUI?: boolean;

View File

@ -56,7 +56,6 @@ export type IFlagKey =
| 'extendedMetrics'
| 'removeUnsafeInlineStyleSrc'
| 'originMiddleware'
| 'projectListImprovements'
| 'useProjectReadModel'
| 'addonUsageMetrics'
| 'onboardingMetrics'
@ -280,10 +279,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE,
false,
),
projectListImprovements: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_PROJECT_LIST_IMPROVEMENTS,
false,
),
useProjectReadModel: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_USE_PROJECT_READ_MODEL,
false,

View File

@ -51,7 +51,6 @@ process.nextTick(async () => {
enableLegacyVariants: false,
extendedMetrics: true,
originMiddleware: true,
projectListImprovements: true,
useProjectReadModel: true,
addonUsageMetrics: true,
onboardingMetrics: true,