Compare commits

...

12 Commits

Author SHA1 Message Date
Piyush Gupta
41bc62fb3b fix: action name 2024-11-13 13:36:01 +05:30
Piyush Gupta
041f26ab2d fix: rename variabl;es 2024-11-13 13:10:32 +05:30
Piyush Gupta
d03b01cb67 fix: typo 2024-11-13 13:07:49 +05:30
Piyush Gupta
c7981fc4d9 fix: copy logic and consistency changes 2024-11-13 13:06:03 +05:30
Piyush Gupta
a1719c2258 Merge branch 'main' of https://github.com/formbricks/formbricks into action-env-to-prod 2024-11-13 11:04:40 +05:30
SaiSawant1
a5275c2ead minor fix 2024-11-11 23:41:22 +05:30
SaiSawant1
eec71d9a98 copy dev to prod fixed multiple copies of no code and
copy action with code once if of key exists display error.
2024-11-11 23:20:56 +05:30
Piyush Gupta
d547a70013 Merge branch 'main' of https://github.com/formbricks/formbricks into action-env-to-prod 2024-11-11 18:54:54 +05:30
SaiSawant1
8870b7d710 Merge branch 'main' into action-env-to-prod 2024-11-11 09:59:54 +05:30
SaiSawant1
3c8f6945ba copy no-code action to production
and copy production to action
2024-11-10 21:39:46 +05:30
SaiSawant1
e1bd66b009 dev-to-prod ui 2024-11-07 01:15:50 +05:30
SaiSawant1
abbd69e355 fetched enviroments 2024-11-07 00:35:25 +05:30
7 changed files with 133 additions and 5 deletions

View File

@@ -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<string[] | undefined>();
const [inactiveSurveys, setInactiveSurveys] = useState<string[] | undefined>();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(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 <LoadingSpinner />;
if (error) return <ErrorComponent />;
@@ -98,6 +163,22 @@ export const ActionActivityTab = ({ actionClass, environmentId }: ActivityTabPro
<p className="text-sm text-slate-700">{capitalizeFirstLetter(actionClass.type)}</p>
</div>
</div>
<div className="">
<Label className="text-xs font-normal text-slate-500">Environment</Label>
<div className="items-center-center flex gap-2">
<p className="text-xs text-slate-700">
{environment.type === "development" ? "Development" : "Production"}
</p>
<Button
onClick={() => {
copyAction(actionClass);
}}
className="m-0 p-0 text-xs font-medium text-black underline underline-offset-4 focus:ring-0 focus:ring-offset-0"
variant="minimal">
{environment.type === "development" ? "Copy to Production" : "Copy to Development"}
</Button>
</div>
</div>
</div>
</div>
);

View File

@@ -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 && (
<ActionDetailModal
environmentId={environmentId}
environment={environment}
open={isActionDetailModalOpen}
setOpen={setActionDetailModalOpen}
actionClasses={actionClasses}
actionClass={activeActionClass}
isReadOnly={isReadOnly}
otherEnvActionClasses={otherEnvActionClasses}
otherEnvironment={otherEnvironment}
/>
)}
</>

View File

@@ -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: <ActionActivityTab actionClass={actionClass} environmentId={environmentId} />,
children: (
<ActionActivityTab
otherEnvActionClasses={otherEnvActionClasses}
otherEnvironment={otherEnvironment}
isReadOnly={isReadOnly}
environment={environment}
actionClass={actionClass}
environmentId={environmentId}
/>
),
},
{
title: t("common.settings"),

View File

@@ -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 }) => {
<PageContentWrapper>
<PageHeader pageTitle={t("common.actions")} cta={!isReadOnly ? renderAddActionButton() : undefined} />
<ActionClassesTable
environment={currentEnvironment}
otherEnvironment={otherEnvironment}
otherEnvActionClasses={otherEnvActionClasses}
environmentId={params.environmentId}
actionClasses={actionClasses}
isReadOnly={isReadOnly}>

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",