From 450d978d9fb868960fc78a69434f7cdc02f0232b Mon Sep 17 00:00:00 2001 From: RemiBonnet Date: Fri, 31 Jul 2026 15:30:49 +0200 Subject: [PATCH 1/3] fix(services): isolate agentic workflows from service list --- .../$environmentId/overview/route.tsx | 7 +- .../src/lib/domains-services-data-access.ts | 15 +++- libs/domains/services/feature/src/index.ts | 2 + .../agentic-workflow-service-list.spec.tsx | 33 ++++++++ .../agentic-workflow-service-list.tsx | 80 +++++++++++++++++++ .../use-agentic-workflow-services.ts | 16 ++++ .../lib/service-list/service-list.spec.tsx | 18 +++++ package.json | 2 +- yarn.lock | 10 +-- 9 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.spec.tsx create mode 100644 libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx create mode 100644 libs/domains/services/feature/src/lib/hooks/use-agentic-workflow-services/use-agentic-workflow-services.ts diff --git a/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/overview/route.tsx b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/overview/route.tsx index d99c028b8b6..57a2a628f12 100644 --- a/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/overview/route.tsx +++ b/apps/console/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/overview/route.tsx @@ -2,6 +2,7 @@ import { type IconName } from '@fortawesome/fontawesome-common-types' import { Outlet, createFileRoute, useMatchRoute } from '@tanstack/react-router' import { Link as RouterLink } from '@tanstack/react-router' import posthog from 'posthog-js' +import { useFeatureFlagEnabled } from 'posthog-js/react' import { useEffect, useMemo } from 'react' import { ClusterAvatar, @@ -22,7 +23,7 @@ import { } from '@qovery/domains/environments/feature' import { useEnvironmentsOverview } from '@qovery/domains/projects/feature' import { isArgoCd, isEditableService } from '@qovery/domains/services/data-access' -import { ArgoCdServiceList, useServices } from '@qovery/domains/services/feature' +import { AgenticWorkflowServiceList, ArgoCdServiceList, useServices } from '@qovery/domains/services/feature' import { Heading, Icon, Link, Navbar, Section, Tooltip } from '@qovery/shared/ui' export const Route = createFileRoute( @@ -44,6 +45,7 @@ function RouteComponent() { const { data: deploymentStatus } = useDeploymentStatus({ environmentId }) const { data: cluster } = useCluster({ organizationId, clusterId: environment?.cluster_id, suspense: true }) const { data: services = [] } = useServices({ environmentId, suspense: true }) + const isAgenticWorkflowEnabled = Boolean(useFeatureFlagEnabled('argentic-workflow')) useClusterRunningStatusSocket({ organizationId, @@ -195,6 +197,9 @@ function RouteComponent() { )} {shouldDisplayArgoCdServicesBelowQovery && } + {isServicesListTab && isAgenticWorkflowEnabled && ( + + )} diff --git a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts index de59538e01e..9acfdff2ac9 100644 --- a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts +++ b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts @@ -1,5 +1,7 @@ import { createQueryKeys, type inferQueryKeys } from '@lukemorales/query-key-factory' import { + type AgenticWorkflowResponse, + AgenticWorkflowsApi, ApplicationActionsApi, type ApplicationAdvancedSettings, ApplicationConfigurationApi, @@ -104,6 +106,7 @@ const helmsApi = new HelmsApi() const terraformsApi = new TerraformsApi() const blueprintApi = new BlueprintMainCallsApi() const blueprintCatalogApi = new BlueprintCatalogApi() +const agenticWorkflowsApi = new AgenticWorkflowsApi() const applicationMainCallsApi = new ApplicationMainCallsApi() const containerMainCallsApi = new ContainerMainCallsApi() @@ -238,7 +241,7 @@ export function isArgoCd(service?: AnyService): service is ArgoCd { } export function isEditableService(service: AnyService): service is EditableService { - return !isArgoCd(service) + return ['APPLICATION', 'CONTAINER', 'DATABASE', 'JOB', 'HELM', 'TERRAFORM'].includes(service.service_type) } export function isEditableServiceType(serviceType?: ServiceType): serviceType is EditableServiceType { @@ -365,6 +368,13 @@ export const services = createQueryKeys('services', { ) }, }), + listAgenticWorkflows: (environmentId: string) => ({ + queryKey: [environmentId], + async queryFn(): Promise { + const response = await agenticWorkflowsApi.listAgenticWorkflows(environmentId) + return response.data.results ?? [] + }, + }), argocdManifest: (serviceId: string) => ({ queryKey: [serviceId], async queryFn() { @@ -987,7 +997,7 @@ export const mutations = { environment: Environment payload: EnvironmentServiceIdsAllRequest }) { - const response = await environmentActionApi.deleteSelectedServices(environment.id, payload) + const response = await environmentActionApi.deleteSelectedServices(environment.id, undefined, payload) return response.data }, async deleteService({ @@ -1022,6 +1032,7 @@ export const mutations = { terraformMainCallsApi.deleteTerraform.bind(terraformMainCallsApi)( serviceId, undefined, + undefined, skipDestroy ? 'SKIP_DESTROY' : undefined ), serviceType, diff --git a/libs/domains/services/feature/src/index.ts b/libs/domains/services/feature/src/index.ts index f798adcc252..744892f77ba 100644 --- a/libs/domains/services/feature/src/index.ts +++ b/libs/domains/services/feature/src/index.ts @@ -67,6 +67,8 @@ export * from './lib/hooks/use-recent-services/use-recent-services' export * from './lib/hooks/use-favorite-services/use-favorite-services' export * from './lib/service-actions/service-actions' export * from './lib/argocd-service-list/argocd-service-list' +export * from './lib/agentic-workflow-service-list/agentic-workflow-service-list' +export * from './lib/hooks/use-agentic-workflow-services/use-agentic-workflow-services' export * from './lib/argocd-manifest/argocd-manifest' export * from './lib/service-deployment-status-label/service-deployment-status-label' export * from './lib/service-overview/service-header/service-header' diff --git a/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.spec.tsx b/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.spec.tsx new file mode 100644 index 00000000000..3d2bf230e10 --- /dev/null +++ b/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.spec.tsx @@ -0,0 +1,33 @@ +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { AgenticWorkflowServiceList } from './agentic-workflow-service-list' + +const mockUseAgenticWorkflowServices = jest.fn() + +jest.mock('../hooks/use-agentic-workflow-services/use-agentic-workflow-services', () => ({ + useAgenticWorkflowServices: () => mockUseAgenticWorkflowServices(), +})) + +describe('AgenticWorkflowServiceList', () => { + it('should not render an empty section', () => { + mockUseAgenticWorkflowServices.mockReturnValue({ data: [] }) + const { container } = renderWithProviders() + + expect(container).toBeEmptyDOMElement() + }) + + it('should render agentic workflows in their own section', () => { + mockUseAgenticWorkflowServices.mockReturnValue({ + data: [ + { id: 'workflow-1', name: 'Review pull requests', enabled: true, model: { type: 'CLAUDE' } }, + { id: 'workflow-2', name: 'Triage incidents', enabled: false, model: { type: 'OPENAI' } }, + ], + }) + renderWithProviders() + + expect(screen.getByRole('heading', { name: 'Agentic workflows' })).toBeInTheDocument() + expect(screen.getByText('Review pull requests')).toBeInTheDocument() + expect(screen.getByText('Triage incidents')).toBeInTheDocument() + expect(screen.getByText('1 enabled')).toBeInTheDocument() + expect(screen.getByText('CLAUDE')).toBeInTheDocument() + }) +}) diff --git a/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx b/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx new file mode 100644 index 00000000000..5277d0b36fa --- /dev/null +++ b/libs/domains/services/feature/src/lib/agentic-workflow-service-list/agentic-workflow-service-list.tsx @@ -0,0 +1,80 @@ +import { type AgenticWorkflowResponse } from 'qovery-typescript-axios' +import { Badge, Heading, Icon, Section, TablePrimitives, Tooltip } from '@qovery/shared/ui' +import { useAgenticWorkflowServices } from '../hooks/use-agentic-workflow-services/use-agentic-workflow-services' + +const { Table } = TablePrimitives + +const tableGridLayoutClassName = 'grid w-full grid-cols-[minmax(350px,1fr)_minmax(180px,260px)_140px]' + +export interface AgenticWorkflowServiceListProps { + environmentId: string +} + +export function AgenticWorkflowServiceList({ environmentId }: AgenticWorkflowServiceListProps) { + const { data: services = [] } = useAgenticWorkflowServices({ environmentId, suspense: true }) + + if (services.length === 0) return null + + const enabledServicesCount = services.filter(({ enabled }) => enabled).length + + return ( +
+
+ + Agentic workflows + +

AI workflows triggered through webhooks and connected services.

+
+ +
+
+ + {enabledServicesCount} enabled + +
+ + + + + Service + + + Model + + + Status + + + + + {services.map((service: AgenticWorkflowResponse) => ( + + +
+ + + {service.name} + +
+
+ + {service.model?.type ?? '-'} + + + + {service.enabled ? 'Enabled' : 'Disabled'} + + +
+ ))} +
+
+
+
+ ) +} + +export default AgenticWorkflowServiceList diff --git a/libs/domains/services/feature/src/lib/hooks/use-agentic-workflow-services/use-agentic-workflow-services.ts b/libs/domains/services/feature/src/lib/hooks/use-agentic-workflow-services/use-agentic-workflow-services.ts new file mode 100644 index 00000000000..f712ad971e8 --- /dev/null +++ b/libs/domains/services/feature/src/lib/hooks/use-agentic-workflow-services/use-agentic-workflow-services.ts @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query' +import { queries } from '@qovery/state/util-queries' + +export interface UseAgenticWorkflowServicesProps { + environmentId: string + suspense?: boolean +} + +export function useAgenticWorkflowServices({ environmentId, suspense = false }: UseAgenticWorkflowServicesProps) { + return useQuery({ + ...queries.services.listAgenticWorkflows(environmentId), + suspense, + }) +} + +export default useAgenticWorkflowServices 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 58e52241fe8..f67bda65b57 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 @@ -469,6 +469,24 @@ describe('ServiceList', () => { expect(screen.queryByText('ARGOCD APP')).not.toBeInTheDocument() }) + it('should not pass agentic workflows to the standard service table', () => { + mockServicesData = [ + { + id: 'agentic-workflow-id', + name: 'Review pull requests', + service_type: 'AGENTIC_WORKFLOW', + serviceType: 'AGENTIC_WORKFLOW', + enabled: true, + model: { type: 'CLAUDE' }, + }, + ] + + renderWithProviders() + + expect(screen.getByText('No service found')).toBeInTheDocument() + expect(screen.queryByText('Review pull requests')).not.toBeInTheDocument() + }) + it('should navigate to service on row click', async () => { const { userEvent } = renderWithProviders() const rows = screen.getAllByRole('row') diff --git a/package.json b/package.json index 81e51a29890..4a71d5c4a93 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "mermaid": "11.6.0", "monaco-editor": "0.53.0", "posthog-js": "1.345.1", - "qovery-typescript-axios": "1.1.934", + "qovery-typescript-axios": "1.1.936", "react": "18.3.1", "react-country-flag": "3.0.2", "react-datepicker": "4.12.0", diff --git a/yarn.lock b/yarn.lock index f5e9b3bada0..28f2fa449a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6343,7 +6343,7 @@ __metadata: prettier: 3.2.5 prettier-plugin-tailwindcss: 0.5.14 pretty-quick: 4.0.0 - qovery-typescript-axios: 1.1.934 + qovery-typescript-axios: 1.1.936 qovery-ws-typescript-axios: 0.1.621 react: 18.3.1 react-country-flag: 3.0.2 @@ -25863,12 +25863,12 @@ __metadata: languageName: node linkType: hard -"qovery-typescript-axios@npm:1.1.934": - version: 1.1.934 - resolution: "qovery-typescript-axios@npm:1.1.934" +"qovery-typescript-axios@npm:1.1.936": + version: 1.1.936 + resolution: "qovery-typescript-axios@npm:1.1.936" dependencies: axios: 1.18.1 - checksum: 916504676e45b051ada1c1a910d655e5ac3f7fada105e1a48e7e91e0b626ebd00a195741c09be2811612a7820f8cf005b185fd2cec7554cab54f85a1871532a7 + checksum: 85a74d09513b04cc9a0f01627ea67261035f45bda02a87f583f04e969be3b0bd746d8caf5d971361fce96e7597c12e688243e27adf44d299de0cf49256e3b34b languageName: node linkType: hard From e2b778802e440bdbb403a178cc1d3349af1b99a7 Mon Sep 17 00:00:00 2001 From: RemiBonnet Date: Fri, 31 Jul 2026 15:42:51 +0200 Subject: [PATCH 2/3] chore(services): remove unrelated api changes --- .../src/lib/domains-services-data-access.ts | 3 +-- package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts index 9acfdff2ac9..0caa6c8d2e4 100644 --- a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts +++ b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts @@ -997,7 +997,7 @@ export const mutations = { environment: Environment payload: EnvironmentServiceIdsAllRequest }) { - const response = await environmentActionApi.deleteSelectedServices(environment.id, undefined, payload) + const response = await environmentActionApi.deleteSelectedServices(environment.id, payload) return response.data }, async deleteService({ @@ -1032,7 +1032,6 @@ export const mutations = { terraformMainCallsApi.deleteTerraform.bind(terraformMainCallsApi)( serviceId, undefined, - undefined, skipDestroy ? 'SKIP_DESTROY' : undefined ), serviceType, diff --git a/package.json b/package.json index 4a71d5c4a93..81e51a29890 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "mermaid": "11.6.0", "monaco-editor": "0.53.0", "posthog-js": "1.345.1", - "qovery-typescript-axios": "1.1.936", + "qovery-typescript-axios": "1.1.934", "react": "18.3.1", "react-country-flag": "3.0.2", "react-datepicker": "4.12.0", diff --git a/yarn.lock b/yarn.lock index 28f2fa449a4..f5e9b3bada0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6343,7 +6343,7 @@ __metadata: prettier: 3.2.5 prettier-plugin-tailwindcss: 0.5.14 pretty-quick: 4.0.0 - qovery-typescript-axios: 1.1.936 + qovery-typescript-axios: 1.1.934 qovery-ws-typescript-axios: 0.1.621 react: 18.3.1 react-country-flag: 3.0.2 @@ -25863,12 +25863,12 @@ __metadata: languageName: node linkType: hard -"qovery-typescript-axios@npm:1.1.936": - version: 1.1.936 - resolution: "qovery-typescript-axios@npm:1.1.936" +"qovery-typescript-axios@npm:1.1.934": + version: 1.1.934 + resolution: "qovery-typescript-axios@npm:1.1.934" dependencies: axios: 1.18.1 - checksum: 85a74d09513b04cc9a0f01627ea67261035f45bda02a87f583f04e969be3b0bd746d8caf5d971361fce96e7597c12e688243e27adf44d299de0cf49256e3b34b + checksum: 916504676e45b051ada1c1a910d655e5ac3f7fada105e1a48e7e91e0b626ebd00a195741c09be2811612a7820f8cf005b185fd2cec7554cab54f85a1871532a7 languageName: node linkType: hard From 62d553fba02c01369ba305eac524ed5d447ddd28 Mon Sep 17 00:00:00 2001 From: RemiBonnet Date: Fri, 31 Jul 2026 16:14:17 +0200 Subject: [PATCH 3/3] fix(services): preserve editable service fallback --- .../data-access/src/lib/domains-services-data-access.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts index 0caa6c8d2e4..f35a54b415a 100644 --- a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts +++ b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts @@ -241,7 +241,7 @@ export function isArgoCd(service?: AnyService): service is ArgoCd { } export function isEditableService(service: AnyService): service is EditableService { - return ['APPLICATION', 'CONTAINER', 'DATABASE', 'JOB', 'HELM', 'TERRAFORM'].includes(service.service_type) + return !isArgoCd(service) && (service.service_type as string) !== 'AGENTIC_WORKFLOW' } export function isEditableServiceType(serviceType?: ServiceType): serviceType is EditableServiceType {