|
| 1 | +import { type ArgocdAssociatedServicesResponse } from 'qovery-typescript-axios' |
| 2 | +import { Suspense, useState } from 'react' |
| 3 | +import { IconEnum } from '@qovery/shared/enums' |
| 4 | +import { Heading, Icon, InputSearch, Link, LoaderSpinner, Section, TreeView } from '@qovery/shared/ui' |
| 5 | +import { useArgoCdAssociatedServices } from '../hooks/use-argocd-associated-services/use-argocd-associated-services' |
| 6 | + |
| 7 | +export interface ArgoCdAssociatedServicesListModalProps { |
| 8 | + organizationId: string |
| 9 | + clusterId: string |
| 10 | + onClose: () => void |
| 11 | + associatedServicesCount: number |
| 12 | +} |
| 13 | + |
| 14 | +interface Service { |
| 15 | + service_id: string |
| 16 | + service_name: string |
| 17 | +} |
| 18 | + |
| 19 | +interface Environment { |
| 20 | + environment_id: string |
| 21 | + environment_name: string |
| 22 | + services: Service[] |
| 23 | +} |
| 24 | + |
| 25 | +interface Project { |
| 26 | + project_id: string |
| 27 | + project_name: string |
| 28 | + environments: Environment[] |
| 29 | +} |
| 30 | + |
| 31 | +export function argoCdGroupByProjectEnvironmentsServices( |
| 32 | + data: ArgocdAssociatedServicesResponse[], |
| 33 | + searchValue?: string |
| 34 | +) { |
| 35 | + const projects: Project[] = [] |
| 36 | + |
| 37 | + data.forEach(({ project_id, project_name, environment_id, environment_name, service_id, service_name }) => { |
| 38 | + if ( |
| 39 | + searchValue === undefined || |
| 40 | + project_name.toLowerCase().includes(searchValue.toLowerCase()) || |
| 41 | + environment_name.toLowerCase().includes(searchValue.toLowerCase()) || |
| 42 | + service_name.toLowerCase().includes(searchValue.toLowerCase()) |
| 43 | + ) { |
| 44 | + let project = projects.find((proj) => proj.project_id === project_id) |
| 45 | + if (!project) { |
| 46 | + project = { |
| 47 | + project_id, |
| 48 | + project_name, |
| 49 | + environments: [], |
| 50 | + } |
| 51 | + projects.push(project) |
| 52 | + } |
| 53 | + |
| 54 | + let environment = project.environments.find((env) => env.environment_id === environment_id) |
| 55 | + if (!environment) { |
| 56 | + environment = { |
| 57 | + environment_id, |
| 58 | + environment_name, |
| 59 | + services: [], |
| 60 | + } |
| 61 | + project.environments.push(environment) |
| 62 | + } |
| 63 | + |
| 64 | + environment.services.push({ service_id, service_name }) |
| 65 | + } |
| 66 | + }) |
| 67 | + |
| 68 | + return projects |
| 69 | +} |
| 70 | + |
| 71 | +export function ArgoCdAssociatedServicesListModal({ |
| 72 | + organizationId, |
| 73 | + clusterId, |
| 74 | + associatedServicesCount, |
| 75 | + onClose, |
| 76 | +}: ArgoCdAssociatedServicesListModalProps) { |
| 77 | + return ( |
| 78 | + <Section className="p-6"> |
| 79 | + <Heading className="mb-6 text-2xl text-neutral">Associated services ({associatedServicesCount})</Heading> |
| 80 | + <Suspense fallback={<ArgoCdAssociatedServicesListModalSkeleton />}> |
| 81 | + <ArgoCdAssociatedServicesListModalContent |
| 82 | + organizationId={organizationId} |
| 83 | + clusterId={clusterId} |
| 84 | + onClose={onClose} |
| 85 | + /> |
| 86 | + </Suspense> |
| 87 | + </Section> |
| 88 | + ) |
| 89 | +} |
| 90 | + |
| 91 | +function ArgoCdAssociatedServicesListModalSkeleton() { |
| 92 | + return ( |
| 93 | + <div className="flex h-40 items-start justify-center p-5"> |
| 94 | + <LoaderSpinner className="w-5" /> |
| 95 | + </div> |
| 96 | + ) |
| 97 | +} |
| 98 | + |
| 99 | +function ArgoCdAssociatedServicesListModalContent({ |
| 100 | + organizationId, |
| 101 | + clusterId, |
| 102 | + onClose, |
| 103 | +}: Omit<ArgoCdAssociatedServicesListModalProps, 'associatedServicesCount'>) { |
| 104 | + const { data: argoCdAssociatedServices = [] } = useArgoCdAssociatedServices({ |
| 105 | + clusterId, |
| 106 | + suspense: true, |
| 107 | + }) |
| 108 | + const [searchValue, setSearchValue] = useState<string | undefined>() |
| 109 | + |
| 110 | + const data = argoCdGroupByProjectEnvironmentsServices(argoCdAssociatedServices, searchValue) |
| 111 | + |
| 112 | + return ( |
| 113 | + <> |
| 114 | + <InputSearch |
| 115 | + className="mb-3" |
| 116 | + placeholder="Search by project, environment, service name" |
| 117 | + onChange={(value) => setSearchValue(value)} |
| 118 | + /> |
| 119 | + {data.length > 0 ? ( |
| 120 | + <TreeView.Root |
| 121 | + type="single" |
| 122 | + collapsible |
| 123 | + className="rounded border border-neutral bg-surface-neutral-subtle px-4 py-2" |
| 124 | + > |
| 125 | + {data.map((project) => ( |
| 126 | + <TreeView.Item key={project.project_id} value={project.project_name}> |
| 127 | + <TreeView.Trigger>{project.project_name}</TreeView.Trigger> |
| 128 | + <TreeView.Content> |
| 129 | + {project.environments.map((environment) => ( |
| 130 | + <TreeView.Root key={environment.environment_id} type="single" collapsible> |
| 131 | + <TreeView.Item value={environment.environment_name}> |
| 132 | + <TreeView.Trigger> |
| 133 | + <Link |
| 134 | + color="brand" |
| 135 | + onClick={() => onClose()} |
| 136 | + to="/organization/$organizationId/project/$projectId/environment/$environmentId" |
| 137 | + params={{ |
| 138 | + organizationId, |
| 139 | + environmentId: environment.environment_id, |
| 140 | + projectId: project.project_id, |
| 141 | + }} |
| 142 | + className="text-sm" |
| 143 | + > |
| 144 | + {environment.environment_name} |
| 145 | + </Link> |
| 146 | + </TreeView.Trigger> |
| 147 | + <TreeView.Content> |
| 148 | + <ul> |
| 149 | + {environment.services.map((service) => ( |
| 150 | + <li key={service.service_id} className="border-l border-neutral"> |
| 151 | + <Link |
| 152 | + color="brand" |
| 153 | + onClick={() => onClose()} |
| 154 | + to="/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId" |
| 155 | + params={{ |
| 156 | + organizationId, |
| 157 | + environmentId: environment.environment_id, |
| 158 | + serviceId: service.service_id, |
| 159 | + projectId: project.project_id, |
| 160 | + }} |
| 161 | + className="flex items-center py-1.5 pl-5 text-sm" |
| 162 | + > |
| 163 | + <Icon name={IconEnum.ARGOCD} width={20} className="mr-2" /> |
| 164 | + {service.service_name} |
| 165 | + </Link> |
| 166 | + </li> |
| 167 | + ))} |
| 168 | + </ul> |
| 169 | + </TreeView.Content> |
| 170 | + </TreeView.Item> |
| 171 | + </TreeView.Root> |
| 172 | + ))} |
| 173 | + </TreeView.Content> |
| 174 | + </TreeView.Item> |
| 175 | + ))} |
| 176 | + </TreeView.Root> |
| 177 | + ) : ( |
| 178 | + <div className="px-5 py-4 text-center"> |
| 179 | + <Icon iconName="wave-pulse" className="text-neutral-subtle" /> |
| 180 | + <p className="mt-1 text-xs font-medium text-neutral-subtle">No value found</p> |
| 181 | + </div> |
| 182 | + )} |
| 183 | + </> |
| 184 | + ) |
| 185 | +} |
| 186 | + |
| 187 | +export default ArgoCdAssociatedServicesListModal |
0 commit comments