From cc5fde38c4229d8b18cdcbe1696603ac87ecc1c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:12:44 +0000 Subject: [PATCH 1/7] Initial plan From 5a34d687711c59fcd5729fd31c6d3ce10f53315b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:16:39 +0000 Subject: [PATCH 2/7] Add DataRetentionSelector and AdminDataRetention components Co-authored-by: duckbytes <32309223+duckbytes@users.noreply.github.com> --- src/components/DataRetentionSelector.test.tsx | 88 +++++++++++++++ src/components/DataRetentionSelector.tsx | 78 +++++++++++++ src/scenes/AdminControl/AdminControl.js | 2 + .../Components/AdminDataRetention.test.tsx | 97 +++++++++++++++++ .../Components/AdminDataRetention.tsx | 103 ++++++++++++++++++ 5 files changed, 368 insertions(+) create mode 100644 src/components/DataRetentionSelector.test.tsx create mode 100644 src/components/DataRetentionSelector.tsx create mode 100644 src/scenes/AdminControl/Components/AdminDataRetention.test.tsx create mode 100644 src/scenes/AdminControl/Components/AdminDataRetention.tsx diff --git a/src/components/DataRetentionSelector.test.tsx b/src/components/DataRetentionSelector.test.tsx new file mode 100644 index 000000000..d2a9f4adb --- /dev/null +++ b/src/components/DataRetentionSelector.test.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import DataRetentionSelector, { + TimeUnit, + DataRetentionValue, +} from "./DataRetentionSelector"; + +describe("DataRetentionSelector", () => { + const defaultValue: DataRetentionValue = { + value: 30, + unit: TimeUnit.DAYS, + }; + + test("renders with default value", () => { + const onChange = jest.fn(); + render(); + + expect(screen.getByLabelText("Retention Time")).toHaveValue(30); + expect(screen.getByLabelText("Unit")).toHaveTextContent("Days"); + }); + + test("calls onChange when value changes", () => { + const onChange = jest.fn(); + render(); + + const valueInput = screen.getByLabelText("Retention Time"); + userEvent.clear(valueInput); + userEvent.type(valueInput, "60"); + + expect(onChange).toHaveBeenCalledWith({ + value: 60, + unit: TimeUnit.DAYS, + }); + }); + + test("calls onChange when unit changes", () => { + const onChange = jest.fn(); + render(); + + const unitSelect = screen.getByLabelText("Unit"); + userEvent.click(unitSelect); + userEvent.click(screen.getByText("Weeks")); + + expect(onChange).toHaveBeenCalledWith({ + value: 30, + unit: TimeUnit.WEEKS, + }); + }); + + test("displays all time unit options", () => { + const onChange = jest.fn(); + render(); + + const unitSelect = screen.getByLabelText("Unit"); + userEvent.click(unitSelect); + + expect(screen.getByText("Days")).toBeInTheDocument(); + expect(screen.getByText("Weeks")).toBeInTheDocument(); + expect(screen.getByText("Months")).toBeInTheDocument(); + expect(screen.getByText("Years")).toBeInTheDocument(); + }); + + test("can be disabled", () => { + const onChange = jest.fn(); + render( + + ); + + const valueInput = screen.getByLabelText("Retention Time"); + const unitSelect = screen.getByLabelText("Unit"); + + expect(valueInput).toBeDisabled(); + expect(unitSelect).toBeDisabled(); + }); + + test("does not allow negative values", () => { + const onChange = jest.fn(); + render(); + + const valueInput = screen.getByLabelText("Retention Time"); + expect(valueInput).toHaveAttribute("min", "0"); + }); +}); diff --git a/src/components/DataRetentionSelector.tsx b/src/components/DataRetentionSelector.tsx new file mode 100644 index 000000000..dc9fd0f85 --- /dev/null +++ b/src/components/DataRetentionSelector.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import { Grid, TextField, MenuItem } from "@mui/material"; + +export enum TimeUnit { + DAYS = "days", + WEEKS = "weeks", + MONTHS = "months", + YEARS = "years", +} + +export type DataRetentionValue = { + value: number; + unit: TimeUnit; +}; + +type DataRetentionSelectorProps = { + value: DataRetentionValue; + onChange: (value: DataRetentionValue) => void; + disabled?: boolean; +}; + +const DataRetentionSelector: React.FC = ({ + value, + onChange, + disabled = false, +}) => { + const handleValueChange = (event: React.ChangeEvent) => { + const newValue = parseInt(event.target.value, 10); + if (newValue >= 0 || event.target.value === "") { + onChange({ + ...value, + value: newValue || 0, + }); + } + }; + + const handleUnitChange = (event: React.ChangeEvent) => { + onChange({ + ...value, + unit: event.target.value as TimeUnit, + }); + }; + + return ( + + + + + + + Days + Weeks + Months + Years + + + + ); +}; + +export default DataRetentionSelector; diff --git a/src/scenes/AdminControl/AdminControl.js b/src/scenes/AdminControl/AdminControl.js index 8ed315556..fdc0460a4 100644 --- a/src/scenes/AdminControl/AdminControl.js +++ b/src/scenes/AdminControl/AdminControl.js @@ -5,6 +5,7 @@ import AdminAddVehicle from "./Components/AdminAddVehicle"; import AdminAddLocation from "./Components/AdminAddLocation"; import AdminAddDeliverableType from "./Components/AdminAddDeliverableType"; import AdminAddRiderResponsibility from "./Components/AdminAddRiderResponsibility"; +import AdminDataRetention from "./Components/AdminDataRetention"; import { Stack } from "@mui/material"; import { RiderResponsibilityChips } from "./Components/RiderResponsibilityChips"; import { DeliverableTypeChips } from "./Components/DeliverableTypeChips"; @@ -12,6 +13,7 @@ import { DeliverableTypeChips } from "./Components/DeliverableTypeChips"; export function AdminControl() { return ( + diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx new file mode 100644 index 000000000..ed1d1f58e --- /dev/null +++ b/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx @@ -0,0 +1,97 @@ +import React from "react"; +import AdminDataRetention from "./AdminDataRetention"; +import { render } from "../../../test-utils"; +import * as models from "../../../models"; +import { userRoles } from "../../../apiConsts"; +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +const preloadedState = { + whoami: { + user: new models.User({ roles: [userRoles.user, userRoles.admin] }), + }, + loadingReducer: { + GET_WHOAMI: false, + }, + tenantId: "tenant-id", +}; + +describe("AdminDataRetention", () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + + test("renders the component with default values", async () => { + render(, { preloadedState }); + + expect( + screen.getByText("Set data retention time") + ).toBeInTheDocument(); + expect( + screen.getByText( + "Determine how long data should be retained before automatic deletion" + ) + ).toBeInTheDocument(); + expect(screen.getByLabelText("Retention Time")).toHaveValue(30); + expect(screen.getByLabelText("Unit")).toBeInTheDocument(); + }); + + test("allows changing retention time value", () => { + render(, { preloadedState }); + + const valueInput = screen.getByLabelText("Retention Time"); + userEvent.clear(valueInput); + userEvent.type(valueInput, "60"); + + expect(valueInput).toHaveValue(60); + }); + + test("allows changing time unit", () => { + render(, { preloadedState }); + + const unitSelect = screen.getByLabelText("Unit"); + userEvent.click(unitSelect); + userEvent.click(screen.getByText("Weeks")); + + expect(screen.getByLabelText("Unit")).toHaveTextContent("Weeks"); + }); + + test("save button is enabled and clickable", () => { + render(, { preloadedState }); + + const saveButton = screen.getByRole("button", { + name: "Save retention settings", + }); + expect(saveButton).not.toBeDisabled(); + + userEvent.click(saveButton); + // Button should be clickable without errors + }); + + test("displays forbidden message if user is not admin", () => { + render(, { + preloadedState: { + ...preloadedState, + whoami: { user: new models.User({ roles: [userRoles.user] }) }, + }, + }); + + expect(screen.queryByText("Set data retention time")).toBeNull(); + expect( + screen.getByText("You don't have permission to view this page.") + ).toBeInTheDocument(); + }); + + test("displays skeleton while loading", () => { + render(, { + preloadedState: { + ...preloadedState, + loadingReducer: { + GET_WHOAMI: true, + }, + }, + }); + + expect(screen.queryByText("Set data retention time")).toBeNull(); + }); +}); diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.tsx new file mode 100644 index 000000000..c66ad14ed --- /dev/null +++ b/src/scenes/AdminControl/Components/AdminDataRetention.tsx @@ -0,0 +1,103 @@ +import { Button, Skeleton, Stack, Typography } from "@mui/material"; +import { makeStyles } from "tss-react/mui"; +import React, { useEffect, useState } from "react"; +import { PaddedPaper } from "../../../styles/common"; +import { useDispatch, useSelector } from "react-redux"; +import { getWhoami } from "../../../redux/Selectors"; +import Forbidden from "../../../ErrorComponents/Forbidden"; +import { createLoadingSelector } from "../../../redux/LoadingSelectors"; +import * as models from "../../../models/index"; +import DataRetentionSelector, { + DataRetentionValue, + TimeUnit, +} from "../../../components/DataRetentionSelector"; +import { displayInfoNotification } from "../../../redux/notifications/NotificationsActions"; + +const initialDataRetentionState: DataRetentionValue = { + value: 30, + unit: TimeUnit.DAYS, +}; + +const useStyles = makeStyles()({ + root: { + width: "100%", + maxWidth: 460, + }, +}); + +function AdminDataRetention() { + const [state, setState] = useState( + initialDataRetentionState + ); + const loadingSelector = createLoadingSelector(["GET_WHOAMI"]); + const whoamiFetching = useSelector(loadingSelector); + const [isPosting, setIsPosting] = useState(false); + const dispatch = useDispatch(); + const { classes } = useStyles(); + const whoami = useSelector(getWhoami); + + function handleSave() { + try { + setIsPosting(true); + // TODO: Implement actual save logic with DataStore or API + console.log("Saving data retention settings:", state); + dispatch( + displayInfoNotification( + `Data retention set to ${state.value} ${state.unit}` + ) + ); + setIsPosting(false); + } catch (error) { + console.log("error saving data retention settings:", error); + setIsPosting(false); + } + } + + if (whoamiFetching) { + return ( + + + + ); + } else if (!whoami.roles.includes(models.Role.ADMIN)) { + return ; + } else { + return ( + + + + Set data retention time + + + Determine how long data should be retained before + automatic deletion + + + + + + ); + } +} + +export default AdminDataRetention; From 24a919af191cb0b750fc9f04490908f7935619ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:19:42 +0000 Subject: [PATCH 3/7] Fix code review issues - remove unnecessary try-catch and unused import Co-authored-by: duckbytes <32309223+duckbytes@users.noreply.github.com> --- .../Components/AdminDataRetention.tsx | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.tsx index c66ad14ed..c2c1c8bd4 100644 --- a/src/scenes/AdminControl/Components/AdminDataRetention.tsx +++ b/src/scenes/AdminControl/Components/AdminDataRetention.tsx @@ -1,6 +1,6 @@ import { Button, Skeleton, Stack, Typography } from "@mui/material"; import { makeStyles } from "tss-react/mui"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { PaddedPaper } from "../../../styles/common"; import { useDispatch, useSelector } from "react-redux"; import { getWhoami } from "../../../redux/Selectors"; @@ -37,20 +37,15 @@ function AdminDataRetention() { const whoami = useSelector(getWhoami); function handleSave() { - try { - setIsPosting(true); - // TODO: Implement actual save logic with DataStore or API - console.log("Saving data retention settings:", state); - dispatch( - displayInfoNotification( - `Data retention set to ${state.value} ${state.unit}` - ) - ); - setIsPosting(false); - } catch (error) { - console.log("error saving data retention settings:", error); - setIsPosting(false); - } + setIsPosting(true); + // TODO: Implement actual save logic with DataStore or API + console.log("Saving data retention settings:", state); + dispatch( + displayInfoNotification( + `Data retention set to ${state.value} ${state.unit}` + ) + ); + setIsPosting(false); } if (whoamiFetching) { From 4459fa470d232d349bbfec4f7682eb84a842ab6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:21:02 +0000 Subject: [PATCH 4/7] Improve input handling and add loading state delay Co-authored-by: duckbytes <32309223+duckbytes@users.noreply.github.com> --- src/components/DataRetentionSelector.tsx | 11 ++++++----- .../Components/AdminDataRetention.tsx | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/DataRetentionSelector.tsx b/src/components/DataRetentionSelector.tsx index dc9fd0f85..1c33f3445 100644 --- a/src/components/DataRetentionSelector.tsx +++ b/src/components/DataRetentionSelector.tsx @@ -26,12 +26,13 @@ const DataRetentionSelector: React.FC = ({ }) => { const handleValueChange = (event: React.ChangeEvent) => { const newValue = parseInt(event.target.value, 10); - if (newValue >= 0 || event.target.value === "") { - onChange({ - ...value, - value: newValue || 0, - }); + if (isNaN(newValue) && event.target.value !== "") { + return; } + onChange({ + ...value, + value: isNaN(newValue) ? 0 : newValue, + }); }; const handleUnitChange = (event: React.ChangeEvent) => { diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.tsx index c2c1c8bd4..6fd0d88e3 100644 --- a/src/scenes/AdminControl/Components/AdminDataRetention.tsx +++ b/src/scenes/AdminControl/Components/AdminDataRetention.tsx @@ -38,14 +38,17 @@ function AdminDataRetention() { function handleSave() { setIsPosting(true); - // TODO: Implement actual save logic with DataStore or API - console.log("Saving data retention settings:", state); - dispatch( - displayInfoNotification( - `Data retention set to ${state.value} ${state.unit}` - ) - ); - setIsPosting(false); + // Simulate async operation for better UX + setTimeout(() => { + // TODO: Implement actual save logic with DataStore or API + console.log("Saving data retention settings:", state); + dispatch( + displayInfoNotification( + `Data retention set to ${state.value} ${state.unit}` + ) + ); + setIsPosting(false); + }, 500); } if (whoamiFetching) { From bcab89d3ea2c188e0271a68f6d7bdd02e90d992e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:37:26 +0000 Subject: [PATCH 5/7] Address PR feedback: use Stack, move to bottom, add indefinite retention, warning dialog, left-align button Co-authored-by: duckbytes <32309223+duckbytes@users.noreply.github.com> --- src/components/DataRetentionSelector.test.tsx | 4 +- src/components/DataRetentionSelector.tsx | 58 +++++---- src/scenes/AdminControl/AdminControl.js | 2 +- .../Components/AdminDataRetention.test.tsx | 61 +++++++++- .../Components/AdminDataRetention.tsx | 114 +++++++++++++++--- 5 files changed, 190 insertions(+), 49 deletions(-) diff --git a/src/components/DataRetentionSelector.test.tsx b/src/components/DataRetentionSelector.test.tsx index d2a9f4adb..251d4a10d 100644 --- a/src/components/DataRetentionSelector.test.tsx +++ b/src/components/DataRetentionSelector.test.tsx @@ -78,11 +78,11 @@ describe("DataRetentionSelector", () => { expect(unitSelect).toBeDisabled(); }); - test("does not allow negative values", () => { + test("minimum allowed value is 1", () => { const onChange = jest.fn(); render(); const valueInput = screen.getByLabelText("Retention Time"); - expect(valueInput).toHaveAttribute("min", "0"); + expect(valueInput).toHaveAttribute("min", "1"); }); }); diff --git a/src/components/DataRetentionSelector.tsx b/src/components/DataRetentionSelector.tsx index 1c33f3445..71d0d9a09 100644 --- a/src/components/DataRetentionSelector.tsx +++ b/src/components/DataRetentionSelector.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Grid, TextField, MenuItem } from "@mui/material"; +import { Stack, TextField, MenuItem } from "@mui/material"; export enum TimeUnit { DAYS = "days", @@ -43,36 +43,32 @@ const DataRetentionSelector: React.FC = ({ }; return ( - - - - - - - Days - Weeks - Months - Years - - - + + + + Days + Weeks + Months + Years + + ); }; diff --git a/src/scenes/AdminControl/AdminControl.js b/src/scenes/AdminControl/AdminControl.js index fdc0460a4..6a156a757 100644 --- a/src/scenes/AdminControl/AdminControl.js +++ b/src/scenes/AdminControl/AdminControl.js @@ -13,7 +13,6 @@ import { DeliverableTypeChips } from "./Components/DeliverableTypeChips"; export function AdminControl() { return ( - @@ -21,6 +20,7 @@ export function AdminControl() { + ); } diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx index ed1d1f58e..be2bc91b7 100644 --- a/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx +++ b/src/scenes/AdminControl/Components/AdminDataRetention.test.tsx @@ -32,13 +32,38 @@ describe("AdminDataRetention", () => { "Determine how long data should be retained before automatic deletion" ) ).toBeInTheDocument(); - expect(screen.getByLabelText("Retention Time")).toHaveValue(30); + expect( + screen.getByLabelText( + "Retain data indefinitely (no automatic deletion)" + ) + ).toBeChecked(); + expect(screen.queryByLabelText("Retention Time")).toBeNull(); + }); + + test("allows toggling indefinite retention", () => { + render(, { preloadedState }); + + const checkbox = screen.getByLabelText( + "Retain data indefinitely (no automatic deletion)" + ); + expect(checkbox).toBeChecked(); + + userEvent.click(checkbox); + + expect(checkbox).not.toBeChecked(); + expect(screen.getByLabelText("Retention Time")).toBeInTheDocument(); expect(screen.getByLabelText("Unit")).toBeInTheDocument(); }); test("allows changing retention time value", () => { render(, { preloadedState }); + // Uncheck indefinite retention first + const checkbox = screen.getByLabelText( + "Retain data indefinitely (no automatic deletion)" + ); + userEvent.click(checkbox); + const valueInput = screen.getByLabelText("Retention Time"); userEvent.clear(valueInput); userEvent.type(valueInput, "60"); @@ -49,6 +74,12 @@ describe("AdminDataRetention", () => { test("allows changing time unit", () => { render(, { preloadedState }); + // Uncheck indefinite retention first + const checkbox = screen.getByLabelText( + "Retain data indefinitely (no automatic deletion)" + ); + userEvent.click(checkbox); + const unitSelect = screen.getByLabelText("Unit"); userEvent.click(unitSelect); userEvent.click(screen.getByText("Weeks")); @@ -68,6 +99,34 @@ describe("AdminDataRetention", () => { // Button should be clickable without errors }); + test("shows warning dialog for short retention periods", async () => { + render(, { preloadedState }); + + // Uncheck indefinite retention + const checkbox = screen.getByLabelText( + "Retain data indefinitely (no automatic deletion)" + ); + userEvent.click(checkbox); + + // Set a short retention period (5 days) + const valueInput = screen.getByLabelText("Retention Time"); + userEvent.clear(valueInput); + userEvent.type(valueInput, "5"); + + // Click save + const saveButton = screen.getByRole("button", { + name: "Save retention settings", + }); + userEvent.click(saveButton); + + // Warning dialog should appear + await waitFor(() => { + expect( + screen.getByText("Short Retention Period Warning") + ).toBeInTheDocument(); + }); + }); + test("displays forbidden message if user is not admin", () => { render(, { preloadedState: { diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.tsx index 6fd0d88e3..00183cf9a 100644 --- a/src/scenes/AdminControl/Components/AdminDataRetention.tsx +++ b/src/scenes/AdminControl/Components/AdminDataRetention.tsx @@ -1,4 +1,11 @@ -import { Button, Skeleton, Stack, Typography } from "@mui/material"; +import { + Button, + Checkbox, + FormControlLabel, + Skeleton, + Stack, + Typography, +} from "@mui/material"; import { makeStyles } from "tss-react/mui"; import React, { useState } from "react"; import { PaddedPaper } from "../../../styles/common"; @@ -12,6 +19,7 @@ import DataRetentionSelector, { TimeUnit, } from "../../../components/DataRetentionSelector"; import { displayInfoNotification } from "../../../redux/notifications/NotificationsActions"; +import ConfirmationDialog from "../../../components/ConfirmationDialog"; const initialDataRetentionState: DataRetentionValue = { value: 30, @@ -29,6 +37,8 @@ function AdminDataRetention() { const [state, setState] = useState( initialDataRetentionState ); + const [noRetention, setNoRetention] = useState(true); + const [showWarningDialog, setShowWarningDialog] = useState(false); const loadingSelector = createLoadingSelector(["GET_WHOAMI"]); const whoamiFetching = useSelector(loadingSelector); const [isPosting, setIsPosting] = useState(false); @@ -36,21 +46,59 @@ function AdminDataRetention() { const { classes } = useStyles(); const whoami = useSelector(getWhoami); - function handleSave() { + // Calculate total days for comparison + const getTotalDays = (value: number, unit: TimeUnit): number => { + switch (unit) { + case TimeUnit.DAYS: + return value; + case TimeUnit.WEEKS: + return value * 7; + case TimeUnit.MONTHS: + return value * 30; + case TimeUnit.YEARS: + return value * 365; + default: + return value; + } + }; + + function handleSaveConfirmed() { setIsPosting(true); // Simulate async operation for better UX setTimeout(() => { // TODO: Implement actual save logic with DataStore or API - console.log("Saving data retention settings:", state); - dispatch( - displayInfoNotification( - `Data retention set to ${state.value} ${state.unit}` - ) - ); + if (noRetention) { + console.log("Saving data retention settings: indefinite"); + dispatch( + displayInfoNotification( + "Data retention set to indefinite (no automatic deletion)" + ) + ); + } else { + console.log("Saving data retention settings:", state); + dispatch( + displayInfoNotification( + `Data retention set to ${state.value} ${state.unit}` + ) + ); + } setIsPosting(false); + setShowWarningDialog(false); }, 500); } + function handleSave() { + // Check if retention is enabled and less than 7 days + if (!noRetention) { + const totalDays = getTotalDays(state.value, state.unit); + if (totalDays < 7) { + setShowWarningDialog(true); + return; + } + } + handleSaveConfirmed(); + } + if (whoamiFetching) { return ( @@ -63,13 +111,16 @@ function AdminDataRetention() { } else if (!whoami.roles.includes(models.Role.ADMIN)) { return ; } else { + const totalDays = !noRetention + ? getTotalDays(state.value, state.unit) + : 0; return ( @@ -84,15 +135,50 @@ function AdminDataRetention() { Determine how long data should be retained before automatic deletion - + setNoRetention(e.target.checked) + } + disabled={isPosting} + /> + } + label="Retain data indefinitely (no automatic deletion)" /> - + setShowWarningDialog(false)} + onConfirmation={handleSaveConfirmed} + dialogTitle="Short Retention Period Warning" + > + + You are setting a very short data retention period of{" "} + + {totalDays} day{totalDays !== 1 ? "s" : ""} + + . All data older than this will be automatically deleted. + + + Are you sure you want to proceed with this setting? + + ); } From a33b3e2d9b82b22f4e3fa6c9477520ac95c6932c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:38:40 +0000 Subject: [PATCH 6/7] Fix minimum value handling and document day calculation assumptions Co-authored-by: duckbytes <32309223+duckbytes@users.noreply.github.com> --- src/components/DataRetentionSelector.tsx | 2 +- src/scenes/AdminControl/Components/AdminDataRetention.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/DataRetentionSelector.tsx b/src/components/DataRetentionSelector.tsx index 71d0d9a09..0a38aaf32 100644 --- a/src/components/DataRetentionSelector.tsx +++ b/src/components/DataRetentionSelector.tsx @@ -31,7 +31,7 @@ const DataRetentionSelector: React.FC = ({ } onChange({ ...value, - value: isNaN(newValue) ? 0 : newValue, + value: isNaN(newValue) ? 1 : Math.max(1, newValue), }); }; diff --git a/src/scenes/AdminControl/Components/AdminDataRetention.tsx b/src/scenes/AdminControl/Components/AdminDataRetention.tsx index 00183cf9a..a79cefd70 100644 --- a/src/scenes/AdminControl/Components/AdminDataRetention.tsx +++ b/src/scenes/AdminControl/Components/AdminDataRetention.tsx @@ -47,6 +47,10 @@ function AdminDataRetention() { const whoami = useSelector(getWhoami); // Calculate total days for comparison + // Note: This uses approximate values for simplicity: + // - Months are assumed to be 30 days + // - Years are assumed to be 365 days + // For precise calculations, backend should use actual calendar dates const getTotalDays = (value: number, unit: TimeUnit): number => { switch (unit) { case TimeUnit.DAYS: @@ -54,9 +58,9 @@ function AdminDataRetention() { case TimeUnit.WEEKS: return value * 7; case TimeUnit.MONTHS: - return value * 30; + return value * 30; // Approximate case TimeUnit.YEARS: - return value * 365; + return value * 365; // Approximate, not accounting for leap years default: return value; } From 92d7e8748818f058c0353e0798d25cfa970d7534 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 01:16:21 +0000 Subject: [PATCH 7/7] Fix test failures - add IDs to TextFields and update test assertions Co-authored-by: duckbytes <32309223+duckbytes@users.noreply.github.com> --- src/components/DataRetentionSelector.test.tsx | 4 ++-- src/components/DataRetentionSelector.tsx | 2 ++ .../AdminControl/Components/AdminDataRetention.test.tsx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/DataRetentionSelector.test.tsx b/src/components/DataRetentionSelector.test.tsx index 251d4a10d..c1a087f6c 100644 --- a/src/components/DataRetentionSelector.test.tsx +++ b/src/components/DataRetentionSelector.test.tsx @@ -55,7 +55,7 @@ describe("DataRetentionSelector", () => { const unitSelect = screen.getByLabelText("Unit"); userEvent.click(unitSelect); - expect(screen.getByText("Days")).toBeInTheDocument(); + expect(screen.getAllByText("Days").length).toBeGreaterThan(0); expect(screen.getByText("Weeks")).toBeInTheDocument(); expect(screen.getByText("Months")).toBeInTheDocument(); expect(screen.getByText("Years")).toBeInTheDocument(); @@ -75,7 +75,7 @@ describe("DataRetentionSelector", () => { const unitSelect = screen.getByLabelText("Unit"); expect(valueInput).toBeDisabled(); - expect(unitSelect).toBeDisabled(); + expect(unitSelect).toHaveAttribute("aria-disabled", "true"); }); test("minimum allowed value is 1", () => { diff --git a/src/components/DataRetentionSelector.tsx b/src/components/DataRetentionSelector.tsx index 0a38aaf32..faae69088 100644 --- a/src/components/DataRetentionSelector.tsx +++ b/src/components/DataRetentionSelector.tsx @@ -45,6 +45,7 @@ const DataRetentionSelector: React.FC = ({ return ( = ({ sx={{ flex: 1 }} /> { userEvent.click(unitSelect); userEvent.click(screen.getByText("Weeks")); - expect(screen.getByLabelText("Unit")).toHaveTextContent("Weeks"); + // Check that the unit field now displays "Weeks" + expect(unitSelect).toHaveTextContent("Weeks"); }); test("save button is enabled and clickable", () => {