diff --git a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx index bca2636e0b..6acdfc60e5 100644 --- a/frontend/src/component/feature/CreateFeature/CreateFeature.tsx +++ b/frontend/src/component/feature/CreateFeature/CreateFeature.tsx @@ -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 ( ({ 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); diff --git a/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/EditProject.tsx b/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/EditProject.tsx index 1cb4a821e7..3f7d90fdfe 100644 --- a/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/EditProject.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/EditProject.tsx @@ -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={} /> - + ); diff --git a/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateEnterpriseSettings.tsx b/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateEnterpriseSettings.tsx index c2eb11764c..215d0ab1ab 100644 --- a/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateEnterpriseSettings.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateEnterpriseSettings.tsx @@ -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 = () => { diff --git a/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateProject.tsx b/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateProject.tsx index 01ebbf7cdb..35c79ae723 100644 --- a/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateProject.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/Settings/EditProject/UpdateProject.tsx @@ -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 '${ diff --git a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx index 7d96b5e753..7d971052ed 100644 --- a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx +++ b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx @@ -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(); diff --git a/frontend/src/component/project/ProjectList/ProjectGroup.tsx b/frontend/src/component/project/ProjectList/ProjectGroup.tsx index d2ff284b1e..72a1945bc8 100644 --- a/frontend/src/component/project/ProjectList/ProjectGroup.tsx +++ b/frontend/src/component/project/ProjectList/ProjectGroup.tsx @@ -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}`} > - handleHover(project.id) - } + onHover={() => {}} name={project.name} mode={project.mode} memberCount={ diff --git a/frontend/src/component/project/ProjectList/ProjectList.tsx b/frontend/src/component/project/ProjectList/ProjectList.tsx index f0d1cad876..ab2a73261b 100644 --- a/frontend/src/component/project/ProjectList/ProjectList.tsx +++ b/frontend/src/component/project/ProjectList/ProjectList.tsx @@ -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({}); 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 = () => { ); diff --git a/frontend/src/hooks/api/getters/useProject/getProjectFetcher.ts b/frontend/src/hooks/api/getters/useProject/getProjectFetcher.ts deleted file mode 100644 index 649589ea74..0000000000 --- a/frontend/src/hooks/api/getters/useProject/getProjectFetcher.ts +++ /dev/null @@ -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, - }; -}; diff --git a/frontend/src/hooks/api/getters/useProject/useProject.ts b/frontend/src/hooks/api/getters/useProject/useProject.ts deleted file mode 100644 index 9179a6ada6..0000000000 --- a/frontend/src/hooks/api/getters/useProject/useProject.ts +++ /dev/null @@ -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(KEY, fetcher, options); - - const refetch = useCallback(() => { - mutate(); - }, [mutate]); - - return { - project: data || fallbackProject, - loading: !error && !data, - error, - refetch, - }; -}; - -export default useProject; diff --git a/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.test.ts b/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.test.ts new file mode 100644 index 0000000000..945bee1808 --- /dev/null +++ b/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.test.ts @@ -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); +}); diff --git a/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.ts b/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.ts index 502857e259..8ab01a0b9b 100644 --- a/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.ts +++ b/frontend/src/hooks/api/getters/useProjectOverview/useProjectOverview.ts @@ -50,4 +50,12 @@ export const useProjectOverviewNameOrId = (id: string): string => { return useProjectOverview(id).project.name || id; }; +export const featuresCount = ( + project: Pick, +) => { + return project.featureTypeCounts + .map((count) => count.count) + .reduce((a, b) => a + b, 0); +}; + export default useProjectOverview; diff --git a/frontend/src/hooks/useDefaultProjectSettings.ts b/frontend/src/hooks/useDefaultProjectSettings.ts index cd8ec720ca..83ac1b403c 100644 --- a/frontend/src/hooks/useDefaultProjectSettings.ts +++ b/frontend/src/hooks/useDefaultProjectSettings.ts @@ -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