fix(usage): synchronize setup consent and dismissal state

This commit is contained in:
Paul Nothaft
2026-09-08 17:26:47 +02:00
2 changed files with 11 additions and 4 deletions
+4 -3
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { Navigate, useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Key, Mail, Lock, Eye, EyeOff, AlertCircle, ArrowLeft, ArrowRight, Copy, Check, ExternalLink, Bug, Lightbulb, Star, Coffee } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { toast } from 'react-toastify';
@@ -65,6 +65,7 @@ export const SetupPage: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { login } = useAdminAuth();
const queryClient = useQueryClient();
const { data: status, isLoading: statusLoading, isError: statusError } = useQuery({
queryKey: ['setup-status'],
@@ -292,7 +293,7 @@ export const SetupPage: React.FC = () => {
const enableUsageReporting = async () => {
setIsEnablingUsageReporting(true);
try {
await productUsageService.enable();
queryClient.setQueryData(['productUsage'], await productUsageService.enable());
toast.success(t('setup.usageReporting.enabled'));
} catch {
toast.warn(t('setup.usageReporting.enableFailed'));
@@ -307,7 +308,7 @@ export const SetupPage: React.FC = () => {
// the same question seconds later on their first dashboard visit.
const skipUsageReporting = async () => {
try {
await productUsageService.promptSeen();
queryClient.setQueryData(['productUsage'], await productUsageService.promptSeen());
} catch { /* best-effort — worst case the dashboard asks once more */ }
setStep('community');
};
@@ -39,12 +39,15 @@ beforeEach(() => {
vi.clearAllMocks();
vi.mocked(usage.status).mockResolvedValue({ status: 'disabled', collector_url: 'https://custom-collector.example.test' } as never);
vi.mocked(usage.enable).mockResolvedValue({ status: 'active' } as never);
vi.mocked(usage.promptSeen).mockResolvedValue({ status: 'disabled', prompt_shown: true } as never);
HTMLDialogElement.prototype.showModal = function () { this.setAttribute('open', ''); };
});
afterEach(cleanup);
let client: QueryClient;
async function reachInvitation() {
render(<QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { retry: false } } })}>
client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
render(<QueryClientProvider client={client}>
<MemoryRouter><SetupPage /></MemoryRouter>
</QueryClientProvider>);
fireEvent.change(await screen.findByLabelText('setup.tokenLabel'), { target: { value: 'test-token' } });
@@ -82,6 +85,7 @@ it('uses the full settings disclosure and configured collector before accepting
fireEvent.click(dialog.getByRole('button', { name: 'productUsage.enable' }));
await screen.findByText('setup.community.mission');
expect(usage.enable).toHaveBeenCalledTimes(1);
expect(client.getQueryData(['productUsage'])).toMatchObject({ status: 'active' });
});
it('skipping the invitation never enables reporting', async () => {
@@ -89,6 +93,8 @@ it('skipping the invitation never enables reporting', async () => {
fireEvent.click(screen.getByRole('button', { name: 'setup.usageReporting.skip' }));
await screen.findByText('setup.community.mission');
expect(usage.enable).not.toHaveBeenCalled();
expect(usage.promptSeen).toHaveBeenCalledTimes(1);
expect(client.getQueryData(['productUsage'])).toMatchObject({ status: 'disabled', prompt_shown: true });
});
it.each(['failed', 'invalid'])('keeps setup usable when collector configuration is %s', async (failure) => {