fix: survey preview not working for suid enabled link surveys backport (#6259)

This commit is contained in:
Piyush Gupta
2025-07-18 17:41:14 +05:30
committed by GitHub
parent 908614b4e2
commit ec78038caa
4 changed files with 74 additions and 32 deletions

View File

@@ -5,6 +5,7 @@ import { ShareSurveyModal } from "@/app/(app)/environments/[environmentId]/surve
import { SurveyStatusDropdown } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { EditPublicSurveyAlertDialog } from "@/modules/survey/components/edit-public-survey-alert-dialog";
import { useSingleUseId } from "@/modules/survey/hooks/useSingleUseId";
import { copySurveyToOtherEnvironmentAction } from "@/modules/survey/list/actions";
import { Badge } from "@/modules/ui/components/badge";
import { Button } from "@/modules/ui/components/button";
@@ -12,7 +13,7 @@ import { IconBar } from "@/modules/ui/components/iconbar";
import { useTranslate } from "@tolgee/react";
import { BellRing, Eye, SquarePenIcon } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { TEnvironment } from "@formbricks/types/environment";
import { TSegment } from "@formbricks/types/segment";
@@ -48,17 +49,16 @@ export const SurveyAnalysisCTA = ({
isFormbricksCloud,
}: SurveyAnalysisCTAProps) => {
const { t } = useTranslate();
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [loading, setLoading] = useState(false);
const [modalState, setModalState] = useState<ModalState>({
start: searchParams.get("share") === "true",
share: false,
});
const surveyUrl = useMemo(() => `${publicDomain}/s/${survey.id}`, [survey.id, publicDomain]);
const { refreshSingleUseId } = useSingleUseId(survey);
const widgetSetupCompleted = survey.type === "app" && environment.appSetupCompleted;
@@ -102,9 +102,18 @@ export const SurveyAnalysisCTA = ({
setLoading(false);
};
const getPreviewUrl = () => {
const separator = surveyUrl.includes("?") ? "&" : "?";
return `${surveyUrl}${separator}preview=true`;
const getPreviewUrl = async () => {
const surveyUrl = new URL(`${publicDomain}/s/${survey.id}`);
if (survey.singleUse?.enabled) {
const newId = await refreshSingleUseId();
if (newId) {
surveyUrl.searchParams.set("suId", newId);
}
}
surveyUrl.searchParams.set("preview", "true");
return surveyUrl.toString();
};
const [isCautionDialogOpen, setIsCautionDialogOpen] = useState(false);
@@ -119,7 +128,10 @@ export const SurveyAnalysisCTA = ({
{
icon: Eye,
tooltip: t("common.preview"),
onClick: () => window.open(getPreviewUrl(), "_blank"),
onClick: async () => {
const previewUrl = await getPreviewUrl();
window.open(previewUrl, "_blank");
},
isVisible: survey.type === "link",
},
{

View File

@@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
import { afterEach, describe, expect, test, vi } from "vitest";
import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import { getSurveyUrl } from "../../utils";
vi.mock("react-hot-toast", () => ({
toast: {
@@ -11,6 +12,24 @@ vi.mock("react-hot-toast", () => ({
},
}));
// Mock the useSingleUseId hook
vi.mock("@/modules/survey/hooks/useSingleUseId", () => ({
useSingleUseId: vi.fn(() => ({
singleUseId: "test-single-use-id",
refreshSingleUseId: vi.fn().mockResolvedValue("test-single-use-id"),
})),
}));
// Mock the survey utils
vi.mock("../../utils", () => ({
getSurveyUrl: vi.fn((survey, publicDomain, language) => {
if (language && language !== "en") {
return `${publicDomain}/s/${survey.id}?lang=${language}`;
}
return `${publicDomain}/s/${survey.id}`;
}),
}));
const survey: TSurvey = {
id: "survey-id",
name: "Test Survey",
@@ -161,7 +180,7 @@ describe("ShareSurveyLink", () => {
expect(toast.success).toHaveBeenCalledWith("common.copied_to_clipboard");
});
test("opens the preview link in a new tab when preview button is clicked (no query params)", () => {
test("opens the preview link in a new tab when preview button is clicked (no query params)", async () => {
render(
<ShareSurveyLink
survey={survey}
@@ -175,10 +194,13 @@ describe("ShareSurveyLink", () => {
const previewButton = screen.getByLabelText("environments.surveys.preview_survey_in_a_new_tab");
fireEvent.click(previewButton);
// Wait for the async function to complete
await new Promise((resolve) => setTimeout(resolve, 0));
expect(global.open).toHaveBeenCalledWith(`${surveyUrl}?preview=true`, "_blank");
});
test("opens the preview link in a new tab when preview button is clicked (with query params)", () => {
test("opens the preview link in a new tab when preview button is clicked (with query params)", async () => {
const surveyWithParamsUrl = `${publicDomain}/s/survey-id?foo=bar`;
render(
<ShareSurveyLink
@@ -193,6 +215,9 @@ describe("ShareSurveyLink", () => {
const previewButton = screen.getByLabelText("environments.surveys.preview_survey_in_a_new_tab");
fireEvent.click(previewButton);
// Wait for the async function to complete
await new Promise((resolve) => setTimeout(resolve, 0));
expect(global.open).toHaveBeenCalledWith(`${surveyWithParamsUrl}&preview=true`, "_blank");
});
@@ -215,7 +240,9 @@ describe("ShareSurveyLink", () => {
});
test("updates the survey URL when the language is changed", () => {
const { rerender } = render(
const mockGetSurveyUrl = vi.mocked(getSurveyUrl);
render(
<ShareSurveyLink
survey={survey}
publicDomain={publicDomain}
@@ -231,16 +258,7 @@ describe("ShareSurveyLink", () => {
const germanOption = screen.getByText("German");
fireEvent.click(germanOption);
rerender(
<ShareSurveyLink
survey={survey}
publicDomain={publicDomain}
surveyUrl={surveyUrl}
setSurveyUrl={setSurveyUrl}
locale={locale}
/>
);
expect(setSurveyUrl).toHaveBeenCalled();
expect(surveyUrl).toContain("lang=de");
expect(mockGetSurveyUrl).toHaveBeenCalledWith(survey, publicDomain, "de");
expect(setSurveyUrl).toHaveBeenCalledWith(`${publicDomain}/s/${survey.id}?lang=de`);
});
});

View File

@@ -1,5 +1,6 @@
"use client";
import { useSingleUseId } from "@/modules/survey/hooks/useSingleUseId";
import { Button } from "@/modules/ui/components/button";
import { useTranslate } from "@tolgee/react";
import { Copy, SquareArrowOutUpRight } from "lucide-react";
@@ -32,6 +33,22 @@ export const ShareSurveyLink = ({
setSurveyUrl(url);
};
const { refreshSingleUseId } = useSingleUseId(survey);
const getPreviewUrl = async () => {
const previewUrl = new URL(surveyUrl);
if (survey.singleUse?.enabled) {
const newId = await refreshSingleUseId();
if (newId) {
previewUrl.searchParams.set("suId", newId);
}
}
previewUrl.searchParams.set("preview", "true");
return previewUrl.toString();
};
return (
<div className={"flex max-w-full flex-col items-center justify-center gap-2 md:flex-row"}>
<SurveyLinkDisplay surveyUrl={surveyUrl} key={surveyUrl} />
@@ -53,14 +70,9 @@ export const ShareSurveyLink = ({
title={t("environments.surveys.preview_survey_in_a_new_tab")}
aria-label={t("environments.surveys.preview_survey_in_a_new_tab")}
disabled={!surveyUrl}
onClick={() => {
let previewUrl = surveyUrl;
if (previewUrl.includes("?")) {
previewUrl += "&preview=true";
} else {
previewUrl += "?preview=true";
}
window.open(previewUrl, "_blank");
onClick={async () => {
const url = await getPreviewUrl();
window.open(url, "_blank");
}}>
{t("common.preview")}
<SquareArrowOutUpRight />

View File

@@ -14,7 +14,7 @@ export const useSingleUseId = (survey: TSurvey | TSurveyList) => {
if (survey.singleUse?.enabled) {
const response = await generateSingleUseIdsAction({
surveyId: survey.id,
isEncrypted: !!survey.singleUse?.isEncrypted,
isEncrypted: Boolean(survey.singleUse?.isEncrypted),
count: 1,
});