Skip to content

Commit

Permalink
🚀 allow every content type (#996)
Browse files Browse the repository at this point in the history
* 🚀 allow every content type

* 🚀 content type

* 🚀 content type

* ci: apply automated fixes

* 🚀 file upload

* ci: apply automated fixes

* fix: style

* ci: apply automated fixes

* 🤯

* ci: apply automated fixes

* 😭

* fix: style

* chore: small stuff

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Kaske <[email protected]>
  • Loading branch information
3 people authored Sep 8, 2024
1 parent 7cef4a1 commit ce39358
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 52 deletions.
16 changes: 14 additions & 2 deletions apps/checker/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ func Ping(ctx context.Context, client *http.Client, inputData request.CheckerReq
}
}
if inputData.Method != http.MethodGet {
req.Header.Set("Content-Type", "application/json")
head := req.Header
_, ok := head["Content-Type"]
if !ok {
// by default we set the content type to application/json if it's a POST request
req.Header.Set("Content-Type", "application/json")
}
}

timing := Timing{}
Expand Down Expand Up @@ -188,9 +193,16 @@ func SinglePing(ctx context.Context, client *http.Client, inputData request.Ping
for key, value := range inputData.Headers {
req.Header.Set(key, value)
}

if inputData.Method != http.MethodGet {
req.Header.Set("Content-Type", "application/json")
head := req.Header
_, ok := head["Content-Type"]
if !ok {
// by default we set the content type to application/json if it's a POST request
req.Header.Set("Content-Type", "application/json")
}
}

timing := Timing{}

trace := &httptrace.ClientTrace{
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/components/forms/monitor/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,13 @@ export function MonitorForm({
textBodyAssertions,
} = form.getValues();

if (body && body !== "") {
if (
body &&
body !== "" &&
headers?.some(
(h) => h.key === "Content-Type" && h.value === "application/json",
)
) {
const validJSON = validateJSON(body);
if (!validJSON) {
return { error: "Not a valid JSON object.", data: undefined };
Expand Down
190 changes: 149 additions & 41 deletions apps/web/src/components/forms/monitor/section-requests.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"use client";

import { Wand2, X } from "lucide-react";
import * as React from "react";
import type * as React from "react";
import { useFieldArray } from "react-hook-form";
import type { UseFormReturn } from "react-hook-form";

import {
monitorMethods,
monitorMethodsSchema,
} from "@openstatus/db/src/schema";
import type { InsertMonitor, WorkspacePlan } from "@openstatus/db/src/schema";
import type { InsertMonitor } from "@openstatus/db/src/schema";
import {
Button,
FormControl,
Expand All @@ -31,20 +31,37 @@ import {
TooltipTrigger,
} from "@openstatus/ui";

import { toast } from "@/lib/toast";
import { useRef, useState } from "react";
import { SectionHeader } from "../shared/section-header";

const contentTypes = [
{ value: "application/octet-stream", label: "Binary File" },
{ value: "application/json", label: "JSON" },
{ value: "application/xml", label: "XML" },
{ value: "application/yaml", label: "YAML" },
{ value: "application/edn", label: "EDN" },
{ value: "application/other", label: "Other" },
{ value: "none", label: "None" },
];

interface Props {
form: UseFormReturn<InsertMonitor>;
}

// TODO: add Dialog with response informations when pingEndpoint!

export function SectionRequests({ form }: Props) {
const { fields, append, remove } = useFieldArray({
const { fields, append, prepend, remove, update } = useFieldArray({
name: "headers",
control: form.control,
});
const inputRef = useRef<HTMLInputElement>(null);
const watchMethod = form.watch("method");
const [file, setFile] = useState<string | undefined>(undefined);
const [content, setContent] = useState<string | undefined>(
fields.find((field) => field.key === "Content-Type")?.value,
);

const validateJSON = (value?: string) => {
if (!value) return;
Expand All @@ -60,6 +77,30 @@ export function SectionRequests({ form }: Props) {
}
};

const uploadFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files?.[0]) {
const file = event.target.files[0];

// File too big, return error
const fileSize = file.size / 1024 / 1024; // in MiB
if (fileSize > 10) {
// Display error message
toast.error("File size is too big. Max 10MB allowed.");
return;
}

const reader = new FileReader();
reader.onload = (event) => {
if (event.target?.result && typeof event.target.result === "string") {
form.setValue("body", event.target?.result);
setFile(file.name);
}
};

reader.readAsDataURL(file);
}
};

const onPrettifyJSON = () => {
const body = form.getValues("body");
const obj = validateJSON(body);
Expand All @@ -86,6 +127,7 @@ export function SectionRequests({ form }: Props) {
onValueChange={(value) => {
field.onChange(monitorMethodsSchema.parse(value));
form.resetField("body", { defaultValue: "" });
setContent(undefined);
}}
defaultValue={field.value}
>
Expand Down Expand Up @@ -113,18 +155,12 @@ export function SectionRequests({ form }: Props) {
<FormItem className="sm:col-span-6">
<FormLabel>URL</FormLabel>
<FormControl>
{/* <InputWithAddons
leading="https://"
placeholder="documenso.com/api/health"
{...field}
/> */}
<Input
className="bg-muted"
placeholder="https://documenso.com/api/health"
{...field}
/>
</FormControl>
{/* <FormMessage /> */}
</FormItem>
)}
/>
Expand Down Expand Up @@ -160,7 +196,9 @@ export function SectionRequests({ form }: Props) {
size="icon"
variant="ghost"
type="button"
onClick={() => remove(Number(field.id))}
onClick={() => {
remove(index);
}}
>
<X className="h-4 w-4" />
</Button>
Expand All @@ -183,43 +221,113 @@ export function SectionRequests({ form }: Props) {
control={form.control}
name="body"
render={({ field }) => (
<FormItem>
<FormItem className="space-y-1.5">
<div className="flex items-end justify-between">
<FormLabel>Body</FormLabel>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<FormLabel className="flex items-center space-x-2">
Body
<Select
defaultValue={content}
onValueChange={(value) => {
setContent(value);

if (content === "application/octet-stream") {
form.setValue("body", "");
setFile(undefined);
}

const contentIndex = fields.findIndex(
(field) => field.key === "Content-Type",
);

if (contentIndex >= 0) {
if (value === "none") {
remove(contentIndex);
} else {
update(contentIndex, {
key: "Content-Type",
value,
});
}
} else {
prepend({ key: "Content-Type", value });
}
}}
>
<SelectTrigger
variant={"ghost"}
className="ml-1 h-7 text-muted-foreground text-xs"
>
<SelectValue placeholder="Content-Type" />
</SelectTrigger>
<SelectContent>
{contentTypes.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormLabel>
{watchMethod === "POST" &&
fields.some(
(field) =>
field.key === "Content-Type" &&
field.value === "application/json",
) && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={onPrettifyJSON}
>
<Wand2 className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Prettify JSON</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
<div className="space-y-2">
<FormControl>
{content === "application/octet-stream" ? (
<>
<Button
type="button"
variant="ghost"
size="icon"
onClick={onPrettifyJSON}
variant="outline"
onClick={() => inputRef.current?.click()}
className="max-w-56"
>
<Wand2 className="h-4 w-4" />
<span className="truncate">
{file || form.getValues("body") || "Upload file"}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Prettify JSON</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<input
type="file"
onChange={uploadFile}
ref={inputRef}
hidden
/>
</>
) : (
<>
<Textarea
rows={8}
placeholder='{ "hello": "world" }'
{...field}
/>
<FormDescription>Write your payload.</FormDescription>
</>
)}
</FormControl>
<FormMessage />
</div>
<FormControl>
{/* FIXME: cannot enter 'Enter' */}
<Textarea
rows={8}
placeholder='{ "hello": "world" }'
{...field}
/>
</FormControl>
<FormDescription>
Write your json payload. We automatically append{" "}
<code>
&quot;Content-Type&quot;: &quot;application/json&quot;
</code>{" "}
to the request header.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
Expand Down
1 change: 1 addition & 0 deletions packages/db/src/schema/monitors/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const headersSchema = z
.optional();

export const insertMonitorSchema = createInsertSchema(monitor, {
name: z.string().min(1, "Name must be at least 1 character long"),
periodicity: monitorPeriodicitySchema.default("10m"),
url: z.string().url(), // find a better way to not always start with "https://" including the `InputWithAddons`
status: monitorStatusSchema.default("active"),
Expand Down
33 changes: 25 additions & 8 deletions packages/ui/src/components/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,40 @@ import { Check, ChevronDown } from "lucide-react";
import * as React from "react";

import { cn } from "../lib/utils";
import { cva, VariantProps } from "class-variance-authority";

const Select = SelectPrimitive.Root;

const SelectGroup = SelectPrimitive.Group;

const SelectValue = SelectPrimitive.Value;

const selectVariants = cva(
"flex w-full h-10 items-center justify-between px-3 rounded-md border border-input ring-offset-background py-2 text-sm placeholder:text-muted-foreground focus:ring-ring focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
{
variants: {
variant: {
default: "bg-transparent",

ghost: "border-none space-x-2",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface SelectTriggerProps
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>,
VariantProps<typeof selectVariants> {}

const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
SelectTriggerProps
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border bg-transparent px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
className={cn(selectVariants({ variant: props.variant }), className)}
{...props}
>
{children}
Expand All @@ -43,7 +60,7 @@ const SelectContent = React.forwardRef<
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 min-w-[8rem] overflow-hidden rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
className
)}
position={position}
{...props}
Expand All @@ -52,7 +69,7 @@ const SelectContent = React.forwardRef<
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
Expand Down Expand Up @@ -82,7 +99,7 @@ const SelectItem = React.forwardRef<
ref={ref}
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
className
)}
{...props}
>
Expand Down

0 comments on commit ce39358

Please sign in to comment.