mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 10:25:06 +03:00
## Context Initial ground work for Marketplace Integrations (feature flagged under the flag `marketplaceIntegrations`), in which some integrations will be fetched over a network request (than hard-coded within the repo). Feature flag just controls whether to we need to fetch them. Similarly, categories under "Explore" will also be fetched, and the flag just decides whether that should be enabled. We're also planning to update the overview tab for integrations as well, in which integrations fetched over the network request will use the new UI (altho not functional just yet) ## To test Note that the LOC changes are seemingly large, but that's just because I've added in a types file for the Supabase project that we're using to store the integrations data - [x] Verify that behaviour is status quo with the flag off (Priority) - [x] Verify that behaviour is status quo on CLI / self-host env - [x] Verify that with the flag on you'll see one of the new integrations fetched over network (Grafana), including an additional category "Observability" (and clicking into it will show the new UI) - [x] Verify that all other existing integrations are status quo --------- Co-authored-by: Katerina Skroumpelou <sk.katherine@gmail.com>
156 lines
4.7 KiB
TypeScript
156 lines
4.7 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { IS_PLATFORM, useFeatureFlags, useFlag, useParams } from 'common'
|
|
import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations'
|
|
import { ProjectLayout } from 'components/layouts/ProjectLayout'
|
|
import AlertError from 'components/ui/AlertError'
|
|
import { ProductMenuItem } from 'components/ui/ProductMenu/ProductMenuItem'
|
|
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
|
import { withAuth } from 'hooks/misc/withAuth'
|
|
import { useRouter } from 'next/router'
|
|
import { PropsWithChildren } from 'react'
|
|
import { Menu, Separator } from 'ui'
|
|
import { GenericSkeletonLoader } from 'ui-patterns'
|
|
|
|
import { marketplaceCategoriesQueryOptions } from '@/data/marketplace/integration-categories-query'
|
|
|
|
/**
|
|
* Layout component for the Integrations section
|
|
* Provides sidebar navigation for integrations
|
|
*/
|
|
export const ProjectIntegrationsLayout = withAuth(({ children }: PropsWithChildren) => {
|
|
const router = useRouter()
|
|
const segments = router.asPath.split('/')
|
|
// construct the page url to be used to determine the active state for the sidebar
|
|
const page = `${segments[3]}${segments[4] ? `/${segments[4]}` : ''}`
|
|
|
|
return (
|
|
<ProjectLayout
|
|
title={'Integrations'}
|
|
product="Integrations"
|
|
isBlocking={false}
|
|
productMenu={
|
|
<>
|
|
<IntegrationCategoriesMenu page={page} />
|
|
<Separator />
|
|
<InstalledIntegrationsMenu page={page} />
|
|
</>
|
|
}
|
|
>
|
|
{children}
|
|
</ProjectLayout>
|
|
)
|
|
})
|
|
|
|
const IntegrationCategoriesMenu = ({ page }: { page: string }) => {
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
const { hasLoaded: flagsLoaded } = useFeatureFlags()
|
|
const isMarketplaceEnabled = useFlag('marketplaceIntegrations')
|
|
|
|
const urlParams = new URLSearchParams(router.asPath.split('?')[1] || '')
|
|
const categoryParam = urlParams.get('category')
|
|
const pageKey = categoryParam || page
|
|
|
|
const { integrationsWrappers: showWrappers } = useIsFeatureEnabled(['integrations:wrappers'])
|
|
const { data: categories = [], isPending: isPendingCategories } = useQuery(
|
|
marketplaceCategoriesQueryOptions({ enabled: isMarketplaceEnabled })
|
|
)
|
|
|
|
const isLoading = IS_PLATFORM
|
|
? !flagsLoaded || (isMarketplaceEnabled && isPendingCategories)
|
|
: false
|
|
|
|
const allCategories = [
|
|
{
|
|
name: 'All',
|
|
key: 'integrations',
|
|
url: `/project/${ref}/integrations`,
|
|
pages: ['integrations'],
|
|
items: [],
|
|
},
|
|
...(showWrappers
|
|
? [
|
|
{
|
|
name: 'Wrappers',
|
|
key: 'wrapper',
|
|
url: `/project/${ref}/integrations?category=wrapper`,
|
|
items: [],
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
name: 'Postgres Modules',
|
|
key: 'postgres_extension',
|
|
url: `/project/${ref}/integrations?category=postgres_extension`,
|
|
items: [],
|
|
},
|
|
...categories.map((category) => ({
|
|
name: category.title,
|
|
key: category.slug,
|
|
url: `/project/${ref}/integrations?category=${category.slug}`,
|
|
items: [],
|
|
})),
|
|
]
|
|
|
|
return (
|
|
<div className="px-4 py-6 md:px-6">
|
|
<Menu.Group title={<span className="uppercase font-mono">Explore</span>} />
|
|
{isLoading ? (
|
|
<GenericSkeletonLoader />
|
|
) : (
|
|
<div>
|
|
{allCategories.map((item) => (
|
|
<ProductMenuItem key={item.key} isActive={pageKey === item.key} item={item} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const InstalledIntegrationsMenu = ({ page }: { page: string }) => {
|
|
const { ref } = useParams()
|
|
|
|
const {
|
|
installedIntegrations: integrations,
|
|
error,
|
|
isLoading,
|
|
isSuccess,
|
|
isError,
|
|
} = useInstalledIntegrations()
|
|
|
|
const installedIntegrationItems = integrations.map((integration) => ({
|
|
name: integration.name,
|
|
label: integration.status,
|
|
key: `integrations/${integration.id}`,
|
|
url: `/project/${ref}/integrations/${integration.id}/overview`,
|
|
icon: (
|
|
<div className="relative w-6 h-6 bg-white border rounded flex items-center justify-center">
|
|
{integration.icon({ className: 'p-1' })}
|
|
</div>
|
|
),
|
|
items: [],
|
|
}))
|
|
|
|
return (
|
|
<div className="px-4 py-6 md:px-6">
|
|
<Menu.Group title={<span className="uppercase font-mono">Installed</span>} />
|
|
{isLoading && <GenericSkeletonLoader />}
|
|
{isError && (
|
|
<AlertError
|
|
showIcon={false}
|
|
error={error}
|
|
subject="Failed to retrieve installed integrations"
|
|
/>
|
|
)}
|
|
{isSuccess && (
|
|
<div>
|
|
{installedIntegrationItems.map((item) => (
|
|
<ProductMenuItem key={item.key} isActive={page === item.key} item={item} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|