useIntegrationDetail.ts122 lines · main
1import { useParams } from 'common'
2import { useRouter } from 'next/router'
3import { useEffect, useMemo } from 'react'
4
5import { useAvailableIntegrations } from './useAvailableIntegrations'
6import { useInstalledIntegrations } from './useInstalledIntegrations'
7import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query'
8import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
9import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
10
11export type IntegrationTab = {
12 label: string
13 href: string
14 active: boolean
15}
16
17export const useIntegrationDetail = () => {
18 const router = useRouter()
19 const { ref, id, pageId, childId } = useParams()
20
21 const { integrationsWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
22
23 const { data: project } = useSelectedProjectQuery()
24 const { data: extensions } = useDatabaseExtensionsQuery({
25 projectRef: project?.ref,
26 connectionString: project?.connectionString,
27 })
28
29 const { data: allIntegrations, isPending: isAvailableLoading } = useAvailableIntegrations()
30 const { installedIntegrations, isLoading: isInstalledLoading } = useInstalledIntegrations()
31
32 // wrapped in useMemo to avoid UI resets when installing additional extensions like pg_net
33 const integration = useMemo(() => allIntegrations.find((i) => i.id === id), [allIntegrations, id])
34 const installation = useMemo(
35 () => installedIntegrations.find((i) => i.id === id),
36 [installedIntegrations, id]
37 )
38
39 const installableExtensions = (extensions ?? []).filter((ext) =>
40 (integration?.requiredExtensions ?? []).includes(ext.name)
41 )
42 const extensionsInstalled = installableExtensions.every((x) => x.installed_version)
43
44 // installedIntegrations doesn't return wrappers unless there's a wrapper created
45 const isInstalled =
46 !!integration && (!!installation || (integration.type === 'wrapper' && extensionsInstalled))
47
48 const navItems = useMemo(() => {
49 if (!integration?.navigation) return []
50 return isInstalled
51 ? integration.navigation
52 : integration.navigation.filter((nav) => nav.route === 'overview')
53 }, [integration, isInstalled])
54
55 const activeRoute = pageId ?? 'overview'
56
57 const activeNav = useMemo(
58 () => navItems.find((nav) => nav.route === activeRoute),
59 [navItems, activeRoute]
60 )
61
62 const isKnownRoute = !!activeNav
63 const layout = (activeNav?.layout ?? 'full') as 'full' | 'constrained'
64
65 const tabs: IntegrationTab[] = useMemo(
66 () =>
67 navItems.map((nav) => ({
68 label: nav.label,
69 href: `/project/${ref}/integrations/${id}/${nav.route}`,
70 active: nav.route === activeRoute,
71 })),
72 [navItems, ref, id, activeRoute]
73 )
74
75 const Component = useMemo(
76 () => integration?.navigate({ id, pageId, childId }),
77 [integration, id, pageId, childId]
78 )
79
80 const isReady = !!router?.isReady
81 const isWrapperBlocked = !integrationsWrappers && !!id?.endsWith('_wrapper')
82
83 const pageTitle = integration?.name ?? 'Integration not found'
84 const pageSubTitle =
85 integration?.description ?? 'If you think this is an error, please contact support'
86
87 useEffect(() => {
88 if (
89 router?.isReady &&
90 !isAvailableLoading &&
91 !isInstalledLoading &&
92 !!integration &&
93 pageId &&
94 !isKnownRoute
95 ) {
96 router.replace(`/project/${ref}/integrations/${id}/overview`)
97 }
98 }, [router, pageId, ref, id, integration, isKnownRoute, isAvailableLoading, isInstalledLoading])
99
100 return {
101 ref,
102 id,
103 pageId,
104 childId,
105 activeRoute,
106 activeNav,
107 isKnownRoute,
108 layout,
109 tabs,
110 isReady,
111 isWrapperBlocked,
112 pageTitle,
113 pageSubTitle,
114 integrationsWrappers,
115 integration,
116 installation,
117 isInstalled,
118 isAvailableLoading,
119 isInstalledLoading,
120 Component,
121 }
122}