log drain fixes 2: the return of the fix (#28392)

This commit is contained in:
Jordi Enric authored and GitHub committed 2024-08-05 21:46:33 +02:00
1 parent daebf486c1
commit 4671c91c8a
2 files changed
+137 -120

No files matched your search

@@ -34,17 +34,19 @@ import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import toast from 'react-hot-toast'
import { useEffect, useState } from 'react'
import { ReactNode, useEffect, useState } from 'react'
import { TrashIcon } from 'lucide-react'
import { LogDrainData, useLogDrainsQuery } from 'data/log-drains/log-drains-query'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import Link from 'next/link'
import { Select, SelectContent, SelectItem, SelectTrigger } from '@ui/components/shadcn/ui/select'
import { FormDescription } from '@ui/components/shadcn/ui/form'
const FORM_ID = 'log-drain-destination-form'
const formUnion = z.discriminatedUnion('type', [
z.object({
type: z.literal('webhook'),
url: z.string().url('Webhook URL is required and must be a valid URL'),
url: z.string().url('Endpoint URL is required and must be a valid URL'),
http: z.enum(['http1', 'http2']),
gzip: z.boolean(),
headers: z.record(z.string(), z.string()).optional(),
@@ -56,9 +58,6 @@ const formUnion = z.discriminatedUnion('type', [
}),
z.object({
type: z.literal('elastic'),
url: z.string().url({ message: 'URL is required and must be a valid URL' }),
username: z.string().min(1, { message: 'Username is required' }),
password: z.string().min(1, { message: 'Password is required' }),
}),
z.object({
type: z.literal('postgres'),
@@ -90,7 +89,7 @@ function LogDrainFormItem({
label: string
formControl: any
placeholder?: string
description?: string
description?: ReactNode
type?: string
defaultValue?: string
}) {
@@ -131,6 +130,13 @@ export function LogDrainDestinationSheetForm({
onSubmit: (values: z.infer<typeof formSchema>) => void
mode: 'create' | 'update'
}) {
const CREATE_DEFAULT_HEADERS = {
'Content-Type': 'application/json',
}
const DEFAULT_HEADERS =
mode === 'create' ? CREATE_DEFAULT_HEADERS : defaultValues?.config?.headers || {}
const { ref } = useParams()
const { data: logDrains } = useLogDrainsQuery({
ref,
@@ -147,12 +153,10 @@ export function LogDrainDestinationSheetForm({
type: defaultType,
http: defaultValues?.config?.http || 'http2',
gzip: defaultValues?.config?.gzip || true,
headers: defaultValues?.config?.headers || {},
headers: DEFAULT_HEADERS,
url: defaultValues?.config?.url || '',
api_key: defaultValues?.config?.api_key || '',
region: defaultValues?.config?.region || '',
username: defaultValues?.config?.username || '',
password: defaultValues?.config?.password || '',
},
})
@@ -207,7 +211,7 @@ export function LogDrainDestinationSheetForm({
<SheetHeader>
<SheetTitle>Add destination</SheetTitle>
</SheetHeader>
<SheetSection>
<SheetSection className="!px-0">
<Form_Shadcn_ {...form}>
<form
id={FORM_ID}
@@ -225,7 +229,7 @@ export function LogDrainDestinationSheetForm({
form.handleSubmit(onSubmit)(e)
}}
>
<div className="space-y-4">
<div className="space-y-4 px-content">
<LogDrainFormItem
value="name"
placeholder="My Destination"
@@ -239,57 +243,69 @@ export function LogDrainDestinationSheetForm({
formControl={form.control}
/> */}
{mode === 'create' && (
<RadioGroupStacked
defaultValue={defaultType}
value={form.getValues('type')}
onValueChange={(v: LogDrainType) => form.setValue('type', v)}
>
{LOG_DRAIN_TYPES.map((type) => (
<RadioGroupStackedItem
value={type.value}
key={type.value}
id={type.value}
label={type.name}
description={type.description}
className="text-left"
/>
))}
</RadioGroupStacked>
<FormItemLayout layout="horizontal" label="Type">
<Select
defaultValue={defaultType}
value={form.getValues('type')}
onValueChange={(v: LogDrainType) => form.setValue('type', v)}
>
<SelectTrigger>
{LOG_DRAIN_TYPES.find((t) => t.value === type)?.name}
</SelectTrigger>
<SelectContent>
{LOG_DRAIN_TYPES.map((type) => (
<SelectItem
value={type.value}
key={type.value}
id={type.value}
className="text-left"
>
{type.name}
</SelectItem>
))}
</SelectContent>
<FormDescription className="mt-2">
{LOG_DRAIN_TYPES.find((t) => t.value === type)?.description}
</FormDescription>
</Select>
</FormItemLayout>
)}
</div>
<div className="space-y-4 mt-6">
<div className="space-y-6 mt-4">
{type === 'webhook' && (
<>
<LogDrainFormItem
value="url"
label="Webhook URL"
formControl={form.control}
placeholder="https://example.com/webhooks/log-drain"
/>
<FormField_Shadcn_
control={form.control}
name="http"
render={({ field }) => (
<FormItem_Shadcn_>
<FormControl_Shadcn_>
<RadioGroupStacked onValueChange={field.onChange} value={field.value}>
<FormItem_Shadcn_ asChild>
<FormControl_Shadcn_>
<RadioGroupStackedItem value="http1" label="HTTP/1" />
</FormControl_Shadcn_>
</FormItem_Shadcn_>
<FormItem_Shadcn_ asChild>
<FormControl_Shadcn_>
<RadioGroupStackedItem value="http2" label="HTTP/2" />
</FormControl_Shadcn_>
</FormItem_Shadcn_>
</RadioGroupStacked>
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
)}
/>
<div className="px-content space-y-6">
<LogDrainFormItem
value="url"
label="Endpoint URL"
formControl={form.control}
placeholder="https://example.com/log-drain"
/>
<FormField_Shadcn_
control={form.control}
name="http"
render={({ field }) => (
<FormItemLayout layout="horizontal" label="HTTP Version">
<FormControl_Shadcn_>
<RadioGroupStacked onValueChange={field.onChange} value={field.value}>
<FormItem_Shadcn_ asChild>
<FormControl_Shadcn_>
<RadioGroupStackedItem value="http1" label="HTTP/1" />
</FormControl_Shadcn_>
</FormItem_Shadcn_>
<FormItem_Shadcn_ asChild>
<FormControl_Shadcn_>
<RadioGroupStackedItem value="http2" label="HTTP/2" />
</FormControl_Shadcn_>
</FormItem_Shadcn_>
</RadioGroupStacked>
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
</FormItemLayout>
)}
/>
</div>
{/* <FormField_Shadcn_
control={form.control}
@@ -309,38 +325,57 @@ export function LogDrainDestinationSheetForm({
)}
/> */}
<div>
<FormLabel_Shadcn_>Custom Headers</FormLabel_Shadcn_>
{hasHeaders &&
Object.keys(headers || {})?.map((headerKey) => (
<div
className="flex hover:bg-background-alternative text-sm text-foreground items-center font-mono border-b p-1.5 group"
key={headerKey}
>
<div className="w-full px-1">{headerKey}</div>
<div className="w-full px-1 truncate" title={headers?.[headerKey]}>
{headers?.[headerKey]}
<div className="space-y-2">
<div className="px-content">
<FormLabel_Shadcn_>Custom Headers</FormLabel_Shadcn_>
<p className="text-xs text-foreground-lighter">
Set custom headers when draining logs to the Endpoint URL
</p>
</div>
<div className="divide-y border-y">
{hasHeaders &&
Object.keys(headers || {})?.map((headerKey) => (
<div
className="flex text-sm px-content text-foreground items-center font-mono py-1.5 group"
key={headerKey}
>
<div className="w-full">{headerKey}</div>
<div className="w-full truncate" title={headers?.[headerKey]}>
{headers?.[headerKey]}
</div>
<Button
className="justify-self-end opacity-0 group-hover:opacity-100 w-7"
type="text"
title="Remove"
icon={<TrashIcon />}
onClick={() => removeHeader(headerKey)}
></Button>
</div>
<Button
className="justify-self-end opacity-0 group-hover:opacity-100"
type="text"
title="Remove"
icon={<TrashIcon />}
onClick={() => removeHeader(headerKey)}
></Button>
</div>
))}
))}
</div>
</div>
</>
)}
{type === 'datadog' && (
<div className="grid gap-4">
<div className="grid gap-4 px-content">
<LogDrainFormItem
type="password"
value="api_key"
label="API Key"
formControl={form.control}
description="The API Key obtained from the Datadog dashboard."
description={
<>
The API Key obtained from the Datadog dashboard{' '}
<Link
target="_blank"
rel="noopener noreferrer"
className="text-sm underline text-foreground"
href="https://app.datadoghq.com/organization-settings/api-keys"
>
here
</Link>
</>
}
/>
<FormField_Shadcn_
name="region"
@@ -369,22 +404,6 @@ export function LogDrainDestinationSheetForm({
/>
</div>
)}
{type === 'elastic' && (
<div className="grid gap-4">
<LogDrainFormItem value="url" label="Filebeat URL" formControl={form.control} />
<LogDrainFormItem
value="username"
label="Username"
formControl={form.control}
/>
<LogDrainFormItem
type="password"
value="password"
label="Password"
formControl={form.control}
/>
</div>
)}
</div>
</form>
</Form_Shadcn_>
@@ -397,17 +416,23 @@ export function LogDrainDestinationSheetForm({
e.stopPropagation()
addHeader()
}}
className="flex gap-2 mt-2 items-center"
className="flex gap-2 mt-4 items-center px-content"
>
<label className="sr-only" htmlFor="header-name">
Header name
</label>
<Input_Shadcn_
size={'tiny'}
id="header-name"
type="text"
placeholder="x-header-name"
value={newCustomHeader.name}
onChange={(e) => setNewCustomHeader({ ...newCustomHeader, name: e.target.value })}
/>
<label className="sr-only" htmlFor="header-value">
Header value
</label>
<Input_Shadcn_
size={'tiny'}
id="header-value"
type="text"
placeholder="Header value"
value={newCustomHeader.value}
@@ -421,7 +446,9 @@ export function LogDrainDestinationSheetForm({
)}
</SheetSection>
<SheetFooter className="p-4">
<SheetSection></SheetSection>
<SheetFooter className="p-content">
<Button form={FORM_ID} loading={isLoading} htmlType="submit" type="primary">
Save destination
</Button>
@@ -1,33 +1,29 @@
import { Computer, DogIcon, WebhookIcon } from 'lucide-react'
import { BracesIcon, DogIcon } from 'lucide-react'
const iconProps = { size: 24, strokeWidth: 1.5, className: 'text-foreground-light' }
export const LOG_DRAIN_TYPES = [
{
value: 'webhook',
name: 'Webhook',
description: 'Forward logs to a custom HTTP endpoint',
icon: <WebhookIcon {...iconProps} />,
name: 'Endpoint',
description: 'Forward logs as a POST request to a custom HTTP endpoint',
icon: <BracesIcon {...iconProps} />,
},
{
value: 'datadog',
name: 'Data dog',
name: 'Datadog',
description: 'Datadog is a monitoring service for cloud-scale applications',
icon: <DogIcon {...iconProps} />,
},
{
value: 'elastic',
name: 'Elastic Filebeat',
description: 'Filebeat is a lightweight shipper for forwarding and centralizing log data',
icon: <Computer {...iconProps} />,
},
] as const
export const LOG_DRAIN_SOURCE_VALUES = LOG_DRAIN_TYPES.map((source) => source.value)
// Added postgres and bigquery here because the generated api types require them but they're not available yet.
// Once we add them they can be removed from this type and added to the LOG_DRAIN_TYPES array.
export type LogDrainType = (typeof LOG_DRAIN_TYPES)[number]['value'] | 'postgres' | 'bigquery'
export type LogDrainType =
| (typeof LOG_DRAIN_TYPES)[number]['value']
| 'postgres'
| 'bigquery'
| 'elastic'
export const DATADOG_REGIONS = [
{
@@ -61,12 +57,6 @@ export type LogDrainDatadogConfig = {
region: string
}
export type LogDrainElasticConfig = {
url: string
username: string
password: string
}
export type LogDrainWebhookConfig = {
url: string
}