Files
supabase/studio/components/interfaces/Database/Extensions/EnableExtensionModal.tsx
2023-01-27 14:56:54 +08:00

203 lines
6.3 KiB
TypeScript

import { FC, useEffect, useState } from 'react'
import { Button, Input, Form, Modal, Listbox, IconPlus, IconDatabase } from 'ui'
import type { PostgresExtension, PostgresSchema } from '@supabase/postgres-meta'
import { useStore } from 'hooks'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
interface Props {
visible: boolean
extension: PostgresExtension
onCancel: () => void
}
const EnableExtensionModal: FC<Props> = ({ visible, extension, onCancel }) => {
const { ui, meta } = useStore()
const [defaultSchema, setDefaultSchema] = useState()
const [fetchingSchemaInfo, setFetchingSchemaInfo] = useState(false)
const schemas = meta.schemas.list()
// [Joshen] Worth checking in with users - whether having this schema selection
// might be confusing, and if we should have a tooltip to explain that schemas
// are just concepts of namespace, you can use that extension no matter where it's
// installed in
useEffect(() => {
let cancel = false
if (visible) {
const checkExtensionSchema = async () => {
if (!cancel) {
setFetchingSchemaInfo(true)
setDefaultSchema(undefined)
}
const res = await meta.query(
`select * from pg_available_extension_versions where name = '${extension.name}'`
)
if (!res.error && !cancel) setDefaultSchema(res[0].schema)
setFetchingSchemaInfo(false)
}
checkExtensionSchema()
}
return () => {
cancel = true
}
}, [visible])
const validate = (values: any) => {
const errors: any = {}
if (values.schema === 'custom' && !values.name) errors.name = 'Required field'
return errors
}
const onSubmit = async (values: any, { setSubmitting }: any) => {
setSubmitting(true)
const schema =
defaultSchema !== undefined && defaultSchema !== null
? defaultSchema
: values.schema === 'custom'
? values.name
: values.schema
if (!schema.startsWith('pg_')) {
const { error: createSchemaError } = await meta.query(`create schema if not exists ${schema}`)
if (createSchemaError) {
return ui.setNotification({
error: createSchemaError,
category: 'error',
message: `Failed to create schema: ${createSchemaError.message}`,
})
}
}
const { error: createExtensionError } = await meta.extensions.create({
schema,
name: extension.name,
version: extension.default_version,
cascade: true,
})
if (createExtensionError) {
ui.setNotification({
error: createExtensionError,
category: 'error',
message: `Failed to toggle ${extension.name.toUpperCase()}: ${
createExtensionError.message
}`,
})
} else {
ui.setNotification({
category: 'success',
message: `${extension.name.toUpperCase()} is on.`,
})
}
setSubmitting(false)
onCancel()
}
return (
<Modal
closable
hideFooter
visible={visible}
onCancel={onCancel}
size="small"
header={
<div className="flex items-baseline gap-2">
<h5 className="text-sm text-scale-1200">Confirm to enable</h5>
<code className="text-xs">{extension.name}</code>
</div>
}
>
<Form
initialValues={{
name: extension.name, // Name of new schema, if creating new
schema: 'extensions',
}}
validate={validate}
onSubmit={onSubmit}
>
{({ isSubmitting, values }: any) => {
return (
<div className="space-y-4 py-4">
<Modal.Content>
{fetchingSchemaInfo ? (
<div className="space-y-2">
<ShimmeringLoader />
<div className="w-3/4">
<ShimmeringLoader />
</div>
</div>
) : defaultSchema ? (
<Input
disabled
id="schema"
name="schema"
value={defaultSchema}
label="Select a schema to enable the extension for"
descriptionText={`Extension must be installed in ${defaultSchema}.`}
/>
) : (
<Listbox
size="small"
name="schema"
label="Select a schema to enable the extension for"
>
<Listbox.Option
key="custom"
id="custom"
label={`Create a new schema "${extension.name}"`}
value="custom"
addOnBefore={() => <IconPlus size={16} strokeWidth={1.5} />}
>
Create a new schema "{extension.name}"
</Listbox.Option>
<Modal.Separator />
{/* @ts-ignore */}
{schemas.map((schema: PostgresSchema) => {
return (
<Listbox.Option
key={schema.id}
id={schema.name}
label={schema.name}
value={schema.name}
addOnBefore={() => <IconDatabase size={16} strokeWidth={1.5} />}
>
{schema.name}
</Listbox.Option>
)
})}
</Listbox>
)}
</Modal.Content>
{values.schema === 'custom' && (
<Modal.Content>
<Input id="name" name="name" label="Schema name" />
</Modal.Content>
)}
<Modal.Separator />
<Modal.Content>
<div className="flex items-center justify-end space-x-2">
<Button type="default" disabled={isSubmitting} onClick={() => onCancel()}>
Cancel
</Button>
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
Enable extension
</Button>
</div>
</Modal.Content>
</div>
)
}}
</Form>
</Modal>
)
}
export default EnableExtensionModal