-
-
- {selectedRows.length} selected {pluralize(selectedRows.length, 'service')}
-
-
-
-
-
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-cells/index.ts b/libs/domains/services/feature/src/lib/service-list/service-list-cells/index.ts
index f0cbe4b47a6..2178322cf04 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list-cells/index.ts
+++ b/libs/domains/services/feature/src/lib/service-list/service-list-cells/index.ts
@@ -1,4 +1,3 @@
+export * from './service-last-deployment-cell'
export * from './service-name-cell'
export * from './service-version-cell'
-export * from './service-last-deployment-cell'
-export * from './service-running-status-cell'
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-last-deployment-cell.tsx b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-last-deployment-cell.tsx
index 9964d9d8588..62ffa71bff0 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-last-deployment-cell.tsx
+++ b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-last-deployment-cell.tsx
@@ -1,40 +1,75 @@
+import { type Environment } from 'qovery-typescript-axios'
+import { PropsWithChildren, useCallback } from 'react'
import { type AnyService } from '@qovery/domains/services/data-access'
-import { Icon, Link, Tooltip } from '@qovery/shared/ui'
-import { dateUTCString, timeAgo } from '@qovery/shared/util-dates'
-import useDeploymentStatus from '../../hooks/use-deployment-status/use-deployment-status'
+import { DevopsCopilotTroubleshootTrigger } from '@qovery/shared/devops-copilot/feature'
+import { DeploymentAction, Link, StatusChip } from '@qovery/shared/ui'
+import { useServiceDeploymentAndRunningStatuses } from '../../hooks/use-service-deployment-and-running-statuses/use-service-deployment-and-running-statuses'
type ServiceLastDeploymentCellProps = {
service: AnyService
- organizationId: string
- projectId: string
- environmentId: string
+ environment: Environment
}
-export function ServiceLastDeploymentCell({
- service,
- organizationId,
- projectId,
- environmentId,
-}: ServiceLastDeploymentCellProps) {
- const { data: deploymentStatus } = useDeploymentStatus({ environmentId: environmentId, serviceId: service.id })
- const date = deploymentStatus?.last_deployment_date
+export function ServiceLastDeploymentCell({ service, environment }: ServiceLastDeploymentCellProps) {
+ const {
+ data: { deploymentStatus },
+ } = useServiceDeploymentAndRunningStatuses({ environmentId: environment.id, service })
+ const subAction = deploymentStatus?.status_details?.sub_action
+ const triggerAction = subAction !== 'NONE' ? subAction : deploymentStatus?.status_details?.action
- return date ? (
-
event.stopPropagation()}
- >
-
- {timeAgo(new Date(date))}
-
-
-
+ const WrappingLink = useCallback(
+ ({ children }: PropsWithChildren) => {
+ return (
+
{
+ e.stopPropagation()
+ }}
+ >
+ {children}
+
+ )
+ },
+ [environment, service, deploymentStatus?.execution_id]
+ )
+
+ return deploymentStatus?.state === 'READY' ? (
+
Never been deployed
) : (
-
-
+
+
+
+
+
+
+ {deploymentStatus?.status_details?.status === 'ERROR' && (
+
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
+
+
+ )}
+
+
+
+
+
+
)
}
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-name-cell.tsx b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-name-cell.tsx
index d4002558cb1..7b1a69c9b62 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-name-cell.tsx
+++ b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-name-cell.tsx
@@ -1,206 +1,89 @@
import { type Environment } from 'qovery-typescript-axios'
import { match } from 'ts-pattern'
import { type AnyService } from '@qovery/domains/services/data-access'
-import { AnimatedGradientText, Badge, Button, Icon, Link, Tooltip } from '@qovery/shared/ui'
-import { formatCronExpression, pluralize, upperCaseFirstLetter } from '@qovery/shared/util-js'
-import useDeploymentStatus from '../../hooks/use-deployment-status/use-deployment-status'
-import ServiceActions from '../../service-actions/service-actions'
+import { Button, Icon, Tooltip } from '@qovery/shared/ui'
+import { formatCronExpression } from '@qovery/shared/util-js'
import { ServiceAvatar } from '../../service-avatar/service-avatar'
import ServiceLinksPopover from '../../service-links-popover/service-links-popover'
import ServiceTemplateIndicator from '../../service-template-indicator/service-template-indicator'
export function ServiceNameCell({ service, environment }: { service: AnyService; environment: Environment }) {
- const { data: deploymentStatus } = useDeploymentStatus({ environmentId: environment.id, serviceId: service.id })
- const deploymentRequestsCount = Number(deploymentStatus?.deployment_requests_count)
-
- const LinkDeploymentStatus = () => {
- // const environmentLog = ENVIRONMENT_LOGS_URL(environment.organization.id, environment.project.id, environment.id)
- // const deploymentLog = DEPLOYMENT_LOGS_VERSION_URL(service.id, deploymentStatus?.execution_id)
- // const precheckLog = ENVIRONMENT_PRE_CHECK_LOGS_URL(deploymentStatus?.execution_id ?? '')
-
- return match(deploymentStatus?.state)
- .with('DEPLOYMENT_QUEUED', 'DELETE_QUEUED', 'STOP_QUEUED', 'RESTART_QUEUED', (s) => (
-
{upperCaseFirstLetter(s).replace('_', ' ')}...
- ))
- .with('CANCELED', () =>
Last deployment aborted)
- .with('DEPLOYING', 'RESTARTING', 'BUILDING', 'DELETING', 'CANCELING', 'STOPPING', (s) => (
- // TODO new-nav : Route not yet created
- //
e.stopPropagation()}
- // >
-
-
-
- {upperCaseFirstLetter(s)}...
-
-
-
- //
- ))
- .with('DEPLOYMENT_ERROR', 'DELETE_ERROR', 'STOP_ERROR', 'RESTART_ERROR', 'BUILD_ERROR', () => (
-
{
- e.stopPropagation()
- // TODO new-nav : Route not yet created
- // navigate({ to: (environmentLog + deploymentLog) as never })
- }}
- >
- Last deployment failed
-
-
- ))
- .otherwise(() => null)
- }
-
return (
-
-
-
-
-
- {match(service)
- .with({ serviceType: 'DATABASE' }, (db) => {
- return (
-
-
-
- e.stopPropagation()}
- >
- {db.name}
-
-
+
+
+
+
+
+
+ {match(service)
+ .with({ serviceType: 'DATABASE' }, (db) => {
+ return (
+
+
+
+ {db.name}
+
+
-
-
- )
- })
- .with({ serviceType: 'JOB' }, (job) => {
- const schedule = match(job)
- .with(
- { job_type: 'CRON' },
- ({ schedule }) =>
- `Triggered: ${formatCronExpression(schedule.cronjob?.scheduled_at)} (${schedule.cronjob?.timezone})`
)
- .with({ job_type: 'LIFECYCLE' }, ({ schedule }) => {
- const actions = [
- schedule.on_start && 'Deploy',
- schedule.on_stop && 'Stop',
- schedule.on_delete && 'Delete',
- ]
- .filter(Boolean)
- .join(' - ')
- return actions ? `Triggered on: ${actions}` : undefined
- })
- .exhaustive()
+ })
+ .with({ serviceType: 'JOB' }, (job) => {
+ const schedule = match(job)
+ .with(
+ { job_type: 'CRON' },
+ ({ schedule }) =>
+ `Triggered: ${formatCronExpression(schedule.cronjob?.scheduled_at)} (${schedule.cronjob?.timezone})`
+ )
+ .with({ job_type: 'LIFECYCLE' }, ({ schedule }) => {
+ const actions = [
+ schedule.on_start && 'Deploy',
+ schedule.on_stop && 'Stop',
+ schedule.on_delete && 'Delete',
+ ]
+ .filter(Boolean)
+ .join(' - ')
+ return actions ? `Triggered on: ${actions}` : undefined
+ })
+ .exhaustive()
- return (
-
-
-
- e.stopPropagation()}
- >
- {service.name}
-
-
-
-
-
-
-
+ return (
+
+
+
+ {service.name}
+
+
+
+
+
+
+
-
+ )
+ })
+ .otherwise(() => (
+
+
+ {service.name}
+
- )
- })
- .otherwise(() => (
-
-
- e.stopPropagation()}
- >
- {service.name}
-
-
-
-
- ))}
+ ))}
+
- {deploymentRequestsCount > 0 && (
-
e.stopPropagation()}>
+
-
-
- {deploymentRequestsCount}
-
-
- )}
-
-
-
- {'auto_deploy' in service && service.auto_deploy && (
-
-
-
-
-
- )}
-
e.stopPropagation()}>
-
-
-
-
-
-
-
-
-
-
-
-
-
e.stopPropagation()}>
-
+
+
+
+
+
+
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-running-status-cell.tsx b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-running-status-cell.tsx
deleted file mode 100644
index 8f857e57df6..00000000000
--- a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-running-status-cell.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import { type Environment } from 'qovery-typescript-axios'
-import { ServiceSubActionDto } from 'qovery-ws-typescript-axios'
-import { type PropsWithChildren, useContext } from 'react'
-import { match } from 'ts-pattern'
-import { type AnyService } from '@qovery/domains/services/data-access'
-import { DevopsCopilotContext } from '@qovery/shared/devops-copilot/context'
-import { Icon, Link, Skeleton, StatusChip, Tooltip } from '@qovery/shared/ui'
-import { useCheckRunningStatusClosed } from '../../hooks/use-check-running-status-closed/use-check-running-status-closed'
-import { useServiceDeploymentAndRunningStatuses } from '../../hooks/use-service-deployment-and-running-statuses/use-service-deployment-and-running-statuses'
-
-type ServiceRunningStatusCellProps = {
- service: AnyService
- organizationId: string
- projectId: string
- environment: Environment
- clusterId: string
-}
-
-export function ServiceRunningStatusCell({
- service,
- organizationId,
- projectId,
- environment,
- clusterId,
-}: ServiceRunningStatusCellProps) {
- const { data } = useServiceDeploymentAndRunningStatuses({ environmentId: environment.id, service })
- const { runningStatus, deploymentStatus } = data
- const { setDevopsCopilotOpen, sendMessageRef } = useContext(DevopsCopilotContext)
-
- const { data: checkRunningStatusClosed } = useCheckRunningStatusClosed({
- clusterId,
- environmentId: environment.id,
- })
-
- const Wrapper = ({ children }: PropsWithChildren) =>
{children}
-
- const value = match(runningStatus?.triggered_action)
- .with(
- { sub_action: ServiceSubActionDto.TERRAFORM_PLAN_ONLY },
- () => 'Plan ' + runningStatus?.stateLabel?.toLowerCase()
- )
- .with(
- { sub_action: ServiceSubActionDto.TERRAFORM_PLAN_AND_APPLY },
- () => 'Apply ' + runningStatus?.stateLabel?.toLowerCase()
- )
- .with(
- { sub_action: ServiceSubActionDto.TERRAFORM_MIGRATE_STATE },
- () => 'Migrate state ' + runningStatus?.stateLabel?.toLowerCase()
- )
- .with(
- { sub_action: ServiceSubActionDto.TERRAFORM_FORCE_UNLOCK_STATE },
- () => 'Force unlock ' + runningStatus?.stateLabel?.toLowerCase()
- )
- .with(
- { sub_action: ServiceSubActionDto.TERRAFORM_DESTROY },
- () => 'Destroy ' + runningStatus?.stateLabel?.toLowerCase()
- )
- .with({ sub_action: ServiceSubActionDto.NONE }, () => runningStatus?.stateLabel)
- .with(undefined, () => runningStatus?.stateLabel)
- .exhaustive()
-
- const serviceStatus = match(service)
- .with({ serviceType: 'DATABASE', mode: 'MANAGED' }, () => deploymentStatus?.state)
- .otherwise(() => runningStatus?.state)
-
- const isError = serviceStatus?.includes('ERROR')
-
- if (checkRunningStatusClosed) {
- return (
-
-
- e.stopPropagation()}
- className="gap-2 whitespace-nowrap text-sm"
- size="md"
- color="neutral"
- variant="outline"
- radius="full"
- >
-
- Status unavailable
-
-
-
- )
- }
-
- return (
-
-
-
- e.stopPropagation()}
- className="gap-2 whitespace-nowrap text-sm"
- size="md"
- color="neutral"
- variant="outline"
- radius="full"
- >
-
- {value}
- {isError && (
- {
- event.preventDefault()
- event.stopPropagation()
- }}
- >
- {
- event.preventDefault()
- event.stopPropagation()
- setDevopsCopilotOpen(true)
- sendMessageRef?.current?.('Why did my deployment fail?')
- }}
- >
-
- Ask AI Copilot for diagnostic
-
-
-
-
- }
- >
- {
- event.preventDefault()
- event.stopPropagation()
- setDevopsCopilotOpen(true)
- sendMessageRef?.current?.('Why did my deployment fail?')
- }}
- className="group cursor-pointer"
- >
-
-
-
-
- )}
-
-
-
-
- )
-}
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx
index 0cd96651c06..6fc1fe634dc 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx
+++ b/libs/domains/services/feature/src/lib/service-list/service-list-cells/service-version-cell.tsx
@@ -19,7 +19,7 @@ import {
isJobContainerSource,
isJobGitSource,
} from '@qovery/shared/enums'
-import { ExternalLink, Icon, Tooltip, Truncate } from '@qovery/shared/ui'
+import { ExternalLink, Icon, Tooltip } from '@qovery/shared/ui'
import { buildGitProviderUrl } from '@qovery/shared/util-git'
import { containerRegistryKindToIcon } from '@qovery/shared/util-js'
import LastCommit from '../../last-commit/last-commit'
@@ -34,144 +34,155 @@ type ServiceVersionCellProps = {
export function ServiceVersionCell({ service, organizationId, projectId }: ServiceVersionCellProps) {
const gitInfo = (service: Application | Job | Helm | Terraform, gitRepository?: ApplicationGitRepository) =>
gitRepository && (
- e.stopPropagation()}>
-
-
-
-
-
-
-
- {gitRepository.branch && gitRepository.url && (
-
-
+ e.stopPropagation()}>
+
+
+
+
-
+
+ {gitRepository.name}
+
-
+
+ {gitRepository.branch && gitRepository.url && (
+
+
+
+
+ {gitRepository.branch}
+
+
+
+ )}
+
+
+
+
+ {'auto_deploy' in service && service.auto_deploy && (
+
+
+
+
+
)}
-
)
const containerInfo = (containerImage?: Pick) =>
containerImage && (
- e.stopPropagation()}>
-
-
-
+ e.stopPropagation()}>
+
+
+
- {containerImage.registry.name.length >= 20 && (
- <>
- {containerImage.registry.name}
- >
- )}{' '}
+ {containerImage.registry.name}
+
{containerImage.registry.url}
}
>
-
- {containerImage.registry.name.length >= 20 ? (
-
- ) : (
- containerImage.registry.name.toLowerCase()
- )}
+
+ {containerImage.registry.name.toLowerCase()}
-
-
-
+
+
+
+ {containerImage.image_name}
+
{(service.serviceType === 'CONTAINER' ||
(service.serviceType === 'JOB' && isJobContainerSource(service.source))) && (
-
+
+
+
)}
)
const datasourceInfo = (datasource?: Pick) =>
datasource && (
-
-
+
+
- {datasource.type.toLowerCase().replace('sql', 'SQL').replace('db', 'DB')}
-
-
- v
- {datasource.version}
-
+
+ {datasource.type.toLowerCase().replace('sql', 'SQL').replace('db', 'DB')}
+
+
+
+
+ v{datasource.version}
+
)
const helmInfo = (helmRepository?: HelmSourceRepositoryResponse) =>
helmRepository && (
-
-
e.stopPropagation()}>
-
-
+ e.stopPropagation()}>
+
+
+
- {helmRepository.repository?.name.length > 20 && (
- <>
- {helmRepository.repository?.name}
- >
- )}
+ {helmRepository.repository?.name}
+
{helmRepository.repository?.url}
}
>
-
- {helmRepository.repository?.name.length > 20 ? (
-
- ) : (
- helmRepository.repository?.name.toLowerCase()
- )}
+
+ {helmRepository.repository?.name?.toLowerCase()}
-
-
-
-
+
+
+
+ {helmRepository.chart_name}
-
+
{service.serviceType === 'HELM' && (
-
+
+
+
)}
)
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list-skeleton.tsx b/libs/domains/services/feature/src/lib/service-list/service-list-skeleton.tsx
deleted file mode 100644
index ef564b5831f..00000000000
--- a/libs/domains/services/feature/src/lib/service-list/service-list-skeleton.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Skeleton, TablePrimitives } from '@qovery/shared/ui'
-
-const { Table } = TablePrimitives
-
-export function ServiceListSkeleton() {
- const columnSizes = ['40%', '15%', '15%', '20%', '10%']
-
- return (
-
-
-
- {[...Array(5)].map((_, index) => (
-
-
-
- ))}
-
-
-
- {[...Array(3)].map((_, index) => (
-
- {[...Array(5)].map((_, index) => (
-
- {index === 0 ? (
-
-
-
-
- ) : (
-
- )}
-
- ))}
-
- ))}
-
-
- )
-}
-
-export default ServiceListSkeleton
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list.spec.tsx b/libs/domains/services/feature/src/lib/service-list/service-list.spec.tsx
index f1e4097988a..5902965d8a4 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list.spec.tsx
+++ b/libs/domains/services/feature/src/lib/service-list/service-list.spec.tsx
@@ -424,13 +424,6 @@ describe('ServiceList', () => {
const rows = screen.getAllByRole('row')
expect(rows).toHaveLength(5)
})
- it('should filter services by name', async () => {
- const { userEvent } = renderWithProviders()
- await userEvent.click(screen.getAllByRole('button', { name: /service/i })[0])
- await userEvent.click(screen.getByRole('menuitem', { name: /front-end/i }))
- const rows = screen.getAllByRole('row')
- expect(rows).toHaveLength(2)
- }, 10000)
it('should navigate to service on row click', async () => {
const { userEvent } = renderWithProviders()
const rows = screen.getAllByRole('row')
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list.tsx b/libs/domains/services/feature/src/lib/service-list/service-list.tsx
index 30f608fa392..e5ce7efacbb 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list.tsx
+++ b/libs/domains/services/feature/src/lib/service-list/service-list.tsx
@@ -11,8 +11,9 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table'
+import clsx from 'clsx'
import { type Environment } from 'qovery-typescript-axios'
-import { type ComponentProps, Fragment, useMemo, useState } from 'react'
+import { type ComponentProps, Fragment, useCallback, useMemo, useState } from 'react'
import { match } from 'ts-pattern'
import {
Badge,
@@ -20,23 +21,19 @@ import {
EmptyState,
Icon,
Link,
+ Section,
Skeleton,
+ StatusChip,
TableFilter,
TablePrimitives,
Tooltip,
- Truncate,
} from '@qovery/shared/ui'
-import { twMerge } from '@qovery/shared/util-js'
+import { pluralize, twMerge } from '@qovery/shared/util-js'
import { useListDeploymentStages } from '../hooks/use-list-deployment-stages/use-list-deployment-stages'
import { useServices } from '../hooks/use-services/use-services'
-import { ServiceAvatar } from '../service-avatar/service-avatar'
+import { ServiceActions } from '../service-actions/service-actions'
import { ServiceListActionBar } from './service-list-action-bar'
-import {
- ServiceLastDeploymentCell,
- ServiceNameCell,
- ServiceRunningStatusCell,
- ServiceVersionCell,
-} from './service-list-cells'
+import { ServiceLastDeploymentCell, ServiceNameCell, ServiceVersionCell } from './service-list-cells'
const { Table } = TablePrimitives
@@ -44,8 +41,62 @@ export interface ServiceListProps extends ComponentProps {
environment: Environment
}
+export const tableGridLayoutClassName =
+ 'grid w-full grid-cols-[44px_minmax(250px,1.5fr)_48px_minmax(150px,1fr)_minmax(320px,1.24fr)_130px]'
+
+export const ServiceListSkeleton = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {[...Array(6)].map((_, index) => (
+
+ {index === 0 ? (
+
+
+
+ ) : index === 2 ? null : (
+
+ )}
+
+ ))}
+
+
+
+ {[...Array(3)].map((_, index) => (
+
+ {[...Array(6)].map((_, index) => (
+
+ {index === 0 ? (
+
+
+
+ ) : index === 2 ? (
+
+ ) : (
+
+ )}
+
+ ))}
+
+ ))}
+
+
+
+
+
+ )
+}
+
export function ServiceList({ className, containerClassName, environment, ...props }: ServiceListProps) {
- const clusterId = environment.cluster_id || ''
const environmentId = environment.id || ''
const organizationId = environment.organization.id || ''
const projectId = environment.project.id || ''
@@ -70,8 +121,19 @@ export function ServiceList({ className, containerClassName, environment, ...pro
return map
}, [deploymentStages])
+ const actualServices = useMemo(() => {
+ return services.map((service) => {
+ return {
+ ...service,
+ status: match(service)
+ .with({ serviceType: 'DATABASE', mode: 'MANAGED' }, () => service.deploymentStatus?.state)
+ .otherwise(() => service.runningStatus?.state),
+ }
+ })
+ }, [services])
+
const sortedServices = useMemo(() => {
- return [...services].sort((a, b) => {
+ return [...actualServices].sort((a, b) => {
const aIsSkipped = skippedServicesMap.get(a.id) || false
const bIsSkipped = skippedServicesMap.get(b.id) || false
@@ -81,9 +143,9 @@ export function ServiceList({ className, containerClassName, environment, ...pro
return 0
})
- }, [services, skippedServicesMap])
+ }, [actualServices, skippedServicesMap])
- const columnHelper = createColumnHelper<(typeof services)[number]>()
+ const columnHelper = createColumnHelper<(typeof actualServices)[number]>()
const columns = useMemo(
() => [
columnHelper.display({
@@ -91,8 +153,7 @@ export function ServiceList({ className, containerClassName, environment, ...pro
enableColumnFilter: false,
enableSorting: false,
header: ({ table }) => (
-
- {/** XXX: fix css weird 1px vertical shift when checked/unchecked **/}
+
{
const isDisabled = !row.getCanSelect()
const checkbox = (
- {
- if (checked === 'indeterminate') {
- return
- }
- row.toggleSelected(checked)
- }}
- />
+
+ {/** XXX: fix css weird 1px vertical shift when checked/unchecked **/}
+ {
+ if (checked === 'indeterminate') {
+ return
+ }
+ row.toggleSelected(checked)
+ }}
+ />
+
)
return (
-
+
+
+
)
},
}),
columnHelper.accessor('name', {
header: 'Service',
- enableColumnFilter: true,
- enableSorting: false,
- filterFn: 'arrIncludesSome',
+ enableColumnFilter: false,
+ enableSorting: true,
size: 57,
- meta: {
- customFacetEntry({ value, row }) {
- const service = row?.original
- const serviceType = service?.serviceType
- if (!serviceType) {
- return null
- }
- return (
-
-
-
-
- )
- },
+ cell: (info) => {
+ return
},
+ }),
+ columnHelper.accessor('status', {
+ header: () => null,
+ enableColumnFilter: false,
+ enableSorting: false,
cell: (info) => {
- return (
-
-
-
- )
+ const serviceStatus = match(info.row.original)
+ .with({ serviceType: 'DATABASE', mode: 'MANAGED' }, () => info.row.original.deploymentStatus?.state)
+ .otherwise(() => info.row.original.runningStatus?.state)
+
+ return
},
}),
- columnHelper.accessor('runningStatus.stateLabel', {
- id: 'runningStatus',
- header: 'Service status',
- enableColumnFilter: true,
+ columnHelper.display({
+ id: 'last_deployment',
+ header: 'Last deployment',
+ enableColumnFilter: false,
enableSorting: false,
- filterFn: 'arrIncludesSome',
- size: 15,
- cell: (info) => (
-
- ),
+ cell: (info) => ,
}),
columnHelper.accessor('version', {
header: 'Target version',
enableColumnFilter: false,
enableSorting: false,
- size: 30,
cell: (info) => {
return (
)
},
}),
- columnHelper.accessor('deploymentStatus.last_deployment_date', {
- header: 'Last deployment',
+ columnHelper.display({
+ id: 'actions',
+ header: 'Actions',
enableColumnFilter: false,
- enableSorting: true,
- size: 3,
+ enableSorting: false,
cell: (info) => {
return (
-
+ e.stopPropagation()}>
+
+
)
},
}),
],
- [columnHelper, environment, clusterId, organizationId, projectId, environmentId]
+ [columnHelper, environment, organizationId, projectId]
)
const table = useReactTable({
@@ -242,43 +286,71 @@ export function ServiceList({ className, containerClassName, environment, ...pro
const selectedRows = table.getSelectedRowModel().rows.map(({ original }) => original)
- const statusFacetedUniqueValues = Array.from(
- table.getColumn('runningStatus')?.getFacetedUniqueValues().entries() ?? []
- )
+ const statusFacetedUniqueValues = Array.from(table.getColumn('status')?.getFacetedUniqueValues().entries() ?? [])
+
+ const ServicesBadges = useCallback(() => {
+ const getLabel = (value: string, count: number) => {
+ const statusLabel = value.toLowerCase()
+
+ return match(value)
+ .with('RUNNING', 'STOPPED', () => `${count} ${statusLabel}`)
+ .with('ERROR', () => `${count} in error`)
+ .otherwise(() => `${count} ${pluralize(count, statusLabel)}`)
+ }
+
+ return statusFacetedUniqueValues.some(([value]) => value === undefined) ? (
+
+ ) : (
+ statusFacetedUniqueValues.map(([value, count]: [string, number]) => (
+ 'green' as const)
+ .with('ERROR', () => 'red' as const)
+ .otherwise(() => 'neutral' as const)}
+ className="text-ssm font-medium"
+ >
+ {getLabel(value, count)}
+
+ ))
+ )
+ }, [statusFacetedUniqueValues])
if (services.length === 0) {
return (
- New service
+ New service
)
}
+ const handleNavigateToService = (serviceId: string) => {
+ navigate({
+ to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/overview',
+ params: { organizationId, projectId, environmentId, serviceId },
+ })
+ }
+
return (
-
- {statusFacetedUniqueValues.some(([value]) => value === undefined) ? (
-
- ) : (
- statusFacetedUniqueValues.map(([value, count]: [string, number]) => (
-
- {count} {value}
-
- ))
- )}
+
+
{table.getHeaderGroups().map((headerGroup) => (
-
+
{headerGroup.headers.map((header, i) => (
{header.column.getCanFilter() ? (
@@ -328,19 +402,19 @@ export function ServiceList({ className, containerClassName, environment, ...pro
{table.getRowModel().rows.map((row) => (
{
- navigate({
- to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/overview',
- params: { organizationId, projectId, environmentId, serviceId: row.original.id },
- })
+ className={`h-[60px] w-full cursor-pointer hover:bg-surface-neutral-subtle ${tableGridLayoutClassName}`}
+ tabIndex={0}
+ onClick={() => handleNavigateToService(row.original.id)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') handleNavigateToService(row.original.id)
}}
>
{row.getVisibleCells().map((cell, i) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}