-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): add more detail + filter to project list page (#2201)
Signed-off-by: Remington Breeze <[email protected]>
- Loading branch information
Showing
12 changed files
with
1,137 additions
and
832 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
70 changes: 62 additions & 8 deletions
70
ui/src/features/project/list/project-item/project-item.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,69 @@ | ||
import { Tooltip } from 'antd'; | ||
import classNames from 'classnames'; | ||
import { Link, generatePath } from 'react-router-dom'; | ||
|
||
import { paths } from '@ui/config/paths'; | ||
import { Description } from '@ui/features/common/description'; | ||
import { HealthStatusIcon } from '@ui/features/common/health-status/health-status-icon'; | ||
import { PromotionStatusIcon } from '@ui/features/common/promotion-status/promotion-status-icon'; | ||
import { getStageColors } from '@ui/features/stage/utils'; | ||
import { Project, Stage } from '@ui/gen/v1alpha1/generated_pb'; | ||
|
||
import * as styles from './project-item.module.less'; | ||
import { StagePopover } from './stage-popover'; | ||
|
||
type Props = { | ||
name: string; | ||
}; | ||
export const ProjectItem = ({ project, stages }: { project?: Project; stages?: Stage[] }) => { | ||
const stageColorMap = getStageColors(project?.metadata?.name || '', stages || []); | ||
|
||
export const ProjectItem = ({ name }: Props) => ( | ||
<Link className={styles.tile} to={generatePath(paths.project, { name })}> | ||
<div className={styles.title}>{name}</div> | ||
</Link> | ||
); | ||
return ( | ||
<Link | ||
className={styles.tile} | ||
to={generatePath(paths.project, { name: project?.metadata?.name })} | ||
> | ||
<div className={classNames(styles.title, 'mb-2')}>{project?.metadata?.name}</div> | ||
<Description item={project as Project} loading={false} /> | ||
{(stages || []).length > 0 && ( | ||
<div className='flex items-center gap-x-3 gap-y-1 flex-wrap mt-4'> | ||
{stages?.map((stage) => ( | ||
<Tooltip | ||
key={stage.metadata?.name} | ||
placement='bottom' | ||
title={ | ||
stage?.status?.lastPromotion?.name && ( | ||
<StagePopover | ||
promotionName={stage?.status?.lastPromotion?.name} | ||
project={project?.metadata?.name} | ||
freightName={stage?.status?.currentFreight?.name} | ||
stageName={stage?.metadata?.name} | ||
/> | ||
) | ||
} | ||
> | ||
<div | ||
className='flex items-center mb-2 text-white rounded py-1 px-2 font-semibold bg-gray-600' | ||
style={{ backgroundColor: stageColorMap[stage.metadata?.name || ''] }} | ||
> | ||
{stage.status?.health && ( | ||
<div className='mr-2'> | ||
<HealthStatusIcon health={stage.status?.health} hideColor={true} /> | ||
</div> | ||
)} | ||
{!stage?.status?.currentPromotion && stage.status?.lastPromotion && ( | ||
<div className='mr-2'> | ||
<PromotionStatusIcon | ||
placement='top' | ||
status={stage.status?.lastPromotion?.status} | ||
color='white' | ||
size='1x' | ||
/> | ||
</div> | ||
)} | ||
{stage.metadata?.name} | ||
</div> | ||
</Tooltip> | ||
))} | ||
</div> | ||
)} | ||
</Link> | ||
); | ||
}; |
66 changes: 66 additions & 0 deletions
66
ui/src/features/project/list/project-item/stage-popover.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { useQuery } from '@connectrpc/connect-query'; | ||
import { faBox, faClock } from '@fortawesome/free-solid-svg-icons'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import moment from 'moment'; | ||
import { useMemo } from 'react'; | ||
import { generatePath, useNavigate } from 'react-router-dom'; | ||
|
||
import { paths } from '@ui/config/paths'; | ||
import { getAlias } from '@ui/features/common/utils'; | ||
import { | ||
getFreight, | ||
getPromotion | ||
} from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; | ||
import { Freight, Promotion } from '@ui/gen/v1alpha1/generated_pb'; | ||
|
||
export const StagePopover = ({ | ||
promotionName, | ||
project, | ||
freightName, | ||
stageName | ||
}: { | ||
promotionName: string; | ||
project?: string; | ||
freightName?: string; | ||
stageName?: string; | ||
}) => { | ||
const { data: promotionData } = useQuery(getPromotion, { name: promotionName, project }); | ||
const promotion = useMemo(() => promotionData?.result?.value as Promotion, [promotionData]); | ||
const { data: freightData } = useQuery( | ||
getFreight, | ||
{ name: freightName, project }, | ||
{ enabled: !!freightName } | ||
); | ||
|
||
const _label = ({ children }: { children: string }) => ( | ||
<div className='text-xs font-semibold text-neutral-300 mb-1'>{children}</div> | ||
); | ||
|
||
const navigate = useNavigate(); | ||
|
||
return ( | ||
<div> | ||
<_label>LAST PROMOTED</_label> | ||
<div className='flex items-center mb-4'> | ||
<FontAwesomeIcon icon={faClock} className='mr-2' /> | ||
<div> | ||
{moment(promotion?.metadata?.creationTimestamp?.toDate()).format('MMM do yyyy HH:mm:ss')} | ||
</div> | ||
</div> | ||
<_label>CURRENT FREIGHT</_label> | ||
<div className='flex items-center mb-2'> | ||
<FontAwesomeIcon icon={faBox} className='mr-2' /> | ||
<div>{getAlias(freightData?.result?.value as Freight)}</div> | ||
</div> | ||
<div | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
navigate(generatePath(paths.stage, { name: project, stageName })); | ||
}} | ||
className='underline text-blue-400 font-semibold w-full text-center cursor-pointer' | ||
> | ||
DETAILS | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { useQuery } from '@connectrpc/connect-query'; | ||
import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { AutoComplete, Button } from 'antd'; | ||
import { useState } from 'react'; | ||
|
||
import { listProjects } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; | ||
|
||
export const ProjectListFilter = ({ | ||
onChange, | ||
init | ||
}: { | ||
onChange: (filter: string) => void; | ||
init?: string; | ||
}) => { | ||
const { data } = useQuery(listProjects); | ||
const [filter, setFilter] = useState(init || ''); | ||
|
||
return ( | ||
<div className='flex items-center w-2/3'> | ||
<AutoComplete | ||
placeholder='Filter...' | ||
options={data?.projects.map((p) => ({ value: p.metadata?.name }))} | ||
onChange={setFilter} | ||
className='w-full mr-2' | ||
value={filter} | ||
onKeyDown={(e) => { | ||
if (e.key === 'Enter') { | ||
onChange(filter); | ||
} | ||
}} | ||
/> | ||
<Button type='primary' onClick={() => onChange(filter)}> | ||
<FontAwesomeIcon icon={faMagnifyingGlass} /> | ||
</Button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
.list { | ||
display: grid; | ||
margin-top: ~'@{sizeMD}px'; | ||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); | ||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); | ||
gap: ~'@{sizeMD}px'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,64 @@ | ||
import { useQuery } from '@connectrpc/connect-query'; | ||
import { Empty } from 'antd'; | ||
import { createQueryOptions, useQuery, useTransport } from '@connectrpc/connect-query'; | ||
import { useQueries } from '@tanstack/react-query'; | ||
import { Empty, Pagination } from 'antd'; | ||
import { useState } from 'react'; | ||
|
||
import { LoadingState } from '@ui/features/common'; | ||
import { listProjects } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; | ||
import { | ||
listProjects, | ||
listStages | ||
} from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; | ||
|
||
import { ProjectItem } from './project-item/project-item'; | ||
import { ProjectListFilter } from './project-list-filter'; | ||
import * as styles from './projects-list.module.less'; | ||
|
||
export const ProjectsList = () => { | ||
const { data, isLoading } = useQuery(listProjects, {}); | ||
const [pageSize, setPageSize] = useState(9); | ||
const [page, setPage] = useState(1); | ||
const [filter, setFilter] = useState(''); | ||
|
||
const { data, isLoading } = useQuery(listProjects, { | ||
pageSize: pageSize, | ||
page: page - 1, | ||
filter | ||
}); | ||
|
||
const transport = useTransport(); | ||
const stageData = useQueries({ | ||
queries: (data?.projects || []).map((proj) => { | ||
return createQueryOptions(listStages, { project: proj?.metadata?.name }, { transport }); | ||
}) | ||
}); | ||
|
||
if (isLoading) return <LoadingState />; | ||
|
||
if (!data || data.projects.length === 0) return <Empty />; | ||
|
||
return ( | ||
<div className={styles.list}> | ||
{data.projects.map((project) => ( | ||
<ProjectItem key={project.metadata?.name} name={project.metadata?.name} /> | ||
))} | ||
</div> | ||
<> | ||
<div className='flex items-center mb-6'> | ||
<ProjectListFilter onChange={setFilter} init={filter} /> | ||
<Pagination | ||
total={data?.total || 0} | ||
className='ml-auto flex-shrink-0' | ||
pageSize={pageSize} | ||
current={page} | ||
onChange={(page, pageSize) => { | ||
setPage(page); | ||
setPageSize(pageSize); | ||
}} | ||
/> | ||
</div> | ||
<div className={styles.list}> | ||
{data.projects.map((proj, i) => ( | ||
<ProjectItem | ||
key={proj?.metadata?.name} | ||
project={proj} | ||
stages={stageData[i]?.data?.stages} | ||
/> | ||
))} | ||
</div> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.