Merge pull request #15850 from supabase/chore/vercel-manual-sync-envs

chore: vercel integration manual env sync
This commit is contained in:
Alaister Young authored and GitHub committed 2023-07-18 13:59:11 +08:00
commit 31e9ca5e95
8 files changed
+410 -56

No files matched your search

@@ -1,9 +1,11 @@
import { useState } from 'react'
import { toast } from 'react-hot-toast'
import { ENV_VAR_RAW_KEYS } from 'components/interfaces/Integrations/Integrations-Vercel.constants'
import { Markdown } from 'components/interfaces/Markdown'
import { vercelIcon } from 'components/to-be-cleaned/ListIcons'
import { useIntegrationConnectionsCreateMutation } from 'data/integrations/integration-connections-create-mutation'
import { useIntegrationsVercelConnectionSyncEnvsMutation } from 'data/integrations/integrations-vercel-connection-sync-envs-mutation'
import { VercelProjectsResponse } from 'data/integrations/integrations-vercel-projects-query'
import { IntegrationProjectConnection } from 'data/integrations/integrations.types'
import { useSelectedOrganization } from 'hooks'
@@ -23,6 +25,7 @@ export interface ProjectLinkerProps {
onCreateConnections?: () => void
installedConnections: IntegrationProjectConnection[] | undefined
setLoading?: (x: boolean) => void
showSkip?: boolean
}
const UNDEFINED_SELECT_VALUE = 'undefined'
@@ -34,18 +37,26 @@ const ProjectLinker = ({
onCreateConnections: _onCreateConnections,
installedConnections = [],
setLoading,
showSkip = false,
}: ProjectLinkerProps) => {
const selectedOrganization = useSelectedOrganization()
const [supabaseProjectRef, setSupabaseProjectRef] = useState(UNDEFINED_SELECT_VALUE)
const [vercelProjectId, setVercelProjectId] = useState(UNDEFINED_SELECT_VALUE)
const { mutateAsync: syncEnvs } = useIntegrationsVercelConnectionSyncEnvsMutation()
const { mutate: createConnections, isLoading } = useIntegrationConnectionsCreateMutation({
onSuccess() {
async onSuccess({ id }) {
try {
await syncEnvs({ connectionId: id })
} catch (error: any) {
toast.error('Failed to sync environment variables: ', error.message)
}
if (setLoading) setLoading(false)
_onCreateConnections?.()
},
onSettled() {
onError() {
if (setLoading) setLoading(false)
},
})
@@ -198,7 +209,18 @@ const ProjectLinker = ({
</Panel>
</div>
</div>
<div className="flex w-full justify-end">
<div className="flex w-full justify-end gap-2">
{showSkip && (
<Button
size="medium"
type="default"
onClick={() => {
_onCreateConnections?.()
}}
>
Skip
</Button>
)}
<Button
size="medium"
className="self-end"
+292 -19
View File
@@ -688,9 +688,13 @@ export interface paths {
*/
post: operations["VercelIntegrationController_createVercelIntegration"];
};
"/platform/integrations/vercel/{organization_integration_id}": {
/** Removes Vercel organization integration with the given id */
delete: operations["VercelIntegrationController_removeVercelIntegration"];
};
"/platform/integrations/vercel/projects/{organization_integration_id}": {
/** Gets vercel projects for the given organization */
get: operations["VercelProjectController_getProjects"];
/** Gets vercel projects with the given organization integration id */
get: operations["VercelProjectController_getVercelProjects"];
};
"/platform/integrations/vercel/connections/{organization_integration_id}": {
/** Gets installed vercel project connections for the given organization integration */
@@ -700,6 +704,10 @@ export interface paths {
/** Connects a Vercel project to a supabase project */
post: operations["VercelConnectionsController_createVercelConnection"];
};
"/platform/integrations/vercel/connections/{connection_id}/sync-envs": {
/** Syncs supabase project envs with given connection id */
post: operations["VercelConnectionsController_syncVercelConnectionEnvs"];
};
"/platform/integrations/vercel/connections/{connection_id}": {
/** Deletes vercel project connection */
delete: operations["VercelConnectionsController_deleteVercelConnection"];
@@ -776,6 +784,10 @@ export interface paths {
/** Migrates org to org-level billing. */
post: operations["BillingMigrationController_migrateToOrgLevelBilling"];
};
"/system/billing/migrate/org-level-billing-preview": {
/** Previews the migration of the organization to the new org level billing. */
post: operations["BillingMigrationController_preview"];
};
"/system/integrations/vercel/webhooks": {
/** Processes Vercel event */
post: operations["VercelWebhooksController_processEvent"];
@@ -1235,6 +1247,23 @@ export interface paths {
/** Deletes objects */
delete: operations["StorageObjectsController_deleteObjects"];
};
"/v1/branch/{branch_id}": {
/**
* Get database branch config
* @description Fetches configurations of the specified database branch
*/
get: operations["BranchController_getBranchDetails"];
/**
* Delete a database branch
* @description Deletes the specified database branch
*/
delete: operations["BranchController_deleteBranch"];
/**
* Update database branch config
* @description Updates the configuration of the specified database branch
*/
patch: operations["BranchController_updateBranch"];
};
"/v1/projects": {
/**
* List all projects
@@ -1247,6 +1276,23 @@ export interface paths {
"/v1/projects/{ref}/api-keys": {
get: operations["ApiKeysController_getProjectApiKeys"];
};
"/v1/projects/{ref}/branches": {
/**
* List all database branches
* @description Returns all database branches of the specified project.
*/
get: operations["BranchesController_getBranches"];
/**
* Create a database branch
* @description Creates a database branch from the specified project.
*/
post: operations["BranchesController_createBranch"];
/**
* Disables preview branching
* @description Disables preview branching for the specified project
*/
delete: operations["BranchesController_disableBranch"];
};
"/v1/projects/{ref}/custom-hostname": {
/** Gets project's custom hostname config */
get: operations["CustomHostnamesController_getCustomHostnameConfig"];
@@ -3026,6 +3072,15 @@ export interface components {
postgrest: string;
"supabase-postgres": string;
};
BranchResponse: {
id: string;
name: string;
project_ref: string;
is_default: boolean;
git_branch: string;
created_at: string;
updated_at: string;
};
ProjectDetailResponse: {
cloud_provider: string;
db_host: string;
@@ -3046,6 +3101,7 @@ export interface components {
volumeSizeGb?: number;
maxDatabasePreprovisionGb?: number;
lastDatabaseResizeAt?: string;
preview_branches: (components["schemas"]["BranchResponse"])[];
};
ProjectRefResponse: {
id: number;
@@ -3704,6 +3760,14 @@ export interface components {
framework?: string | null;
link?: components["schemas"]["VercelProjectLink"];
};
GetVercelProjectsResponse: {
projects: (components["schemas"]["IntegrationVercelProject"])[];
pagination: {
count?: number;
next?: number | null;
prev?: number | null;
};
};
GetVercelConnections: {
id: string;
inserted_at: string;
@@ -3716,13 +3780,18 @@ export interface components {
IntegrationConnection: {
foreign_project_id: string;
supabase_project_ref: string;
integration_id: string;
metadata: Record<string, never>;
};
CreateVercelConnectionsBody: {
organization_integration_id: string;
connection: components["schemas"]["IntegrationConnection"];
};
CreateVercelConnectionResponse: {
id: string;
};
DeleteVercelConnectionResponse: {
id: string;
};
FunctionResponse: {
id: string;
slug: string;
@@ -3798,6 +3867,21 @@ export interface components {
GetMetricsResponse: {
metrics: (components["schemas"]["ProjectMetric"])[];
};
BranchDetailResponse: {
db_port: number;
ref: string;
postgres_version: string;
/** @enum {string} */
status: "ACTIVE_HEALTHY" | "ACTIVE_UNHEALTHY" | "COMING_UP" | "GOING_DOWN" | "INACTIVE" | "INIT_FAILED" | "REMOVED" | "RESTORING" | "UNKNOWN" | "UPGRADING" | "PAUSING";
db_host: string;
db_user?: string;
db_pass?: string;
jwt_secret?: string;
};
UpdateBranchBody: {
branch_name?: string;
git_branch?: string;
};
DatabaseResponse: {
/** @description Database host */
host: string;
@@ -3808,6 +3892,11 @@ export interface components {
name: string;
api_key: string;
};
CreateBranchBody: {
branch_name: string;
git_branch?: string;
region?: string;
};
UpdateCustomHostnameResponse: {
/** @enum {string} */
status: "1_not_started" | "2_initiated" | "3_challenge_verified" | "4_origin_setup_completed" | "5_services_reconfigured";
@@ -4010,6 +4099,16 @@ export interface components {
redirect_uris: (string)[];
icon?: string;
};
TokenDTO: {
/** @enum {string} */
grant_type: "authorization_code" | "refresh_token";
client_id: string;
client_secret: string;
code?: string;
code_verifier?: string;
redirect_uri?: string;
refresh_token?: string;
};
AuthorizationsApproveBody: {
organization_id: string;
};
@@ -8072,20 +8171,38 @@ export interface operations {
500: never;
};
};
/** Gets vercel projects for the given organization */
VercelProjectController_getProjects: {
/** Removes Vercel organization integration with the given id */
VercelIntegrationController_removeVercelIntegration: {
parameters: {
path: {
organization_integration_id: string;
};
};
responses: {
200: never;
/** @description Failed to remove Vercel organization integration with the given id */
500: never;
};
};
/** Gets vercel projects with the given organization integration id */
VercelProjectController_getVercelProjects: {
parameters: {
query: {
search?: string;
from?: string;
limit: string;
};
path: {
organization_integration_id: string;
};
};
responses: {
200: {
content: {
"application/json": (components["schemas"]["IntegrationVercelProject"])[];
"application/json": components["schemas"]["GetVercelProjectsResponse"];
};
};
/** @description Failed to get vercel projects for the given organization */
/** @description Failed to get vercel projects with the given organization integration id */
500: never;
};
};
@@ -8114,11 +8231,28 @@ export interface operations {
};
};
responses: {
201: never;
201: {
content: {
"application/json": components["schemas"]["CreateVercelConnectionResponse"];
};
};
/** @description Failed to create project connections */
500: never;
};
};
/** Syncs supabase project envs with given connection id */
VercelConnectionsController_syncVercelConnectionEnvs: {
parameters: {
path: {
connection_id: string;
};
};
responses: {
201: never;
/** @description Failed to sync supabase project envs with given connection id */
500: never;
};
};
/** Deletes vercel project connection */
VercelConnectionsController_deleteVercelConnection: {
parameters: {
@@ -8127,7 +8261,11 @@ export interface operations {
};
};
responses: {
200: never;
200: {
content: {
"application/json": components["schemas"]["DeleteVercelConnectionResponse"];
};
};
/** @description Failed to delete vercel integration project connection */
500: never;
};
@@ -8462,6 +8600,19 @@ export interface operations {
500: never;
};
};
/** Previews the migration of the organization to the new org level billing. */
BillingMigrationController_preview: {
requestBody: {
content: {
"application/json": components["schemas"]["MigrateToOrgLevelBillingBody"];
};
};
responses: {
201: never;
/** @description Failed to preview org billing organization */
500: never;
};
};
/** Processes Vercel event */
VercelWebhooksController_processEvent: {
parameters: {
@@ -8517,6 +8668,70 @@ export interface operations {
};
};
};
/**
* Get database branch config
* @description Fetches configurations of the specified database branch
*/
BranchController_getBranchDetails: {
parameters: {
path: {
/** @description Branch ID */
branch_id: string;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["BranchDetailResponse"];
};
};
/** @description Failed to update database branch */
500: never;
};
};
/**
* Delete a database branch
* @description Deletes the specified database branch
*/
BranchController_deleteBranch: {
parameters: {
path: {
/** @description Branch ID */
branch_id: string;
};
};
responses: {
200: never;
/** @description Failed to delete database branch */
500: never;
};
};
/**
* Update database branch config
* @description Updates the configuration of the specified database branch
*/
BranchController_updateBranch: {
parameters: {
path: {
/** @description Branch ID */
branch_id: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdateBranchBody"];
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["BranchResponse"];
};
};
/** @description Failed to update database branch */
500: never;
};
};
ApiKeysController_getProjectApiKeys: {
parameters: {
path: {
@@ -8533,6 +8748,70 @@ export interface operations {
403: never;
};
};
/**
* List all database branches
* @description Returns all database branches of the specified project.
*/
BranchesController_getBranches: {
parameters: {
path: {
/** @description Project ref */
ref: string;
};
};
responses: {
200: {
content: {
"application/json": (components["schemas"]["BranchResponse"])[];
};
};
/** @description Failed to retrieve database branches */
500: never;
};
};
/**
* Create a database branch
* @description Creates a database branch from the specified project.
*/
BranchesController_createBranch: {
parameters: {
path: {
/** @description Project ref */
ref: string;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["CreateBranchBody"];
};
};
responses: {
201: {
content: {
"application/json": components["schemas"]["BranchResponse"];
};
};
/** @description Failed to create database branch */
500: never;
};
};
/**
* Disables preview branching
* @description Disables preview branching for the specified project
*/
BranchesController_disableBranch: {
parameters: {
path: {
/** @description Project ref */
ref: string;
};
};
responses: {
200: never;
/** @description Failed to disable preview branching */
500: never;
};
};
/** Gets project's custom hostname config */
CustomHostnamesController_getCustomHostnameConfig: {
parameters: {
@@ -9467,7 +9746,7 @@ export interface operations {
state?: string;
response_mode?: string;
code_challenge?: string;
code_challenge_method?: "plain" | "sha256";
code_challenge_method?: "plain" | "sha256" | "S256";
};
};
responses: {
@@ -9476,15 +9755,9 @@ export interface operations {
};
/** Exchange auth code for user's access and refresh token */
OAuthController_token: {
parameters: {
query: {
grant_type: "authorization_code" | "refresh_token";
client_id: string;
client_secret: string;
code?: string;
code_verifier?: string;
redirect_uri?: string;
refresh_token?: string;
requestBody: {
content: {
"application/x-www-form-urlencoded": components["schemas"]["TokenDTO"];
};
};
responses: {
@@ -1,6 +1,6 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { post } from 'data/fetchers'
import { integrationKeys } from './keys'
export type IntegrationConnectionsCreateVariables = {
@@ -17,15 +17,21 @@ export async function createIntegrationConnections({
organizationIntegrationId,
connection,
}: IntegrationConnectionsCreateVariables) {
const response = await post(`${API_URL}/integrations/vercel/connections`, {
organization_integration_id: organizationIntegrationId,
connection,
const { data, error } = await post('/platform/integrations/vercel/connections', {
body: {
organization_integration_id: organizationIntegrationId,
connection: {
foreign_project_id: connection.foreign_project_id,
supabase_project_ref: connection.supabase_project_ref,
metadata: connection.metadata,
},
},
})
if (response.error) {
throw response.error
if (error) {
throw error
}
return response
return data
}
type IntegrationConnectionsCreateData = Awaited<ReturnType<typeof createIntegrationConnections>>
@@ -0,0 +1,53 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { post } from 'data/fetchers'
export type IntegrationsVercelConnectionSyncEnvsVariables = {
connectionId: string
}
export async function syncEnvsIntegrationsVercelConnection({
connectionId,
}: IntegrationsVercelConnectionSyncEnvsVariables) {
const { data, error } = await post(
'/platform/integrations/vercel/connections/{connection_id}/sync-envs',
{
params: {
path: { connection_id: connectionId },
},
}
)
if (error) {
throw error
}
return data
}
type IntegrationsVercelConnectionSyncEnvsData = Awaited<
ReturnType<typeof syncEnvsIntegrationsVercelConnection>
>
export const useIntegrationsVercelConnectionSyncEnvsMutation = ({
onSuccess,
...options
}: Omit<
UseMutationOptions<
IntegrationsVercelConnectionSyncEnvsData,
unknown,
IntegrationsVercelConnectionSyncEnvsVariables
>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<
IntegrationsVercelConnectionSyncEnvsData,
unknown,
IntegrationsVercelConnectionSyncEnvsVariables
>((vars) => syncEnvsIntegrationsVercelConnection(vars), {
async onSuccess(data, variables, context) {
await onSuccess?.(data, variables, context)
},
...options,
})
}
@@ -1,26 +1,13 @@
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { get } from 'data/fetchers'
import { useCallback } from 'react'
import { integrationKeys } from './keys'
import { VercelFramework } from './integrations.types'
import { integrationKeys } from './keys'
export type VercelProjectsVariables = {
organization_integration_id: string | undefined
}
export type VercelProjectsResponse = {
id: string
name: string
framework: VercelFramework
metadata: {
id: string
name: string
framework: string
link: string
}
}
export async function getVercelProjects(
{ organization_integration_id }: VercelProjectsVariables,
signal?: AbortSignal
@@ -29,20 +16,30 @@ export async function getVercelProjects(
throw new Error('organization_integration_id is required')
}
const response = await get(
`${API_URL}/integrations/vercel/projects/${organization_integration_id}`,
const { data, error } = await get(
'/platform/integrations/vercel/projects/{organization_integration_id}',
{
params: {
path: { organization_integration_id },
query: {
// [Alaister]: setting a large limit here to avoid pagination
// until we have merged the new shadcn listbox which will support it
limit: '1000',
},
},
signal,
}
)
if (response.error) {
throw response.error
if (error) {
throw error
}
return response as VercelProjectsResponse[]
return data.projects
}
export type VercelProjectsData = Awaited<ReturnType<typeof getVercelProjects>>
export type VercelProjectsResponse = VercelProjectsData[0]
export type VercelProjectsError = unknown
export const useVercelProjectsQuery = <TData = VercelProjectsData>(
@@ -47,7 +47,6 @@ export const useVercelIntegrationCreateMutation = ({
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
console.log('mutating')
return useMutation<VercelIntegrationCreateData, unknown, VercelIntegrationCreateVariables>(
(vars) => createVercelIntegration(vars),
{
@@ -10,6 +10,7 @@ import PasswordStrengthBar from 'components/ui/PasswordStrengthBar'
import { useProjectApiQuery } from 'data/config/project-api-query'
import { useIntegrationConnectionsCreateMutation } from 'data/integrations/integration-connections-create-mutation'
import { useIntegrationsQuery } from 'data/integrations/integrations-query'
import { useIntegrationsVercelConnectionSyncEnvsMutation } from 'data/integrations/integrations-vercel-connection-sync-envs-mutation'
import { useVercelProjectsQuery } from 'data/integrations/integrations-vercel-projects-query'
import { Integration } from 'data/integrations/integrations.types'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
@@ -93,7 +94,8 @@ const CreateProject = ({
} = useParams()
const { mutateAsync: createConnections, isLoading: isLoadingCreateConnections } =
useIntegrationConnectionsCreateMutation({})
useIntegrationConnectionsCreateMutation()
const { mutateAsync: syncEnvs } = useIntegrationsVercelConnectionSyncEnvsMutation()
const { data: organizationData, isLoading: isLoadingOrganizationsQuery } = useOrganizationsQuery()
@@ -256,9 +258,8 @@ const CreateProject = ({
const projectDetails = vercelProjects?.find((x) => x.id === foreignProjectId)
// Wrap the createConnections function call in a try-catch block
try {
await createConnections({
const { id: connectionId } = await createConnections({
organizationIntegrationId: organizationIntegration?.id,
connection: {
foreign_project_id: foreignProjectId,
@@ -274,6 +275,8 @@ const CreateProject = ({
},
orgSlug: selectedOrganization?.slug,
})
await syncEnvs({ connectionId })
} catch (error) {
console.error('An error occurred during createConnections:', error)
return
@@ -70,7 +70,7 @@ const VercelIntegration: NextPageWithLayout = () => {
<Markdown
className="text-scale-900"
content={`
This Supabase integration manages your envioemnt variables automatically to provide the latest keys in the unlikely event that you will need to refresh your JWT token.
This Supabase integration manages your environment variables automatically to provide the latest keys in the unlikely event that you will need to refresh your JWT token.
`}
/>
</header>
@@ -85,6 +85,7 @@ This Supabase integration manages your envioemnt variables automatically to prov
}}
installedConnections={integration?.connections}
setLoading={setLoading}
showSkip={true}
/>
</ScaffoldContainer>
</>