)
+ const stageFromServiceId = getStageFromServiceId(deploymentStages ?? [], serviceId)
+
if (!serviceStatus) return null
return (
-
-
-
- db.mode === 'CONTAINER')
- .otherwise(() => true)}
- />
-
+
)
diff --git a/libs/pages/logs/environment/src/lib/feature/environment-stages-feature/environment-stages-feature.spec.tsx b/libs/pages/logs/environment/src/lib/feature/environment-stages-feature/environment-stages-feature.spec.tsx
new file mode 100644
index 00000000000..7a258f81169
--- /dev/null
+++ b/libs/pages/logs/environment/src/lib/feature/environment-stages-feature/environment-stages-feature.spec.tsx
@@ -0,0 +1,121 @@
+import { type DeploymentStageWithServicesStatuses } from 'qovery-typescript-axios'
+import { renderWithProviders, screen } from '@qovery/shared/util-tests'
+import {
+ EnvironmentStagesFeature,
+ type EnvironmentStagesFeatureProps,
+ matchServicesWithStatuses,
+} from './environment-stages-feature'
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useParams: () => ({ versionId: 'version-1' }),
+}))
+
+jest.mock('@qovery/domains/services/feature', () => ({
+ ...jest.requireActual('@qovery/domains/services/feature'),
+ useServices: () => ({
+ data: [
+ { id: 'app-1', name: 'App 1', serviceType: 'APPLICATION' },
+ { id: 'app-2', name: 'App 2', serviceType: 'APPLICATION' },
+ ],
+ }),
+}))
+
+describe('EnvironmentStagesFeature', () => {
+ const defaultProps: EnvironmentStagesFeatureProps = {
+ environment: {
+ id: 'env-1',
+ name: 'Test Environment',
+ organization: { id: 'org-1' },
+ project: { id: 'proj-1' },
+ cloud_provider: {
+ provider: 'AWS',
+ },
+ },
+ environmentStatus: {
+ state: 'RUNNING',
+ total_duration_sec: 120,
+ },
+ deploymentStages: [
+ {
+ stage: {
+ id: 'stage-1',
+ name: 'Stage 1',
+ status: 'ONGOING',
+ steps: { total_duration_sec: 120 },
+ },
+ applications: [
+ {
+ id: 'app-1',
+ name: 'App 1',
+ state: 'BUILDING',
+ is_part_last_deployment: true,
+ steps: { total_duration_sec: 60 },
+ },
+ {
+ id: 'app-2',
+ name: 'App 2',
+ state: 'STOPPED',
+ is_part_last_deployment: false,
+ steps: { total_duration_sec: 30 },
+ },
+ ],
+ },
+ ],
+ preCheckStage: {
+ status: 'SUCCESS',
+ total_duration_sec: 30,
+ },
+ }
+
+ it('renders loading spinner when environmentStatus is undefined', () => {
+ renderWithProviders(
)
+ expect(screen.getByTestId('spinner')).toBeInTheDocument()
+ })
+
+ it('renders stages and services', () => {
+ renderWithProviders(
)
+ expect(screen.getByText('Stage 1')).toBeInTheDocument()
+ expect(screen.getByText('App 1')).toBeInTheDocument()
+ })
+
+ it('renders service duration', () => {
+ renderWithProviders(
)
+ expect(screen.getByText('1m 0s')).toBeInTheDocument()
+ })
+
+ it('does not render skipped services when hideSkipped is true', async () => {
+ const { userEvent } = renderWithProviders(
)
+
+ await userEvent.click(screen.getByLabelText('Hide skipped'))
+
+ expect(screen.getByText('App 1')).toBeInTheDocument()
+ expect(screen.queryByText('App 2')).not.toBeInTheDocument()
+ })
+
+ it('should correctly map services and stages', () => {
+ const input = [
+ {
+ stage: { id: 'stage1', name: 'Stage 1' },
+ applications: [{ id: 'app1', name: 'App 1', state: 'RUNNING' }],
+ databases: [{ id: 'db1', name: 'DB 1', state: 'STOPPED' }],
+ containers: [],
+ jobs: [],
+ helms: [],
+ },
+ ]
+
+ const expectedOutput = [
+ {
+ stage: { id: 'stage1', name: 'Stage 1' },
+ services: [
+ { id: 'app1', name: 'App 1', state: 'RUNNING', status: 'RUNNING' },
+ { id: 'db1', name: 'DB 1', state: 'STOPPED', status: 'STOPPED' },
+ ],
+ },
+ ]
+
+ const result = matchServicesWithStatuses(input as DeploymentStageWithServicesStatuses[])
+ expect(result).toEqual(expectedOutput)
+ })
+})
diff --git a/libs/pages/logs/environment/src/lib/feature/environment-stages-feature/environment-stages-feature.tsx b/libs/pages/logs/environment/src/lib/feature/environment-stages-feature/environment-stages-feature.tsx
new file mode 100644
index 00000000000..bffd4dd86b0
--- /dev/null
+++ b/libs/pages/logs/environment/src/lib/feature/environment-stages-feature/environment-stages-feature.tsx
@@ -0,0 +1,195 @@
+import { type CheckedState } from '@radix-ui/react-checkbox'
+import clsx from 'clsx'
+import {
+ type DeploymentStageWithServicesStatuses,
+ type Environment,
+ type EnvironmentStatus,
+ type EnvironmentStatusesWithStagesPreCheckStage,
+} from 'qovery-typescript-axios'
+import { useState } from 'react'
+import { NavLink, useParams } from 'react-router-dom'
+import { Fragment } from 'react/jsx-runtime'
+import { match } from 'ts-pattern'
+import { EnvironmentStages } from '@qovery/domains/environment-logs/feature'
+import { type AnyService } from '@qovery/domains/services/data-access'
+import { ServiceAvatar, useServices } from '@qovery/domains/services/feature'
+import { DEPLOYMENT_LOGS_URL, DEPLOYMENT_LOGS_VERSION_URL, ENVIRONMENT_LOGS_URL } from '@qovery/shared/routes'
+import { Icon, LoaderSpinner, StageStatusChip, StatusChip, Tooltip, Truncate } from '@qovery/shared/ui'
+import { useDocumentTitle } from '@qovery/shared/util-hooks'
+import { upperCaseFirstLetter } from '@qovery/shared/util-js'
+
+export interface EnvironmentStagesFeatureProps {
+ environment: Environment
+ deploymentStages?: DeploymentStageWithServicesStatuses[]
+ environmentStatus?: EnvironmentStatus
+ preCheckStage?: EnvironmentStatusesWithStagesPreCheckStage
+}
+
+export function matchServicesWithStatuses(deploymentStages?: DeploymentStageWithServicesStatuses[]) {
+ if (!deploymentStages) return []
+
+ return deploymentStages.map((deploymentStage) => {
+ const serviceTypes = ['applications', 'databases', 'containers', 'jobs', 'helms'] as const
+
+ const services = serviceTypes
+ .map((serviceType) => deploymentStage[serviceType])
+ .flat()
+ .map((service) => ({ ...service, status: service?.state }))
+
+ return { services, stage: deploymentStage.stage }
+ })
+}
+
+export function EnvironmentStagesFeature({
+ environment,
+ environmentStatus,
+ deploymentStages,
+ preCheckStage,
+}: EnvironmentStagesFeatureProps) {
+ useDocumentTitle(`Environment stages ${environment ? `- ${environment?.name}` : '- Loading...'}`)
+
+ const { versionId } = useParams()
+ const { data: services = [] } = useServices({ environmentId: environment.id })
+
+ const [hideSkipped, setHideSkipped] = useState
(false)
+
+ const getServiceById = (id: string) => services.find((service) => service.id === id) as AnyService
+
+ if (!environmentStatus) {
+ return (
+
+ )
+ }
+
+ return (
+
+
+ <>
+ {matchServicesWithStatuses(deploymentStages)?.map((s) => {
+ const stageTotalDurationSec = s.stage?.steps?.total_duration_sec ?? 0
+ const stageName = s?.stage?.name || ''
+
+ if (hideSkipped && s?.stage?.status === 'SKIPPED') return null
+
+ return (
+
+
+
+
+
+
+
+ {s?.stage?.description && (
+
+
+
+
+
+ )}
+
+ {match(s.stage?.status)
+ .with('CANCELED', 'DONE', 'ERROR', () => (
+
+ {Math.floor(stageTotalDurationSec / 60)}m {stageTotalDurationSec % 60}s
+
+ ))
+ .otherwise(() => null)}
+
+
+
+ {s.services.length > 0 ? (
+ s.services.map((service) => {
+ const fullService = getServiceById(service.id!)
+ const serviceTotalDurationSec = service?.steps?.total_duration_sec
+
+ if (hideSkipped && !service.is_part_last_deployment) return null
+ if (!fullService) return null
+
+ return (
+
+
+
+
+ {serviceTotalDurationSec && (
+
+ {Math.floor(serviceTotalDurationSec / 60)}m {serviceTotalDurationSec % 60}s
+
+ )}
+
+
+
+ )
+ })
+ ) : (
+
+
+
No service for this stage.
+
+ )}
+
+
+
+
+ )
+ })}
+ >
+
+
+ )
+}
+
+export default EnvironmentStagesFeature
diff --git a/libs/pages/logs/environment/src/lib/feature/pod-logs-feature/pod-logs-feature.tsx b/libs/pages/logs/environment/src/lib/feature/pod-logs-feature/pod-logs-feature.tsx
index 38cbdd23db0..b95c7c32cc9 100644
--- a/libs/pages/logs/environment/src/lib/feature/pod-logs-feature/pod-logs-feature.tsx
+++ b/libs/pages/logs/environment/src/lib/feature/pod-logs-feature/pod-logs-feature.tsx
@@ -1,62 +1,37 @@
-import clsx from 'clsx'
-import { Link, useLocation, useParams } from 'react-router-dom'
-import { match } from 'ts-pattern'
+import {
+ type DeploymentStageWithServicesStatuses,
+ type Environment,
+ type EnvironmentStatus,
+ type Status,
+} from 'qovery-typescript-axios'
+import { useParams } from 'react-router-dom'
import { ListServiceLogs } from '@qovery/domains/service-logs/feature'
-import { ServiceStateChip, useService } from '@qovery/domains/services/feature'
-import { DEPLOYMENT_LOGS_URL, ENVIRONMENT_LOGS_URL, SERVICE_LOGS_URL } from '@qovery/shared/routes'
+import { useService } from '@qovery/domains/services/feature'
import { useDocumentTitle } from '@qovery/shared/util-hooks'
+import { getServiceStatusesById } from '../deployment-logs-feature/deployment-logs-feature'
export interface PodLogsFeatureProps {
- clusterId: string
+ environment: Environment
+ deploymentStages?: DeploymentStageWithServicesStatuses[]
+ environmentStatus?: EnvironmentStatus
}
-export function LinkLogs({ title, url, statusChip = true }: { title: string; url: string; statusChip?: boolean }) {
- const { environmentId = '', serviceId = '' } = useParams()
- const location = useLocation()
-
- const isActive = location.pathname.includes(url)
- return (
-
- {statusChip && (
-
- )}
-
- {title}
-
- )
-}
-
-export function PodLogsFeature({ clusterId }: PodLogsFeatureProps) {
- const { organizationId = '', projectId = '', environmentId = '', serviceId = '' } = useParams()
- const { data: service } = useService({ environmentId, serviceId })
+export function PodLogsFeature({ environment, deploymentStages, environmentStatus }: PodLogsFeatureProps) {
+ const { serviceId = '' } = useParams()
+ const { data: service } = useService({ environmentId: environment.id, serviceId })
useDocumentTitle(`Service logs ${service ? `- ${service?.name}` : '- Loading...'}`)
+ const serviceStatus = getServiceStatusesById(deploymentStages, serviceId) as Status
+
return (
-
-
-
- db.mode === 'CONTAINER')
- .otherwise(() => true)}
- />
-
-
+
+
)
}
diff --git a/libs/pages/logs/environment/src/lib/feature/pre-check-logs-feature/pre-check-logs-feature.spec.tsx b/libs/pages/logs/environment/src/lib/feature/pre-check-logs-feature/pre-check-logs-feature.spec.tsx
new file mode 100644
index 00000000000..8b6e1ba0a4e
--- /dev/null
+++ b/libs/pages/logs/environment/src/lib/feature/pre-check-logs-feature/pre-check-logs-feature.spec.tsx
@@ -0,0 +1,22 @@
+import { Route, Routes } from 'react-router-dom'
+import { environmentFactoryMock } from '@qovery/shared/factories'
+import { renderWithProviders } from '@qovery/shared/util-tests'
+import PreCheckLogsFeature, { type PreCheckLogsFeatureProps } from './pre-check-logs-feature'
+
+describe('PreCheckLogsFeature', () => {
+ const props: PreCheckLogsFeatureProps = {
+ environment: environmentFactoryMock(1)[0],
+ }
+
+ it('should render successfully', () => {
+ const { baseElement } = renderWithProviders(
+
+ }
+ />
+
+ )
+ expect(baseElement).toBeTruthy()
+ })
+})
diff --git a/libs/pages/logs/environment/src/lib/feature/pre-check-logs-feature/pre-check-logs-feature.tsx b/libs/pages/logs/environment/src/lib/feature/pre-check-logs-feature/pre-check-logs-feature.tsx
new file mode 100644
index 00000000000..9835ad98f64
--- /dev/null
+++ b/libs/pages/logs/environment/src/lib/feature/pre-check-logs-feature/pre-check-logs-feature.tsx
@@ -0,0 +1,20 @@
+import { type Environment, type EnvironmentStatusesWithStagesPreCheckStage } from 'qovery-typescript-axios'
+import { ListPreCheckLogs } from '@qovery/domains/environment-logs/feature'
+import { useDocumentTitle } from '@qovery/shared/util-hooks'
+
+export interface PreCheckLogsFeatureProps {
+ environment: Environment
+ preCheckStage?: EnvironmentStatusesWithStagesPreCheckStage
+}
+
+export function PreCheckLogsFeature({ environment, preCheckStage }: PreCheckLogsFeatureProps) {
+ useDocumentTitle('Environment Pre-check logs')
+
+ return (
+
+
+
+ )
+}
+
+export default PreCheckLogsFeature
diff --git a/libs/pages/logs/environment/src/lib/feature/sidebar-history-feature/sidebar-history-feature.spec.tsx b/libs/pages/logs/environment/src/lib/feature/sidebar-history-feature/sidebar-history-feature.spec.tsx
deleted file mode 100644
index 51d648d8e96..00000000000
--- a/libs/pages/logs/environment/src/lib/feature/sidebar-history-feature/sidebar-history-feature.spec.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { renderWithProviders } from '@qovery/shared/util-tests'
-import SidebarHistoryFeature, { type SidebarHistoryFeatureProps } from './sidebar-history-feature'
-
-describe('SidebarHistoryFeature', () => {
- const props: SidebarHistoryFeatureProps = {
- versionId: '1',
- serviceId: '2',
- }
-
- it('should render successfully', () => {
- const { baseElement } = renderWithProviders(
)
- expect(baseElement).toBeTruthy()
- })
-})
diff --git a/libs/pages/logs/environment/src/lib/feature/sidebar-history-feature/sidebar-history-feature.tsx b/libs/pages/logs/environment/src/lib/feature/sidebar-history-feature/sidebar-history-feature.tsx
deleted file mode 100644
index 291b8774aea..00000000000
--- a/libs/pages/logs/environment/src/lib/feature/sidebar-history-feature/sidebar-history-feature.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { useLocation, useParams } from 'react-router-dom'
-import { useDeploymentHistory } from '@qovery/domains/environments/feature'
-import { useDeploymentStatus } from '@qovery/domains/environments/feature'
-import { useServices } from '@qovery/domains/services/feature'
-import { ENVIRONMENT_LOGS_URL, SERVICE_LOGS_URL } from '@qovery/shared/routes'
-import SidebarHistory from '../../ui/sidebar-history/sidebar-history'
-
-export interface SidebarHistoryFeatureProps {
- versionId?: string
- serviceId?: string
-}
-
-export function SidebarHistoryFeature({ versionId, serviceId }: SidebarHistoryFeatureProps) {
- const { organizationId = '', projectId = '', environmentId = '' } = useParams()
- const { data } = useDeploymentHistory({ environmentId })
- const { data: services } = useServices({ environmentId })
- const { data: deploymentStatus } = useDeploymentStatus({ environmentId })
-
- const { pathname } = useLocation()
- const pathLogs = ENVIRONMENT_LOGS_URL(organizationId, projectId, environmentId)
- const serviceLogsPath = pathname.includes(SERVICE_LOGS_URL(serviceId))
-
- const defaultServiceId = serviceId || services?.[0]?.id
-
- if (!data || !defaultServiceId || serviceLogsPath) return
-
- return (
-
- )
-}
-
-export default SidebarHistoryFeature
diff --git a/libs/pages/logs/environment/src/lib/page-environment-logs.tsx b/libs/pages/logs/environment/src/lib/page-environment-logs.tsx
index b34e36cb17c..58ee1f12479 100644
--- a/libs/pages/logs/environment/src/lib/page-environment-logs.tsx
+++ b/libs/pages/logs/environment/src/lib/page-environment-logs.tsx
@@ -1,65 +1,76 @@
import { type QueryClient } from '@tanstack/react-query'
-import { type DeploymentStageWithServicesStatuses, type EnvironmentStatus } from 'qovery-typescript-axios'
+import {
+ type DeploymentStageWithServicesStatuses,
+ type EnvironmentStatus,
+ type EnvironmentStatusesWithStagesPreCheckStage,
+} from 'qovery-typescript-axios'
import { useCallback, useState } from 'react'
-import { Route, Routes, matchPath, useLocation, useParams } from 'react-router-dom'
+import { Navigate, Route, Routes, matchPath, useLocation, useParams } from 'react-router-dom'
import { useEnvironment } from '@qovery/domains/environments/feature'
import { ServiceStageIdsProvider } from '@qovery/domains/service-logs/feature'
-import { useServices } from '@qovery/domains/services/feature'
import {
DEPLOYMENT_LOGS_URL,
DEPLOYMENT_LOGS_VERSION_URL,
ENVIRONMENT_LOGS_URL,
+ ENVIRONMENT_PRE_CHECK_LOGS_URL,
+ ENVIRONMENT_STAGES_URL,
SERVICE_LOGS_URL,
} from '@qovery/shared/routes'
-import { Icon } from '@qovery/shared/ui'
+import { LoaderSpinner } from '@qovery/shared/ui'
import { useDocumentTitle } from '@qovery/shared/util-hooks'
import { QOVERY_WS } from '@qovery/shared/util-node-env'
import { useReactQueryWsSubscription } from '@qovery/state/util-queries'
import DeploymentLogsFeature from './feature/deployment-logs-feature/deployment-logs-feature'
+import EnvironmentStagesFeature from './feature/environment-stages-feature/environment-stages-feature'
import PodLogsFeature from './feature/pod-logs-feature/pod-logs-feature'
-import Sidebar from './ui/sidebar/sidebar'
+import PreCheckLogsFeature from './feature/pre-check-logs-feature/pre-check-logs-feature'
export function PageEnvironmentLogs() {
const { organizationId = '', projectId = '', environmentId = '' } = useParams()
-
+ const location = useLocation()
const { data: environment } = useEnvironment({ environmentId })
useDocumentTitle(`Environment logs ${environment ? `- ${environment?.name}` : '- Loading...'}`)
- const location = useLocation()
- const matchDeployment = matchPath<'serviceId', string>(
- ENVIRONMENT_LOGS_URL() + DEPLOYMENT_LOGS_URL(),
+ const matchEnvironmentStageVersion = matchPath<'versionId', string>(
+ ENVIRONMENT_LOGS_URL() + ENVIRONMENT_STAGES_URL(':versionId'),
location.pathname
)
const matchDeploymentVersion = matchPath<'versionId' | 'serviceId', string>(
ENVIRONMENT_LOGS_URL() + DEPLOYMENT_LOGS_VERSION_URL(),
location.pathname
)
- const matchServiceLogs = matchPath<'serviceId', string>(
- ENVIRONMENT_LOGS_URL() + SERVICE_LOGS_URL(),
- location.pathname
- )
- const versionId =
+ const deploymentVersionId =
matchDeploymentVersion?.params.versionId !== ':versionId' ? matchDeploymentVersion?.params.versionId : undefined
- const matchServiceId = matchDeploymentVersion || matchServiceLogs || matchDeployment
- const serviceId = matchServiceId?.params.serviceId !== ':serviceId' ? matchServiceId?.params.serviceId : undefined
+ const stageVersionId =
+ matchEnvironmentStageVersion?.params.versionId !== ':versionId'
+ ? matchEnvironmentStageVersion?.params.versionId
+ : undefined
- const { data: services } = useServices({ environmentId })
-
- const [statusStages, setStatusStages] = useState
()
+ const [deploymentStages, setDeploymentStages] = useState()
const [environmentStatus, setEnvironmentStatus] = useState()
+ const [preCheckStage, setPreCheckStage] = useState()
const messageHandler = useCallback(
(
_: QueryClient,
- { stages, environment }: { stages: DeploymentStageWithServicesStatuses[]; environment: EnvironmentStatus }
+ {
+ stages,
+ environment,
+ pre_check_stage,
+ }: {
+ stages: DeploymentStageWithServicesStatuses[]
+ environment: EnvironmentStatus
+ pre_check_stage: EnvironmentStatusesWithStagesPreCheckStage
+ }
) => {
- setStatusStages(stages)
+ setDeploymentStages(stages)
setEnvironmentStatus(environment)
+ setPreCheckStage(pre_check_stage)
},
- [setStatusStages, setEnvironmentStatus]
+ [setDeploymentStages]
)
useReactQueryWsSubscription({
url: QOVERY_WS + '/deployment/status',
@@ -68,54 +79,97 @@ export function PageEnvironmentLogs() {
cluster: environment?.cluster_id,
project: projectId,
environment: environmentId,
- version: versionId,
+ version: deploymentVersionId || stageVersionId,
},
enabled:
Boolean(organizationId) && Boolean(environment?.cluster_id) && Boolean(projectId) && Boolean(environmentId),
onMessage: messageHandler,
})
- if (!environment) return
+ if (!environment)
+ return (
+
+ )
return (
-
+
+ }
+ />
+
+ }
+ />
+ }
+ />
+ }
+ />
}
+ element={
+
+ }
/>
+
+ }
+ />
+
+ }
+ />
+
}
/>
- } />
- {(location.pathname === `${ENVIRONMENT_LOGS_URL(organizationId, projectId, environmentId)}/` ||
- location.pathname === ENVIRONMENT_LOGS_URL(organizationId, projectId, environmentId)) && (
-
-
-
-
- Please select a service on the left menu to access its deployment logs or live logs.
-
- You can access the deployment logs only for the services recently deployed (
- in purple).
-
-
-
-
- )}
)
}
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-history/sidebar-history.spec.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-history/sidebar-history.spec.tsx
deleted file mode 100644
index 09645a4c6f1..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-history/sidebar-history.spec.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { StateEnum } from 'qovery-typescript-axios'
-import { ENVIRONMENT_LOGS_URL } from '@qovery/shared/routes'
-import { trimId } from '@qovery/shared/util-js'
-import { renderWithProviders, screen } from '@qovery/shared/util-tests'
-import SidebarHistory, { type SidebarHistoryProps } from './sidebar-history'
-
-const currentDate = new Date().toString()
-const mockNavigate = jest.fn()
-
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
- useNavigate: () => mockNavigate,
-}))
-
-describe('SidebarHistory', () => {
- const props: SidebarHistoryProps = {
- data: [
- {
- id: '1',
- created_at: currentDate,
- applications: [
- {
- id: '4',
- created_at: currentDate,
- name: 'my-app',
- },
- ],
- },
- {
- id: '2',
- created_at: currentDate,
- applications: [
- {
- id: '4',
- created_at: currentDate,
- name: 'my-app-1',
- },
- ],
- },
- {
- id: '3',
- created_at: currentDate,
- applications: [
- {
- id: '1',
- created_at: currentDate,
- name: 'my-app-3',
- },
- ],
- },
- ],
- pathLogs: ENVIRONMENT_LOGS_URL('1', '2', '3'),
- serviceId: '4',
- versionId: '5',
- environmentState: StateEnum.BUILDING,
- }
-
- it('should render successfully', () => {
- const { baseElement } = renderWithProviders()
- expect(baseElement).toBeTruthy()
- })
-
- it('should button to back logs home', async () => {
- renderWithProviders()
-
- const btn = screen.getByTestId('btn-back-logs')
- expect(btn).toHaveAttribute('href', '/organization/1/project/2/environment/3/logs')
- })
-
- it('should open the menu and navigate to the logs page', async () => {
- const { userEvent } = renderWithProviders()
-
- const btn = screen.getByRole('button', { name: 'Deployment log history' })
- await userEvent.click(btn)
-
- const item = screen.getByText(trimId(props.data[1].id))
- expect(item.parentElement?.parentElement?.parentElement).toHaveAttribute(
- 'href',
- '/organization/1/project/2/environment/3/logs/4/deployment-logs/2'
- )
- })
-
- it('should have latest label if the environmentState is not new', async () => {
- props.environmentState = StateEnum.DEPLOYED
- renderWithProviders()
-
- screen.getByText('Latest')
- })
-
- it('should have new label if deployment is in progress', async () => {
- props.environmentState = StateEnum.CANCELING
- props.versionId = '3'
- props.serviceId = '1'
-
- renderWithProviders()
- screen.getByText('New')
- })
-
- it('should have a button with numbers of deployment before the latest', async () => {
- props.environmentState = StateEnum.DEPLOYED
- props.versionId = '3'
- props.serviceId = '1'
- renderWithProviders()
-
- screen.getByText('2')
- })
-})
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-history/sidebar-history.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-history/sidebar-history.tsx
deleted file mode 100644
index eecc25d8c4f..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-history/sidebar-history.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { type DeploymentHistoryEnvironment, type StateEnum } from 'qovery-typescript-axios'
-import { match } from 'ts-pattern'
-import { DEPLOYMENT_LOGS_VERSION_URL } from '@qovery/shared/routes'
-import { Badge, Button, Icon, Link, Menu, MenuAlign, type MenuData, StatusChip, Tooltip } from '@qovery/shared/ui'
-import { dateFullFormat } from '@qovery/shared/util-dates'
-import { trimId } from '@qovery/shared/util-js'
-
-export interface SidebarHistoryProps {
- data: DeploymentHistoryEnvironment[]
- pathLogs: string
- serviceId: string
- versionId?: string
- environmentState?: StateEnum
-}
-
-export function SidebarHistory({ data, serviceId, versionId, pathLogs, environmentState }: SidebarHistoryProps) {
- const menuHistory: MenuData = [
- {
- items:
- data?.map((item, index) => ({
- name: item.id,
- itemContentCustom: (
-
-
-
-
- {trimId(item.id)}
-
-
-
{dateFullFormat(item.created_at)}
-
- ),
- link: {
- url: pathLogs + DEPLOYMENT_LOGS_VERSION_URL(serviceId, item.id),
- },
- })) || [],
- },
- ]
-
- function findPositionById(data: DeploymentHistoryEnvironment[], versionId = '') {
- const index = data.findIndex((item) => item.id === versionId)
- return index !== -1 ? index : 0
- }
-
- const showNewTag = match(environmentState)
- .with(
- 'DEPLOYING',
- 'DELETING',
- 'RESTARTING',
- 'BUILDING',
- 'STOP_QUEUED',
- 'CANCELING',
- 'QUEUED',
- 'DELETE_QUEUED',
- 'DEPLOYMENT_QUEUED',
- () => true
- )
- .otherwise(() => false)
-
- if (data.length === 0) return null
-
- const currentPosition = findPositionById(data, versionId)
-
- return (
-
-
-
-
-
-
-
- )
-}
-
-export default SidebarHistory
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline-item/sidebar-pipeline-item.spec.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline-item/sidebar-pipeline-item.spec.tsx
deleted file mode 100644
index 2f892243e08..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline-item/sidebar-pipeline-item.spec.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { ServiceDeploymentStatusEnum, StateEnum } from 'qovery-typescript-axios'
-import { applicationFactoryMock } from '@qovery/shared/factories'
-import { renderWithProviders, screen, waitFor } from '@qovery/shared/util-tests'
-import SidebarPipelineItem, { type SidebarPipelineItemProps } from './sidebar-pipeline-item'
-
-const applications = applicationFactoryMock(2)
-
-describe('SidebarPipelineItem', () => {
- const props: SidebarPipelineItemProps = {
- serviceId: '1',
- index: 1,
- services: applications,
- currentStage: {
- stage: {
- id: 'stage-1',
- name: 'Stage 1',
- },
- applications: [
- {
- id: '1',
- state: StateEnum.READY,
- message: 'hello',
- service_deployment_status: ServiceDeploymentStatusEnum.UP_TO_DATE,
- },
- ],
- databases: [],
- containers: [],
- jobs: [],
- },
- }
-
- it('should render successfully', () => {
- const { baseElement, getByText } = renderWithProviders()
- expect(baseElement).toBeTruthy()
- expect(getByText('Stage 1')).toBeInTheDocument()
- })
-
- it('should have message when no services', async () => {
- props.services = []
- props.currentStage = {
- stage: {
- id: 'stage-1',
- name: 'Stage 1',
- },
- applications: [],
- databases: [],
- containers: [],
- jobs: [],
- }
-
- const { userEvent } = renderWithProviders()
-
- await userEvent.click(screen.getByTestId('toggle-stage'))
-
- waitFor(() => {
- expect(screen.getByText('No service for this stage')).toBeInTheDocument()
- })
- })
-})
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline-item/sidebar-pipeline-item.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline-item/sidebar-pipeline-item.tsx
deleted file mode 100644
index 8524cd245d3..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline-item/sidebar-pipeline-item.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { type DeploymentStageWithServicesStatuses, type Status } from 'qovery-typescript-axios'
-import { useContext, useEffect, useState } from 'react'
-import { Link, useParams } from 'react-router-dom'
-import { ServiceStageIdsContext } from '@qovery/domains/service-logs/feature'
-import { type AnyService } from '@qovery/domains/services/data-access'
-import { ServiceAvatar } from '@qovery/domains/services/feature'
-import { DEPLOYMENT_LOGS_VERSION_URL, ENVIRONMENT_LOGS_URL } from '@qovery/shared/routes'
-import { BadgeDeploymentOrder, Icon, StatusChip } from '@qovery/shared/ui'
-
-export function mergeServices(
- applications?: Status[],
- databases?: Status[],
- containers?: Status[],
- jobs?: Status[],
- helms?: Status[]
-) {
- return (
- (applications &&
- databases &&
- containers &&
- jobs &&
- helms && [...applications, ...databases, ...containers, ...jobs, ...helms]) ||
- []
- )
-}
-
-export interface SidebarPipelineItemProps {
- index: number
- services: AnyService[]
- currentStage: DeploymentStageWithServicesStatuses
- serviceId?: string
- versionId?: string
-}
-
-export function SidebarPipelineItem({ currentStage, index, serviceId, versionId, services }: SidebarPipelineItemProps) {
- const { organizationId = '', projectId = '', environmentId = '' } = useParams()
- const { updateStageId } = useContext(ServiceStageIdsContext)
-
- const currentApplication = (serviceId: string) => services.find((service: AnyService) => service.id === serviceId)
-
- const servicesStages = mergeServices(
- currentStage.applications,
- currentStage.databases,
- currentStage.containers,
- currentStage.jobs,
- currentStage.helms
- )
-
- const [openStage, setOpenStage] = useState(servicesStages.length > 0)
-
- useEffect(() => {
- const activeStage = servicesStages.some((service: Status) => service.id === serviceId)
- if (activeStage) {
- currentStage.stage?.id && updateStageId(currentStage.stage.id)
- }
- }, [serviceId, currentStage.stage?.id, servicesStages, updateStageId])
-
- return (
-
-
setOpenStage(!openStage)}
- >
-
- {currentStage?.stage?.name}
-
-
- {openStage && (
-
- {servicesStages.length > 0 ? (
- servicesStages.map((service, index) => (
-
- {currentApplication(service.id) && (
-
-
-
- {currentApplication(service.id)?.name}
-
-
-
- )}
-
- ))
- ) : (
-
-
-
No service for this stage
-
- )}
-
- )}
-
- )
-}
-
-export default SidebarPipelineItem
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline/sidebar-pipeline.spec.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline/sidebar-pipeline.spec.tsx
deleted file mode 100644
index 389d435a2f4..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline/sidebar-pipeline.spec.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { applicationFactoryMock } from '@qovery/shared/factories'
-import { renderWithProviders, screen } from '@qovery/shared/util-tests'
-import SidebarPipeline, { type SidebarPipelineProps } from './sidebar-pipeline'
-
-describe('SidebarPipeline', () => {
- const props: SidebarPipelineProps = {
- services: applicationFactoryMock(2),
- serviceId: '1',
- }
-
- it('should render successfully', () => {
- const { baseElement } = renderWithProviders()
- expect(baseElement).toBeTruthy()
- })
-
- it('should have loader when statusStages is undefined', () => {
- renderWithProviders()
- screen.getByTestId('spinner')
- })
-})
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline/sidebar-pipeline.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline/sidebar-pipeline.tsx
deleted file mode 100644
index d66a645ae36..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-pipeline/sidebar-pipeline.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { type DeploymentStageWithServicesStatuses } from 'qovery-typescript-axios'
-import { type AnyService } from '@qovery/domains/services/data-access'
-import { LoaderSpinner } from '@qovery/shared/ui'
-import SidebarPipelineItem from '../sidebar-pipeline-item/sidebar-pipeline-item'
-
-export interface SidebarPipelineProps {
- services: AnyService[]
- serviceId?: string
- versionId?: string
- statusStages?: DeploymentStageWithServicesStatuses[]
-}
-
-export function SidebarPipeline({ services, versionId, serviceId, statusStages }: SidebarPipelineProps) {
- return (
-
-
Pipeline
- {!statusStages ? (
-
-
-
- ) : (
- statusStages.map((currentStage: DeploymentStageWithServicesStatuses, index: number) => (
-
- ))
- )}
-
- )
-}
-
-export default SidebarPipeline
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-status/sidebar-status.spec.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-status/sidebar-status.spec.tsx
deleted file mode 100644
index f5115db7290..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-status/sidebar-status.spec.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { render } from '__tests__/utils/setup-jest'
-import { applicationDeploymentsFactoryMock } from '@qovery/shared/factories'
-import SidebarStatus, { type SidebarStatusProps } from './sidebar-status'
-
-describe('SidebarStatus', () => {
- const props: SidebarStatusProps = {
- environmentDeploymentHistory: applicationDeploymentsFactoryMock(1)[0],
- }
-
- it('should render successfully', () => {
- const { baseElement } = render()
- expect(baseElement).toBeTruthy()
- })
-})
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar-status/sidebar-status.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar-status/sidebar-status.tsx
deleted file mode 100644
index 47831c88744..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar-status/sidebar-status.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { type EnvironmentStatus } from 'qovery-typescript-axios'
-import { Icon, StatusChip, Tooltip } from '@qovery/shared/ui'
-import { dateFullFormat, dateUTCString } from '@qovery/shared/util-dates'
-
-export interface SidebarStatusProps {
- environmentStatus?: EnvironmentStatus
-}
-
-export function SidebarStatus(props: SidebarStatusProps) {
- const { environmentStatus } = props
-
- return (
-
-
- Deployment status:
-
-
-
- Deployment Execution id:
-
-
- {environmentStatus?.last_deployment_id && environmentStatus?.last_deployment_id.length > 23
- ? `${environmentStatus?.last_deployment_id?.substring(
- 0,
- 8
- )}...${environmentStatus?.last_deployment_id?.slice(-8)}`
- : environmentStatus?.last_deployment_id}
-
-
-
- {environmentStatus?.last_deployment_date && (
-
- Deployment start time:
-
- {dateFullFormat(environmentStatus.last_deployment_date)}
-
-
- )}
-
- Parallel Deployment:
-
- 7{' '}
-
-
-
-
-
-
-
-
- )
-}
-
-export default SidebarStatus
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar/sidebar.spec.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar/sidebar.spec.tsx
deleted file mode 100644
index 489d5128227..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar/sidebar.spec.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { applicationFactoryMock } from '@qovery/shared/factories'
-import { renderWithProviders, screen } from '@qovery/shared/util-tests'
-import Sidebar, { type SidebarProps } from './sidebar'
-
-describe('Sidebar', () => {
- const props: SidebarProps = {
- services: applicationFactoryMock(2),
- serviceId: '1',
- versionId: '2',
- }
-
- it('should render successfully', () => {
- const { baseElement } = renderWithProviders()
- expect(baseElement).toBeTruthy()
- })
-
- it('should toggles sidebar visibility when the button is clicked', async () => {
- renderWithProviders()
- const sidebar = screen.getByTestId('sidebar')
- const toggleButton = screen.getByTestId('sidebar-resize-button')
-
- toggleButton.click()
-
- expect(toggleButton).toHaveClass('border-l')
- expect(sidebar).toHaveClass('w-full')
- })
-})
diff --git a/libs/pages/logs/environment/src/lib/ui/sidebar/sidebar.tsx b/libs/pages/logs/environment/src/lib/ui/sidebar/sidebar.tsx
deleted file mode 100644
index 82a0998b0e5..00000000000
--- a/libs/pages/logs/environment/src/lib/ui/sidebar/sidebar.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import {
- type DeploymentHistoryEnvironment,
- type DeploymentStageWithServicesStatuses,
- type EnvironmentStatus,
- type StateEnum,
-} from 'qovery-typescript-axios'
-import { useState } from 'react'
-import { type AnyService } from '@qovery/domains/services/data-access'
-import { Icon } from '@qovery/shared/ui'
-import SidebarHistoryFeature from '../../feature/sidebar-history-feature/sidebar-history-feature'
-import SidebarPipeline from '../sidebar-pipeline/sidebar-pipeline'
-import SidebarStatus from '../sidebar-status/sidebar-status'
-
-export interface SidebarProps {
- services: AnyService[]
- statusStages?: DeploymentStageWithServicesStatuses[]
- environmentStatus?: EnvironmentStatus
- currentEnvironmentState?: StateEnum
- environmentDeploymentHistory?: DeploymentHistoryEnvironment
- versionId?: string
- serviceId?: string
-}
-
-export function Sidebar({ services, statusStages, environmentStatus, versionId, serviceId }: SidebarProps) {
- const [openSidebar, setOpenSidebar] = useState(true)
-
- return (
-
-
-
-
-
-
-
setOpenSidebar(!openSidebar)}
- className={`flex w-5 cursor-pointer items-center justify-center border-neutral-500 text-neutral-350 transition-all duration-150 ease-in-out hover:bg-neutral-600 ${
- openSidebar ? 'border-l' : ''
- } `}
- >
-
-
-
- )
-}
-
-export default Sidebar
diff --git a/libs/pages/services/src/lib/ui/container/container.tsx b/libs/pages/services/src/lib/ui/container/container.tsx
index 9df4e883522..523a1c17a78 100644
--- a/libs/pages/services/src/lib/ui/container/container.tsx
+++ b/libs/pages/services/src/lib/ui/container/container.tsx
@@ -111,8 +111,8 @@ export function Container({ children }: PropsWithChildren) {
link: `${SERVICES_URL(organizationId, projectId, environmentId)}${SERVICES_GENERAL_URL}`,
},
{
- icon: ,
- name: 'Deployments',
+ icon: ,
+ name: 'Deployments History',
active:
location.pathname === `${SERVICES_URL(organizationId, projectId, environmentId)}${SERVICES_DEPLOYMENTS_URL}`,
link: `${SERVICES_URL(organizationId, projectId, environmentId)}${SERVICES_DEPLOYMENTS_URL}`,
diff --git a/libs/pages/services/src/lib/ui/page-settings-deployment-pipeline/page-settings-deployment-pipeline.tsx b/libs/pages/services/src/lib/ui/page-settings-deployment-pipeline/page-settings-deployment-pipeline.tsx
index b4fcc373914..1c613628ab6 100644
--- a/libs/pages/services/src/lib/ui/page-settings-deployment-pipeline/page-settings-deployment-pipeline.tsx
+++ b/libs/pages/services/src/lib/ui/page-settings-deployment-pipeline/page-settings-deployment-pipeline.tsx
@@ -141,20 +141,20 @@ export function PageSettingsDeploymentPipeline(props: PageSettingsDeploymentPipe
heading={
-
-
+
+
+ {description && (
+
+
+
+
+
+ )}
- {description && (
-
-
-
-
-
- )}