Skip to content

Commit

Permalink
chore: Move user's analytics consent to the backend (#6505)
Browse files Browse the repository at this point in the history
  • Loading branch information
amanape authored Jan 30, 2025
1 parent 0afe889 commit c54911d
Show file tree
Hide file tree
Showing 20 changed files with 219 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AnalyticsConsentFormModal } from "#/components/features/analytics/analytics-consent-form-modal";
import OpenHands from "#/api/open-hands";
import { SettingsProvider } from "#/context/settings-context";
import { AuthProvider } from "#/context/auth-context";

describe("AnalyticsConsentFormModal", () => {
it("should call saveUserSettings with default settings on confirm reset settings", async () => {
const user = userEvent.setup();
const onCloseMock = vi.fn();
const saveUserSettingsSpy = vi.spyOn(OpenHands, "saveSettings");

render(<AnalyticsConsentFormModal onClose={onCloseMock} />, {
wrapper: ({ children }) => (
<AuthProvider>
<QueryClientProvider client={new QueryClient()}>
<SettingsProvider>{children}</SettingsProvider>
</QueryClientProvider>
</AuthProvider>
),
});

const confirmButton = screen.getByTestId("confirm-preferences");
await user.click(confirmButton);

expect(saveUserSettingsSpy).toHaveBeenCalledWith({
user_consents_to_analytics: true,
agent: "CodeActAgent",
confirmation_mode: false,
enable_default_condenser: false,
github_token: undefined,
language: "en",
llm_api_key: undefined,
llm_base_url: "",
llm_model: "anthropic/claude-3-5-sonnet-20241022",
remote_runtime_resource_factor: 1,
security_analyzer: "",
unset_github_token: undefined,
});
expect(onCloseMock).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ describe("Sidebar", () => {
within(accountSettingsModal).getByLabelText(/GITHUB\$TOKEN_LABEL/i);
await user.type(tokenInput, "new-token");

const analyticsConsentInput =
within(accountSettingsModal).getByTestId("analytics-consent");
await user.click(analyticsConsentInput);

const saveButton =
within(accountSettingsModal).getByTestId("save-settings");
await user.click(saveButton);
Expand All @@ -96,6 +100,7 @@ describe("Sidebar", () => {
llm_model: "anthropic/claude-3-5-sonnet-20241022",
remote_runtime_resource_factor: 1,
security_analyzer: "",
user_consents_to_analytics: true,
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,74 @@
import { screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "test-utils";
import { AccountSettingsModal } from "#/components/shared/modals/account-settings/account-settings-modal";
import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers";
import OpenHands from "#/api/open-hands";
import * as ConsentHandlers from "#/utils/handle-capture-consent";

describe("AccountSettingsModal", () => {
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");

beforeEach(() => {
vi.resetAllMocks();
afterEach(() => {
vi.clearAllMocks();
});

it.skip("should set the appropriate user analytics consent default", async () => {
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
user_consents_to_analytics: true,
});
renderWithProviders(<AccountSettingsModal onClose={() => {}} />);

const analyticsConsentInput = screen.getByTestId("analytics-consent");
await waitFor(() => expect(analyticsConsentInput).toBeChecked());
});

it("should save the users consent to analytics when saving account settings", async () => {
const user = userEvent.setup();
renderWithProviders(<AccountSettingsModal onClose={() => {}} />);

const analyticsConsentInput = screen.getByTestId("analytics-consent");
await user.click(analyticsConsentInput);

const saveButton = screen.getByTestId("save-settings");
await user.click(saveButton);

expect(saveSettingsSpy).toHaveBeenCalledWith({
agent: "CodeActAgent",
confirmation_mode: false,
enable_default_condenser: false,
language: "en",
llm_base_url: "",
llm_model: "anthropic/claude-3-5-sonnet-20241022",
remote_runtime_resource_factor: 1,
security_analyzer: "",
user_consents_to_analytics: true,
});
});

it("should call handleCaptureConsent with the analytics consent value if the save is successful", async () => {
const user = userEvent.setup();
const handleCaptureConsentSpy = vi.spyOn(
ConsentHandlers,
"handleCaptureConsent",
);
renderWithProviders(<AccountSettingsModal onClose={() => {}} />);

const analyticsConsentInput = screen.getByTestId("analytics-consent");
await user.click(analyticsConsentInput);

const saveButton = screen.getByTestId("save-settings");
await user.click(saveButton);

expect(handleCaptureConsentSpy).toHaveBeenCalledWith(true);

await user.click(analyticsConsentInput);
await user.click(saveButton);

expect(handleCaptureConsentSpy).toHaveBeenCalledWith(false);
});

it("should send all settings data when saving account settings", async () => {
Expand Down Expand Up @@ -39,11 +97,11 @@ describe("AccountSettingsModal", () => {
llm_model: "anthropic/claude-3-5-sonnet-20241022",
remote_runtime_resource_factor: 1,
security_analyzer: "",
user_consents_to_analytics: false,
});
});

it("should render a checkmark and not the input if the github token is set", async () => {
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
github_token_is_set: true,
Expand All @@ -61,7 +119,6 @@ describe("AccountSettingsModal", () => {

it("should send an unset github token property when pressing disconnect", async () => {
const user = userEvent.setup();
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
github_token_is_set: true,
Expand All @@ -86,7 +143,6 @@ describe("AccountSettingsModal", () => {

it("should not unset the github token when changing the language", async () => {
const user = userEvent.setup();
const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
getSettingsSpy.mockResolvedValue({
...MOCK_DEFAULT_USER_SETTINGS,
github_token_is_set: true,
Expand All @@ -111,6 +167,7 @@ describe("AccountSettingsModal", () => {
llm_model: "anthropic/claude-3-5-sonnet-20241022",
remote_runtime_resource_factor: 1,
security_analyzer: "",
user_consents_to_analytics: false,
});
});
});
13 changes: 0 additions & 13 deletions frontend/__tests__/routes/_oh.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe("frontend/routes/_oh", () => {
// The user has not consented to tracking
const consentForm = await screen.findByTestId("user-capture-consent-form");
expect(handleCaptureConsentSpy).not.toHaveBeenCalled();
expect(localStorage.getItem("analytics-consent")).toBeNull();

const submitButton = within(consentForm).getByRole("button", {
name: /confirm preferences/i,
Expand All @@ -83,7 +82,6 @@ describe("frontend/routes/_oh", () => {

// The user has now consented to tracking
expect(handleCaptureConsentSpy).toHaveBeenCalledWith(true);
expect(localStorage.getItem("analytics-consent")).toBe("true");
expect(
screen.queryByTestId("user-capture-consent-form"),
).not.toBeInTheDocument();
Expand All @@ -106,17 +104,6 @@ describe("frontend/routes/_oh", () => {
});
});

it("should not render the user consent form if the user has already made a decision", async () => {
localStorage.setItem("analytics-consent", "true");
renderWithProviders(<RouteStub />);

await waitFor(() => {
expect(
screen.queryByTestId("user-capture-consent-form"),
).not.toBeInTheDocument();
});
});

// TODO: Likely failing due to how tokens are now handled in context. Move to e2e tests
it.skip("should render a new project button if a token is set", async () => {
localStorage.setItem("token", "test-token");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "#/components/shared/modals/confirmation-modals/base-modal";
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
import { ModalBody } from "#/components/shared/modals/modal-body";
import { useCurrentSettings } from "#/context/settings-context";
import { handleCaptureConsent } from "#/utils/handle-capture-consent";

interface AnalyticsConsentFormModalProps {
Expand All @@ -14,13 +15,21 @@ interface AnalyticsConsentFormModalProps {
export function AnalyticsConsentFormModal({
onClose,
}: AnalyticsConsentFormModalProps) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const { saveUserSettings } = useCurrentSettings();

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const analytics = formData.get("analytics") === "on";

handleCaptureConsent(analytics);
localStorage.setItem("analytics-consent", analytics.toString());
await saveUserSettings(
{ user_consents_to_analytics: analytics },
{
onSuccess: () => {
handleCaptureConsent(analytics);
},
},
);

onClose();
};
Expand All @@ -46,6 +55,7 @@ export function AnalyticsConsentFormModal({
</label>

<ModalButton
testId="confirm-preferences"
type="submit"
text="Confirm Preferences"
className="bg-primary text-white w-full hover:opacity-80"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,21 @@ import { useConfig } from "#/hooks/query/use-config";
import { useCurrentSettings } from "#/context/settings-context";
import { GitHubTokenInput } from "./github-token-input";
import { PostSettings } from "#/types/settings";
import { useGitHubUser } from "#/hooks/query/use-github-user";

interface AccountSettingsFormProps {
onClose: () => void;
selectedLanguage: string;
gitHubError: boolean;
analyticsConsent: string | null;
}

export function AccountSettingsForm({
onClose,
selectedLanguage,
gitHubError,
analyticsConsent,
}: AccountSettingsFormProps) {
export function AccountSettingsForm({ onClose }: AccountSettingsFormProps) {
const { isError: isGitHubError } = useGitHubUser();
const { data: config } = useConfig();
const { saveUserSettings, settings } = useCurrentSettings();
const { t } = useTranslation();

const githubTokenIsSet = !!settings?.GITHUB_TOKEN_IS_SET;
const analyticsConsentValue = !!settings?.USER_CONSENTS_TO_ANALYTICS;
const selectedLanguage = settings?.LANGUAGE || "en";

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand All @@ -44,6 +40,7 @@ export function AccountSettingsForm({
const analytics = formData.get("analytics")?.toString() === "on";

const newSettings: Partial<PostSettings> = {};
newSettings.user_consents_to_analytics = analytics;

if (ghToken) newSettings.github_token = ghToken;

Expand All @@ -57,11 +54,11 @@ export function AccountSettingsForm({
if (languageKey) newSettings.LANGUAGE = languageKey;
}

await saveUserSettings(newSettings);

handleCaptureConsent(analytics);
const ANALYTICS = analytics.toString();
localStorage.setItem("analytics-consent", ANALYTICS);
await saveUserSettings(newSettings, {
onSuccess: () => {
handleCaptureConsent(analytics);
},
});

onClose();
};
Expand Down Expand Up @@ -117,12 +114,12 @@ export function AccountSettingsForm({
)}
</>
)}
{gitHubError && (
{isGitHubError && (
<p className="text-danger text-xs">
{t(I18nKey.GITHUB$TOKEN_INVALID)}
</p>
)}
{githubTokenIsSet && !gitHubError && (
{githubTokenIsSet && !isGitHubError && (
<ModalButton
testId="disconnect-github"
variant="text-like"
Expand All @@ -135,9 +132,10 @@ export function AccountSettingsForm({

<label className="flex gap-2 items-center self-start">
<input
data-testid="analytics-consent"
name="analytics"
type="checkbox"
defaultChecked={analyticsConsent === "true"}
defaultChecked={analyticsConsentValue}
/>
{t(I18nKey.ANALYTICS$ENABLE)}
</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useGitHubUser } from "#/hooks/query/use-github-user";
import { useSettings } from "#/hooks/query/use-settings";
import { ModalBackdrop } from "../modal-backdrop";
import { AccountSettingsForm } from "./account-settings-form";

Expand All @@ -8,20 +6,9 @@ interface AccountSettingsModalProps {
}

export function AccountSettingsModal({ onClose }: AccountSettingsModalProps) {
const user = useGitHubUser();
const { data: settings } = useSettings();

// FIXME: Bad practice to use localStorage directly
const analyticsConsent = localStorage.getItem("analytics-consent");

return (
<ModalBackdrop onClose={onClose}>
<AccountSettingsForm
onClose={onClose}
selectedLanguage={settings?.LANGUAGE || "en"}
gitHubError={user.isError}
analyticsConsent={analyticsConsent}
/>
<AccountSettingsForm onClose={onClose} />
</ModalBackdrop>
);
}
Loading

0 comments on commit c54911d

Please sign in to comment.