mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
Feat/exp project features (#5351)
This PR adds feature toggle list on the project on a separate page as an experiment
This commit is contained in:
parent
02da9b1d34
commit
2dd2d520e3
@ -0,0 +1,202 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import useProject, {
|
||||||
|
useProjectNameOrId,
|
||||||
|
} from 'hooks/api/getters/useProject/useProject';
|
||||||
|
import { Box, styled } from '@mui/material';
|
||||||
|
import { ProjectFeatureToggles } from '../ProjectFeatureToggles/ProjectFeatureToggles';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
import { useLastViewedProject } from 'hooks/useLastViewedProject';
|
||||||
|
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch';
|
||||||
|
import {
|
||||||
|
ISortingRules,
|
||||||
|
PaginatedProjectFeatureToggles,
|
||||||
|
} from '../ProjectFeatureToggles/PaginatedProjectFeatureToggles';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { PaginationBar } from 'component/common/PaginationBar/PaginationBar';
|
||||||
|
import { SortingRule } from 'react-table';
|
||||||
|
|
||||||
|
const refreshInterval = 15 * 1000;
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
[theme.breakpoints.down('md')]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledProjectToggles = styled('div')(() => ({
|
||||||
|
width: '100%',
|
||||||
|
minWidth: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContentContainer = styled(Box)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
minWidth: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const DEFAULT_PAGE_LIMIT = 25;
|
||||||
|
|
||||||
|
const PaginatedProjectOverview = () => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const { project, loading: projectLoading } = useProject(projectId, {
|
||||||
|
refreshInterval,
|
||||||
|
});
|
||||||
|
const [pageLimit, setPageLimit] = useState(DEFAULT_PAGE_LIMIT);
|
||||||
|
const [currentOffset, setCurrentOffset] = useState(0);
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = useState(
|
||||||
|
searchParams.get('search') || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
const [sortingRules, setSortingRules] = useState<ISortingRules>({
|
||||||
|
sortBy: 'createdBy',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
isFavoritesPinned: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
features: searchFeatures,
|
||||||
|
total,
|
||||||
|
refetch,
|
||||||
|
loading,
|
||||||
|
initialLoad,
|
||||||
|
} = useFeatureSearch(
|
||||||
|
currentOffset,
|
||||||
|
pageLimit,
|
||||||
|
sortingRules,
|
||||||
|
projectId,
|
||||||
|
searchValue,
|
||||||
|
{
|
||||||
|
refreshInterval,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { environments } = project;
|
||||||
|
const fetchNextPage = () => {
|
||||||
|
if (!loading) {
|
||||||
|
setCurrentOffset(Math.min(total, currentOffset + pageLimit));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const fetchPrevPage = () => {
|
||||||
|
setCurrentOffset(Math.max(0, currentOffset - pageLimit));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasPreviousPage = currentOffset > 0;
|
||||||
|
const hasNextPage = currentOffset + pageLimit < total;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledContentContainer>
|
||||||
|
<StyledProjectToggles>
|
||||||
|
<PaginatedProjectFeatureToggles
|
||||||
|
key={
|
||||||
|
loading && searchFeatures.length === 0
|
||||||
|
? 'loading'
|
||||||
|
: 'ready'
|
||||||
|
}
|
||||||
|
features={searchFeatures}
|
||||||
|
style={{ width: '100%', margin: 0 }}
|
||||||
|
environments={environments}
|
||||||
|
initialLoad={initialLoad && searchFeatures.length === 0}
|
||||||
|
loading={loading && searchFeatures.length === 0}
|
||||||
|
onChange={refetch}
|
||||||
|
total={total}
|
||||||
|
searchValue={searchValue}
|
||||||
|
setSearchValue={setSearchValue}
|
||||||
|
sortingRules={sortingRules}
|
||||||
|
setSortingRules={setSortingRules}
|
||||||
|
paginationBar={
|
||||||
|
<StickyPaginationBar>
|
||||||
|
<PaginationBar
|
||||||
|
total={total}
|
||||||
|
hasNextPage={hasNextPage}
|
||||||
|
hasPreviousPage={hasPreviousPage}
|
||||||
|
fetchNextPage={fetchNextPage}
|
||||||
|
fetchPrevPage={fetchPrevPage}
|
||||||
|
currentOffset={currentOffset}
|
||||||
|
pageLimit={pageLimit}
|
||||||
|
setPageLimit={setPageLimit}
|
||||||
|
/>
|
||||||
|
</StickyPaginationBar>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledProjectToggles>
|
||||||
|
</StyledContentContainer>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledStickyBar = styled('div')(({ theme }) => ({
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
zIndex: 9999,
|
||||||
|
borderBottomLeftRadius: theme.shape.borderRadiusMedium,
|
||||||
|
borderBottomRightRadius: theme.shape.borderRadiusMedium,
|
||||||
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
|
boxShadow: `0px -2px 8px 0px rgba(32, 32, 33, 0.06)`,
|
||||||
|
height: '52px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledStickyBarContentContainer = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '100%',
|
||||||
|
minWidth: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StickyPaginationBar: React.FC = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<StyledStickyBar>
|
||||||
|
<StyledStickyBarContentContainer>
|
||||||
|
{children}
|
||||||
|
</StyledStickyBarContentContainer>
|
||||||
|
</StyledStickyBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated remove when flag `featureSearchFrontend` is removed
|
||||||
|
*/
|
||||||
|
export const ExperimentalProjectFeatures = () => {
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
const projectName = useProjectNameOrId(projectId);
|
||||||
|
const { project, loading, refetch } = useProject(projectId, {
|
||||||
|
refreshInterval,
|
||||||
|
});
|
||||||
|
const { features, environments } = project;
|
||||||
|
usePageTitle(`Project overview – ${projectName}`);
|
||||||
|
const { setLastViewed } = useLastViewedProject();
|
||||||
|
const featureSearchFrontend = useUiFlag('featureSearchFrontend');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLastViewed(projectId);
|
||||||
|
}, [projectId, setLastViewed]);
|
||||||
|
|
||||||
|
if (featureSearchFrontend) return <PaginatedProjectOverview />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledContentContainer>
|
||||||
|
<StyledProjectToggles>
|
||||||
|
<ProjectFeatureToggles
|
||||||
|
style={{ width: '100%', margin: 0 }}
|
||||||
|
key={loading ? 'loading' : 'ready'}
|
||||||
|
features={features}
|
||||||
|
environments={environments}
|
||||||
|
loading={loading}
|
||||||
|
onChange={refetch}
|
||||||
|
/>
|
||||||
|
</StyledProjectToggles>
|
||||||
|
</StyledContentContainer>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -40,6 +40,7 @@ import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadg
|
|||||||
import { Badge } from 'component/common/Badge/Badge';
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
import { ProjectDoraMetrics } from './ProjectDoraMetrics/ProjectDoraMetrics';
|
import { ProjectDoraMetrics } from './ProjectDoraMetrics/ProjectDoraMetrics';
|
||||||
import { UiFlags } from 'interfaces/uiConfig';
|
import { UiFlags } from 'interfaces/uiConfig';
|
||||||
|
import { ExperimentalProjectFeatures } from './ExperimentalProjectFeatures/ExperimentalProjectFeatures';
|
||||||
|
|
||||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -283,6 +284,10 @@ export const Project = () => {
|
|||||||
<Route path='environments' element={<ProjectEnvironment />} />
|
<Route path='environments' element={<ProjectEnvironment />} />
|
||||||
<Route path='archive' element={<ProjectFeaturesArchive />} />
|
<Route path='archive' element={<ProjectFeaturesArchive />} />
|
||||||
<Route path='logs' element={<ProjectLog />} />
|
<Route path='logs' element={<ProjectLog />} />
|
||||||
|
<Route
|
||||||
|
path='features'
|
||||||
|
element={<ExperimentalProjectFeatures />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='change-requests'
|
path='change-requests'
|
||||||
element={<ProjectChangeRequests />}
|
element={<ProjectChangeRequests />}
|
||||||
|
@ -87,6 +87,7 @@ interface IPaginatedProjectFeatureTogglesProps {
|
|||||||
paginationBar: JSX.Element;
|
paginationBar: JSX.Element;
|
||||||
sortingRules: ISortingRules;
|
sortingRules: ISortingRules;
|
||||||
setSortingRules: (sortingRules: ISortingRules) => void;
|
setSortingRules: (sortingRules: ISortingRules) => void;
|
||||||
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
|
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
|
||||||
@ -107,6 +108,7 @@ export const PaginatedProjectFeatureToggles = ({
|
|||||||
paginationBar,
|
paginationBar,
|
||||||
sortingRules,
|
sortingRules,
|
||||||
setSortingRules,
|
setSortingRules,
|
||||||
|
style = {},
|
||||||
}: IPaginatedProjectFeatureTogglesProps) => {
|
}: IPaginatedProjectFeatureTogglesProps) => {
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
const bodyLoadingRef = useLoading(loading);
|
const bodyLoadingRef = useLoading(loading);
|
||||||
@ -536,7 +538,7 @@ export const PaginatedProjectFeatureToggles = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const showPaginationBar = Boolean(total && total > DEFAULT_PAGE_LIMIT);
|
const showPaginationBar = Boolean(total && total > DEFAULT_PAGE_LIMIT);
|
||||||
const style = showPaginationBar
|
const paginatedStyles = showPaginationBar
|
||||||
? {
|
? {
|
||||||
borderBottomLeftRadius: 0,
|
borderBottomLeftRadius: 0,
|
||||||
borderBottomRightRadius: 0,
|
borderBottomRightRadius: 0,
|
||||||
@ -549,7 +551,7 @@ export const PaginatedProjectFeatureToggles = ({
|
|||||||
disableLoading
|
disableLoading
|
||||||
disablePadding
|
disablePadding
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
sx={style}
|
style={{ ...style, ...paginatedStyles }}
|
||||||
header={
|
header={
|
||||||
<Box
|
<Box
|
||||||
ref={headerLoadingRef}
|
ref={headerLoadingRef}
|
||||||
|
@ -74,6 +74,7 @@ interface IProjectFeatureTogglesProps {
|
|||||||
loading: boolean;
|
loading: boolean;
|
||||||
onChange: () => void;
|
onChange: () => void;
|
||||||
total?: number;
|
total?: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
|
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
|
||||||
@ -91,6 +92,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
environments: newEnvironments = [],
|
environments: newEnvironments = [],
|
||||||
onChange,
|
onChange,
|
||||||
total,
|
total,
|
||||||
|
style = {},
|
||||||
}: IProjectFeatureTogglesProps) => {
|
}: IProjectFeatureTogglesProps) => {
|
||||||
const { classes: styles } = useStyles();
|
const { classes: styles } = useStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -506,6 +508,7 @@ export const ProjectFeatureToggles = ({
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
disablePadding
|
disablePadding
|
||||||
className={styles.container}
|
className={styles.container}
|
||||||
|
style={style}
|
||||||
header={
|
header={
|
||||||
<Box
|
<Box
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user