webhook sections combined
This commit is contained in:
parent
a81b1f0e58
commit
31dfc7afd6
@ -16,10 +16,9 @@ import { DangerZoneSection } from "@client/pages/settings/components/DangerZoneS
|
|||||||
import { DisplaySettingsSection } from "@client/pages/settings/components/DisplaySettingsSection"
|
import { DisplaySettingsSection } from "@client/pages/settings/components/DisplaySettingsSection"
|
||||||
import { EnvironmentSettingsSection } from "@client/pages/settings/components/EnvironmentSettingsSection"
|
import { EnvironmentSettingsSection } from "@client/pages/settings/components/EnvironmentSettingsSection"
|
||||||
import { GradcrackerSection } from "@client/pages/settings/components/GradcrackerSection"
|
import { GradcrackerSection } from "@client/pages/settings/components/GradcrackerSection"
|
||||||
import { JobCompleteWebhookSection } from "@client/pages/settings/components/JobCompleteWebhookSection"
|
|
||||||
import { JobspySection } from "@client/pages/settings/components/JobspySection"
|
import { JobspySection } from "@client/pages/settings/components/JobspySection"
|
||||||
import { ModelSettingsSection } from "@client/pages/settings/components/ModelSettingsSection"
|
import { ModelSettingsSection } from "@client/pages/settings/components/ModelSettingsSection"
|
||||||
import { PipelineWebhookSection } from "@client/pages/settings/components/PipelineWebhookSection"
|
import { WebhooksSection } from "@client/pages/settings/components/WebhooksSection"
|
||||||
import { ResumeProjectsSection } from "@client/pages/settings/components/ResumeProjectsSection"
|
import { ResumeProjectsSection } from "@client/pages/settings/components/ResumeProjectsSection"
|
||||||
import { SearchTermsSection } from "@client/pages/settings/components/SearchTermsSection"
|
import { SearchTermsSection } from "@client/pages/settings/components/SearchTermsSection"
|
||||||
import { UkvisajobsSection } from "@client/pages/settings/components/UkvisajobsSection"
|
import { UkvisajobsSection } from "@client/pages/settings/components/UkvisajobsSection"
|
||||||
@ -452,13 +451,10 @@ export const SettingsPage: React.FC = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
/>
|
/>
|
||||||
<PipelineWebhookSection
|
<WebhooksSection
|
||||||
values={pipelineWebhook}
|
pipelineWebhook={pipelineWebhook}
|
||||||
isLoading={isLoading}
|
jobCompleteWebhook={jobCompleteWebhook}
|
||||||
isSaving={isSaving}
|
webhookSecretHint={envSettings.private.webhookSecretHint}
|
||||||
/>
|
|
||||||
<JobCompleteWebhookSection
|
|
||||||
values={jobCompleteWebhook}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -56,7 +56,6 @@ describe("EnvironmentSettingsSection", () => {
|
|||||||
expect(screen.getByText("sk-1********")).toBeInTheDocument()
|
expect(screen.getByText("sk-1********")).toBeInTheDocument()
|
||||||
expect(screen.getByText("pass********")).toBeInTheDocument()
|
expect(screen.getByText("pass********")).toBeInTheDocument()
|
||||||
expect(screen.getByText("abcd********")).toBeInTheDocument()
|
expect(screen.getByText("abcd********")).toBeInTheDocument()
|
||||||
expect(screen.getByText("sec-********")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("Not set")).toBeInTheDocument()
|
expect(screen.getByText("Not set")).toBeInTheDocument()
|
||||||
|
|
||||||
// Basic Auth
|
// Basic Auth
|
||||||
|
|||||||
@ -50,16 +50,6 @@ export const EnvironmentSettingsSection: React.FC<EnvironmentSettingsSectionProp
|
|||||||
error={errors.openrouterApiKey?.message as string | undefined}
|
error={errors.openrouterApiKey?.message as string | undefined}
|
||||||
current={formatSecretHint(privateValues.openrouterApiKeyHint)}
|
current={formatSecretHint(privateValues.openrouterApiKeyHint)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingsInput
|
|
||||||
label="Webhook secret"
|
|
||||||
inputProps={register("webhookSecret")}
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter new secret"
|
|
||||||
disabled={isLoading || isSaving}
|
|
||||||
error={errors.webhookSecret?.message as string | undefined}
|
|
||||||
current={formatSecretHint(privateValues.webhookSecretHint)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import { useFormContext } from "react-hook-form"
|
|
||||||
|
|
||||||
import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
|
||||||
import { UpdateSettingsInput } from "@shared/settings-schema"
|
|
||||||
import type { WebhookValues } from "@client/pages/settings/types"
|
|
||||||
import { SettingsInput } from "@client/pages/settings/components/SettingsInput"
|
|
||||||
|
|
||||||
type JobCompleteWebhookSectionProps = {
|
|
||||||
values: WebhookValues
|
|
||||||
isLoading: boolean
|
|
||||||
isSaving: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JobCompleteWebhookSection: React.FC<JobCompleteWebhookSectionProps> = ({
|
|
||||||
values,
|
|
||||||
isLoading,
|
|
||||||
isSaving,
|
|
||||||
}) => {
|
|
||||||
const { default: defaultJobCompleteWebhookUrl, effective: effectiveJobCompleteWebhookUrl } = values
|
|
||||||
const { register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AccordionItem value="job-complete-webhook" className="border rounded-lg px-4">
|
|
||||||
<AccordionTrigger className="hover:no-underline py-4">
|
|
||||||
<span className="text-base font-semibold">Job Complete Webhook</span>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pb-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<SettingsInput
|
|
||||||
label="Job completion webhook URL"
|
|
||||||
inputProps={register("jobCompleteWebhookUrl")}
|
|
||||||
placeholder={defaultJobCompleteWebhookUrl || "https://..."}
|
|
||||||
disabled={isLoading || isSaving}
|
|
||||||
error={errors.jobCompleteWebhookUrl?.message as string | undefined}
|
|
||||||
helper={`When set, the server sends a POST when you mark a job as applied (includes the job description). Default: ${defaultJobCompleteWebhookUrl || "—"}.`}
|
|
||||||
current={effectiveJobCompleteWebhookUrl || "—"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import React from "react"
|
|
||||||
import { useFormContext } from "react-hook-form"
|
|
||||||
|
|
||||||
import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
|
||||||
import { UpdateSettingsInput } from "@shared/settings-schema"
|
|
||||||
import type { WebhookValues } from "@client/pages/settings/types"
|
|
||||||
import { SettingsInput } from "@client/pages/settings/components/SettingsInput"
|
|
||||||
|
|
||||||
type PipelineWebhookSectionProps = {
|
|
||||||
values: WebhookValues
|
|
||||||
isLoading: boolean
|
|
||||||
isSaving: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PipelineWebhookSection: React.FC<PipelineWebhookSectionProps> = ({
|
|
||||||
values,
|
|
||||||
isLoading,
|
|
||||||
isSaving,
|
|
||||||
}) => {
|
|
||||||
const { default: defaultPipelineWebhookUrl, effective: effectivePipelineWebhookUrl } = values
|
|
||||||
const { register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AccordionItem value="pipeline-webhook" className="border rounded-lg px-4">
|
|
||||||
<AccordionTrigger className="hover:no-underline py-4">
|
|
||||||
<span className="text-base font-semibold">Pipeline Webhook</span>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pb-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<SettingsInput
|
|
||||||
label="Pipeline status webhook URL"
|
|
||||||
inputProps={register("pipelineWebhookUrl")}
|
|
||||||
placeholder={defaultPipelineWebhookUrl || "https://..."}
|
|
||||||
disabled={isLoading || isSaving}
|
|
||||||
error={errors.pipelineWebhookUrl?.message as string | undefined}
|
|
||||||
helper={`When set, the server sends a POST on pipeline completion/failure. Default: ${defaultPipelineWebhookUrl || "—"}.`}
|
|
||||||
current={effectivePipelineWebhookUrl || "—"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { render, screen } from "@testing-library/react"
|
||||||
|
import { useForm, FormProvider } from "react-hook-form"
|
||||||
|
|
||||||
|
import { Accordion } from "@/components/ui/accordion"
|
||||||
|
import { WebhooksSection } from "./WebhooksSection"
|
||||||
|
import { UpdateSettingsInput } from "@shared/settings-schema"
|
||||||
|
|
||||||
|
const WebhooksHarness = () => {
|
||||||
|
const methods = useForm<UpdateSettingsInput>({
|
||||||
|
defaultValues: {
|
||||||
|
pipelineWebhookUrl: "https://pipeline.com",
|
||||||
|
jobCompleteWebhookUrl: "https://job.com",
|
||||||
|
webhookSecret: "",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<Accordion type="multiple" defaultValue={["webhooks"]}>
|
||||||
|
<WebhooksSection
|
||||||
|
pipelineWebhook={{
|
||||||
|
default: "https://default-p.com",
|
||||||
|
effective: "https://pipeline.com",
|
||||||
|
}}
|
||||||
|
jobCompleteWebhook={{
|
||||||
|
default: "https://default-j.com",
|
||||||
|
effective: "https://job.com",
|
||||||
|
}}
|
||||||
|
webhookSecretHint="sec-"
|
||||||
|
isLoading={false}
|
||||||
|
isSaving={false}
|
||||||
|
/>
|
||||||
|
</Accordion>
|
||||||
|
</FormProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("WebhooksSection", () => {
|
||||||
|
it("renders both webhook sections and the secret", () => {
|
||||||
|
render(<WebhooksHarness />)
|
||||||
|
|
||||||
|
expect(screen.getByText("Pipeline Status")).toBeInTheDocument()
|
||||||
|
expect(screen.getByText("Job Completion")).toBeInTheDocument()
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue("https://pipeline.com")).toBeInTheDocument()
|
||||||
|
expect(screen.getByDisplayValue("https://job.com")).toBeInTheDocument()
|
||||||
|
|
||||||
|
expect(screen.getByText("sec-********")).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { useFormContext } from "react-hook-form"
|
||||||
|
|
||||||
|
import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import { UpdateSettingsInput } from "@shared/settings-schema"
|
||||||
|
import type { WebhookValues } from "@client/pages/settings/types"
|
||||||
|
import { SettingsInput } from "@client/pages/settings/components/SettingsInput"
|
||||||
|
|
||||||
|
type WebhooksSectionProps = {
|
||||||
|
pipelineWebhook: WebhookValues
|
||||||
|
jobCompleteWebhook: WebhookValues
|
||||||
|
webhookSecretHint: string | null
|
||||||
|
isLoading: boolean
|
||||||
|
isSaving: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WebhooksSection: React.FC<WebhooksSectionProps> = ({
|
||||||
|
pipelineWebhook,
|
||||||
|
jobCompleteWebhook,
|
||||||
|
webhookSecretHint,
|
||||||
|
isLoading,
|
||||||
|
isSaving,
|
||||||
|
}) => {
|
||||||
|
const { register, formState: { errors } } = useFormContext<UpdateSettingsInput>()
|
||||||
|
|
||||||
|
const formatSecretHint = (hint: string | null) => (hint ? `${hint}********` : "Not set")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccordionItem value="webhooks" className="border rounded-lg px-4">
|
||||||
|
<AccordionTrigger className="hover:no-underline py-4">
|
||||||
|
<span className="text-base font-semibold">Webhooks</span>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pb-4">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="text-sm font-medium">Pipeline Status</div>
|
||||||
|
<SettingsInput
|
||||||
|
label="Webhook URL"
|
||||||
|
inputProps={register("pipelineWebhookUrl")}
|
||||||
|
placeholder={pipelineWebhook.default || "https://..."}
|
||||||
|
disabled={isLoading || isSaving}
|
||||||
|
error={errors.pipelineWebhookUrl?.message as string | undefined}
|
||||||
|
helper={`When set, the server sends a POST on pipeline completion/failure. Default: ${pipelineWebhook.default || "—"}.`}
|
||||||
|
current={pipelineWebhook.effective || "—"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="text-sm font-medium">Job Completion</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<SettingsInput
|
||||||
|
label="Webhook URL"
|
||||||
|
inputProps={register("jobCompleteWebhookUrl")}
|
||||||
|
placeholder={jobCompleteWebhook.default || "https://..."}
|
||||||
|
disabled={isLoading || isSaving}
|
||||||
|
error={errors.jobCompleteWebhookUrl?.message as string | undefined}
|
||||||
|
helper={`When set, the server sends a POST when you mark a job as applied (includes the job description). Default: ${jobCompleteWebhook.default || "—"}.`}
|
||||||
|
current={jobCompleteWebhook.effective || "—"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingsInput
|
||||||
|
label="Webhook Secret"
|
||||||
|
inputProps={register("webhookSecret")}
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter new secret"
|
||||||
|
disabled={isLoading || isSaving}
|
||||||
|
error={errors.webhookSecret?.message as string | undefined}
|
||||||
|
helper="Secret sent to webhook"
|
||||||
|
current={formatSecretHint(webhookSecretHint)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user