CreateReportModal.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { useRouter } from 'next/router'
  3. import { useMemo } from 'react'
  4. import { SubmitHandler, useForm } from 'react-hook-form'
  5. import { toast } from 'sonner'
  6. import { Button, Form, FormControl, FormField, Input, Modal, Textarea } from 'ui'
  7. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  8. import * as z from 'zod'
  9. import { useContentUpsertMutation } from '@/data/content/content-upsert-mutation'
  10. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  11. import { uuidv4 } from '@/lib/helpers'
  12. import { useProfile } from '@/lib/profile'
  13. export interface CreateReportModal {
  14. visible: boolean
  15. onCancel: () => void
  16. afterSubmit: () => void
  17. }
  18. const formSchema = z.object({
  19. name: z.string().min(1, 'Required'),
  20. description: z.string().optional(),
  21. })
  22. type CustomReport = z.infer<typeof formSchema>
  23. export const CreateReportModal = ({ visible, onCancel, afterSubmit }: CreateReportModal) => {
  24. const router = useRouter()
  25. const { profile } = useProfile()
  26. const { data: project } = useSelectedProjectQuery()
  27. const ref = project?.ref ?? 'default'
  28. // Preserve date range query parameters when navigating to new report
  29. const preservedQueryParams = useMemo(() => {
  30. const { its, ite, isHelper, helperText } = router.query
  31. const params = new URLSearchParams()
  32. if (its && typeof its === 'string') params.set('its', its)
  33. if (ite && typeof ite === 'string') params.set('ite', ite)
  34. if (isHelper && typeof isHelper === 'string') params.set('isHelper', isHelper)
  35. if (helperText && typeof helperText === 'string') params.set('helperText', helperText)
  36. const queryString = params.toString()
  37. return queryString ? `?${queryString}` : ''
  38. }, [router.query])
  39. const { mutate: upsertContent, isPending: isCreating } = useContentUpsertMutation({
  40. onSuccess: (_, vars) => {
  41. toast.success('Successfully created new report')
  42. const newReportId = vars.payload.id
  43. router.push(`/project/${ref}/observability/${newReportId}${preservedQueryParams}`)
  44. afterSubmit()
  45. },
  46. onError: (error) => {
  47. toast.error(`Failed to create report: ${error.message}`)
  48. },
  49. })
  50. const createCustomReport: SubmitHandler<CustomReport> = async ({ name, description }) => {
  51. if (!ref) return console.error('Project ref is required')
  52. if (!profile) return console.error('Profile is required')
  53. upsertContent({
  54. projectRef: ref,
  55. payload: {
  56. id: uuidv4(),
  57. type: 'report',
  58. name,
  59. description: description || '',
  60. visibility: 'project',
  61. owner_id: profile?.id,
  62. content: {
  63. schema_version: 1,
  64. period_start: {
  65. time_period: '7d',
  66. date: '',
  67. },
  68. period_end: {
  69. time_period: 'today',
  70. date: '',
  71. },
  72. interval: '1d',
  73. layout: [],
  74. },
  75. },
  76. })
  77. }
  78. const handleCancel = () => {
  79. onCancel()
  80. form.reset()
  81. }
  82. const form = useForm<CustomReport>({
  83. resolver: zodResolver(formSchema as any),
  84. defaultValues: { name: '', description: '' },
  85. })
  86. const { isDirty } = form.formState
  87. return (
  88. <Modal
  89. visible={visible}
  90. onCancel={handleCancel}
  91. hideFooter
  92. header="Create a custom report"
  93. size="small"
  94. >
  95. <Form {...form}>
  96. <form onSubmit={form.handleSubmit(createCustomReport)} noValidate>
  97. <Modal.Content>
  98. <FormField
  99. control={form.control}
  100. name="name"
  101. render={({ field }) => (
  102. <FormItemLayout name="name" layout="vertical" label="Name">
  103. <FormControl>
  104. <Input {...field} id="name" />
  105. </FormControl>
  106. </FormItemLayout>
  107. )}
  108. />
  109. </Modal.Content>
  110. <Modal.Content>
  111. <FormField
  112. control={form.control}
  113. name="description"
  114. render={({ field }) => (
  115. <FormItemLayout name="description" layout="vertical" label="Description">
  116. <FormControl>
  117. <Textarea
  118. {...field}
  119. id="description"
  120. rows={4}
  121. placeholder="Describe your custom report"
  122. className="resize-none"
  123. />
  124. </FormControl>
  125. </FormItemLayout>
  126. )}
  127. />
  128. </Modal.Content>
  129. <Modal.Separator />
  130. <Modal.Content className="flex items-center justify-end gap-2">
  131. <Button htmlType="reset" type="default" onClick={handleCancel} disabled={isCreating}>
  132. Cancel
  133. </Button>
  134. <Button htmlType="submit" loading={isCreating} disabled={isCreating || !isDirty}>
  135. Create report
  136. </Button>
  137. </Modal.Content>
  138. </form>
  139. </Form>
  140. </Modal>
  141. )
  142. }