Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update project overview endpoint #5518

Merged
merged 5 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
StyledWidgetTitle,
} from './ProjectInfo.styles';
import { OverridableComponent } from '@mui/material/OverridableComponent';
import { FeatureTypeCount } from '../../../../interfaces/project';

export interface IToggleTypesWidgetProps {
features: IFeatureToggleListItem[];
export interface IFlagTypesWidgetProps {
featureTypeCounts: FeatureTypeCount[];
}

const StyledTypeCount = styled(StyledCount)(({ theme }) => ({
Expand Down Expand Up @@ -53,23 +54,34 @@ const ToggleTypesRow = ({ type, Icon, count }: IToggleTypeRowProps) => {
);
};

export const ToggleTypesWidget = ({ features }: IToggleTypesWidgetProps) => {
export const FlagTypesWidget = ({
featureTypeCounts,
}: IFlagTypesWidgetProps) => {
const featureTypeStats = useMemo(() => {
const release =
features?.filter((feature) => feature.type === 'release').length ||
0;
featureTypeCounts.find(
(featureType) => featureType.type === 'release',
)?.count || 0;

const experiment =
features?.filter((feature) => feature.type === 'experiment')
.length || 0;
featureTypeCounts.find(
(featureType) => featureType.type === 'experiment',
)?.count || 0;

const operational =
features?.filter((feature) => feature.type === 'operational')
.length || 0;
featureTypeCounts.find(
(featureType) => featureType.type === 'operational',
)?.count || 0;

const kill =
features?.filter((feature) => feature.type === 'kill-switch')
.length || 0;
featureTypeCounts.find(
(featureType) => featureType.type === 'kill-switch',
)?.count || 0;

const permission =
features?.filter((feature) => feature.type === 'permission')
.length || 0;
featureTypeCounts.find(
(featureType) => featureType.type === 'permission',
)?.count || 0;

return {
release,
Expand All @@ -78,7 +90,7 @@ export const ToggleTypesWidget = ({ features }: IToggleTypesWidgetProps) => {
'kill-switch': kill,
permission,
};
}, [features]);
}, [featureTypeCounts]);

return (
<StyledProjectInfoWidgetContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import type { IFeatureToggleListItem } from 'interfaces/featureToggle';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
import { HealthWidget } from './HealthWidget';
import { ToggleTypesWidget } from './ToggleTypesWidget';
import { FlagTypesWidget } from './FlagTypesWidget';
import { MetaWidget } from './MetaWidget';
import { ProjectMembersWidget } from './ProjectMembersWidget';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ChangeRequestsWidget } from './ChangeRequestsWidget';
import { flexRow } from 'themes/themeStyles';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
import { FeatureTypeCount } from '../../../../interfaces/project';

interface IProjectInfoProps {
id: string;
memberCount: number;
features: IFeatureToggleListItem[];
featureTypeCounts: FeatureTypeCount[];
health: number;
description?: string;
stats: ProjectStatsSchema;
Expand Down Expand Up @@ -43,7 +44,7 @@ const ProjectInfo = ({
description,
memberCount,
health,
features,
featureTypeCounts,
stats,
}: IProjectInfoProps) => {
const { isEnterprise } = useUiConfig();
Expand Down Expand Up @@ -98,7 +99,7 @@ const ProjectInfo = ({
/>
}
/>
<ToggleTypesWidget features={features} />
<FlagTypesWidget featureTypeCounts={featureTypeCounts} />
</StyledProjectInfoSidebarContainer>
</aside>
);
Expand Down
33 changes: 28 additions & 5 deletions frontend/src/component/project/Project/ProjectOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
} from './ProjectFeatureToggles/PaginatedProjectFeatureToggles';

import { useTableState } from 'hooks/useTableState';
import useProjectOverview from '../../../hooks/api/getters/useProjectOverview/useProjectOverview';
import { FeatureTypeCount } from '../../../interfaces/project';

const refreshInterval = 15 * 1000;

Expand Down Expand Up @@ -47,7 +49,7 @@ const PaginatedProjectOverview: FC<{
storageKey?: string;
}> = ({ fullWidth, storageKey = 'project-overview' }) => {
const projectId = useRequiredPathParam('projectId');
const { project, loading: projectLoading } = useProject(projectId, {
const { project } = useProjectOverview(projectId, {
refreshInterval,
});

Expand Down Expand Up @@ -82,8 +84,14 @@ const PaginatedProjectOverview: FC<{
},
);

const { members, features, health, description, environments, stats } =
project;
const {
members,
featureTypeCounts,
health,
description,
environments,
stats,
} = project;

return (
<StyledContainer key={projectId}>
Expand All @@ -92,7 +100,7 @@ const PaginatedProjectOverview: FC<{
description={description}
memberCount={members}
health={health}
features={features}
featureTypeCounts={featureTypeCounts}
stats={stats}
/>
<StyledContentContainer>
Expand Down Expand Up @@ -138,14 +146,29 @@ const ProjectOverview = () => {

if (featureSearchFrontend) return <PaginatedProjectOverview />;

const featureTypeCounts = features.reduce(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is so the old component will work with refactor. This code will be removed when flag gets removed.

(acc: FeatureTypeCount[], feature) => {
const existingEntry = acc.find(
(entry) => entry.type === feature.type,
);
if (existingEntry) {
existingEntry.count += 1;
} else {
acc.push({ type: feature.type, count: 1 });
}
return acc;
},
[],
);

return (
<StyledContainer>
<ProjectInfo
id={projectId}
description={description}
memberCount={members}
health={health}
features={features}
featureTypeCounts={featureTypeCounts}
stats={stats}
/>
<StyledContentContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject';
import EditProject from './EditProject/EditProject';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useProjectOverviewNameOrId } from '../../../../../hooks/api/getters/useProjectOverview/useProjectOverview';

export const Settings = () => {
const projectId = useRequiredPathParam('projectId');
const projectName = useProjectNameOrId(projectId);
const projectName = useProjectOverviewNameOrId(projectId);
const { hasAccess } = useContext(AccessContext);
const { isOss } = useUiConfig();
usePageTitle(`Project configuration – ${projectName}`);
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/hooks/api/getters/useProject/useProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const fallbackProject: IProject = {
},
};

/**
* @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);
Expand All @@ -41,7 +45,10 @@ const useProject = (id: string, options: SWRConfiguration = {}) => {
refetch,
};
};

/**
* @deprecated It is recommended to use useProjectOverviewNameOrId instead, unless you need project features.
* In that case, we probably should create a project features endpoint and use that instead if features needed.
*/
export const useProjectNameOrId = (id: string): string => {
return useProject(id).project.name || id;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';

export const getProjectOverviewFetcher = (id: string) => {
const fetcher = () => {
const path = formatApiPath(`api/admin/projects/${id}/overview`);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Project overview'))
.then((res) => res.json());
};

const KEY = `api/admin/projects/${id}/overview`;

return {
fetcher,
KEY,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import useSWR, { SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { getProjectOverviewFetcher } from './getProjectOverviewFetcher';
import { IProjectOverview } from 'interfaces/project';

const fallbackProject: IProjectOverview = {
featureTypeCounts: [],
environments: [],
name: '',
health: 0,
members: 0,
version: '1',
description: 'Default',
favorite: false,
mode: 'open',
defaultStickiness: 'default',
stats: {
archivedCurrentWindow: 0,
archivedPastWindow: 0,
avgTimeToProdCurrentWindow: 0,
createdCurrentWindow: 0,
createdPastWindow: 0,
projectActivityCurrentWindow: 0,
projectActivityPastWindow: 0,
projectMembersAddedCurrentWindow: 0,
},
};

const useProjectOverview = (id: string, options: SWRConfiguration = {}) => {
const { KEY, fetcher } = getProjectOverviewFetcher(id);
const { data, error, mutate } = useSWR<IProjectOverview>(
KEY,
fetcher,
options,
);

const refetch = useCallback(() => {
mutate();
}, [mutate]);

return {
project: data || fallbackProject,
loading: !error && !data,
error,
refetch,
};
};

export const useProjectOverviewNameOrId = (id: string): string => {
return useProjectOverview(id).project.name || id;
};

export default useProjectOverview;
22 changes: 22 additions & 0 deletions frontend/src/interfaces/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export type FeatureNamingType = {
description: string;
};

export type FeatureTypeCount = {
type: string;
count: number;
};

export interface IProject {
id?: string;
members: number;
Expand All @@ -38,6 +43,23 @@ export interface IProject {
featureNaming?: FeatureNamingType;
}

export interface IProjectOverview {
id?: string;
members: number;
version: string;
name: string;
description?: string;
environments: Array<ProjectEnvironmentType>;
health: number;
stats: ProjectStatsSchema;
featureTypeCounts: FeatureTypeCount[];
favorite: boolean;
mode: ProjectMode;
defaultStickiness: string;
featureLimit?: number;
featureNaming?: FeatureNamingType;
}

export interface IProjectHealthReport extends IProject {
staleCount: number;
potentiallyStaleCount: number;
Expand Down
Loading