Skip to content

Commit

Permalink
Merge branch 'main' into save-to-session
Browse files Browse the repository at this point in the history
  • Loading branch information
timarney authored Dec 12, 2024
2 parents 4fc17e3 + 0ffdaca commit e21d6ec
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ import {
JSONResponse,
} from "@lib/responseDownloadFormats/types";
import { getFullTemplateByID } from "@lib/templates";
import { AddressComponents, FormElement, FormElementTypes, VaultStatus } from "@lib/types";
import {
AddressComponents,
FormElement,
FormElementTypes,
StartFromExclusiveResponse,
VaultStatus,
} from "@lib/types";
import { isResponseId } from "@lib/validation/validation";
import {
confirmResponses,
Expand Down Expand Up @@ -70,7 +76,6 @@ export const fetchSubmissions = async ({
if (!formId) {
return {
submissions: [],
lastEvaluatedKey: null,
};
}

Expand All @@ -80,29 +85,38 @@ export const fetchSubmissions = async ({
? (ucfirst(status) as VaultStatus)
: VaultStatus.NEW;

let currentLastEvaluatedKey = null;

// build up lastEvaluatedKey from lastKey url param
if (lastKey && isResponseId(String(lastKey))) {
currentLastEvaluatedKey = {
Status: selectedStatus,
NAME_OR_CONF: `NAME#${lastKey}`,
FormID: formId,
};
let startFromExclusiveResponse: StartFromExclusiveResponse | undefined = undefined;

// build up startFromExclusiveResponse from lastKey url param
if (lastKey) {
const splitLastKey = lastKey.split("_");

// Make sure both components of lastKey are valid
if (
isResponseId(String(splitLastKey[0])) &&
isNaN(new Date(Number(splitLastKey[1])).getTime()) === false
) {
startFromExclusiveResponse = {
name: splitLastKey[0],
status: selectedStatus,
createdAt: Number(splitLastKey[1]),
};
}
}

const { submissions, lastEvaluatedKey } = await listAllSubmissions(
ability,
formId,
selectedStatus,
undefined,
currentLastEvaluatedKey
);
const { submissions, startFromExclusiveResponse: nextStartFromExclusiveResponse } =
await listAllSubmissions(
ability,
formId,
selectedStatus,
undefined,
startFromExclusiveResponse
);

return { submissions, lastEvaluatedKey };
return { submissions, startFromExclusiveResponse: nextStartFromExclusiveResponse };
} catch (e) {
logMessage.error(`Error fetching submissions for form ${formId}: ${(e as Error).message}`);
return { error: true, submissions: [], lastEvaluatedKey: null };
return { error: true, submissions: [] };
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React, { useEffect, useReducer, useState } from "react";
import { NagwareResult, VaultStatus, VaultSubmissionOverview } from "@lib/types";
import {
NagwareResult,
StartFromExclusiveResponse,
VaultStatus,
VaultSubmissionOverview,
} from "@lib/types";
import { useTranslation } from "@i18n/client";
import { SkipLinkReusable } from "@clientComponents/globals/SkipLinkReusable";
import Link from "next/link";
Expand Down Expand Up @@ -28,7 +33,7 @@ interface DownloadTableProps {
formId: string;
nagwareResult: NagwareResult | null;
responseDownloadLimit: number;
lastEvaluatedKey?: Record<string, string> | null;
startFromExclusiveResponse: StartFromExclusiveResponse | null;
overdueAfter: number;
statusFilter: StatusFilter;
}
Expand All @@ -39,7 +44,7 @@ export const DownloadTable = ({
formId,
nagwareResult,
responseDownloadLimit,
lastEvaluatedKey,
startFromExclusiveResponse,
overdueAfter,
statusFilter,
}: DownloadTableProps) => {
Expand Down Expand Up @@ -184,7 +189,7 @@ export const DownloadTable = ({
<tr className="border-y-1 border-slate-400 bg-slate-100 py-2">
<td colSpan={5} className="px-4 py-2">
<Pagination
lastEvaluatedKey={lastEvaluatedKey}
startFromExclusiveResponse={startFromExclusiveResponse}
formId={formId}
responseDownloadLimit={responseDownloadLimit}
recordCount={vaultSubmissions.length}
Expand Down Expand Up @@ -266,7 +271,7 @@ export const DownloadTable = ({
<tr className="border-y-1 border-slate-300 bg-slate-100 py-2">
<td colSpan={5} className="px-4 py-2">
<Pagination
lastEvaluatedKey={lastEvaluatedKey}
startFromExclusiveResponse={startFromExclusiveResponse}
formId={formId}
responseDownloadLimit={responseDownloadLimit}
recordCount={vaultSubmissions.length}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Link from "next/link";
import { useRouter, useSearchParams, useParams } from "next/navigation";
import { BackArrowIcon, ForwardArrowIcon, StartIcon } from "@serverComponents/icons";
import { useTranslation } from "@i18n/client";
import { StartFromExclusiveResponse } from "@lib/types";

const decodeBase64Url = (base64Url: string) => {
const pureBase64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
Expand All @@ -15,12 +16,12 @@ const encodeBase64Url = (payload: string) => {
};

export const Pagination = ({
lastEvaluatedKey,
startFromExclusiveResponse,
formId,
responseDownloadLimit,
recordCount,
}: {
lastEvaluatedKey: Record<string, string> | null | undefined;
startFromExclusiveResponse: StartFromExclusiveResponse | null;
formId: string;
responseDownloadLimit: number;
recordCount: number;
Expand All @@ -33,12 +34,11 @@ export const Pagination = ({
const searchParams = useSearchParams();
const { statusFilter } = useParams<{ statusFilter: string }>();

// Extract responseId from lastEvaluatedKey object
const lastEvaluatedResponseId = lastEvaluatedKey
? lastEvaluatedKey.NAME_OR_CONF.split("#")[1]
: "end"; // If lastEvaluatedKey is null, we're on the last page
const lastEvaluatedResponse = startFromExclusiveResponse
? `${startFromExclusiveResponse.name}_${startFromExclusiveResponse.createdAt}`
: "end"; // If startFromExclusiveResponse is null, we're on the last page

// Keep track of the last evaluated key for each of our pages.
// Keep track of the startFromExclusiveResponse for each of our pages.
// The first item in the array is always "start" and the last item is always "end"
const [keys, setKeys] = useState<string[]>(() => {
try {
Expand All @@ -50,9 +50,7 @@ export const Pagination = ({
} catch (e) {
// If the base64 encoded string has been tampered with, redirect to the first page
router.push(
`/${language}/form-builder/${formId}/responses/${
statusFilter ? `/${statusFilter}` : "/new"
}`
`/${language}/form-builder/${formId}/responses/${statusFilter ? `/${statusFilter}` : "/"}`
);
// Needed to satisfy typescript as router.push returns void and not never
return ["start"];
Expand All @@ -62,30 +60,30 @@ export const Pagination = ({
// When going back, we pop the last item off the keys array
const previousKeys = keys.slice(0, -1);

// When going back, we need the lastEvaluatedResponseId of the previous page
const previousLastEvaluatedResponseId = keys[keys.indexOf(lastEvaluatedResponseId) - 2];
// When going back, we need the lastEvaluatedResponse of the previous page
const previousLastEvaluatedResponse = keys[keys.indexOf(lastEvaluatedResponse) - 2];

// Used to determine "start" and "end" for the current page
const currentPageNumber = keys.indexOf(lastEvaluatedResponseId);
const currentPageNumber = keys.indexOf(lastEvaluatedResponse);

// If we're going back to the first page, just load the base url in case there are newer responses waiting
let previousLink = "";
if (previousLastEvaluatedResponseId !== "start") {
if (previousLastEvaluatedResponse !== "start") {
previousLink = `?keys=${encodeBase64Url(
previousKeys.join(",")
)}&lastKey=${previousLastEvaluatedResponseId}`;
)}&lastKey=${previousLastEvaluatedResponse}`;
}

// Only append the lastEvaluatedResponseId to the keys array if it's not already there
if (!keys.includes(lastEvaluatedResponseId)) {
setKeys([...keys, lastEvaluatedResponseId]);
// Only append the lastEvaluatedResponse to the keys array if it's not already there
if (!keys.includes(lastEvaluatedResponse)) {
setKeys([...keys, lastEvaluatedResponse]);
}

// lastEvaluatedKey is null when we're on the last page
const isLastPage = lastEvaluatedKey === null;
// startFromExclusiveResponse is null when we're on the last page
const isLastPage = startFromExclusiveResponse === null;

// previousLastEvaluatedResponseId is undefined when we're on the first page
const isFirstPage = previousLastEvaluatedResponseId === undefined;
// previousLastEvaluatedResponse is undefined when we're on the first page
const isFirstPage = previousLastEvaluatedResponse === undefined;

// Calculate the start and end of the current page
const start = responseDownloadLimit * (currentPageNumber - 1) + 1;
Expand All @@ -95,13 +93,13 @@ export const Pagination = ({
<>
<Link
href={`/${language}/form-builder/${formId}/responses${
statusFilter ? `/${statusFilter}` : "/new"
statusFilter ? `/${statusFilter}` : "/"
}`}
legacyBehavior
>
<a
href={`/${language}/form-builder/${formId}/responses${
statusFilter ? `/${statusFilter}` : "/new"
statusFilter ? `/${statusFilter}` : "/"
}`}
className={`group mr-4 inline-block ${
isFirstPage ? "pointer-events-none opacity-50" : ""
Expand All @@ -116,13 +114,13 @@ export const Pagination = ({
<div className="float-right inline-block">
<Link
href={`/${language}/form-builder/${formId}/responses${
statusFilter ? `/${statusFilter}` : "/new"
statusFilter ? `/${statusFilter}` : "/"
}${previousLink}`}
legacyBehavior
>
<a
href={`/${language}/form-builder/${formId}/responses${
statusFilter ? `/${statusFilter}` : "/new"
statusFilter ? `/${statusFilter}` : "/"
}${previousLink}`}
className={`group mr-4 inline-block ${
isFirstPage ? "pointer-events-none opacity-50" : ""
Expand All @@ -136,14 +134,14 @@ export const Pagination = ({
{t("downloadResponsesTable.header.pagination.showing", { start, end })}
<Link
href={`/${language}/form-builder/${formId}/responses${
statusFilter ? `/${statusFilter}` : "/new"
}?keys=${encodeBase64Url(keys.join(","))}&lastKey=${lastEvaluatedResponseId}`}
statusFilter ? `/${statusFilter}` : "/"
}?keys=${encodeBase64Url(keys.join(","))}&lastKey=${lastEvaluatedResponse}`}
legacyBehavior
>
<a
href={`/${language}/form-builder/${formId}/responses${
statusFilter ? `/${statusFilter}` : "/new"
}?keys=${encodeBase64Url(keys.join(","))}&lastKey=${lastEvaluatedResponseId}`}
statusFilter ? `/${statusFilter}` : "/"
}?keys=${encodeBase64Url(keys.join(","))}&lastKey=${lastEvaluatedResponse}`}
className={`group ml-4 inline-block ${
isLastPage ? "pointer-events-none opacity-50" : ""
}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DownloadTable } from "./DownloadTable";
import { NoResponses } from "./NoResponses";
import { useTemplateStore } from "@lib/store/useTemplateStore";
import { TitleAndDescription } from "./TitleAndDescription";
import { NagLevel, VaultSubmissionOverview } from "@lib/types";
import { NagLevel, StartFromExclusiveResponse, VaultSubmissionOverview } from "@lib/types";
import { RetrievalError } from "./RetrievalError";
import { fetchSubmissions } from "../actions";
import { StatusFilter } from "../types";
Expand Down Expand Up @@ -48,26 +48,26 @@ export const Responses = ({
const [state, setState] = useState<{
loading: boolean;
submissions: VaultSubmissionOverview[];
lastEvaluatedKey: Record<string, string> | null | undefined;
startFromExclusiveResponse: StartFromExclusiveResponse | null;
error: boolean;
}>({ loading: true, submissions: [], lastEvaluatedKey: null, error: false });
}>({ loading: true, submissions: [], startFromExclusiveResponse: null, error: false });

useEffect(() => {
fetchSubmissions({
formId,
lastKey,
status: statusFilter,
lastKey,
})
.then(({ submissions, lastEvaluatedKey, error }) => {
.then(({ submissions, startFromExclusiveResponse, error }) => {
setState({
loading: false,
submissions,
lastEvaluatedKey: lastEvaluatedKey,
startFromExclusiveResponse: startFromExclusiveResponse ?? null,
error: Boolean(error),
});
})
.catch(() =>
setState({ loading: false, submissions: [], lastEvaluatedKey: null, error: true })
setState({ loading: false, submissions: [], startFromExclusiveResponse: null, error: true })
);
}, [formId, lastKey, statusFilter, forceRefresh]);

Expand Down Expand Up @@ -97,7 +97,7 @@ export const Responses = ({
formId={formId}
nagwareResult={nagwareResult}
responseDownloadLimit={responseDownloadLimit}
lastEvaluatedKey={state.lastEvaluatedKey}
startFromExclusiveResponse={state.startFromExclusiveResponse}
overdueAfter={overdueAfter}
/>
</>
Expand Down Expand Up @@ -132,7 +132,7 @@ export const Responses = ({
formId={formId}
nagwareResult={nagwareResult}
responseDownloadLimit={responseDownloadLimit}
lastEvaluatedKey={state.lastEvaluatedKey}
startFromExclusiveResponse={state.startFromExclusiveResponse}
overdueAfter={overdueAfter}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("Download Table", () => {
nagwareResult={null}
responseDownloadLimit={0}
overdueAfter={0}
startFromExclusiveResponse={null}
/>
);
const table = rendered.getByRole("table");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@ export const ProblemsReported = ({ formId }: { formId: string }) => {
const [problemSubmissions, setProblemSubmissions] = useState<{
loading: boolean;
submissions: VaultSubmissionOverview[];
lastEvaluatedKey: Record<string, string> | null | undefined;
error: boolean;
}>({ loading: true, submissions: [], lastEvaluatedKey: null, error: false });
}>({ loading: true, submissions: [], error: false });

useEffect(() => {
fetchSubmissions({
formId,
lastKey: null,
status: VaultStatus.PROBLEM,
lastKey: null,
})
.then(({ submissions, error }) => {
setProblemSubmissions({
loading: false,
lastEvaluatedKey: null,
submissions,
error: Boolean(error),
});
Expand All @@ -35,7 +33,6 @@ export const ProblemsReported = ({ formId }: { formId: string }) => {
setProblemSubmissions({
loading: false,
submissions: [],
lastEvaluatedKey: null,
error: true,
})
);
Expand Down
6 changes: 5 additions & 1 deletion lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export type {
SearchParams,
} from "./utility-types";

export type { VaultSubmission, VaultSubmissionOverview } from "./retrieval-types";
export type {
VaultSubmission,
VaultSubmissionOverview,
StartFromExclusiveResponse,
} from "./retrieval-types";

export { VaultStatus } from "./retrieval-types";

Expand Down
6 changes: 6 additions & 0 deletions lib/types/retrieval-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ export type VaultSubmissionOverview = {
createdAt: number;
status: VaultStatus;
};

export type StartFromExclusiveResponse = {
name: string;
status: string;
createdAt: number;
};
Loading

0 comments on commit e21d6ec

Please sign in to comment.