mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 12:55:06 +03:00
## Context Part of marketplace integrations, this moves the last integration over to the new integrations UI Just a heads up though that the code changes imo are quite messy - am trying to figure out how to shift a lot of the integration logic (e.g installing, installation progress tracking, etc) into a code configuration within `Integrations.constants.ts` so pardon the mid-transition state. I reckon we'll be able to clean up things once requirements are bit more clearer. (Refer to "Moving forward" section below for details) ## Changes involved - Much of the details on the Stripe Sync Engine page will now live in the Installation panel <img width="1143" height="512" alt="image" src="https://github.com/user-attachments/assets/cb23e49d-cc4e-4ad6-8a47-0bc3fe81ede7" /> <img width="656" height="955" alt="image" src="https://github.com/user-attachments/assets/ff0e33c5-52ab-480f-b941-ebf3fd0708c5" /> - Code wise, `useStripeSyncStatus` will retrieve `ref` and `connectionString` itself, don't need to pass in as parameters ## To test - [ ] Verify that you can install + uninstall Stripe Sync Engine with the flag off - [ ] Verify that you can install + uninstall Stripe Sync Engine with the flag on ## Moving forward Couple of notes + open questions at the top of my head - We'll need to do away with integration-specific overview UI (e.g Queues, Data API, Webhooks, Wrappers, Stripe Sync Engine) - Everything needs to be defined in code, so `IntegrationDefinition` within `Integrations.constants.ts` is also a bit fluid at the moment as we figure out what properties we need / don't need - We'll need to figure out a way to do the following from a code config POV, keeping in mind that integrations will be fetched remotely from a DB - How to trigger the installation of the integration (e.g from a set of commands? SQL?) - How to track the progress of the installation (e.g We can do long polling but on what data?) - How to uninstall the integration (if applicable, e.g Stripe Sync Engine supports this) - How all this this can work for self-hosted/CLI <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * New modular integration install experience: interactive install sheet with form inputs, installation overview (Extensions & SQL), advanced per-extension schema settings, and dedicated installation settings. * New Stripe Sync Engine overview and action controls for install/upgrade/uninstall flows. * **Bug Fixes & Improvements** * Improved installation status handling, long-polling/status checks, progress/error reporting and telemetry; more robust install/uninstall flows and error recovery. * **Tests** * Updated install-sheet tests to better simulate form submission. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
import { fireEvent, screen, waitFor } from '@testing-library/dom'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { mockAnimationsApi } from 'jsdom-testing-mocks'
|
|
import { customRender } from 'tests/lib/custom-render'
|
|
import { routerMock } from 'tests/lib/route-mock'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { IntegrationDefinition } from '../../Landing/Integrations.constants'
|
|
import { InstallIntegrationSheet } from './InstallIntegrationSheet/InstallIntegrationSheet'
|
|
|
|
mockAnimationsApi()
|
|
|
|
vi.mock('@/hooks/misc/useSelectedProject', () => ({
|
|
useSelectedProjectQuery: () => ({
|
|
data: { ref: 'default', connectionString: 'postgres://localhost' },
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/hooks/useProtectedSchemas', () => ({
|
|
useProtectedSchemas: () => ({ data: [] }),
|
|
}))
|
|
|
|
const mockExtensions = vi.fn()
|
|
vi.mock('@/data/database-extensions/database-extensions-query', () => ({
|
|
useDatabaseExtensionsQuery: () => ({ data: mockExtensions(), isSuccess: true }),
|
|
}))
|
|
|
|
vi.mock('@/data/database/schemas-query', () => ({
|
|
useSchemasQuery: () => ({ data: [{ id: 1, name: 'public' }] }),
|
|
}))
|
|
|
|
const mockExecuteSql = vi.fn()
|
|
vi.mock('@/data/sql/execute-sql-mutation', () => ({
|
|
useExecuteSqlMutation: () => ({ mutateAsync: mockExecuteSql }),
|
|
}))
|
|
|
|
vi.mock('@/data/database-extensions/database-extension-enable-mutation', () => ({
|
|
useDatabaseExtensionEnableMutation: () => ({ mutateAsync: vi.fn() }),
|
|
}))
|
|
|
|
vi.mock('@/components/interfaces/Database/Extensions/Extensions.constants', () => ({
|
|
extensionsWithRecommendedSchemas: {},
|
|
}))
|
|
|
|
vi.mock('./IntegrationOverviewTabV2.utils', () => ({
|
|
getEnableExtensionsSQL: () => 'CREATE EXTENSION IF NOT EXISTS pg_net;',
|
|
getExtensionDefaultSchema: () => 'extensions',
|
|
}))
|
|
|
|
const createIntegration = (overrides: Partial<IntegrationDefinition> = {}): IntegrationDefinition =>
|
|
({
|
|
id: 'test-integration',
|
|
type: 'postgres_extension',
|
|
name: 'Test Integration',
|
|
requiredExtensions: ['pg_net'],
|
|
icon: () => null,
|
|
description: 'Test description',
|
|
docsUrl: null,
|
|
author: { name: 'Test', websiteUrl: 'https://test.com' },
|
|
navigate: () => null,
|
|
...overrides,
|
|
}) as unknown as any
|
|
|
|
const getInstallButton = () => {
|
|
const buttons = screen.getAllByRole('button', { name: 'Install integration' })
|
|
return buttons[buttons.length - 1]
|
|
}
|
|
|
|
describe('InstallIntegrationSheet', () => {
|
|
beforeEach(() => {
|
|
routerMock.setCurrentUrl('/project/default/integrations/test-integration/overview')
|
|
mockExecuteSql.mockReset()
|
|
})
|
|
|
|
it('install button is disabled when extensions are missing even if installationCommand exists', async () => {
|
|
mockExtensions.mockReturnValue([])
|
|
|
|
customRender(
|
|
<InstallIntegrationSheet
|
|
integration={createIntegration({
|
|
installationCommand: vi.fn().mockResolvedValue(undefined),
|
|
})}
|
|
/>
|
|
)
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Install integration' }))
|
|
expect(getInstallButton()).toBeDisabled()
|
|
})
|
|
|
|
it('install button is disabled when extensions are missing and no installationCommand', async () => {
|
|
mockExtensions.mockReturnValue([])
|
|
|
|
customRender(
|
|
<InstallIntegrationSheet
|
|
integration={createIntegration({ installationCommand: undefined })}
|
|
/>
|
|
)
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Install integration' }))
|
|
expect(getInstallButton()).toBeDisabled()
|
|
})
|
|
|
|
it('uses installationCommand instead of SQL when provided', async () => {
|
|
mockExtensions.mockReturnValue([
|
|
{ name: 'pg_net', installed_version: null, default_version: '0.6.0' },
|
|
])
|
|
|
|
const mockCommand = vi.fn().mockResolvedValue(undefined)
|
|
customRender(
|
|
<InstallIntegrationSheet
|
|
integration={createIntegration({ installationCommand: mockCommand })}
|
|
/>
|
|
)
|
|
|
|
await userEvent.click(screen.getByRole('button', { name: 'Install integration' }))
|
|
|
|
// SheetContent renders via a Radix portal, placing the submit button outside
|
|
// the <form> in the DOM. jsdom doesn't support the HTML `form` attribute on
|
|
// buttons, so we submit the form directly instead of clicking the button.
|
|
const form = document.getElementById('installation-settings')!
|
|
fireEvent.submit(form)
|
|
|
|
await waitFor(() => {
|
|
expect(mockCommand).toHaveBeenCalledWith(expect.objectContaining({ ref: 'default' }))
|
|
})
|
|
expect(mockExecuteSql).not.toHaveBeenCalled()
|
|
})
|
|
})
|