| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- import { zodResolver } from '@hookform/resolvers/zod'
- import { useParams } from 'common'
- import { useRouter } from 'next/router'
- import { useEffect } from 'react'
- import { SubmitHandler, useForm } from 'react-hook-form'
- import { toast } from 'sonner'
- import {
- Button,
- Form,
- FormControl,
- FormField,
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
- Separator,
- Sheet,
- SheetContent,
- SheetFooter,
- SheetHeader,
- SheetSection,
- SheetTitle,
- Switch,
- TextArea,
- Tooltip,
- TooltipContent,
- TooltipTrigger,
- } from 'ui'
- import { Admonition } from 'ui-patterns'
- import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
- import * as z from 'zod'
- import { LintInfo } from '../Linter/Linter.constants'
- import { lintInfoMap } from '../Linter/Linter.utils'
- import { generateRuleDescription } from './AdvisorRules.utils'
- import { useLintRuleCreateMutation } from '@/data/lint/create-lint-rule-mutation'
- import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- interface CreateRuleSheetProps {
- lint?: LintInfo
- open: boolean
- onOpenChange: (value: boolean) => void
- }
- const FormSchema = z.object({
- lint_name: z.string().optional(),
- note: z.string().optional(),
- assigned_to: z.string().optional(),
- is_disabled: z.boolean(),
- })
- const defaultValues = {
- lint_name: undefined,
- note: undefined,
- assigned_to: 'all',
- is_disabled: true,
- }
- /**
- * [Joshen] JFYI while the API supports adding rules on a category, I'm intentionally leaving that functionality out for now
- * as the only use case for that would be to ignore _all_ lints in that category (which I'm not sure if that's what we want to advise users doing atm)
- *
- * (Spoken with Hieu) We'll eventually support granularity of Entity/Items as well, just not atm
- */
- export const CreateRuleSheet = ({ lint, open, onOpenChange }: CreateRuleSheetProps) => {
- const router = useRouter()
- const { ref: projectRef } = useParams()
- const routeCategory = router.pathname.split('/').pop()
- const { data: organization } = useSelectedOrganizationQuery()
- const { data: members = [] } = useOrganizationMembersQuery({ slug: organization?.slug })
- const { mutate: createRule, isPending: isCreating } = useLintRuleCreateMutation({
- onSuccess: (_, vars) => {
- const ruleLint = vars.exception.lint_name
- const ruleLintMeta = lintInfoMap.find((x) => x.name === ruleLint)
- toast.success(`Successfully created new rule for ${ruleLintMeta?.title}`)
- if (ruleLintMeta) {
- if (!!routeCategory && routeCategory !== ruleLintMeta.category) {
- router.push(
- `/project/${projectRef}/advisors/rules/${ruleLintMeta.category}?lint=${ruleLintMeta.name}`
- )
- } else {
- // setExpandedLint(ruleLintMeta?.name)
- }
- }
- onOpenChange(false)
- },
- })
- const formId = 'create-lint-rule-form'
- const form = useForm<z.infer<typeof FormSchema>>({
- mode: 'onBlur',
- reValidateMode: 'onChange',
- resolver: zodResolver(FormSchema as any),
- defaultValues,
- })
- const { lint_name, assigned_to, is_disabled } = form.watch()
- const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
- if (!projectRef) return console.error('Project ref is required')
- createRule({
- projectRef,
- exception: {
- ...values,
- lint_category: undefined,
- lint_name: values.lint_name,
- assigned_to: values.assigned_to === 'all' ? undefined : values.assigned_to,
- },
- })
- }
- useEffect(() => {
- if (open) form.reset({ ...defaultValues, lint_name: lint?.name })
- }, [open])
- return (
- <Sheet open={open} onOpenChange={onOpenChange}>
- <SheetContent className="flex flex-col gap-0">
- <SheetHeader className="shrink-0 flex items-center gap-4">
- <SheetTitle>Create a rule for "{lint?.title}"</SheetTitle>
- </SheetHeader>
- <SheetSection className="overflow-auto grow px-0">
- <Form {...form}>
- <form
- id={formId}
- className="flex flex-col gap-y-4"
- onSubmit={form.handleSubmit(onSubmit)}
- >
- <FormField
- name="is_disabled"
- control={form.control}
- render={({ field }) => (
- <FormItemLayout
- layout="flex-row-reverse"
- className="px-5"
- label={`Disable this lint for ${assigned_to === 'all' ? 'project' : 'the assigned member'}`}
- description="Toggles the visiblity of this lint in the Advisor reports"
- >
- <Tooltip>
- <TooltipTrigger type="button">
- <FormControl>
- <Switch
- checked={field.value}
- onCheckedChange={field.onChange}
- disabled={field.disabled || assigned_to === 'all'}
- />
- </FormControl>
- </TooltipTrigger>
- {assigned_to === 'all' && (
- <TooltipContent side="bottom" className="w-72">
- Assign this rule to a specific project member before toggling this option
- off. This will then configure the rule to{' '}
- <span className="text-brand">only be visible</span> to that member in the
- advisor reports.
- </TooltipContent>
- )}
- </Tooltip>
- </FormItemLayout>
- )}
- />
- <Separator />
- <FormField
- name="assigned_to"
- control={form.control}
- render={({ field }) => (
- <FormItemLayout label="Assign rule to" layout="vertical" className="px-5">
- <Select
- onValueChange={(val) => {
- field.onChange(val)
- if (val === 'all') form.setValue('is_disabled', true)
- }}
- defaultValue={field.value}
- >
- <SelectTrigger className="col-span-8">
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="all">All project members</SelectItem>
- {members.map((m) => (
- <SelectItem key={m.gotrue_id} value={m.gotrue_id}>
- {m.username || m.primary_email}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </FormItemLayout>
- )}
- />
- {!!lint_name && (
- <div className="px-5">
- <Admonition showIcon={false} type="default">
- {generateRuleDescription({
- name: lint_name,
- disabled: is_disabled,
- member: members.find((x) => x.gotrue_id === assigned_to),
- })}
- </Admonition>
- </div>
- )}
- <Separator />
- <FormField
- name="note"
- control={form.control}
- render={({ field }) => (
- <FormItemLayout
- layout="vertical"
- className="px-5"
- label="Description"
- labelOptional="Optional"
- >
- <FormControl>
- <TextArea
- {...field}
- rows={4}
- className="text-sm"
- placeholder="e.g Describe why this rule is being set"
- />
- </FormControl>
- </FormItemLayout>
- )}
- />
- </form>
- </Form>
- </SheetSection>
- <SheetFooter>
- <Button disabled={isCreating} type="default" onClick={() => onOpenChange(false)}>
- Cancel
- </Button>
- <Button form={formId} htmlType="submit" loading={isCreating}>
- Create rule
- </Button>
- </SheetFooter>
- </SheetContent>
- </Sheet>
- )
- }
|