M-029 Pass 1 batch 6 (FINAL): migrate 2 five-mutation pages — Pass 1 complete

Drains the last 10 useMutation sites (10 -> 0). Pass 1 is now COMPLETE:

every legacy useMutation site in src/pages and src/components has been

migrated to useTrackedMutation with explicit invalidates contract. The only

remaining useMutation reference in the codebase is inside useTrackedMutation.ts

itself (the wrapper).

Pages migrated:

  - CertificateDetailPage.tsx  5 mutations across 2 components:

                                InlinePolicyEditor.saveMutation invalidates

                                [['certificate', certId]];

                                main page renew/deploy/archive/revoke invalidate

                                various combinations of [['certificate', id]]

                                and [['certificates']].

                                (queryClient + useQueryClient dropped from both)

  - OnboardingWizard.tsx        5 mutations across 4 components:

                                Issuer step create/test invalidates [['issuers']]

                                (test refreshes last_tested_at server-side);

                                CreateTeamModalInline.create invalidates [['teams']];

                                CreateOwnerModalInline.create invalidates [['owners']];

                                CertificateStep.create invalidates

                                [['certificates'], ['dashboard-summary']].

                                (queryClient + useQueryClient dropped from all 4)

Verification:

  legacy useMutation calls   10 -> 0 (-10) — Pass 1 COMPLETE

  useTrackedMutation count   46 -> 61 (+15; some 5-mutation pages collapse

                                two invalidate-pairs into one array literal,

                                hence net is greater than the +10 removal)

Pass 1 totals: 56 useMutation sites -> 0; 0 useTrackedMutation -> 61.

