1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

refactor: replace IProjectCard with openapi type (#8043)

Makes type consistent between frontend and schema generated from backend.
This commit is contained in:
Tymoteusz Czech 2024-09-02 15:25:28 +02:00 committed by GitHub
parent 0b2479517f
commit 6030900b40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 59 additions and 67 deletions

View File

@ -36,7 +36,6 @@ test('When you have no projects, you should not get a project filter', () => {
test('When you have only one project, you should not get a project filter', () => { test('When you have only one project, you should not get a project filter', () => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useEventLogFilters( useEventLogFilters(
// @ts-expect-error: omitting other properties we don't need
() => ({ projects: [{ id: 'a', name: 'A' }] }), () => ({ projects: [{ id: 'a', name: 'A' }] }),
() => ({ features: [] }), () => ({ features: [] }),
), ),
@ -52,9 +51,7 @@ test('When you have two one project, you should not get a project filter', () =>
useEventLogFilters( useEventLogFilters(
() => ({ () => ({
projects: [ projects: [
// @ts-expect-error: omitting other properties we don't need
{ id: 'a', name: 'A' }, { id: 'a', name: 'A' },
// @ts-expect-error: omitting other properties we don't need
{ id: 'b', name: 'B' }, { id: 'b', name: 'B' },
], ],
}), }),

View File

@ -11,11 +11,11 @@ import {
type FeatureSearchResponseSchema, type FeatureSearchResponseSchema,
type SearchFeaturesParams, type SearchFeaturesParams,
} from 'openapi'; } from 'openapi';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
import { useEventCreators } from 'hooks/api/getters/useEventCreators/useEventCreators'; import { useEventCreators } from 'hooks/api/getters/useEventCreators/useEventCreators';
export const useEventLogFilters = ( export const useEventLogFilters = (
projectsHook: () => { projects: IProjectCard[] }, projectsHook: () => { projects: ProjectSchema[] },
featuresHook: (params: SearchFeaturesParams) => { featuresHook: (params: SearchFeaturesParams) => {
features: FeatureSearchResponseSchema[]; features: FeatureSearchResponseSchema[];
}, },
@ -27,7 +27,7 @@ export const useEventLogFilters = (
const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]); const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]);
useEffect(() => { useEffect(() => {
const projectOptions = const projectOptions =
projects?.map((project: IProjectCard) => ({ projects?.map((project: ProjectSchema) => ({
label: project.name, label: project.name,
value: project.id, value: project.id,
})) ?? []; })) ?? [];

View File

@ -1,5 +1,5 @@
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
import GeneralSelect, { import GeneralSelect, {
type ISelectOption, type ISelectOption,
type IGeneralSelectProps, type IGeneralSelectProps,
@ -25,11 +25,11 @@ const FeatureProjectSelect = ({
return null; return null;
} }
const formatOption = (project: IProjectCard) => { const formatOption = (project: ProjectSchema) => {
return { return {
key: project.id, key: project.id,
label: project.name, label: project.name,
title: project.description, title: project.description || '',
sx: { sx: {
whiteSpace: 'pre-line', whiteSpace: 'pre-line',
}, },

View File

@ -10,10 +10,11 @@ export const useProjectColor = () => {
const projectsSortedByCreatedAt = useMemo( const projectsSortedByCreatedAt = useMemo(
() => () =>
projects projects
.sort( .sort((a, b) =>
(a, b) => a.createdAt && b.createdAt
new Date(a.createdAt).getTime() - ? new Date(a.createdAt).getTime() -
new Date(b.createdAt).getTime(), new Date(b.createdAt).getTime()
: 0,
) )
.map((project) => project.id), .map((project) => project.id),
[projects], [projects],

View File

@ -14,7 +14,7 @@ import { ProjectCardFooter } from './ProjectCardFooter/ProjectCardFooter';
import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge'; import { ProjectModeBadge } from './ProjectModeBadge/ProjectModeBadge';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon'; import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { FavoriteAction } from './FavoriteAction/FavoriteAction'; import { FavoriteAction } from './FavoriteAction/FavoriteAction';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
export const ProjectCard = ({ export const ProjectCard = ({
@ -27,7 +27,7 @@ export const ProjectCard = ({
mode, mode,
favorite = false, favorite = false,
owners, owners,
}: IProjectCard) => ( }: ProjectSchema & { onHover?: () => void }) => (
<StyledProjectCard onMouseEnter={onHover}> <StyledProjectCard onMouseEnter={onHover}>
<StyledProjectCardBody> <StyledProjectCardBody>
<StyledDivHeader> <StyledDivHeader>

View File

@ -12,12 +12,12 @@ import { Box, styled } from '@mui/material';
import { flexColumn } from 'themes/themeStyles'; import { flexColumn } from 'themes/themeStyles';
import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; import { TimeAgo } from 'component/common/TimeAgo/TimeAgo';
import { ProjectLastSeen } from './ProjectLastSeen/ProjectLastSeen'; import { ProjectLastSeen } from './ProjectLastSeen/ProjectLastSeen';
import type { IProjectCard } from 'interfaces/project';
import { Highlighter } from 'component/common/Highlighter/Highlighter'; import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { ProjectMembers } from './ProjectCardFooter/ProjectMembers/ProjectMembers'; import { ProjectMembers } from './ProjectCardFooter/ProjectMembers/ProjectMembers';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId'; import { DEFAULT_PROJECT_ID } from 'hooks/api/getters/useDefaultProject/useDefaultProjectId';
import type { ProjectSchema } from 'openapi';
const StyledUpdated = styled('span')(({ theme }) => ({ const StyledUpdated = styled('span')(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -45,6 +45,8 @@ const StyledHeader = styled('div')(({ theme }) => ({
alignItems: 'center', alignItems: 'center',
})); }));
type ProjectCardProps = ProjectSchema & { onHover?: () => void };
export const ProjectCard = ({ export const ProjectCard = ({
name, name,
featureCount, featureCount,
@ -58,7 +60,7 @@ export const ProjectCard = ({
createdAt, createdAt,
lastUpdatedAt, lastUpdatedAt,
lastReportedFlagUsage, lastReportedFlagUsage,
}: IProjectCard) => { }: ProjectCardProps) => {
const { searchQuery } = useSearchHighlightContext(); const { searchQuery } = useSearchHighlightContext();
return ( return (

View File

@ -2,7 +2,7 @@ import { type FC, useContext, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { PageHeader } from 'component/common/PageHeader/PageHeader';
@ -169,7 +169,7 @@ export const ProjectList = () => {
const ProjectGroupComponent = (props: { const ProjectGroupComponent = (props: {
sectionTitle?: string; sectionTitle?: string;
projects: IProjectCard[]; projects: ProjectSchema[];
}) => { }) => {
return ( return (
<ProjectGroup <ProjectGroup

View File

@ -3,8 +3,7 @@ import { Link } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ProjectCard as LegacyProjectCard } from '../ProjectCard/LegacyProjectCard'; import { ProjectCard as LegacyProjectCard } from '../ProjectCard/LegacyProjectCard';
import { ProjectCard as NewProjectCard } from '../ProjectCard/ProjectCard'; import { ProjectCard as NewProjectCard } from '../ProjectCard/ProjectCard';
import type { ProjectSchema } from 'openapi';
import type { IProjectCard } from 'interfaces/project';
import loadingData from './loadingData'; import loadingData from './loadingData';
import { TablePlaceholder } from 'component/common/Table'; import { TablePlaceholder } from 'component/common/Table';
import { styled, Typography } from '@mui/material'; import { styled, Typography } from '@mui/material';
@ -51,14 +50,14 @@ type ProjectGroupProps = {
sectionTitle?: string; sectionTitle?: string;
sectionSubtitle?: string; sectionSubtitle?: string;
HeaderActions?: ReactNode; HeaderActions?: ReactNode;
projects: IProjectCard[]; projects: ProjectSchema[];
loading: boolean; loading: boolean;
/** /**
* @deprecated remove with projectListImprovements * @deprecated remove with projectListImprovements
*/ */
searchValue?: string; searchValue?: string;
placeholder?: string; placeholder?: string;
ProjectCardComponent?: ComponentType<IProjectCard & any>; ProjectCardComponent?: ComponentType<ProjectSchema & any>;
link?: boolean; link?: boolean;
}; };
@ -129,7 +128,7 @@ export const ProjectGroup = ({
show={() => ( show={() => (
<> <>
{loadingData.map( {loadingData.map(
(project: IProjectCard) => ( (project: ProjectSchema) => (
<ProjectCard <ProjectCard
data-loading data-loading
createdAt={project.createdAt} createdAt={project.createdAt}

View File

@ -1,10 +1,10 @@
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
import { groupProjects } from './group-projects'; import { groupProjects } from './group-projects';
test('should check that the project is a user project OR that it is a favorite', () => { test('should check that the project is a user project OR that it is a favorite', () => {
const myProjectIds = new Set(['my1', 'my2', 'my3']); const myProjectIds = new Set(['my1', 'my2', 'my3']);
const projects: IProjectCard[] = [ const projects: ProjectSchema[] = [
{ id: 'my1', favorite: true }, { id: 'my1', favorite: true },
{ id: 'my2', favorite: false }, { id: 'my2', favorite: false },
{ id: 'my3' }, { id: 'my3' },

View File

@ -1,11 +1,11 @@
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
export const groupProjects = ( export const groupProjects = (
myProjectIds: Set<string>, myProjectIds: Set<string>,
filteredProjects: IProjectCard[], filteredProjects: ProjectSchema[],
) => { ) => {
const mine: IProjectCard[] = []; const mine: ProjectSchema[] = [];
const other: IProjectCard[] = []; const other: ProjectSchema[] = [];
for (const project of filteredProjects) { for (const project of filteredProjects) {
if (project.favorite || myProjectIds.has(project.id)) { if (project.favorite || myProjectIds.has(project.id)) {

View File

@ -1,9 +1,9 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { groupProjects } from '../group-projects'; import { groupProjects } from '../group-projects';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
export const useGroupedProjects = ( export const useGroupedProjects = (
filteredAndSortedProjects: IProjectCard[], filteredAndSortedProjects: ProjectSchema[],
myProjects: Set<string>, myProjects: Set<string>,
) => ) =>
useMemo( useMemo(

View File

@ -1,8 +1,8 @@
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { useProjectsSearchAndSort } from './useProjectsSearchAndSort'; import { useProjectsSearchAndSort } from './useProjectsSearchAndSort';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
const projects: IProjectCard[] = [ const projects: ProjectSchema[] = [
{ {
name: 'A - Eagle', name: 'A - Eagle',
id: '1', id: '1',
@ -174,7 +174,7 @@ describe('useProjectsSearchAndSort', () => {
]); ]);
}); });
it('should be able to deal with date', () => { it('should be able to deal with updates', () => {
const hook = renderHook( const hook = renderHook(
(sortBy: string) => (sortBy: string) =>
useProjectsSearchAndSort( useProjectsSearchAndSort(
@ -189,9 +189,9 @@ describe('useProjectsSearchAndSort', () => {
{ {
name: 'Project B', name: 'Project B',
id: '2', id: '2',
createdAt: new Date('2024-02-01'), createdAt: '2024-02-01',
lastUpdatedAt: new Date('2024-02-10'), lastUpdatedAt: '2024-02-10',
lastReportedFlagUsage: new Date('2024-02-15'), lastReportedFlagUsage: '2024-02-15',
}, },
], ],
undefined, undefined,

View File

@ -1,10 +1,10 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { safeRegExp } from '@server/util/escape-regex'; import { safeRegExp } from '@server/util/escape-regex';
import type { IProjectCard } from 'interfaces/project';
import type { sortKeys } from '../ProjectsListSort/ProjectsListSort'; import type { sortKeys } from '../ProjectsListSort/ProjectsListSort';
import type { ProjectSchema } from 'openapi';
export const useProjectsSearchAndSort = ( export const useProjectsSearchAndSort = (
projects: IProjectCard[], projects: ProjectSchema[],
query?: string | null, query?: string | null,
sortBy?: (typeof sortKeys)[number] | null, sortBy?: (typeof sortKeys)[number] | null,
) => ) =>

View File

@ -7,7 +7,7 @@ const loadingData = [
featureCount: 4, featureCount: 4,
createdAt: '', createdAt: '',
description: '', description: '',
mode: '', mode: 'open' as const,
}, },
{ {
id: 'loading2', id: 'loading2',
@ -17,7 +17,7 @@ const loadingData = [
featureCount: 4, featureCount: 4,
createdAt: '', createdAt: '',
description: '', description: '',
mode: '', mode: 'open' as const,
}, },
{ {
id: 'loading3', id: 'loading3',
@ -27,7 +27,7 @@ const loadingData = [
featureCount: 4, featureCount: 4,
createdAt: '', createdAt: '',
description: '', description: '',
mode: '', mode: 'open' as const,
}, },
{ {
id: 'loading4', id: 'loading4',
@ -37,7 +37,7 @@ const loadingData = [
featureCount: 4, featureCount: 4,
createdAt: '', createdAt: '',
description: '', description: '',
mode: '', mode: 'open' as const,
}, },
]; ];

View File

@ -1,6 +1,6 @@
import { Alert, styled } from '@mui/material'; import { Alert, styled } from '@mui/material';
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit'; import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import type { IProjectCard } from 'interfaces/project'; import type { ProjectSchema } from 'openapi';
import type { IFeatureStrategy } from 'interfaces/strategy'; import type { IFeatureStrategy } from 'interfaces/strategy';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { formatStrategyName } from 'utils/strategyNames'; import { formatStrategyName } from 'utils/strategyNames';
@ -22,14 +22,14 @@ const StyledAlert = styled(Alert)(({ theme }) => ({
})); }));
interface ISegmentProjectAlertProps { interface ISegmentProjectAlertProps {
projects: IProjectCard[]; projects: ProjectSchema[];
strategies: ( strategies: (
| IFeatureStrategy | IFeatureStrategy
| ChangeRequestUpdatedStrategy | ChangeRequestUpdatedStrategy
| ChangeRequestNewStrategy | ChangeRequestNewStrategy
)[]; )[];
projectsUsed: string[]; projectsUsed: string[];
availableProjects: IProjectCard[]; availableProjects: ProjectSchema[];
} }
export const SegmentProjectAlert = ({ export const SegmentProjectAlert = ({

View File

@ -2,9 +2,8 @@ import useSWR, { mutate, type SWRConfiguration } from 'swr';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { formatApiPath } from 'utils/formatPath'; import { formatApiPath } from 'utils/formatPath';
import type { IProjectCard } from 'interfaces/project';
import handleErrorResponses from '../httpErrorResponseHandler'; import handleErrorResponses from '../httpErrorResponseHandler';
import type { GetProjectsParams } from 'openapi'; import type { GetProjectsParams, ProjectsSchema } from 'openapi';
const useProjects = (options: SWRConfiguration & GetProjectsParams = {}) => { const useProjects = (options: SWRConfiguration & GetProjectsParams = {}) => {
const KEY = `api/admin/projects${options.archived ? '?archived=true' : ''}`; const KEY = `api/admin/projects${options.archived ? '?archived=true' : ''}`;
@ -18,7 +17,7 @@ const useProjects = (options: SWRConfiguration & GetProjectsParams = {}) => {
.then((res) => res.json()); .then((res) => res.json());
}; };
const { data, error } = useSWR<{ projects: IProjectCard[] }>( const { data, error } = useSWR<{ projects: ProjectsSchema['projects'] }>(
KEY, KEY,
fetcher, fetcher,
options, options,

View File

@ -1,24 +1,8 @@
import type { ProjectSchema, ProjectStatsSchema } from 'openapi'; import type { ProjectStatsSchema } from 'openapi';
import type { IFeatureFlagListItem } from './featureToggle'; import type { IFeatureFlagListItem } from './featureToggle';
import type { ProjectEnvironmentType } from 'component/project/Project/ProjectFeatureToggles/hooks/useEnvironmentsRef'; import type { ProjectEnvironmentType } from 'component/project/Project/ProjectFeatureToggles/hooks/useEnvironmentsRef';
import type { ProjectMode } from 'component/project/Project/hooks/useProjectEnterpriseSettingsForm'; import type { ProjectMode } from 'component/project/Project/hooks/useProjectEnterpriseSettingsForm';
export interface IProjectCard {
name: string;
id: string;
createdAt: string | Date;
health?: number;
description?: string;
featureCount?: number;
mode?: string;
memberCount?: number;
onHover?: () => void;
favorite?: boolean;
owners?: ProjectSchema['owners'];
lastUpdatedAt?: Date | string;
lastReportedFlagUsage?: Date | string;
}
export type FeatureNamingType = { export type FeatureNamingType = {
pattern: string; pattern: string;
example: string; example: string;

View File

@ -41,6 +41,16 @@ export interface ProjectSchema {
health?: number; health?: number;
/** The id of this project */ /** The id of this project */
id: string; id: string;
/**
* Across all flags in your project this is the last time usage metrics where reported from connected applications.
* @nullable
*/
lastReportedFlagUsage?: string | null;
/**
* When this project was last updated.
* @nullable
*/
lastUpdatedAt?: string | null;
/** The number of members this project has */ /** The number of members this project has */
memberCount?: number; memberCount?: number;
/** The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not. */ /** The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not. */