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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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 (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
};
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", () => {