From 609dcabf775fa06e65619800f02093090e1fa06b Mon Sep 17 00:00:00 2001 From: Sai Suhas Sawant <92092643+SaiSawant1@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:47:41 +0530 Subject: [PATCH] feat: promote dev-actions to prod (#4245) Co-authored-by: Piyush Gupta --- .../actions/components/ActionActivityTab.tsx | 89 ++++++++++++++++++- .../actions/components/ActionClassesTable.tsx | 10 +++ .../actions/components/ActionDetailModal.tsx | 18 +++- .../[environmentId]/actions/page.tsx | 15 ++++ packages/lib/messages/de-DE.json | 2 + packages/lib/messages/en-US.json | 2 + packages/lib/messages/pt-BR.json | 2 + 7 files changed, 133 insertions(+), 5 deletions(-) diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionActivityTab.tsx b/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionActivityTab.tsx index c8dc0b95c5..5220884855 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionActivityTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionActivityTab.tsx @@ -1,12 +1,16 @@ "use client"; +import { createActionClassAction } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; +import toast from "react-hot-toast"; import { convertDateTimeStringShort } from "@formbricks/lib/time"; import { capitalizeFirstLetter } from "@formbricks/lib/utils/strings"; -import { TActionClass } from "@formbricks/types/action-classes"; +import { TActionClass, TActionClassInput, TActionClassInputCode } from "@formbricks/types/action-classes"; +import { TEnvironment } from "@formbricks/types/environment"; +import { Button } from "@formbricks/ui/components/Button"; import { ErrorComponent } from "@formbricks/ui/components/ErrorComponent"; import { Label } from "@formbricks/ui/components/Label"; import { LoadingSpinner } from "@formbricks/ui/components/LoadingSpinner"; @@ -15,15 +19,25 @@ import { getActiveInactiveSurveysAction } from "../actions"; interface ActivityTabProps { actionClass: TActionClass; environmentId: string; + environment: TEnvironment; + otherEnvActionClasses: TActionClass[]; + otherEnvironment: TEnvironment; + isReadOnly: boolean; } -export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabProps) => { +export const ActionActivityTab = ({ + actionClass, + otherEnvActionClasses, + otherEnvironment, + environmentId, + environment, + isReadOnly, +}: ActivityTabProps) => { const t = useTranslations(); const [activeSurveys, setActiveSurveys] = useState(); const [inactiveSurveys, setInactiveSurveys] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - useEffect(() => { setLoading(true); @@ -45,6 +59,57 @@ export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabPro updateState(); }, [actionClass.id, environmentId]); + const actionClassNames = useMemo( + () => otherEnvActionClasses.map((actionClass) => actionClass.name), + [otherEnvActionClasses] + ); + + const actionClassKeys = useMemo(() => { + const codeActionClasses: TActionClassInputCode[] = otherEnvActionClasses.filter( + (actionClass) => actionClass.type === "code" + ) as TActionClassInputCode[]; + + return codeActionClasses.map((actionClass) => actionClass.key); + }, [otherEnvActionClasses]); + + const copyAction = async (data: TActionClassInput) => { + const { type } = data; + let copyName = data.name; + try { + if (isReadOnly) { + throw new Error(t("common.you_are_not_authorised_to_perform_this_action")); + } + + if (copyName && actionClassNames.includes(copyName)) { + while (actionClassNames.includes(copyName)) { + copyName += " (copy)"; + } + } + + if (type === "code" && data.key && actionClassKeys.includes(data.key)) { + throw new Error(t("environments.actions.action_with_key_already_exists", { key: data.key })); + } + + let updatedAction = { + ...data, + name: copyName.trim(), + environmentId: otherEnvironment.id, + }; + + const createActionClassResponse = await createActionClassAction({ + action: updatedAction as TActionClassInput, + }); + + if (!createActionClassResponse?.data) { + throw new Error(t("environments.actions.action_copy_failed", {})); + } + + toast.success(t("environments.actions.action_copied_successfully")); + } catch (e: any) { + toast.error(e.message); + } + }; + if (loading) return ; if (error) return ; @@ -98,6 +163,22 @@ export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabPro

{capitalizeFirstLetter(actionClass.type)}

+
+ +
+

+ {environment.type === "development" ? "Development" : "Production"} +

+ +
+
); diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionClassesTable.tsx b/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionClassesTable.tsx index bd3e328557..959cb1842a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionClassesTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionClassesTable.tsx @@ -2,20 +2,27 @@ import { useState } from "react"; import { TActionClass } from "@formbricks/types/action-classes"; +import { TEnvironment } from "@formbricks/types/environment"; import { ActionDetailModal } from "./ActionDetailModal"; interface ActionClassesTableProps { environmentId: string; actionClasses: TActionClass[]; + environment: TEnvironment; children: [JSX.Element, JSX.Element[]]; isReadOnly: boolean; + otherEnvironment: TEnvironment; + otherEnvActionClasses: TActionClass[]; } export const ActionClassesTable = ({ environmentId, actionClasses, + environment, children: [TableHeading, actionRows], isReadOnly, + otherEnvActionClasses, + otherEnvironment, }: ActionClassesTableProps) => { const [isActionDetailModalOpen, setActionDetailModalOpen] = useState(false); @@ -48,11 +55,14 @@ export const ActionClassesTable = ({ {activeActionClass && ( )} diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionDetailModal.tsx b/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionDetailModal.tsx index 9ed437b20c..7a220d97bd 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionDetailModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/actions/components/ActionDetailModal.tsx @@ -1,17 +1,21 @@ import { Code2Icon, MousePointerClickIcon, SparklesIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { TActionClass } from "@formbricks/types/action-classes"; +import { TEnvironment } from "@formbricks/types/environment"; import { ModalWithTabs } from "@formbricks/ui/components/ModalWithTabs"; import { ActionActivityTab } from "./ActionActivityTab"; import { ActionSettingsTab } from "./ActionSettingsTab"; interface ActionDetailModalProps { environmentId: string; + environment: TEnvironment; open: boolean; setOpen: (v: boolean) => void; actionClass: TActionClass; actionClasses: TActionClass[]; isReadOnly: boolean; + otherEnvironment: TEnvironment; + otherEnvActionClasses: TActionClass[]; } export const ActionDetailModal = ({ @@ -20,13 +24,25 @@ export const ActionDetailModal = ({ setOpen, actionClass, actionClasses, + environment, isReadOnly, + otherEnvActionClasses, + otherEnvironment, }: ActionDetailModalProps) => { const t = useTranslations(); const tabs = [ { title: t("common.activity"), - children: , + children: ( + + ), }, { title: t("common.settings"), diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx b/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx index 30c21a0945..af58e597e0 100644 --- a/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/actions/page.tsx @@ -10,6 +10,7 @@ import { getTranslations } from "next-intl/server"; import { redirect } from "next/navigation"; import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { authOptions } from "@formbricks/lib/authOptions"; +import { getEnvironments } from "@formbricks/lib/environment/service"; import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service"; @@ -44,6 +45,17 @@ const Page = async ({ params }) => { throw new Error(t("common.product_not_found")); } + const environments = await getEnvironments(product.id); + const currentEnvironment = environments.find((env) => env.id === params.environmentId); + + if (!currentEnvironment) { + throw new Error(t("common.environment_not_found")); + } + + const otherEnvironment = environments.filter((env) => env.id !== params.environmentId)[0]; + + const otherEnvActionClasses = await getActionClasses(otherEnvironment.id); + const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const { isMember, isBilling } = getAccessFlags(currentUserMembership?.role); @@ -69,6 +81,9 @@ const Page = async ({ params }) => { diff --git a/packages/lib/messages/de-DE.json b/packages/lib/messages/de-DE.json index 451b39ca90..fa49a1b49d 100644 --- a/packages/lib/messages/de-DE.json +++ b/packages/lib/messages/de-DE.json @@ -488,6 +488,8 @@ }, "environments": { "actions": { + "action_copied_successfully": "Aktion erfolgreich kopiert", + "action_copy_failed": "Aktion konnte nicht kopiert werden", "action_created_successfully": "Aktion erfolgreich erstellt", "action_deleted_successfully": "Aktion erfolgreich gelöscht", "action_type": "Aktionstyp", diff --git a/packages/lib/messages/en-US.json b/packages/lib/messages/en-US.json index 4edf74665a..647bfbf313 100644 --- a/packages/lib/messages/en-US.json +++ b/packages/lib/messages/en-US.json @@ -488,6 +488,8 @@ }, "environments": { "actions": { + "action_copied_successfully": "Action copied successfully", + "action_copy_failed": "Action copy failed", "action_created_successfully": "Action created successfully", "action_deleted_successfully": "Action deleted successfully", "action_type": "Action Type", diff --git a/packages/lib/messages/pt-BR.json b/packages/lib/messages/pt-BR.json index 2a4ee0470c..98e5611b24 100644 --- a/packages/lib/messages/pt-BR.json +++ b/packages/lib/messages/pt-BR.json @@ -488,6 +488,8 @@ }, "environments": { "actions": { + "action_copied_successfully": "Ação copiada com sucesso", + "action_copy_failed": "Falha ao copiar a ação", "action_created_successfully": "Ação criada com sucesso", "action_deleted_successfully": "Ação deletada com sucesso", "action_type": "Tipo de Ação",