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

refactor: replace useProject with useProjectOverview (#7087)

This commit is contained in:
Mateusz Kwasniewski 2024-05-20 15:15:24 +02:00 committed by GitHub
parent ee92001bf5
commit 8a6daeee1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 53 additions and 116 deletions

View File

@ -13,8 +13,10 @@ import { CF_CREATE_BTN_ID } from 'utils/testIds';
import { formatUnknownError } from 'utils/formatUnknownError';
import { GO_BACK } from 'constants/navigate';
import { Alert, styled } from '@mui/material';
import useProject from 'hooks/api/getters/useProject/useProject';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useProjectOverview, {
featuresCount,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(2),
@ -54,7 +56,7 @@ const CreateFeature = () => {
errors,
} = useFeatureForm();
const { project: projectInfo } = useProject(project);
const { project: projectInfo } = useProjectOverview(project);
const { createFeatureToggle, loading } = useFeatureApi();
@ -98,7 +100,7 @@ const CreateFeature = () => {
const featureLimitReached = isFeatureLimitReached(
projectInfo.featureLimit,
projectInfo.features.length,
featuresCount(projectInfo),
);
return (
<FormTemplate

View File

@ -1,5 +1,4 @@
import { useMemo } from 'react';
import useProject from 'hooks/api/getters/useProject/useProject';
import type { IFeatureToggle } from 'interfaces/featureToggle';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
@ -9,6 +8,7 @@ import { Link } from 'react-router-dom';
import type { ChangeRequestType } from 'component/changeRequest/changeRequest.types';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const StyledContainer = styled('div')(({ theme }) => ({
display: 'grid',
@ -41,7 +41,7 @@ const FeatureSettingsProjectConfirm = ({
changeRequests,
}: IFeatureSettingsProjectConfirm) => {
const currentProjectId = useRequiredPathParam('projectId');
const { project } = useProject(projectId);
const { project } = useProjectOverview(projectId);
const { isChangeRequestConfiguredInAnyEnv } =
useChangeRequestsEnabled(projectId);

View File

@ -2,7 +2,6 @@ import {
PROJECT_SETTINGS_WRITE,
UPDATE_PROJECT,
} from 'component/providers/AccessProvider/permissions';
import useProject from 'hooks/api/getters/useProject/useProject';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useContext } from 'react';
@ -12,6 +11,9 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { UpdateEnterpriseSettings } from './UpdateEnterpriseSettings';
import { UpdateProject } from './UpdateProject';
import { DeleteProjectForm } from './DeleteProjectForm';
import useProjectOverview, {
featuresCount,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const StyledFormContainer = styled('div')(({ theme }) => ({
display: 'flex',
@ -23,7 +25,7 @@ const EditProject = () => {
const { isEnterprise } = useUiConfig();
const { hasAccess } = useContext(AccessContext);
const id = useRequiredPathParam('projectId');
const { project } = useProject(id);
const { project } = useProjectOverview(id);
if (!project.name) {
return null;
@ -47,7 +49,7 @@ const EditProject = () => {
condition={isEnterprise()}
show={<UpdateEnterpriseSettings project={project} />}
/>
<DeleteProjectForm featureCount={project.features.length} />
<DeleteProjectForm featureCount={featuresCount(project)} />
</StyledFormContainer>
</>
);

View File

@ -3,16 +3,16 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useProjectEnterpriseSettingsForm from 'component/project/Project/hooks/useProjectEnterpriseSettingsForm';
import useProject from 'hooks/api/getters/useProject/useProject';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import ProjectEnterpriseSettingsForm from 'component/project/Project/ProjectEnterpriseSettingsForm/ProjectEnterpriseSettingsForm';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import type { IProject } from 'component/../interfaces/project';
import type { IProjectOverview } from 'component/../interfaces/project';
import { styled } from '@mui/material';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const StyledContainer = styled('div')(({ theme }) => ({
minHeight: 0,
@ -34,7 +34,7 @@ const StyledFormContainer = styled('div')(({ theme }) => ({
}));
interface IUpdateEnterpriseSettings {
project: IProject;
project: IProjectOverview;
}
const EDIT_PROJECT_SETTINGS_BTN = 'EDIT_PROJECT_SETTINGS_BTN';
@ -89,7 +89,7 @@ export const UpdateEnterpriseSettings = ({
--data-raw '${JSON.stringify(getEnterpriseSettingsPayload(), undefined, 2)}'`;
};
const { refetch } = useProject(id);
const { refetch } = useProjectOverview(id);
const { editProjectSettings, loading } = useProjectApi();
const useFeatureNamePatternTracking = () => {

View File

@ -14,10 +14,10 @@ import useToast from 'hooks/useToast';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import type { IProject } from 'interfaces/project';
import useProject from 'hooks/api/getters/useProject/useProject';
import type { IProjectOverview } from 'interfaces/project';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { styled } from '@mui/material';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const StyledContainer = styled('div')<{ isPro: boolean }>(
({ theme, isPro }) => ({
@ -41,7 +41,7 @@ const StyledFormContainer = styled('div')(({ theme }) => ({
}));
interface IUpdateProject {
project: IProject;
project: IProjectOverview;
}
const EDIT_PROJECT_BTN = 'EDIT_PROJECT_BTN';
export const UpdateProject = ({ project }: IUpdateProject) => {
@ -75,7 +75,7 @@ export const UpdateProject = ({ project }: IUpdateProject) => {
);
const { editProject, loading } = useProjectApi();
const { refetch } = useProject(id);
const { refetch } = useProjectOverview(id);
const formatProjectApiCode = () => {
return `curl --location --request PUT '${

View File

@ -6,7 +6,6 @@ import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import ApiError from 'component/common/ApiError/ApiError';
import useToast from 'hooks/useToast';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useProject from 'hooks/api/getters/useProject/useProject';
import { Alert, styled, TableBody, TableRow } from '@mui/material';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import { Link } from 'react-router-dom';
@ -30,7 +29,9 @@ import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
import { EnvironmentHideDialog } from './EnvironmentHideDialog/EnvironmentHideDialog';
import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { useProjectOverviewNameOrId } from '../../../hooks/api/getters/useProjectOverview/useProjectOverview';
import useProjectOverview, {
useProjectOverviewNameOrId,
} from 'hooks/api/getters/useProjectOverview/useProjectOverview';
const StyledAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(4),
@ -58,7 +59,7 @@ const ProjectEnvironmentList = () => {
const { setToastData, setToastApiError } = useToast();
const { environments, loading, error, refetchEnvironments } =
useProjectEnvironments(projectId);
const { project, refetch: refetchProject } = useProject(projectId);
const { project, refetch: refetchProject } = useProjectOverview(projectId);
const { removeEnvironmentFromProject, addEnvironmentToProject } =
useProjectApi();

View File

@ -40,8 +40,7 @@ export const ProjectGroup: React.FC<{
projects: IProjectCard[];
loading: boolean;
searchValue: string;
handleHover: (id: string) => void;
}> = ({ sectionTitle, projects, loading, searchValue, handleHover }) => {
}> = ({ sectionTitle, projects, loading, searchValue }) => {
const useNewProjectCards = useUiFlag('projectsListNewCards');
const [StyledItemsContainer, ProjectCard] = useNewProjectCards
@ -108,9 +107,7 @@ export const ProjectGroup: React.FC<{
to={`/projects/${project.id}`}
>
<ProjectCard
onHover={() =>
handleHover(project.id)
}
onHover={() => {}}
name={project.name}
mode={project.mode}
memberCount={

View File

@ -1,7 +1,5 @@
import { useContext, useEffect, useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { mutate } from 'swr';
import { getProjectFetcher } from 'hooks/api/getters/useProject/getProjectFetcher';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { IProjectCard } from 'interfaces/project';
@ -13,7 +11,7 @@ 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 { useMediaQuery, styled } from '@mui/material';
import { styled, useMediaQuery } from '@mui/material';
import theme from 'themes/theme';
import { Search } from 'component/common/Search/Search';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
@ -93,7 +91,6 @@ export const ProjectListNew = () => {
const { hasAccess } = useContext(AccessContext);
const navigate = useNavigate();
const { projects, loading, error, refetch } = useProjects();
const [fetchedProjects, setFetchedProjects] = useState<projectMap>({});
const { isOss } = useUiConfig();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
@ -144,16 +141,6 @@ export const ProjectListNew = () => {
return groupProjects(myProjects, filteredProjects);
}, [filteredProjects, myProjects, splitProjectList]);
const handleHover = (projectId: string) => {
if (fetchedProjects[projectId]) {
return;
}
const { KEY, fetcher } = getProjectFetcher(projectId);
mutate(KEY, fetcher);
setFetchedProjects((prev) => ({ ...prev, [projectId]: true }));
};
const createButtonData = resolveCreateButtonData(
isOss(),
hasAccess(CREATE_PROJECT),
@ -172,7 +159,6 @@ export const ProjectListNew = () => {
<ProjectGroup
loading={loading}
searchValue={searchValue}
handleHover={handleHover}
{...props}
/>
);

View File

@ -1,20 +0,0 @@
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
export const getProjectFetcher = (id: string) => {
const fetcher = () => {
const path = formatApiPath(`api/admin/projects/${id}`);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Project overview'))
.then((res) => res.json());
};
const KEY = `api/admin/projects/${id}`;
return {
fetcher,
KEY,
};
};

View File

@ -1,55 +0,0 @@
import useSWR, { type SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { getProjectFetcher } from './getProjectFetcher';
import type { IProject } from 'interfaces/project';
const fallbackProject: IProject = {
features: [],
environments: [],
name: '',
health: 0,
members: 0,
version: '1',
description: 'Default',
favorite: false,
mode: 'open',
defaultStickiness: 'default',
featureLimit: 0,
featureNaming: {
description: 'asd',
example: 'A',
pattern: '[A-z]',
},
stats: {
archivedCurrentWindow: 0,
archivedPastWindow: 0,
avgTimeToProdCurrentWindow: 0,
createdCurrentWindow: 0,
createdPastWindow: 0,
projectActivityCurrentWindow: 0,
projectActivityPastWindow: 0,
projectMembersAddedCurrentWindow: 0,
},
};
/**
* @deprecated It is recommended to use useProjectOverview instead, unless you need project features.
* In that case, we should create a project features endpoint and use that instead if features needed.
*/
const useProject = (id: string, options: SWRConfiguration = {}) => {
const { KEY, fetcher } = getProjectFetcher(id);
const { data, error, mutate } = useSWR<IProject>(KEY, fetcher, options);
const refetch = useCallback(() => {
mutate();
}, [mutate]);
return {
project: data || fallbackProject,
loading: !error && !data,
error,
refetch,
};
};
export default useProject;

View File

@ -0,0 +1,16 @@
import { featuresCount } from './useProjectOverview';
test('features count based on feature types', () => {
expect(
featuresCount({
featureTypeCounts: [
{ type: 'release', count: 10 },
{ type: 'operational', count: 20 },
],
}),
).toBe(30);
expect(
featuresCount({ featureTypeCounts: [{ type: 'release', count: 10 }] }),
).toBe(10);
expect(featuresCount({ featureTypeCounts: [] })).toBe(0);
});

View File

@ -50,4 +50,12 @@ export const useProjectOverviewNameOrId = (id: string): string => {
return useProjectOverview(id).project.name || id;
};
export const featuresCount = (
project: Pick<IProjectOverview, 'featureTypeCounts'>,
) => {
return project.featureTypeCounts
.map((count) => count.count)
.reduce((a, b) => a + b, 0);
};
export default useProjectOverview;

View File

@ -1,8 +1,8 @@
import useProject from './api/getters/useProject/useProject';
import useProjectOverview from './api/getters/useProjectOverview/useProjectOverview';
const DEFAULT_STICKINESS = 'default';
export const useDefaultProjectSettings = (projectId: string) => {
const { project, loading, error } = useProject(projectId);
const { project, loading, error } = useProjectOverview(projectId);
return {
defaultStickiness: project.defaultStickiness
? project.defaultStickiness