Openstatus www.openstatus.dev
at 03f4e58f911c15cd7e1ecea95f5bb90c191a7f26 159 lines 5.0 kB view raw
1"use client"; 2 3import { Send } from "lucide-react"; 4import React from "react"; 5import type { UseFormReturn } from "react-hook-form"; 6 7import { deserialize } from "@openstatus/assertions"; 8import type { InsertMonitor } from "@openstatus/db/src/schema"; 9import { 10 type Region, 11 monitorRegions, 12} from "@openstatus/db/src/schema/constants"; 13import { regionDict } from "@openstatus/regions"; 14import { 15 Button, 16 Dialog, 17 DialogContent, 18 DialogHeader, 19 DialogTitle, 20 Select, 21 SelectContent, 22 SelectItem, 23 SelectTrigger, 24 SelectValue, 25 Tooltip, 26 TooltipContent, 27 TooltipProvider, 28 TooltipTrigger, 29} from "@openstatus/ui"; 30 31import { LoadingAnimation } from "@/components/loading-animation"; 32import { RegionInfo } from "@/components/ping-response-analysis/region-info"; 33import { ResponseDetailTabs } from "@/components/ping-response-analysis/response-detail-tabs"; 34import type { RegionChecker } from "@/components/ping-response-analysis/utils"; 35import { toast, toastAction } from "@/lib/toast"; 36import type { Limits } from "@openstatus/db/src/schema/plan/schema"; 37import { getLimit } from "@openstatus/db/src/schema/plan/utils"; 38 39interface Props { 40 form: UseFormReturn<InsertMonitor>; 41 limits: Limits; 42 pingEndpoint( 43 region?: Region, 44 ): Promise<{ data?: RegionChecker; error?: string }>; 45} 46 47export function RequestTestButton({ form, pingEndpoint, limits }: Props) { 48 const [check, setCheck] = React.useState< 49 { data: RegionChecker; error?: string } | undefined 50 >(); 51 const [value, setValue] = React.useState<Region>(monitorRegions[0]); 52 const [isPending, startTransition] = React.useTransition(); 53 54 const onClick = () => { 55 if (isPending) return; 56 57 const { url } = form.getValues(); 58 59 if (!url) { 60 toastAction("test-warning-empty-url"); 61 return; 62 } 63 64 startTransition(async () => { 65 try { 66 const { data, error } = await pingEndpoint(value); 67 if (data) setCheck({ data, error }); 68 const isOk = !error; 69 if (isOk) { 70 toastAction("test-success"); 71 } else { 72 toast.error(error); 73 } 74 } catch { 75 toastAction("error"); 76 } 77 }); 78 }; 79 80 const { flag } = regionDict[value as keyof typeof regionDict]; 81 82 const { statusAssertions, headerAssertions } = form.getValues(); 83 84 const regions = getLimit(limits, "regions"); 85 86 return ( 87 <Dialog open={!!check} onOpenChange={() => setCheck(undefined)}> 88 <div className="group flex h-10 items-center rounded-md bg-transparent text-sm ring-offset-background focus-within:outline-hidden focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"> 89 <Select 90 value={value} 91 onValueChange={(value: Region) => setValue(value)} 92 > 93 <SelectTrigger 94 className="flex-1 rounded-r-none border-accent focus:ring-0" 95 aria-label={value} 96 > 97 <SelectValue>{flag}</SelectValue> 98 </SelectTrigger> 99 <SelectContent> 100 {regions.map((region) => { 101 const { flag } = regionDict[region]; 102 return ( 103 <SelectItem key={region} value={region}> 104 {flag} <span className="ml-1 font-mono">{region}</span> 105 </SelectItem> 106 ); 107 })} 108 </SelectContent> 109 </Select> 110 <TooltipProvider> 111 <Tooltip> 112 <TooltipTrigger asChild> 113 <Button 114 onClick={onClick} 115 disabled={isPending} 116 className="h-full flex-1 rounded-l-none focus:ring-0" 117 variant="secondary" 118 > 119 {isPending ? ( 120 <LoadingAnimation variant="inverse" /> 121 ) : ( 122 <> 123 Test <Send className="ml-2 h-4 w-4" /> 124 </> 125 )} 126 </Button> 127 </TooltipTrigger> 128 <TooltipContent> 129 <p>Ping your endpoint</p> 130 </TooltipContent> 131 </Tooltip> 132 </TooltipProvider> 133 </div> 134 <DialogContent className="max-h-screen w-full overflow-auto sm:max-w-3xl sm:p-8"> 135 <DialogHeader> 136 <DialogTitle>Response</DialogTitle> 137 </DialogHeader> 138 {check?.data.state === "success" ? ( 139 <div className="grid gap-8"> 140 <RegionInfo check={check.data} error={check.error} /> 141 {check.data.state === "success" && check.data.type === "http" ? ( 142 <ResponseDetailTabs 143 timing={check.data.timing} 144 headers={check.data.headers} 145 status={check.data.status} 146 assertions={deserialize( 147 JSON.stringify([ 148 ...(statusAssertions || []), 149 ...(headerAssertions || []), 150 ]), 151 )} 152 /> 153 ) : null} 154 </div> 155 ) : null} 156 </DialogContent> 157 </Dialog> 158 ); 159}