Total work in Pass 1: 6 batches across 21 page files merged --no-ff to master.
This commit is contained in:
Shankar
2026-04-27 02:54:28 +00:00
parent df8fa4aadb
commit 1baefd420a
2 changed files with 27 additions and 32 deletions
+12 -17
View File
@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { getCertificate, getCertificateVersions, triggerRenewal, triggerDeployment, archiveCertificate, revokeCertificate, updateCertificate, getTargets, getJobs, getRenewalPolicies, getProfiles, getProfile, downloadCertificatePEM, exportCertificatePKCS12 } from '../api/client'; import { getCertificate, getCertificateVersions, triggerRenewal, triggerDeployment, archiveCertificate, revokeCertificate, updateCertificate, getTargets, getJobs, getRenewalPolicies, getProfiles, getProfile, downloadCertificatePEM, exportCertificatePKCS12 } from '../api/client';
import { REVOCATION_REASONS } from '../api/types'; import { REVOCATION_REASONS } from '../api/types';
import PageHeader from '../components/PageHeader'; import PageHeader from '../components/PageHeader';
@@ -159,7 +160,6 @@ function DeploymentTimeline({ certId, certStatus, createdAt, issuedAt }: { certI
} }
function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { certId: string; currentPolicyId: string; currentProfileId: string }) { function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { certId: string; currentPolicyId: string; currentProfileId: string }) {
const queryClient = useQueryClient();
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [policyId, setPolicyId] = useState(currentPolicyId); const [policyId, setPolicyId] = useState(currentPolicyId);
const [profileId, setProfileId] = useState(currentProfileId); const [profileId, setProfileId] = useState(currentProfileId);
@@ -181,13 +181,13 @@ function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { cer
enabled: editing, enabled: editing,
}); });
const saveMutation = useMutation({ const saveMutation = useTrackedMutation({
mutationFn: () => updateCertificate(certId, { mutationFn: () => updateCertificate(certId, {
renewal_policy_id: policyId, renewal_policy_id: policyId,
certificate_profile_id: profileId, certificate_profile_id: profileId,
}), }),
invalidates: [['certificate', certId]],
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certificate', certId] });
setEditing(false); setEditing(false);
}, },
}); });
@@ -257,7 +257,6 @@ function InlinePolicyEditor({ certId, currentPolicyId, currentProfileId }: { cer
export default function CertificateDetailPage() { export default function CertificateDetailPage() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const queryClient = useQueryClient();
const [showDeploy, setShowDeploy] = useState(false); const [showDeploy, setShowDeploy] = useState(false);
const [deployTargetId, setDeployTargetId] = useState(''); const [deployTargetId, setDeployTargetId] = useState('');
const [showRevoke, setShowRevoke] = useState(false); const [showRevoke, setShowRevoke] = useState(false);
@@ -291,38 +290,34 @@ export default function CertificateDetailPage() {
enabled: !!cert?.certificate_profile_id, enabled: !!cert?.certificate_profile_id,
}); });
const renewMutation = useMutation({ const renewMutation = useTrackedMutation({
mutationFn: () => triggerRenewal(id!), mutationFn: () => triggerRenewal(id!),
onSuccess: () => { invalidates: [['certificate', id], ['certificates']],
queryClient.invalidateQueries({ queryKey: ['certificate', id] });
queryClient.invalidateQueries({ queryKey: ['certificates'] });
},
}); });
const deployMutation = useMutation({ const deployMutation = useTrackedMutation({
mutationFn: () => triggerDeployment(id!, deployTargetId), mutationFn: () => triggerDeployment(id!, deployTargetId),
invalidates: [['certificate', id]],
onSuccess: () => { onSuccess: () => {
setShowDeploy(false); setShowDeploy(false);
setDeployTargetId(''); setDeployTargetId('');
queryClient.invalidateQueries({ queryKey: ['certificate', id] });
}, },
}); });
const archiveMutation = useMutation({ const archiveMutation = useTrackedMutation({
mutationFn: () => archiveCertificate(id!), mutationFn: () => archiveCertificate(id!),
invalidates: [['certificates']],
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['certificates'] });
navigate('/certificates'); navigate('/certificates');
}, },
}); });
const revokeMutation = useMutation({ const revokeMutation = useTrackedMutation({
mutationFn: () => revokeCertificate(id!, revokeReason), mutationFn: () => revokeCertificate(id!, revokeReason),
invalidates: [['certificate', id], ['certificates']],
onSuccess: () => { onSuccess: () => {
setShowRevoke(false); setShowRevoke(false);
setRevokeReason('unspecified'); setRevokeReason('unspecified');
queryClient.invalidateQueries({ queryKey: ['certificate', id] });
queryClient.invalidateQueries({ queryKey: ['certificates'] });
}, },
}); });
+15 -15
View File
@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { useNavigate, Link } from 'react-router-dom'; import { useNavigate, Link } from 'react-router-dom';
import { import {
getIssuers, getAgents, getProfiles, getOwners, getTeams, getRenewalPolicies, getIssuers, getAgents, getProfiles, getOwners, getTeams, getRenewalPolicies,
@@ -112,7 +113,6 @@ function IssuerStep({ onNext, onSkip, onIssuerCreated }: {
onSkip: () => void; onSkip: () => void;
onIssuerCreated: (issuer: Issuer) => void; onIssuerCreated: (issuer: Issuer) => void;
}) { }) {
const queryClient = useQueryClient();
const [selectedType, setSelectedType] = useState<string | null>(null); const [selectedType, setSelectedType] = useState<string | null>(null);
const [configValues, setConfigValues] = useState<Record<string, unknown>>({}); const [configValues, setConfigValues] = useState<Record<string, unknown>>({});
const [issuerName, setIssuerName] = useState(''); const [issuerName, setIssuerName] = useState('');
@@ -131,23 +131,27 @@ function IssuerStep({ onNext, onSkip, onIssuerCreated }: {
const typeConfig = selectedType ? issuerTypes.find(t => t.id === selectedType) : null; const typeConfig = selectedType ? issuerTypes.find(t => t.id === selectedType) : null;
const createMutation = useMutation({ const createMutation = useTrackedMutation({
mutationFn: () => createIssuer({ mutationFn: () => createIssuer({
name: issuerName || `${typeConfig?.name || selectedType} Issuer`, name: issuerName || `${typeConfig?.name || selectedType} Issuer`,
type: selectedType!, type: selectedType!,
config: configValues as Record<string, unknown>, config: configValues as Record<string, unknown>,
}), }),
invalidates: [['issuers']],
onSuccess: (issuer) => { onSuccess: (issuer) => {
setCreatedIssuer(issuer); setCreatedIssuer(issuer);
onIssuerCreated(issuer); onIssuerCreated(issuer);
queryClient.invalidateQueries({ queryKey: ['issuers'] });
setError(''); setError('');
}, },
onError: (err: Error) => setError(err.message), onError: (err: Error) => setError(err.message),
}); });
const testMutation = useMutation({ // testIssuerConnection updates last_tested_at server-side; refresh the
// issuers list so the timestamp + status columns reflect the new probe.
// The local setTestResult banner still surfaces the immediate pass/fail.
const testMutation = useTrackedMutation({
mutationFn: () => testIssuerConnection(createdIssuer!.id), mutationFn: () => testIssuerConnection(createdIssuer!.id),
invalidates: [['issuers']],
onSuccess: () => setTestResult({ ok: true, msg: 'Connection successful' }), onSuccess: () => setTestResult({ ok: true, msg: 'Connection successful' }),
onError: (err: Error) => setTestResult({ ok: false, msg: err.message }), onError: (err: Error) => setTestResult({ ok: false, msg: err.message }),
}); });
@@ -402,15 +406,14 @@ function CreateTeamModalInline({ isOpen, onClose, onCreated }: {
onClose: () => void; onClose: () => void;
onCreated: (teamId: string) => void; onCreated: (teamId: string) => void;
}) { }) {
const queryClient = useQueryClient();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const mutation = useMutation({ const mutation = useTrackedMutation({
mutationFn: () => createTeam({ name: name.trim(), description: description.trim() }), mutationFn: () => createTeam({ name: name.trim(), description: description.trim() }),
invalidates: [['teams']],
onSuccess: (team) => { onSuccess: (team) => {
queryClient.invalidateQueries({ queryKey: ['teams'] });
setName(''); setName('');
setDescription(''); setDescription('');
setError(''); setError('');
@@ -475,20 +478,19 @@ function CreateOwnerModalInline({ isOpen, onClose, onCreated, teams }: {
onCreated: (ownerId: string) => void; onCreated: (ownerId: string) => void;
teams: { id: string; name: string }[]; teams: { id: string; name: string }[];
}) { }) {
const queryClient = useQueryClient();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [teamId, setTeamId] = useState(''); const [teamId, setTeamId] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const mutation = useMutation({ const mutation = useTrackedMutation({
mutationFn: () => createOwner({ mutationFn: () => createOwner({
name: name.trim(), name: name.trim(),
email: email.trim(), email: email.trim(),
team_id: teamId || undefined, team_id: teamId || undefined,
}), }),
invalidates: [['owners']],
onSuccess: (owner) => { onSuccess: (owner) => {
queryClient.invalidateQueries({ queryKey: ['owners'] });
setName(''); setName('');
setEmail(''); setEmail('');
setTeamId(''); setTeamId('');
@@ -574,7 +576,6 @@ function CertificateStep({ onNext, onSkip, createdIssuerId }: {
onSkip: () => void; onSkip: () => void;
createdIssuerId: string | null; createdIssuerId: string | null;
}) { }) {
const queryClient = useQueryClient();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [commonName, setCommonName] = useState(''); const [commonName, setCommonName] = useState('');
const [sans, setSans] = useState(''); const [sans, setSans] = useState('');
@@ -608,7 +609,7 @@ function CertificateStep({ onNext, onSkip, createdIssuerId }: {
const hasAgents = (agents?.data?.length ?? 0) > 0; const hasAgents = (agents?.data?.length ?? 0) > 0;
const createMutation = useMutation({ const createMutation = useTrackedMutation({
mutationFn: async () => { mutationFn: async () => {
const sanList = sans.split(',').map(s => s.trim()).filter(Boolean); const sanList = sans.split(',').map(s => s.trim()).filter(Boolean);
const cert = await createCertificate({ const cert = await createCertificate({
@@ -626,10 +627,9 @@ function CertificateStep({ onNext, onSkip, createdIssuerId }: {
await triggerRenewal(cert.id); await triggerRenewal(cert.id);
return cert; return cert;
}, },
invalidates: [['certificates'], ['dashboard-summary']],
onSuccess: (cert) => { onSuccess: (cert) => {
setCreated(true); setCreated(true);
queryClient.invalidateQueries({ queryKey: ['certificates'] });
queryClient.invalidateQueries({ queryKey: ['dashboard-summary'] });
setTimeout(() => onNext(cert.common_name), 1500); setTimeout(() => onNext(cert.common_name), 1500);
}, },
onError: (err: Error) => setError(err.message), onError: (err: Error) => setError(err.message),