diff --git a/libs/domains/environments/feature/src/lib/environments-table/environment-section/environment-section.tsx b/libs/domains/environments/feature/src/lib/environments-table/environment-section/environment-section.tsx index ff343493176..0be8a4fb4da 100644 --- a/libs/domains/environments/feature/src/lib/environments-table/environment-section/environment-section.tsx +++ b/libs/domains/environments/feature/src/lib/environments-table/environment-section/environment-section.tsx @@ -1,6 +1,6 @@ import { Link, useNavigate, useParams } from '@tanstack/react-router' import { EnvironmentModeEnum, type EnvironmentOverviewResponse, StateEnum } from 'qovery-typescript-axios' -import { type KeyboardEvent, type MouseEvent } from 'react' +import { type KeyboardEvent, type MouseEvent, useMemo } from 'react' import { match } from 'ts-pattern' import { ClusterAvatar } from '@qovery/domains/clusters/feature' import { Button, Checkbox, DeploymentAction, Heading, Icon, Section, TablePrimitives, Tooltip } from '@qovery/shared/ui' @@ -187,6 +187,11 @@ function EnvRow({ ) } +function lastOperationTimestamp(overview: EnvironmentOverviewResponse) { + const lastDeploymentDate = overview.deployment_status?.last_deployment_date + return lastDeploymentDate ? new Date(lastDeploymentDate).getTime() : 0 +} + export function EnvironmentSection({ type, items, @@ -209,6 +214,16 @@ export function EnvironmentSection({ .with('PREVIEW', () => 'Ephemeral') .exhaustive() + const sortedItems = useMemo(() => { + if (type !== EnvironmentModeEnum.PREVIEW) { + return items + } + + return [...items].sort( + (environmentA, environmentB) => lastOperationTimestamp(environmentB) - lastOperationTimestamp(environmentA) + ) + }, [items, type]) + const EmptyState = () => match(type) .with(EnvironmentModeEnum.PREVIEW, () => { @@ -295,7 +310,7 @@ export function EnvironmentSection({ - {items.map((environmentOverview) => ( + {sortedItems.map((environmentOverview) => ( ({ EnvironmentsTableActionBar: () =>
, })) -function environmentOverview(id: string, mode: EnvironmentModeEnum, name: string): EnvironmentOverviewResponse { +function environmentOverview( + id: string, + mode: EnvironmentModeEnum, + name: string, + lastDeploymentDate?: string +): EnvironmentOverviewResponse { return { id, mode, @@ -48,6 +53,7 @@ function environmentOverview(id: string, mode: EnvironmentModeEnum, name: string service_count: 0, managed_by: 'QOVERY', }, + ...(lastDeploymentDate ? { deployment_status: { last_deployment_date: lastDeploymentDate } } : {}), } as EnvironmentOverviewResponse } @@ -94,6 +100,25 @@ describe('EnvironmentsTable', () => { ]) }) + it('should sort ephemeral environments by last operation with newer environments first', () => { + mockUseProject.mockReturnValue({ data: { name: 'Project Alpha' } }) + mockUseEnvironmentsOverview.mockReturnValue({ + data: [ + environmentOverview('env-1', EnvironmentModeEnum.PREVIEW, 'Bravo', '2024-03-01T00:00:00Z'), + environmentOverview('env-2', EnvironmentModeEnum.PREVIEW, 'Alpha', '2024-01-01T00:00:00Z'), + environmentOverview('env-3', EnvironmentModeEnum.PREVIEW, 'Charlie', '2024-02-01T00:00:00Z'), + ], + }) + + renderWithProviders() + + expect(screen.getAllByRole('link', { name: /^(Alpha|Bravo|Charlie)$/ }).map((link) => link.textContent)).toEqual([ + 'Bravo', + 'Charlie', + 'Alpha', + ]) + }) + it('should preserve checkbox focus when selecting an environment', async () => { mockUseProject.mockReturnValue({ data: { name: 'Project Alpha' } }) mockUseEnvironmentsOverview.mockReturnValue({