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

feat: connect project overview table to search api (#5237)

This commit is contained in:
Mateusz Kwasniewski 2023-11-01 12:05:42 +01:00 committed by GitHub
parent 598d022a5a
commit d074254b61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 18 deletions

View File

@ -72,6 +72,7 @@ interface IProjectFeatureTogglesProps {
features: IProject['features']; features: IProject['features'];
environments: IProject['environments']; environments: IProject['environments'];
loading: boolean; loading: boolean;
onChange: () => void;
} }
const staticColumns = ['Select', 'Actions', 'name', 'favorite']; const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
@ -84,6 +85,7 @@ export const ProjectFeatureToggles = ({
features, features,
loading, loading,
environments: newEnvironments = [], environments: newEnvironments = [],
onChange,
}: IProjectFeatureTogglesProps) => { }: IProjectFeatureTogglesProps) => {
const { classes: styles } = useStyles(); const { classes: styles } = useStyles();
const theme = useTheme(); const theme = useTheme();
@ -118,7 +120,6 @@ export const ProjectFeatureToggles = ({
? [{ environment: 'a' }, { environment: 'b' }, { environment: 'c' }] ? [{ environment: 'a' }, { environment: 'b' }, { environment: 'c' }]
: newEnvironments, : newEnvironments,
); );
const { refetch } = useProject(projectId);
const { isFavoritesPinned, sortTypes, onChangeIsFavoritePinned } = const { isFavoritesPinned, sortTypes, onChangeIsFavoritePinned } =
usePinnedFavorites( usePinnedFavorites(
searchParams.has('favorites') searchParams.has('favorites')
@ -140,9 +141,9 @@ export const ProjectFeatureToggles = ({
} else { } else {
await favorite(projectId, feature.name); await favorite(projectId, feature.name);
} }
refetch(); onChange();
}, },
[projectId, refetch], [projectId, onChange],
); );
const showTagsColumn = useMemo( const showTagsColumn = useMemo(
@ -263,7 +264,7 @@ export const ProjectFeatureToggles = ({
projectId, projectId,
name, name,
isChangeRequestEnabled, isChangeRequestEnabled,
refetch, onChange,
onFeatureToggle, onFeatureToggle,
); );
@ -617,16 +618,14 @@ export const ProjectFeatureToggles = ({
isOpen={Boolean(featureStaleDialogState.featureId)} isOpen={Boolean(featureStaleDialogState.featureId)}
onClose={() => { onClose={() => {
setFeatureStaleDialogState({}); setFeatureStaleDialogState({});
refetch(); onChange();
}} }}
featureId={featureStaleDialogState.featureId || ''} featureId={featureStaleDialogState.featureId || ''}
projectId={projectId} projectId={projectId}
/> />
<FeatureArchiveDialog <FeatureArchiveDialog
isOpen={Boolean(featureArchiveState)} isOpen={Boolean(featureArchiveState)}
onConfirm={() => { onConfirm={onChange}
refetch();
}}
onClose={() => { onClose={() => {
setFeatureArchiveState(undefined); setFeatureArchiveState(undefined);
}} }}

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import useProject, { import useProject, {
useProjectNameOrId, useProjectNameOrId,
} from 'hooks/api/getters/useProject/useProject'; } from 'hooks/api/getters/useProject/useProject';
@ -12,6 +12,7 @@ import { useLastViewedProject } from 'hooks/useLastViewedProject';
import { ProjectStats } from './ProjectStats/ProjectStats'; import { ProjectStats } from './ProjectStats/ProjectStats';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import { useFeatureSearch } from '../../../hooks/api/getters/useFeatureSearch/useFeatureSearch';
const refreshInterval = 15 * 1000; const refreshInterval = 15 * 1000;
@ -34,10 +35,50 @@ const StyledContentContainer = styled(Box)(() => ({
minWidth: 0, minWidth: 0,
})); }));
const InfiniteProjectOverview = () => {
const projectId = useRequiredPathParam('projectId');
const { project, loading: projectLoading } = useProject(projectId, {
refreshInterval,
});
const [nextCursor, setNextCursor] = useState('');
const {
features: searchFeatures,
refetch,
loading,
} = useFeatureSearch(nextCursor, projectId, { refreshInterval });
const { members, features, health, description, environments, stats } =
project;
return (
<StyledContainer>
<ProjectInfo
id={projectId}
description={description}
memberCount={members}
health={health}
features={features}
stats={stats}
/>
<StyledContentContainer>
<ProjectStats stats={project.stats} />
<StyledProjectToggles>
<ProjectFeatureToggles
key={loading ? 'loading' : 'ready'}
features={searchFeatures.features}
environments={environments}
loading={loading}
onChange={refetch}
/>
</StyledProjectToggles>
</StyledContentContainer>
</StyledContainer>
);
};
const ProjectOverview = () => { const ProjectOverview = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const projectName = useProjectNameOrId(projectId); const projectName = useProjectNameOrId(projectId);
const { project, loading } = useProject(projectId, { const { project, loading, refetch } = useProject(projectId, {
refreshInterval, refreshInterval,
}); });
const { members, features, health, description, environments, stats } = const { members, features, health, description, environments, stats } =
@ -45,11 +86,14 @@ const ProjectOverview = () => {
usePageTitle(`Project overview ${projectName}`); usePageTitle(`Project overview ${projectName}`);
const { setLastViewed } = useLastViewedProject(); const { setLastViewed } = useLastViewedProject();
const featureSwitchRefactor = useUiFlag('featureSwitchRefactor'); const featureSwitchRefactor = useUiFlag('featureSwitchRefactor');
const featureSearchFrontend = useUiFlag('featureSearchFrontend');
useEffect(() => { useEffect(() => {
setLastViewed(projectId); setLastViewed(projectId);
}, [projectId, setLastViewed]); }, [projectId, setLastViewed]);
if (featureSearchFrontend) return <InfiniteProjectOverview />;
return ( return (
<StyledContainer> <StyledContainer>
<ProjectInfo <ProjectInfo
@ -71,6 +115,7 @@ const ProjectOverview = () => {
features={features} features={features}
environments={environments} environments={environments}
loading={loading} loading={loading}
onChange={refetch}
/> />
)} )}
elseShow={() => ( elseShow={() => (

View File

@ -13,14 +13,18 @@ interface IUseFeatureSearchOutput {
refetch: () => void; refetch: () => void;
} }
const fallbackFeatures: { features: IFeatureToggleListItem[] } = { const fallbackFeatures: { features: IFeatureToggleListItem[]; total: number } =
{
features: [], features: [],
total: 0,
}; };
export const useFeatureSearch = ( export const useFeatureSearch = (
cursor: string,
projectId = '',
options: SWRConfiguration = {}, options: SWRConfiguration = {},
): IUseFeatureSearchOutput => { ): IUseFeatureSearchOutput => {
const { KEY, fetcher } = getFeatureSearchFetcher(); const { KEY, fetcher } = getFeatureSearchFetcher(projectId, cursor);
const { data, error, mutate } = useSWR<IFeatureSearchResponse>( const { data, error, mutate } = useSWR<IFeatureSearchResponse>(
KEY, KEY,
fetcher, fetcher,
@ -39,9 +43,11 @@ export const useFeatureSearch = (
}; };
}; };
const getFeatureSearchFetcher = () => { const getFeatureSearchFetcher = (projectId: string, cursor: string) => {
const KEY = `api/admin/search/features?projectId=${projectId}&cursor=${cursor}`;
const fetcher = () => { const fetcher = () => {
const path = formatApiPath(`api/admin/search/features`); const path = formatApiPath(KEY);
return fetch(path, { return fetch(path, {
method: 'GET', method: 'GET',
}) })
@ -49,8 +55,6 @@ const getFeatureSearchFetcher = () => {
.then((res) => res.json()); .then((res) => res.json());
}; };
const KEY = `api/admin/search/features`;
return { return {
fetcher, fetcher,
KEY, KEY,

View File

@ -71,6 +71,8 @@ export type UiFlags = {
playgroundImprovements?: boolean; playgroundImprovements?: boolean;
featureSwitchRefactor?: boolean; featureSwitchRefactor?: boolean;
scheduledConfigurationChanges?: boolean; scheduledConfigurationChanges?: boolean;
featureSearchAPI?: boolean;
featureSearchFrontend?: boolean;
}; };
export interface IVersionInfo { export interface IVersionInfo {

View File

@ -86,6 +86,7 @@ exports[`should create default config 1`] = `
"embedProxyFrontend": true, "embedProxyFrontend": true,
"featureNamingPattern": false, "featureNamingPattern": false,
"featureSearchAPI": false, "featureSearchAPI": false,
"featureSearchFrontend": false,
"featureSwitchRefactor": false, "featureSwitchRefactor": false,
"featuresExportImport": true, "featuresExportImport": true,
"filterInvalidClientMetrics": false, "filterInvalidClientMetrics": false,

View File

@ -36,6 +36,7 @@ export type IFlagKey =
| 'playgroundImprovements' | 'playgroundImprovements'
| 'featureSwitchRefactor' | 'featureSwitchRefactor'
| 'featureSearchAPI' | 'featureSearchAPI'
| 'featureSearchFrontend'
| 'scheduledConfigurationChanges'; | 'scheduledConfigurationChanges';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@ -168,6 +169,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_API, process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_API,
false, false,
), ),
featureSearchFrontend: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_FEATURE_SEARCH_FRONTEND,
false,
),
scheduledConfigurationChanges: parseEnvVarBoolean( scheduledConfigurationChanges: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_SCHEDULED_CONFIGURATION_CHANGES, process.env.UNLEASH_EXPERIMENTAL_SCHEDULED_CONFIGURATION_CHANGES,
false, false,

View File

@ -49,6 +49,7 @@ process.nextTick(async () => {
playgroundImprovements: true, playgroundImprovements: true,
featureSwitchRefactor: true, featureSwitchRefactor: true,
featureSearchAPI: true, featureSearchAPI: true,
featureSearchFrontend: false,
}, },
}, },
authentication: { authentication: {