From 08ce026c7a7b583889dd85a164d9c93eafaaa1be Mon Sep 17 00:00:00 2001 From: Matti Nannt Date: Mon, 8 Apr 2024 11:36:06 +0200 Subject: [PATCH] feat: add survey schedule option (#2386) Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com> Co-authored-by: Johannes Co-authored-by: Piyush Gupta --- .../workflows/cron-reportUsageToStripe.yml | 2 +- ...OnDate.yml => cron-surveyStatusUpdate.yml} | 4 +- .github/workflows/cron-weeklySummary.yml | 4 +- .../[surveyId]/components/SummaryHeader.tsx | 1 + .../components/SurveyStatusDropdown.tsx | 26 +-- .../edit/components/ResponseOptionsCard.tsx | 152 +++++++++++------- .../edit/components/SurveyMenuBar.tsx | 3 +- .../surveys/templates/templates.ts | 1 + apps/web/app/api/cron/close_surveys/route.ts | 45 ------ apps/web/app/api/cron/survey-status/route.ts | 70 ++++++++ .../email.ts | 0 .../route.ts | 0 .../types.ts | 0 .../[surveyId]/components/SurveyInactive.tsx | 2 +- apps/web/vercel.json | 2 +- docker/cronjobs | 4 +- .../migration.sql | 5 + packages/database/schema.prisma | 4 +- packages/lib/i18n/i18n.mock.ts | 1 + packages/lib/styling/constants.ts | 1 + packages/lib/survey/service.ts | 15 ++ .../lib/survey/tests/__mock__/survey.mock.ts | 1 + packages/lib/survey/util.ts | 3 + packages/types/surveys.ts | 5 +- packages/ui/DatePicker/index.tsx | 2 +- packages/ui/SurveyStatusIndicator/index.tsx | 19 ++- .../ui/SurveysList/components/SurveyCard.tsx | 9 +- .../SurveysList/components/SurveyFilters.tsx | 1 + 28 files changed, 252 insertions(+), 130 deletions(-) rename .github/workflows/{cron-closeOnDate.yml => cron-surveyStatusUpdate.yml} (87%) delete mode 100644 apps/web/app/api/cron/close_surveys/route.ts create mode 100644 apps/web/app/api/cron/survey-status/route.ts rename apps/web/app/api/cron/{weekly_summary => weekly-summary}/email.ts (100%) rename apps/web/app/api/cron/{weekly_summary => weekly-summary}/route.ts (100%) rename apps/web/app/api/cron/{weekly_summary => weekly-summary}/types.ts (100%) create mode 100644 packages/database/migrations/20240403141559_add_run_on_date_to_survey/migration.sql diff --git a/.github/workflows/cron-reportUsageToStripe.yml b/.github/workflows/cron-reportUsageToStripe.yml index 62a50b5fae..15892818a2 100644 --- a/.github/workflows/cron-reportUsageToStripe.yml +++ b/.github/workflows/cron-reportUsageToStripe.yml @@ -1,4 +1,4 @@ -name: Cron - reportUsageToStripe +name: Cron - Report usage to Stripe on: # "Scheduled workflows run on the latest commit on the default or base branch." diff --git a/.github/workflows/cron-closeOnDate.yml b/.github/workflows/cron-surveyStatusUpdate.yml similarity index 87% rename from .github/workflows/cron-closeOnDate.yml rename to .github/workflows/cron-surveyStatusUpdate.yml index dadfda8e01..95d40fb894 100644 --- a/.github/workflows/cron-closeOnDate.yml +++ b/.github/workflows/cron-surveyStatusUpdate.yml @@ -1,4 +1,4 @@ -name: Cron - closeOnDate +name: Cron - Survey status update on: # "Scheduled workflows run on the latest commit on the default or base branch." @@ -16,7 +16,7 @@ jobs: - name: cURL request if: ${{ env.APP_URL && env.CRON_SECRET }} run: | - curl ${{ env.APP_URL }}/api/cron/close_surveys \ + curl ${{ env.APP_URL }}/api/cron/survey-status \ -X POST \ -H 'content-type: application/json' \ -H 'x-api-key: ${{ env.CRON_SECRET }}' \ diff --git a/.github/workflows/cron-weeklySummary.yml b/.github/workflows/cron-weeklySummary.yml index 0014ff40b2..975128849a 100644 --- a/.github/workflows/cron-weeklySummary.yml +++ b/.github/workflows/cron-weeklySummary.yml @@ -1,4 +1,4 @@ -name: Cron - weeklySummary +name: Cron - Weekly summary on: # "Scheduled workflows run on the latest commit on the default or base branch." @@ -16,7 +16,7 @@ jobs: - name: cURL request if: ${{ env.APP_URL && env.CRON_SECRET }} run: | - curl ${{ env.APP_URL }}/api/cron/weekly_summary \ + curl ${{ env.APP_URL }}/api/cron/weekly-summary \ -X POST \ -H 'content-type: application/json' \ -H 'x-api-key: ${{ env.CRON_SECRET }}' \ diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SummaryHeader.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SummaryHeader.tsx index 6fb88a08d8..74957fd8e4 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SummaryHeader.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SummaryHeader.tsx @@ -119,6 +119,7 @@ const SummaryHeader = ({ )} + {survey.status === "scheduled" && "Scheduled"} {survey.status === "inProgress" && "In-progress"} {survey.status === "paused" && "Paused"} {survey.status === "completed" && "Completed"} diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx index 002b82893c..eb24167eb1 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/SurveyStatusDropdown.tsx @@ -10,18 +10,22 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { SurveyStatusIndicator } from "@formbricks/ui/SurveyStatusIndicator"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip"; +interface SurveyStatusDropdownProps { + environment: TEnvironment; + updateLocalSurveyStatus?: (status: TSurvey["status"]) => void; + survey: TSurvey; +} + export default function SurveyStatusDropdown({ environment, updateLocalSurveyStatus, survey, -}: { - environment: TEnvironment; - updateLocalSurveyStatus?: (status: "draft" | "inProgress" | "paused" | "completed" | "archived") => void; - survey: TSurvey; -}) { +}: SurveyStatusDropdownProps) { const isCloseOnDateEnabled = survey.closeOnDate !== null; const closeOnDate = survey.closeOnDate ? new Date(survey.closeOnDate) : null; - const isStatusChangeDisabled = (isCloseOnDateEnabled && closeOnDate && closeOnDate < new Date()) ?? false; + const isStatusChangeDisabled = + (survey.status === "scheduled" || (isCloseOnDateEnabled && closeOnDate && closeOnDate < new Date())) ?? + false; return ( <> @@ -34,7 +38,7 @@ export default function SurveyStatusDropdown({ value={survey.status} disabled={isStatusChangeDisabled} onValueChange={(value) => { - const castedValue = value as "draft" | "inProgress" | "paused" | "completed"; + const castedValue = value as TSurvey["status"]; updateSurveyAction({ ...survey, status: castedValue }) .then(() => { toast.success( @@ -51,8 +55,7 @@ export default function SurveyStatusDropdown({ toast.error(`Error: ${error.message}`); }); - if (updateLocalSurveyStatus) - updateLocalSurveyStatus(value as "draft" | "inProgress" | "paused" | "completed" | "archived"); + if (updateLocalSurveyStatus) updateLocalSurveyStatus(value as TSurvey["status"]); }}> @@ -64,6 +67,7 @@ export default function SurveyStatusDropdown({ )} + {survey.status === "scheduled" && "Scheduled"} {survey.status === "inProgress" && "In-progress"} {survey.status === "paused" && "Paused"} {survey.status === "completed" && "Completed"} @@ -88,8 +92,8 @@ export default function SurveyStatusDropdown({ - To update the survey status, update the “Close -
survey on date” setting in the Response Options. + To update the survey status, update the schedule and close setting in the survey response + options.
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ResponseOptionsCard.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ResponseOptionsCard.tsx index 364fa3746d..c3e57f2dff 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ResponseOptionsCard.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/ResponseOptionsCard.tsx @@ -1,7 +1,8 @@ "use client"; import * as Collapsible from "@radix-ui/react-collapsible"; -import { CheckIcon } from "lucide-react"; +import { ArrowUpRight, CheckIcon } from "lucide-react"; +import Link from "next/link"; import { KeyboardEventHandler, useEffect, useState } from "react"; import toast from "react-hot-toast"; @@ -27,7 +28,8 @@ export default function ResponseOptionsCard({ const [open, setOpen] = useState(localSurvey.type === "link" ? true : false); const autoComplete = localSurvey.autoComplete !== null; const [redirectToggle, setRedirectToggle] = useState(false); - const [surveyCloseOnDateToggle, setSurveyCloseOnDateToggle] = useState(false); + const [runOnDateToggle, setRunOnDateToggle] = useState(false); + const [closeOnDateToggle, setCloseOnDateToggle] = useState(false); useState; const [redirectUrl, setRedirectUrl] = useState(""); const [surveyClosedMessageToggle, setSurveyClosedMessageToggle] = useState(false); @@ -48,7 +50,8 @@ export default function ResponseOptionsCard({ name: "", subheading: "", }); - const [closeOnDate, setCloseOnDate] = useState(); + const [runOnDate, setRunOnDate] = useState(null); + const [closeOnDate, setCloseOnDate] = useState(null); const isPinProtectionEnabled = localSurvey.pin !== null; @@ -63,19 +66,28 @@ export default function ResponseOptionsCard({ } }; - const handleSurveyCloseOnDateToggle = () => { - if (surveyCloseOnDateToggle && localSurvey.closeOnDate) { - setSurveyCloseOnDateToggle(false); - setCloseOnDate(undefined); - setLocalSurvey({ ...localSurvey, closeOnDate: null }); - return; + const handleRunOnDateToggle = () => { + if (runOnDateToggle) { + setRunOnDateToggle(false); + if (localSurvey.runOnDate) { + setRunOnDate(null); + setLocalSurvey({ ...localSurvey, runOnDate: null }); + } + } else { + setRunOnDateToggle(true); } + }; - if (surveyCloseOnDateToggle) { - setSurveyCloseOnDateToggle(false); - return; + const handleCloseOnDateToggle = () => { + if (closeOnDateToggle) { + setCloseOnDateToggle(false); + if (localSurvey.closeOnDate) { + setCloseOnDate(null); + setLocalSurvey({ ...localSurvey, closeOnDate: null }); + } + } else { + setCloseOnDateToggle(true); } - setSurveyCloseOnDateToggle(true); }; const handleProtectSurveyWithPinToggle = () => { @@ -126,6 +138,15 @@ export default function ResponseOptionsCard({ } }; + const handleRunOnDateChange = (date: Date) => { + const equivalentDate = date?.getDate(); + date?.setUTCHours(0, 0, 0, 0); + date?.setDate(equivalentDate); + + setRunOnDate(date); + setLocalSurvey({ ...localSurvey, runOnDate: date ?? null }); + }; + const handleCloseOnDateChange = (date: Date) => { const equivalentDate = date?.getDate(); date?.setUTCHours(0, 0, 0, 0); @@ -143,7 +164,7 @@ export default function ResponseOptionsCard({ subheading?: string; }) => { const message = { - enabled: surveyCloseOnDateToggle, + enabled: closeOnDateToggle, heading: heading ?? surveyClosedMessage.heading, subheading: subheading ?? surveyClosedMessage.subheading, }; @@ -245,9 +266,14 @@ export default function ResponseOptionsCard({ setVerifyEmailToggle(true); } + if (localSurvey.runOnDate) { + setRunOnDate(localSurvey.runOnDate); + setRunOnDateToggle(true); + } + if (localSurvey.closeOnDate) { setCloseOnDate(localSurvey.closeOnDate); - setSurveyCloseOnDateToggle(true); + setCloseOnDateToggle(true); } }, [ localSurvey, @@ -318,7 +344,7 @@ export default function ResponseOptionsCard({ description="Automatically close the survey after a certain number of responses." childBorder={true}>