chore: server side pagination for responses (#1869)

Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Dhruwang Jariwala
2024-01-10 14:12:40 +05:30
committed by GitHub
parent 1d7d07b3c6
commit a1fa3d6dbb
4 changed files with 50 additions and 19 deletions

View File

@@ -1,7 +1,24 @@
"use server";
import { getServerSession } from "next-auth";
import { revalidatePath } from "next/cache";
import { authOptions } from "@formbricks/lib/authOptions";
import { getResponses } from "@formbricks/lib/response/service";
import { canUserAccessSurvey } from "@formbricks/lib/survey/auth";
import { AuthorizationError } from "@formbricks/types/errors";
import { TResponse } from "@formbricks/types/responses";
export default async function revalidateSurveyIdPath(environmentId: string, surveyId: string) {
revalidatePath(`/environments/${environmentId}/surveys/${surveyId}`);
}
export async function getMoreResponses(surveyId: string, page: number): Promise<TResponse[]> {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
const isAuthorized = await canUserAccessSurvey(session.user.id, surveyId);
if (!isAuthorized) throw new AuthorizationError("Not authorized");
const responses = await getResponses(surveyId, page);
return responses;
}

View File

@@ -1,5 +1,6 @@
"use client";
import { getMoreResponses } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions";
import EmptyInAppSurveys from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/EmptyInAppSurveys";
import React, { useEffect, useRef, useState } from "react";
@@ -29,22 +30,27 @@ export default function ResponseTimeline({
environmentTags,
responsesPerPage,
}: ResponseTimelineProps) {
const [displayedResponses, setDisplayedResponses] = useState<TResponse[]>([]);
const loadingRef = useRef(null);
useEffect(() => {
setDisplayedResponses(responses.slice(0, responsesPerPage));
}, [responses, setDisplayedResponses, responsesPerPage]);
const [fetchedResponses, setFetchedResponses] = useState<TResponse[]>(responses);
const [page, setPage] = useState(2);
const [hasMoreResponses, setHasMoreResponses] = useState<boolean>(responses.length > 0);
useEffect(() => {
const currentLoadingRef = loadingRef.current;
const loadResponses = async () => {
const newResponses = await getMoreResponses(survey.id, page);
if (newResponses.length === 0) {
setHasMoreResponses(false);
} else {
setPage(page + 1);
}
setFetchedResponses((prevResponses) => [...prevResponses, ...newResponses]);
};
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setDisplayedResponses((prevResponses) => [
...prevResponses,
...responses.slice(prevResponses.length, prevResponses.length + responsesPerPage),
]);
if (hasMoreResponses) loadResponses();
}
},
{ threshold: 0.8 }
@@ -59,13 +65,13 @@ export default function ResponseTimeline({
observer.unobserve(currentLoadingRef);
}
};
}, [responses, responsesPerPage]);
}, [responses, responsesPerPage, page, survey.id, fetchedResponses.length, hasMoreResponses]);
return (
<div className="space-y-4">
{survey.type === "web" && displayedResponses.length === 0 && !environment.widgetSetupCompleted ? (
{survey.type === "web" && fetchedResponses.length === 0 && !environment.widgetSetupCompleted ? (
<EmptyInAppSurveys environment={environment} />
) : displayedResponses.length === 0 ? (
) : fetchedResponses.length === 0 ? (
<EmptySpaceFiller
type="response"
environment={environment}
@@ -73,7 +79,7 @@ export default function ResponseTimeline({
/>
) : (
<div>
{displayedResponses.map((response) => {
{fetchedResponses.map((response) => {
return (
<div key={response.id}>
<SingleResponseCard

View File

@@ -1,4 +1,3 @@
import { getAnalysisData } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/data";
import ResponsePage from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponsePage";
import { getServerSession } from "next-auth";
@@ -7,6 +6,8 @@ import { RESPONSES_PER_PAGE, WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdTeamId } from "@formbricks/lib/membership/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getResponses } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/survey/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { getUser } from "@formbricks/lib/user/service";
@@ -16,13 +17,18 @@ export default async function Page({ params }) {
if (!session) {
throw new Error("Unauthorized");
}
const [{ responses, survey }, environment] = await Promise.all([
getAnalysisData(params.surveyId, params.environmentId),
const [responses, survey, environment] = await Promise.all([
getResponses(params.surveyId, 1),
getSurvey(params.surveyId),
getEnvironment(params.environmentId),
]);
if (!environment) {
throw new Error("Environment not found");
}
if (!survey) {
throw new Error("Survey not found");
}
const product = await getProductByEnvironmentId(environment.id);
if (!product) {
throw new Error("Product not found");

View File

@@ -32,6 +32,8 @@ import { formatDateFields } from "../utils/datetime";
import { validateInputs } from "../utils/validate";
import { responseCache } from "./cache";
const RESPONSES_PER_PAGE = 10;
export const responseSelection = {
id: true,
createdAt: true,
@@ -398,8 +400,8 @@ export const getResponses = async (surveyId: string, page?: number): Promise<TRe
createdAt: "desc",
},
],
take: page ? ITEMS_PER_PAGE : undefined,
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
take: page ? RESPONSES_PER_PAGE : undefined,
skip: page ? RESPONSES_PER_PAGE * (page - 1) : undefined,
});
const transformedResponses: TResponse[] = await Promise.all(
@@ -421,7 +423,7 @@ export const getResponses = async (surveyId: string, page?: number): Promise<TRe
throw error;
}
},
[`getResponses-${surveyId}`],
[`getResponses-${surveyId}-${page}`],
{
tags: [responseCache.tag.bySurveyId(surveyId)],
revalidate: SERVICES_REVALIDATION_INTERVAL,