diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx
index 350f0e4316..bcaaf80949 100644
--- a/frontend/src/component/project/Project/Project.tsx
+++ b/frontend/src/component/project/Project/Project.tsx
@@ -26,7 +26,6 @@ import {
import useToast from 'hooks/useToast';
import useQueryParams from 'hooks/useQueryParams';
import { useEffect, useState, type ReactNode } from 'react';
-import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment.tsx';
import ProjectFlags from './ProjectFlags.tsx';
import ProjectHealth from './ProjectHealth/ProjectHealth.tsx';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
@@ -51,7 +50,6 @@ import type { UiFlags } from 'interfaces/uiConfig';
import { HiddenProjectIconWithTooltip } from './HiddenProjectIconWithTooltip/HiddenProjectIconWithTooltip.tsx';
import { ChangeRequestPlausibleProvider } from 'component/changeRequest/ChangeRequestContext';
import { ProjectApplications } from '../ProjectApplications/ProjectApplications.tsx';
-import { ProjectInsights } from './ProjectInsights/ProjectInsights.tsx';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { ProjectArchived } from './ArchiveProject/ProjectArchived.tsx';
import { usePlausibleTracker } from '../../../hooks/usePlausibleTracker.ts';
@@ -385,8 +383,6 @@ export const Project = () => {
/>
}
/>
- } />
- } />
} />
{
- testServerRoute(server, '/api/admin/ui-config', {
- versionInfo: {
- current: { enterprise: 'present' },
- },
- });
- testServerRoute(
- server,
- '/api/admin/projects/default/change-requests/count',
- {
- total: 14,
- approved: 2,
- applied: 0,
- rejected: 0,
- reviewRequired: 10,
- scheduled: 2,
- },
- );
-};
-
-const setupOssApi = () => {
- testServerRoute(server, '/api/admin/ui-config', {
- versionInfo: {
- current: { oss: 'present' },
- },
- });
-};
-
-test('Show enterprise hints', async () => {
- setupOssApi();
- render(
-
- } />
- ,
- {
- route: '/projects/default',
- },
- );
-
- await screen.findByText('Enterprise feature');
-});
-
-test('Show change requests info', async () => {
- setupEnterpriseApi();
- render(
-
- } />
- ,
- {
- route: '/projects/default',
- },
- );
-
- await screen.findByText('To be applied');
- await screen.findByText('10');
- await screen.findByText('4');
- await screen.findByText('14');
-});
diff --git a/frontend/src/component/project/Project/ProjectInsights/ChangeRequests/ChangeRequests.tsx b/frontend/src/component/project/Project/ProjectInsights/ChangeRequests/ChangeRequests.tsx
deleted file mode 100644
index ff25afb72e..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ChangeRequests/ChangeRequests.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import { Box, styled, Typography } from '@mui/material';
-import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
-import { Link } from 'react-router-dom';
-import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
-import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
-import { useChangeRequestsCount } from 'hooks/api/getters/useChangeRequestsCount/useChangeRequestsCount';
-
-const Container = styled(Box)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2.5),
-}));
-
-const BoxesContainer = styled(Box)(({ theme }) => ({
- display: 'flex',
- gap: theme.spacing(1),
- justifyContent: 'space-between',
- flexWrap: 'wrap',
-}));
-
-const NumberBox = styled(Box)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- padding: theme.spacing(1),
- borderRadius: theme.shape.borderRadiusMedium,
- border: `1px solid ${theme.palette.divider}`,
-}));
-
-const OpenBox = styled(Box)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- flex: 1,
- borderRadius: theme.shape.borderRadiusMedium,
- padding: theme.spacing(3),
- border: `2px solid ${theme.palette.primary.main}`,
-}));
-
-const ColorBox = styled(Box)(({ theme }) => ({
- borderRadius: '8px',
- padding: theme.spacing(1, 2),
- display: 'flex',
- gap: theme.spacing(6),
- justifyContent: 'space-between',
- alignItems: 'center',
- marginBottom: theme.spacing(1.5),
- whiteSpace: 'nowrap',
-}));
-
-const ApplyBox = styled(ColorBox)(({ theme }) => ({
- background: theme.palette.success.light,
- marginTop: theme.spacing(2.5),
-}));
-
-const ReviewBox = styled(ColorBox)(({ theme }) => ({
- background: theme.palette.secondary.light,
-}));
-
-const ChangeRequestNavigation = styled(Link)(({ theme }) => ({
- display: 'flex',
- justifyContent: 'space-between',
- textDecoration: 'none',
- color: theme.palette.text.primary,
-}));
-
-const Title = styled(Typography)(({ theme }) => ({
- fontSize: theme.spacing(2),
- color: theme.palette.text.secondary,
- marginBottom: theme.spacing(1),
-}));
-
-const MediumNumber = styled(Typography)(({ theme }) => ({
- fontSize: theme.spacing(3),
- color: theme.palette.text.primary,
-}));
-
-const BigNumber = styled(Typography)(({ theme }) => ({
- fontSize: theme.spacing(5.5),
- color: theme.palette.text.primary,
-}));
-
-export const ChangeRequests = () => {
- const projectId = useRequiredPathParam('projectId');
- const { isOss, isPro } = useUiConfig();
- const { data } = useChangeRequestsCount(projectId);
-
- const { total, applied, rejected, reviewRequired, scheduled, approved } =
- data;
- const toBeApplied = scheduled + approved;
-
- if (isOss() || isPro()) {
- return (
-
- Change requests
-
-
- );
- }
-
- return (
-
-
- Change requests
-
-
-
-
-
-
- Open
-
-
-
- To be applied
- {toBeApplied}
-
-
- To be reviewed
- {reviewRequired}
-
-
-
- Total
- {total}
-
-
- Applied
- {applied}
-
-
- Rejected
- {rejected}
-
-
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.test.tsx b/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.test.tsx
deleted file mode 100644
index 96d49dfa44..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.test.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { screen } from '@testing-library/react';
-import { render } from 'utils/testRenderer';
-import { testServerRoute, testServerSetup } from 'utils/testServer';
-import type { ProjectOverviewSchema } from 'openapi';
-import { Route, Routes } from 'react-router-dom';
-import { FlagTypesUsed } from './FlagTypesUsed.tsx';
-
-const server = testServerSetup();
-
-const setupApi = (overview: ProjectOverviewSchema) => {
- testServerRoute(server, '/api/admin/projects/default/overview', overview);
-};
-
-test('Show outdated SDKs and apps using them', async () => {
- setupApi({
- name: 'default',
- version: 2,
- featureTypeCounts: [
- {
- type: 'release',
- count: 57,
- },
- ],
- onboardingStatus: {
- status: 'onboarded',
- },
- });
- render(
-
-
- }
- />
- ,
- {
- route: '/projects/default',
- },
- );
-
- await screen.findByText('Release');
- await screen.findByText('57');
-});
diff --git a/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.tsx b/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.tsx
deleted file mode 100644
index 55b0bbacb2..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/FlagTypesUsed/FlagTypesUsed.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { type FC, useMemo } from 'react';
-import { styled, type SvgIconTypeMap, Typography } from '@mui/material';
-import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
-
-import type { OverridableComponent } from '@mui/material/OverridableComponent';
-import type { FeatureTypeCountSchema } from 'openapi';
-
-export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({
- margin: '0',
- [theme.breakpoints.down('md')]: {
- display: 'flex',
- flexDirection: 'column',
- position: 'relative',
- },
-}));
-
-export const StyledWidgetTitle = styled(Typography)(({ theme }) => ({
- marginBottom: theme.spacing(2.5),
-}));
-
-export const StyledCount = styled('span')(({ theme }) => ({
- fontSize: theme.typography.h2.fontSize,
- fontWeight: 'bold',
- color: theme.palette.text.primary,
-}));
-
-const StyledTypeCount = styled(StyledCount)(({ theme }) => ({
- marginLeft: 'auto',
- fontWeight: theme.typography.fontWeightRegular,
- color: theme.palette.text.secondary,
-}));
-
-interface IFlagTypeRowProps {
- type: string;
- Icon: OverridableComponent;
- count: number;
-}
-
-const StyledParagraphGridRow = styled('div')(({ theme }) => ({
- display: 'flex',
- gap: theme.spacing(1.5),
- width: '100%',
- margin: theme.spacing(1, 0),
- fontSize: theme.fontSizes.smallBody,
- color: theme.palette.text.secondary,
- alignItems: 'center',
- [theme.breakpoints.down('md')]: {
- margin: 0,
- },
-}));
-
-const FlagTypesRow = ({ type, Icon, count }: IFlagTypeRowProps) => {
- const getTitleText = (str: string) => {
- return str.charAt(0).toUpperCase() + str.slice(1).replace('-', ' ');
- };
-
- return (
-
-
- {getTitleText(type)}
- {count}
-
- );
-};
-
-export const FlagTypesUsed: FC<{
- featureTypeCounts: FeatureTypeCountSchema[];
-}> = ({ featureTypeCounts }) => {
- const featureTypeStats = useMemo(() => {
- const release =
- featureTypeCounts.find(
- (featureType) => featureType.type === 'release',
- )?.count || 0;
-
- const experiment =
- featureTypeCounts.find(
- (featureType) => featureType.type === 'experiment',
- )?.count || 0;
-
- const operational =
- featureTypeCounts.find(
- (featureType) => featureType.type === 'operational',
- )?.count || 0;
-
- const kill =
- featureTypeCounts.find(
- (featureType) => featureType.type === 'kill-switch',
- )?.count || 0;
-
- const permission =
- featureTypeCounts.find(
- (featureType) => featureType.type === 'permission',
- )?.count || 0;
-
- return {
- release,
- experiment,
- operational,
- 'kill-switch': kill,
- permission,
- };
- }, [featureTypeCounts]);
-
- return (
-
-
- Flag types used
-
- {Object.keys(featureTypeStats).map((type) => (
-
- ))}
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx
deleted file mode 100644
index eca097c1cf..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.test.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { screen } from '@testing-library/react';
-import { render } from 'utils/testRenderer';
-import type { ProjectDoraMetricsSchema } from 'openapi';
-import { LeadTimeForChanges } from './LeadTimeForChanges.tsx';
-import { Route, Routes } from 'react-router-dom';
-
-test('Show outdated SDKs and apps using them', async () => {
- const leadTime: ProjectDoraMetricsSchema = {
- features: [
- {
- name: 'ABCD',
- timeToProduction: 57,
- },
- ],
- projectAverage: 67,
- };
- render(
-
-
- }
- />
- ,
- {
- route: '/projects/default',
- },
- );
-
- await screen.findByText('Lead time for changes (per release flag)');
- await screen.findByText('ABCD');
- await screen.findByText('57 days');
- await screen.findByText('Low');
- await screen.findByText('10 days');
-});
diff --git a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx b/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx
deleted file mode 100644
index 655dce8164..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/LeadTimeForChanges/LeadTimeForChanges.tsx
+++ /dev/null
@@ -1,267 +0,0 @@
-import { Box, styled, Tooltip, Typography, useMediaQuery } from '@mui/material';
-import { useMemo } from 'react';
-import { useTable, useGlobalFilter, useSortBy } from 'react-table';
-import {
- Table,
- SortableTableHeader,
- TableBody,
- TableCell,
- TableRow,
- TablePlaceholder,
-} from 'component/common/Table';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { Badge } from 'component/common/Badge/Badge';
-import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
-import theme from 'themes/theme';
-import type { ProjectDoraMetricsSchema } from 'openapi';
-
-const Container = styled(Box)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
-}));
-
-const TableContainer = styled(Box)(({ theme }) => ({
- overflowY: 'auto',
- maxHeight: theme.spacing(45),
-}));
-
-const resolveDoraMetrics = (input: number) => {
- const ONE_MONTH = 30;
- const ONE_WEEK = 7;
-
- if (input >= ONE_MONTH) {
- return Low;
- }
-
- if (input <= ONE_MONTH && input >= ONE_WEEK + 1) {
- return Medium;
- }
-
- if (input <= ONE_WEEK) {
- return High;
- }
-};
-
-interface ILeadTimeForChangesProps {
- leadTime: ProjectDoraMetricsSchema;
- loading: boolean;
-}
-
-const loadingLeadTimeFeatures = [
- { name: 'feature1', timeToProduction: 0 },
- { name: 'feature2', timeToProduction: 0 },
- { name: 'feature3', timeToProduction: 0 },
- { name: 'feature4', timeToProduction: 0 },
- { name: 'feature5', timeToProduction: 2 },
-];
-
-export const LeadTimeForChanges = ({
- leadTime,
- loading,
-}: ILeadTimeForChangesProps) => {
- const columns = useMemo(
- () => [
- {
- Header: 'Name',
- accessor: 'name',
- width: '40%',
- Cell: ({
- row: {
- original: { name },
- },
- }: any) => {
- return (
-
- {name}
-
- );
- },
- sortType: 'alphanumeric',
- },
- {
- Header: 'Time to production',
- id: 'timetoproduction',
- align: 'center',
- Cell: ({ row: { original } }: any) => (
-
-
- {original.timeToProduction} days
-
-
- ),
- width: 220,
- disableGlobalFilter: true,
- disableSortBy: true,
- },
- {
- Header: `Deviation`,
- id: 'deviation',
- align: 'center',
- Cell: ({ row: { original } }: any) => (
-
-
- {Math.round(
- (leadTime.projectAverage
- ? leadTime.projectAverage
- : 0) - original.timeToProduction,
- )}{' '}
- days
-
-
- ),
- width: 300,
- disableGlobalFilter: true,
- disableSortBy: true,
- },
- {
- Header: 'DORA',
- id: 'dora',
- align: 'center',
- Cell: ({ row: { original } }: any) => (
-
-
- {resolveDoraMetrics(original.timeToProduction)}
-
-
- ),
- width: 200,
- disableGlobalFilter: true,
- disableSortBy: true,
- },
- ],
- [JSON.stringify(leadTime.features), loading],
- );
-
- const initialState = useMemo(
- () => ({
- sortBy: [
- {
- id: 'name',
- desc: false,
- },
- ],
- }),
- [],
- );
-
- const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
-
- const {
- getTableProps,
- getTableBodyProps,
- headerGroups,
- rows,
- prepareRow,
- state: { globalFilter },
- setHiddenColumns,
- } = useTable(
- {
- columns: columns as any[],
- data: loading ? loadingLeadTimeFeatures : leadTime.features,
- initialState,
- autoResetGlobalFilter: false,
- autoResetSortBy: false,
- disableSortRemove: true,
- },
- useGlobalFilter,
- useSortBy,
- );
-
- useConditionallyHiddenColumns(
- [
- {
- condition: isExtraSmallScreen,
- columns: ['deviation'],
- },
- ],
- setHiddenColumns,
- columns,
- );
-
- return (
-
-
- Lead time for changes (per release flag)
-
-
-
-
-
- {rows.map((row) => {
- prepareRow(row);
- const { key, ...rowProps } = row.getRowProps();
- return (
-
- {row.cells.map((cell) => {
- const { key, ...cellProps } =
- cell.getCellProps();
- return (
-
- {cell.render('Cell')}
-
- );
- })}
-
- );
- })}
-
-
-
- 0}
- show={
-
- No features with data found “
- {globalFilter}
- ”
-
- }
- />
- }
- />
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/FlagCounts.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/FlagCounts.tsx
deleted file mode 100644
index 226f8c910b..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/FlagCounts.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Box, styled, useTheme } from '@mui/material';
-import { Link } from 'react-router-dom';
-import type { FC } from 'react';
-
-const Dot = styled('span', {
- shouldForwardProp: (prop) => prop !== 'color',
-})<{ color?: string }>(({ theme, color }) => ({
- height: '15px',
- width: '15px',
- borderRadius: '50%',
- display: 'inline-block',
- backgroundColor: color,
-}));
-
-const FlagCountsWrapper = styled(Box)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
-}));
-
-const FlagsCount = styled('p')(({ theme }) => ({
- color: theme.palette.text.secondary,
- marginLeft: theme.spacing(3),
-}));
-
-const StatusWithDot = styled(Box)(({ theme }) => ({
- display: 'flex',
- alignItems: 'center',
- gap: theme.spacing(1),
-}));
-
-export const FlagCounts: FC<{
- projectId: string;
- activeCount: number;
- potentiallyStaleCount: number;
- staleCount: number;
- hideLinks?: boolean;
-}> = ({
- projectId,
- activeCount,
- potentiallyStaleCount,
- staleCount,
- hideLinks = false,
-}) => {
- const theme = useTheme();
-
- return (
-
-
-
-
- Active
-
- {activeCount} feature flags
-
-
-
-
- Potentially stale
- {hideLinks ? null : (
- (configure)
- )}
-
- {potentiallyStaleCount} feature flags
-
-
-
-
- Stale
- {hideLinks ? null : (
-
- (view flags)
-
- )}
-
- {staleCount} feature flags
-
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx
deleted file mode 100644
index dc0d42fa4d..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealth.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { ProjectHealthChart } from './ProjectHealthChart.tsx';
-import { Alert, Box, styled, Typography } from '@mui/material';
-import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-import type { ProjectInsightsSchemaHealth } from 'openapi';
-import type { FC } from 'react';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { FlagCounts } from './FlagCounts.tsx';
-
-const Container = styled(Box)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2),
-}));
-
-export const ProjectHealth: FC<{ health: ProjectInsightsSchemaHealth }> = ({
- health,
-}) => {
- const projectId = useRequiredPathParam('projectId');
- const { staleCount, potentiallyStaleCount, activeCount, rating } = health;
-
- return (
-
- Project Health
- 0}
- show={
-
- Health alert! Review your flags and delete the
- stale flags
-
- }
- />
-
- ({
- display: 'flex',
- gap: theme.spacing(4),
- marginTop: theme.spacing(3),
- })}
- >
-
-
-
-
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart.test.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart.test.tsx
deleted file mode 100644
index 2cd962485c..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart.test.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import '@testing-library/jest-dom';
-import { ProjectHealthChart } from './ProjectHealthChart.tsx';
-import { render } from 'utils/testRenderer';
-import { screen } from '@testing-library/react';
-
-describe('ProjectHealthChart', () => {
- test('renders correctly with no flags', () => {
- const { container } = render(
- ,
- );
-
- const activeCircle = container.querySelector(
- 'circle[data-testid="active-circle"]',
- );
- const staleCircle = container.querySelector(
- 'circle[data-testid="stale-circle"]',
- );
- const potentiallyStaleCircle = container.querySelector(
- 'circle[data-testid="potentially-stale-circle"]',
- );
-
- expect(activeCircle).toBeInTheDocument();
- expect(staleCircle).not.toBeInTheDocument();
- expect(potentiallyStaleCircle).not.toBeInTheDocument();
- });
-
- test('renders correctly with 1 active and 0 stale', () => {
- const { container } = render(
- ,
- );
-
- const activeCircle = container.querySelector(
- 'circle[data-testid="active-circle"]',
- );
- const staleCircle = container.querySelector(
- 'circle[data-testid="stale-circle"]',
- );
- const potentiallyStaleCircle = container.querySelector(
- 'circle[data-testid="potentially-stale-circle"]',
- );
-
- expect(activeCircle).toBeInTheDocument();
- expect(staleCircle).not.toBeInTheDocument();
- expect(potentiallyStaleCircle).not.toBeInTheDocument();
- });
-
- test('renders correctly with 0 active and 1 stale', () => {
- const { container } = render(
- ,
- );
-
- const staleCircle = container.querySelector(
- 'circle[data-testid="stale-circle"]',
- );
-
- expect(staleCircle).toBeInTheDocument();
- });
-
- test('renders correctly with active, stale and potentially stale', () => {
- const { container } = render(
- ,
- );
-
- const activeCircle = container.querySelector(
- 'circle[data-testid="active-circle"]',
- );
- const staleCircle = container.querySelector(
- 'circle[data-testid="stale-circle"]',
- );
- const potentiallyStaleCircle = container.querySelector(
- 'circle[data-testid="potentially-stale-circle"]',
- );
-
- expect(activeCircle).toBeInTheDocument();
- expect(staleCircle).toBeInTheDocument();
- expect(potentiallyStaleCircle).toBeInTheDocument();
- });
-
- test('renders flags count and health', () => {
- const { container } = render(
- ,
- );
-
- expect(screen.queryByText('3 flags')).toBeInTheDocument();
- expect(screen.queryByText('50%')).toBeInTheDocument();
- });
-
- test('renders small values without negative stroke dasharray', () => {
- const { container } = render(
- ,
- );
-
- const activeCircle = container.querySelector(
- 'circle[data-testid="active-circle"]',
- );
- const staleCircle = container.querySelector(
- 'circle[data-testid="stale-circle"]',
- );
- const potentiallyStaleCircle = container.querySelector(
- 'circle[data-testid="potentially-stale-circle"]',
- );
-
- expect(
- activeCircle?.getAttribute('stroke-dasharray')?.charAt(0),
- ).not.toBe('-');
- expect(
- staleCircle?.getAttribute('stroke-dasharray')?.charAt(0),
- ).not.toBe('-');
- expect(
- potentiallyStaleCircle?.getAttribute('stroke-dasharray')?.charAt(0),
- ).not.toBe('-');
- });
-});
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart.tsx
deleted file mode 100644
index 45ddccdd7a..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectHealth/ProjectHealthChart.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import type React from 'react';
-import { useTheme } from '@mui/material';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-
-interface ProgressComponentProps {
- active: number;
- stale: number;
- potentiallyStale: number;
- health: number;
-}
-
-export const ProjectHealthChart: React.FC = ({
- active,
- stale,
- potentiallyStale,
- health,
-}) => {
- const theme = useTheme();
- const gap =
- active === 0 ||
- stale === 0 ||
- active / stale > 30 ||
- stale / active > 30
- ? 0
- : 10;
- const strokeWidth = 6;
- const radius = 50 - strokeWidth / 2;
- const circumference = 2 * Math.PI * radius;
- const gapAngle = (gap / circumference) * 360;
-
- const totalCount = active + stale;
- const activePercentage =
- totalCount === 0 ? 100 : (active / totalCount) * 100;
- const stalePercentage = totalCount === 0 ? 0 : (stale / totalCount) * 100;
- const potentiallyStalePercentage =
- active === 0 ? 0 : (potentiallyStale / totalCount) * 100;
-
- const activeLength = Math.max(
- (activePercentage / 100) * circumference - gap,
- 1,
- );
- const staleLength = Math.max(
- (stalePercentage / 100) * circumference - gap,
- 1,
- );
- const potentiallyStaleLength = Math.max(
- (potentiallyStalePercentage / 100) * circumference - gap,
- 1,
- );
-
- const activeRotation = -90 + gapAngle / 2;
- const potentiallyStaleRotation =
- activeRotation +
- ((activeLength - potentiallyStaleLength) / circumference) * 360;
- const staleRotation =
- activeRotation + (activeLength / circumference) * 360 + gapAngle;
-
- const innerRadius = radius / 1.2;
-
- return (
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx
deleted file mode 100644
index a70a13c738..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsights.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { Box, styled } from '@mui/material';
-import { ChangeRequests } from './ChangeRequests/ChangeRequests.tsx';
-import { LeadTimeForChanges } from './LeadTimeForChanges/LeadTimeForChanges.tsx';
-import { ProjectHealth } from './ProjectHealth/ProjectHealth.tsx';
-import { FlagTypesUsed } from './FlagTypesUsed/FlagTypesUsed.tsx';
-import { ProjectInsightsStats } from './ProjectInsightsStats/ProjectInsightsStats.tsx';
-import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-import { useProjectInsights } from 'hooks/api/getters/useProjectInsights/useProjectInsights';
-import useLoading from 'hooks/useLoading';
-import { ProjectMembers } from './ProjectMembers/ProjectMembers.tsx';
-
-const Container = styled(Box)(({ theme }) => ({
- backgroundColor: theme.palette.background.paper,
- padding: theme.spacing(3),
- borderRadius: theme.shape.borderRadiusLarge,
-}));
-
-const Grid = styled(Box)(({ theme }) => ({
- display: 'grid',
- gap: theme.spacing(2),
- gridTemplateColumns: 'repeat(10, 1fr)',
-}));
-
-const FullWidthContainer = styled(Box)(() => ({
- gridColumn: '1 / -1',
-}));
-
-const WideContainer = styled(Container)(() => ({
- gridColumn: 'span 6',
-}));
-
-const MediumWideContainer = styled(Container)(() => ({
- gridColumn: 'span 4',
-}));
-
-const NarrowContainer = styled(Container)(() => ({
- gridColumn: 'span 2',
-}));
-
-export const ProjectInsights = () => {
- const projectId = useRequiredPathParam('projectId');
- const { data, loading } = useProjectInsights(projectId);
-
- const ref = useLoading(loading);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/HelpPopper.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/HelpPopper.tsx
deleted file mode 100644
index ebff44a7a6..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/HelpPopper.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import type React from 'react';
-import { type FC, useState } from 'react';
-import Close from '@mui/icons-material/Close';
-import HelpOutline from '@mui/icons-material/HelpOutline';
-import {
- Box,
- IconButton,
- Popper,
- Paper,
- ClickAwayListener,
- styled,
-} from '@mui/material';
-import { Feedback } from 'component/common/Feedback/Feedback';
-
-interface IHelpPopperProps {
- id: string;
- children?: React.ReactNode;
-}
-
-const StyledPaper = styled(Paper)(({ theme }) => ({
- padding: theme.spacing(3, 3),
- maxWidth: '350px',
- borderRadius: `${theme.shape.borderRadiusMedium}px`,
- border: `1px solid ${theme.palette.neutral.border}`,
- fontSize: theme.typography.body2.fontSize,
-}));
-
-export const HelpPopper: FC = ({ children, id }) => {
- const [anchor, setAnchorEl] = useState(null);
-
- const onOpen = (event: React.FormEvent) =>
- setAnchorEl(event.currentTarget);
-
- const onClose = () => setAnchorEl(null);
-
- const open = Boolean(anchor);
-
- return (
-
-
- theme.typography.body1.fontSize,
- }}
- />
-
-
- ({ zIndex: theme.zIndex.tooltip })}
- >
-
-
-
-
- theme.typography.body1.fontSize,
- }}
- />
-
- {children}
-
-
-
-
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/ProjectInsightsStats.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/ProjectInsightsStats.tsx
deleted file mode 100644
index fc5e923ab3..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/ProjectInsightsStats.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { Box, styled, Typography } from '@mui/material';
-import type { ProjectStatsSchema } from 'openapi/models';
-import { HelpPopper } from './HelpPopper.tsx';
-import { StatusBox } from './StatusBox.tsx';
-import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
-import { Link } from 'react-router-dom';
-import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-
-const StyledBox = styled(Box)(({ theme }) => ({
- display: 'grid',
- gap: theme.spacing(2),
- gridTemplateColumns: 'repeat(4, 1fr)',
- flexWrap: 'wrap',
- [theme.breakpoints.down('lg')]: {
- gridTemplateColumns: 'repeat(2, 1fr)',
- },
- [theme.breakpoints.down('sm')]: {
- flexDirection: 'column',
- },
-}));
-
-const StyledTimeToProductionDescription = styled(Typography)(({ theme }) => ({
- color: theme.palette.text.secondary,
- fontSize: theme.typography.body2.fontSize,
- lineHeight: theme.typography.body2.lineHeight,
-}));
-
-const NavigationBar = styled(Link)(({ theme }) => ({
- marginLeft: 'auto',
- display: 'flex',
- justifyContent: 'space-between',
- textDecoration: 'none',
- color: theme.palette.text.primary,
-}));
-
-interface IProjectStatsProps {
- stats: ProjectStatsSchema;
-}
-
-export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
- const projectId = useRequiredPathParam('projectId');
- if (Object.keys(stats).length === 0) {
- return null;
- }
-
- const {
- avgTimeToProdCurrentWindow,
- projectActivityCurrentWindow,
- projectActivityPastWindow,
- createdCurrentWindow,
- createdPastWindow,
- archivedCurrentWindow,
- archivedPastWindow,
- } = stats;
-
- return (
-
-
-
- Sum of all configuration and state modifications in the
- project.
-
-
- theme.spacing(1),
- }}
- >
- {avgTimeToProdCurrentWindow}{' '}
- days
-
- }
- customChangeElement={
-
- In project life
-
- }
- percentage
- >
-
- How long did it take on average from a feature flag was
- created until it was enabled in an environment of type
- production. This is calculated only from feature flags with
- the type of "release".
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/StatusBox.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/StatusBox.tsx
deleted file mode 100644
index f4597c1b69..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectInsightsStats/StatusBox.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import type React from 'react';
-import type { FC, ReactNode } from 'react';
-import CallMade from '@mui/icons-material/CallMade';
-import SouthEast from '@mui/icons-material/SouthEast';
-import { Box, Typography, styled } from '@mui/material';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { flexRow } from 'themes/themeStyles';
-
-const StyledTypographyCount = styled(Box)(({ theme }) => ({
- fontSize: theme.fontSizes.largeHeader,
-}));
-
-const StyledBoxChangeContainer = styled(Box)(({ theme }) => ({
- ...flexRow,
- flexDirection: 'column',
- alignItems: 'center',
- marginLeft: theme.spacing(2.5),
-}));
-
-const StyledTypographySubtext = styled(Typography)(({ theme }) => ({
- color: theme.palette.text.secondary,
- fontSize: theme.typography.body2.fontSize,
-}));
-
-const StyledTypographyChange = styled(Typography)(({ theme }) => ({
- marginLeft: theme.spacing(1),
- fontSize: theme.typography.body1.fontSize,
- fontWeight: theme.typography.fontWeightBold,
-}));
-
-const RowContainer = styled(Box)(({ theme }) => ({
- ...flexRow,
-}));
-
-const StyledWidget = styled(Box)(({ theme }) => ({
- padding: theme.spacing(3),
- backgroundColor: theme.palette.background.paper,
- flex: 1,
- display: 'flex',
- flexDirection: 'column',
- gap: theme.spacing(2.5),
- borderRadius: `${theme.shape.borderRadiusLarge}px`,
- [theme.breakpoints.down('lg')]: {
- padding: theme.spacing(2),
- },
-}));
-
-interface IStatusBoxProps {
- title: string;
- boxText: ReactNode;
- change?: number;
- percentage?: boolean;
- customChangeElement?: ReactNode;
- children?: React.ReactNode;
-}
-
-const resolveIcon = (change: number) => {
- if (change > 0) {
- return (
-
- );
- }
- return (
-
- );
-};
-
-const resolveColor = (change: number) => {
- if (change > 0) {
- return 'success.dark';
- }
- return 'warning.dark';
-};
-
-export const StatusBox: FC = ({
- title,
- boxText,
- change,
- percentage,
- children,
- customChangeElement,
-}) => (
-
-
-
- {title}
-
- {children}
-
-
-
- {boxText}
-
-
- {customChangeElement}
-
- }
- elseShow={
-
-
- {resolveIcon(change as number)}
-
- {(change as number) > 0 ? '+' : ''}
- {change}
- {percentage ? '%' : ''}
-
-
-
- this month
-
-
- }
- elseShow={
-
-
- No change
-
-
- }
- />
- }
- />
-
-
-);
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.test.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.test.tsx
deleted file mode 100644
index cb3513792b..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.test.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { screen } from '@testing-library/react';
-import { render } from 'utils/testRenderer';
-import { ProjectMembers } from './ProjectMembers.tsx';
-
-test('Show outdated project members', async () => {
- const members = {
- currentMembers: 10,
- change: 2,
- };
-
- render();
-
- await screen.findByText('10');
- await screen.findByText('+2');
-});
diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.tsx
deleted file mode 100644
index 7f0cd154dc..0000000000
--- a/frontend/src/component/project/Project/ProjectInsights/ProjectMembers/ProjectMembers.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
-import { styled } from '@mui/material';
-import { StatusBox } from '../ProjectInsightsStats/StatusBox.tsx';
-import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
-import { Link } from 'react-router-dom';
-import type { ProjectInsightsSchemaMembers } from 'openapi';
-
-interface IProjectMembersProps {
- members: ProjectInsightsSchemaMembers;
- projectId: string;
-}
-
-const NavigationBar = styled(Link)(({ theme }) => ({
- marginLeft: 'auto',
- display: 'flex',
- justifyContent: 'space-between',
- textDecoration: 'none',
- color: theme.palette.text.primary,
-}));
-
-export const ProjectMembers = ({
- members,
- projectId,
-}: IProjectMembersProps) => {
- const { uiConfig } = useUiConfig();
-
- const link = uiConfig?.versionInfo?.current?.enterprise
- ? `/projects/${projectId}/settings/access`
- : `/admin/users`;
-
- const { currentMembers, change } = members;
- return (
-
-
-
-
-
- );
-};
diff --git a/frontend/src/hooks/api/getters/useProjectInsights/useProjectInsights.ts b/frontend/src/hooks/api/getters/useProjectInsights/useProjectInsights.ts
deleted file mode 100644
index ed6e48edf5..0000000000
--- a/frontend/src/hooks/api/getters/useProjectInsights/useProjectInsights.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { fetcher, useApiGetter } from '../useApiGetter/useApiGetter.js';
-import type { ProjectInsightsSchema } from 'openapi';
-import { formatApiPath } from 'utils/formatPath';
-
-const path = (projectId: string) => `api/admin/projects/${projectId}/insights`;
-
-const placeholderData: ProjectInsightsSchema = {
- stats: {
- avgTimeToProdCurrentWindow: 0,
- createdCurrentWindow: 0,
- createdPastWindow: 0,
- archivedCurrentWindow: 0,
- archivedPastWindow: 0,
- projectActivityCurrentWindow: 0,
- projectActivityPastWindow: 0,
- projectMembersAddedCurrentWindow: 0,
- },
- featureTypeCounts: [
- {
- type: 'experiment',
- count: 0,
- },
- {
- type: 'permission',
- count: 0,
- },
- {
- type: 'release',
- count: 0,
- },
- ],
- leadTime: {
- projectAverage: 0,
- features: [],
- },
- health: {
- rating: 0,
- activeCount: 0,
- potentiallyStaleCount: 0,
- staleCount: 0,
- },
- members: {
- currentMembers: 0,
- change: 0,
- },
-};
-
-export const useProjectInsights = (projectId: string) => {
- const projectPath = formatApiPath(path(projectId));
- const { data, refetch, loading, error } =
- useApiGetter(projectPath, () =>
- fetcher(projectPath, 'Project Insights'),
- );
-
- return { data: data || placeholderData, refetch, loading, error };
-};
diff --git a/src/lib/features/project-insights/project-insights-controller.ts b/src/lib/features/project-insights/project-insights-controller.ts
index 9365cc217d..4c7138e4c3 100644
--- a/src/lib/features/project-insights/project-insights-controller.ts
+++ b/src/lib/features/project-insights/project-insights-controller.ts
@@ -30,6 +30,7 @@ export default class ProjectInsightsController extends Controller {
this.openApiService = services.openApiService;
this.flagResolver = config.flagResolver;
+ // TODO: Remove in v8. This endpoint is deprecated and no longer used by the UI.
this.route({
method: 'get',
path: '/:projectId/insights',
@@ -37,6 +38,7 @@ export default class ProjectInsightsController extends Controller {
permission: NONE,
middleware: [
this.openApiService.validPath({
+ deprecated: true,
tags: ['Projects'],
operationId: 'getProjectInsights',
summary: 'Get an overview of a project insights.',
diff --git a/src/lib/features/project/project-controller.ts b/src/lib/features/project/project-controller.ts
index 34d2272f3c..ba96477732 100644
--- a/src/lib/features/project/project-controller.ts
+++ b/src/lib/features/project/project-controller.ts
@@ -146,6 +146,7 @@ export default class ProjectController extends Controller {
permission: NONE,
middleware: [
this.openApiService.validPath({
+ deprecated: true,
tags: ['Projects'],
operationId: 'getProjectDora',
summary: 'Get an overview project dora metrics.',