Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

promote: moving stable changes from develop to preview #3249

Merged
merged 2 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions web/components/web-hooks/create-webhook-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import { Dialog, Transition } from "@headlessui/react";
// components
import { WebhookForm } from "./form";
import { GeneratedHookDetails } from "./generated-hook-details";
// hooks
import useToast from "hooks/use-toast";
// helpers
import { csvDownload } from "helpers/download.helper";
// utils
import { getCurrentHookAsCSV } from "./utils";
// types
import { IWebhook, IWorkspace, TWebhookEventTypes } from "types";

interface WebhookWithKey {
webHook: IWebhook;
secretKey: string | undefined;
}
interface ICreateWebhookModal {
currentWorkspace: IWorkspace | null;
isOpen: boolean;
clearSecretKey: () => void;
createWebhook: (workspaceSlug: string, data: Partial<IWebhook>) => Promise<WebhookWithKey>;
onClose: () => void;
}

export const CreateWebhookModal: React.FC<ICreateWebhookModal> = (props) => {
const { isOpen, onClose, currentWorkspace, createWebhook, clearSecretKey } = props;
// states
const [generatedWebhook, setGeneratedKey] = useState<IWebhook | null>(null);
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// toast
const { setToastAlert } = useToast();

const handleCreateWebhook = async (formData: IWebhook, webhookEventType: TWebhookEventTypes) => {
if (!workspaceSlug) return;

let payload: Partial<IWebhook> = {
url: formData.url,
};

if (webhookEventType === "all")
payload = {
...payload,
project: true,
cycle: true,
module: true,
issue: true,
issue_comment: true,
};
else
payload = {
...payload,
project: formData.project ?? false,
cycle: formData.cycle ?? false,
module: formData.module ?? false,
issue: formData.issue ?? false,
issue_comment: formData.issue_comment ?? false,
};

await createWebhook(workspaceSlug.toString(), payload)
.then(({ webHook, secretKey }) => {
setToastAlert({
type: "success",
title: "Success!",
message: "Webhook created successfully.",
});

setGeneratedKey(webHook);

const csvData = getCurrentHookAsCSV(currentWorkspace, webHook, secretKey);
csvDownload(csvData, `webhook-secret-key-${Date.now()}`);
})
.catch((error) => {
setToastAlert({
type: "error",
title: "Error!",
message: error?.error ?? "Something went wrong. Please try again.",
});
});
};

const handleClose = () => {
onClose();
setTimeout(() => {
clearSecretKey();
setGeneratedKey(null);
}, 350);
};

return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog
as="div"
className="relative z-20"
onClose={() => {
if (!generatedWebhook) handleClose();
}}
>
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>

<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 p-6 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
{!generatedWebhook ? (
<WebhookForm onSubmit={handleCreateWebhook} handleClose={handleClose} />
) : (
<GeneratedHookDetails webhookDetails={generatedWebhook} handleClose={handleClose} />
)}
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
12 changes: 7 additions & 5 deletions web/components/web-hooks/empty-state.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// next
import { useRouter } from "next/router";
import React from "react";
import Image from "next/image";
// ui
import { Button } from "@plane/ui";
// assets
import EmptyWebhook from "public/empty-state/web-hook.svg";

export const WebhooksEmptyState = () => {
const router = useRouter();
type Props = {
onClick: () => void;
};

export const WebhooksEmptyState: React.FC<Props> = (props) => {
const { onClick } = props;
return (
<div
className={`mx-auto flex w-full items-center justify-center rounded-sm border border-custom-border-200 bg-custom-background-90 px-16 py-10 lg:w-3/4`}
Expand All @@ -19,7 +21,7 @@ export const WebhooksEmptyState = () => {
<p className="mb-7 text-custom-text-300 sm:mb-8">
Create webhooks to receive real-time updates and automate actions
</p>
<Button className="flex items-center gap-1.5" onClick={() => router.push(`${router.asPath}/create/`)}>
<Button className="flex items-center gap-1.5" onClick={onClick}>
Add webhook
</Button>
</div>
Expand Down
112 changes: 25 additions & 87 deletions web/components/web-hooks/form/form.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import React, { FC, useEffect, useState } from "react";
import { useRouter } from "next/router";
import { Controller, useForm } from "react-hook-form";
import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useToast from "hooks/use-toast";
// components
import {
WebhookIndividualEventOptions,
WebhookInput,
WebhookOptions,
WebhookSecretKey,
WebhookToggle,
getCurrentHookAsCSV,
} from "components/web-hooks";
// ui
import { Button } from "@plane/ui";
// helpers
import { csvDownload } from "helpers/download.helper";
// types
import { IWebhook, TWebhookEventTypes } from "types";

type Props = {
data?: Partial<IWebhook>;
onSubmit: (data: IWebhook, webhookEventType: TWebhookEventTypes) => Promise<void>;
handleClose?: () => void;
};

const initialWebhookPayload: Partial<IWebhook> = {
Expand All @@ -36,18 +32,12 @@ const initialWebhookPayload: Partial<IWebhook> = {
};

export const WebhookForm: FC<Props> = observer((props) => {
const { data } = props;
const { data, onSubmit, handleClose } = props;
// states
const [webhookEventType, setWebhookEventType] = useState<TWebhookEventTypes>("all");
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// toast
const { setToastAlert } = useToast();
// mobx store
const {
webhook: { createWebhook, updateWebhook },
workspace: { currentWorkspace },
webhook: { webhookSecretKey },
} = useMobxStore();
// use form
const {
Expand All @@ -58,74 +48,8 @@ export const WebhookForm: FC<Props> = observer((props) => {
defaultValues: { ...initialWebhookPayload, ...data },
});

const handleCreateWebhook = async (formData: IWebhook) => {
if (!workspaceSlug) return;

let payload: Partial<IWebhook> = {
url: formData.url,
};

if (webhookEventType === "all")
payload = {
...payload,
project: true,
cycle: true,
module: true,
issue: true,
issue_comment: true,
};
else
payload = {
...payload,
project: formData.project ?? false,
cycle: formData.cycle ?? false,
module: formData.module ?? false,
issue: formData.issue ?? false,
issue_comment: formData.issue_comment ?? false,
};

await createWebhook(workspaceSlug.toString(), payload)
.then(({ webHook, secretKey }) => {
setToastAlert({
type: "success",
title: "Success!",
message: "Webhook created successfully.",
});

const csvData = getCurrentHookAsCSV(currentWorkspace, webHook, secretKey);
csvDownload(csvData, `webhook-secret-key-${Date.now()}`);

if (webHook && webHook.id)
router.push({ pathname: `/${workspaceSlug}/settings/webhooks/${webHook.id}`, query: { isCreated: true } });
})
.catch((error) => {
setToastAlert({
type: "error",
title: "Error!",
message: error?.error ?? "Something went wrong. Please try again.",
});
});
};

const handleUpdateWebhook = async (formData: IWebhook) => {
if (!workspaceSlug || !data || !data.id) return;

const payload = {
url: formData?.url,
is_active: formData?.is_active,
project: formData?.project,
cycle: formData?.cycle,
module: formData?.module,
issue: formData?.issue,
issue_comment: formData?.issue_comment,
};

return await updateWebhook(workspaceSlug.toString(), data.id, payload);
};

const handleFormSubmit = async (formData: IWebhook) => {
if (data) await handleUpdateWebhook(formData);
else await handleCreateWebhook(formData);
await onSubmit(formData, webhookEventType);
};

useEffect(() => {
Expand Down Expand Up @@ -161,12 +85,26 @@ export const WebhookForm: FC<Props> = observer((props) => {
<div className="mt-4">
{webhookEventType === "individual" && <WebhookIndividualEventOptions control={control} />}
</div>
<div className="mt-8 space-y-8">
{data && <WebhookSecretKey data={data} />}
<Button type="submit" loading={isSubmitting}>
{data ? (isSubmitting ? "Updating..." : "Update") : isSubmitting ? "Creating..." : "Create"}
</Button>
</div>
{data ? (
<div className="mt-8 space-y-8">
<WebhookSecretKey data={data} />

<Button type="submit" loading={isSubmitting}>
{isSubmitting ? "Updating..." : "Update"}
</Button>
</div>
) : (
<div className="flex justify-end gap-2 mt-4">
<Button variant="neutral-primary" onClick={handleClose}>
Discard
</Button>
{!webhookSecretKey && (
<Button type="submit" variant="primary" loading={isSubmitting}>
{isSubmitting ? "Creating..." : "Create"}
</Button>
)}
</div>
)}
</form>
</div>
);
Expand Down
8 changes: 4 additions & 4 deletions web/components/web-hooks/form/secret-key.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ export const WebhookSecretKey: FC<Props> = observer((props) => {
};

const handleRegenerateSecretKey = () => {
if (!workspaceSlug || !webhookId) return;
if (!workspaceSlug || !data.id) return;

setIsRegenerating(true);

regenerateSecretKey(workspaceSlug.toString(), webhookId.toString())
regenerateSecretKey(workspaceSlug.toString(), data.id)
.then(() => {
setToastAlert({
type: "success",
Expand Down Expand Up @@ -92,10 +92,10 @@ export const WebhookSecretKey: FC<Props> = observer((props) => {
<>
{(data || webhookSecretKey) && (
<div className="space-y-2">
<div className="text-sm font-medium">Secret key</div>
{webhookId && <div className="text-sm font-medium">Secret key</div>}
<div className="text-xs text-custom-text-400">Generate a token to sign-in to the webhook payload</div>
<div className="flex items-center gap-4">
<div className="flex min-w-[30rem] max-w-lg items-center justify-between self-stretch rounded border border-custom-border-200 px-2 py-1.5">
<div className="flex flex-grow max-w-lg items-center justify-between self-stretch rounded border border-custom-border-200 px-2 py-1.5">
<div className="select-none overflow-hidden font-medium">
{shouldShowKey ? (
<p className="text-xs">{webhookSecretKey}</p>
Expand Down
33 changes: 33 additions & 0 deletions web/components/web-hooks/generated-hook-details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// components
import { WebhookSecretKey } from "./form";
// ui
import { Button } from "@plane/ui";
// types
import { IWebhook } from "types";

type Props = {
handleClose: () => void;
webhookDetails: IWebhook;
};

export const GeneratedHookDetails: React.FC<Props> = (props) => {
const { handleClose, webhookDetails } = props;

return (
<div>
<div className="space-y-3 mb-3">
<h3 className="text-lg font-medium leading-6 text-custom-text-100">Key created</h3>
<p className="text-sm text-custom-text-400">
Copy and save this secret key in Plane Pages. You can{"'"}t see this key after you hit Close. A CSV file
containing the key has been downloaded.
</p>
</div>
<WebhookSecretKey data={webhookDetails} />
<div className="mt-6 flex justify-end">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Close
</Button>
</div>
</div>
);
};
Loading
Loading