M-029 Pass 1 batch 1: migrate 4 single-mutation pages to useTrackedMutation

Drains the Bundle 8 useMutation backlog (56 -> 52). Each migration declares

explicit invalidates per the M-009 contract; the wrapper invalidates BEFORE

calling the caller's onSuccess so user code drops the redundant qc.invalidateQueries.

Pages migrated:

  - AgentsPage.tsx        invalidates: [['agents'], ['agents', 'retired']]

  - CertificatesPage.tsx  invalidates: [['certificates']]

  - DigestPage.tsx        invalidates: 'noop' (sendDigest is a server-side email

                            dispatch — no client query reflects digest-send state)

  - IssuerDetailPage.tsx  invalidates: [['issuer', id]] (testIssuerConnection

                            updates last_tested_at server-side)

Verification:

  legacy useMutation count   56 -> 52 (-4 sites)

  useTrackedMutation count    0 ->  4 (+4 sites)

  invalidation surface      82 -> 84 (+2; DigestPage is noop, AgentsPage

                                  collapses 2 invalidates into 1 array, others +1)

Closes 4 of 56 sites toward M-029 Pass 1 completion.
This commit is contained in:
shankar0123
2026-04-27 02:37:25 +00:00
parent 0b58662e9a
commit 2057e76706
4 changed files with 17 additions and 12 deletions
+4 -5
View File
@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { 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 { import {
getAgents, getAgents,
listRetiredAgents, listRetiredAgents,
@@ -45,7 +46,6 @@ type ModalMode =
export default function AgentsPage() { export default function AgentsPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const qc = useQueryClient();
const [tab, setTab] = useState<TabKey>('active'); const [tab, setTab] = useState<TabKey>('active');
const [modal, setModal] = useState<ModalMode>({ kind: 'closed' }); const [modal, setModal] = useState<ModalMode>({ kind: 'closed' });
@@ -67,12 +67,11 @@ export default function AgentsPage() {
// and we invalidate both queries on success so the retired tab refreshes and // and we invalidate both queries on success so the retired tab refreshes and
// the active tab drops the row. 409s are converted into modal.mode=blocked so // the active tab drops the row. 409s are converted into modal.mode=blocked so
// the operator can escalate to force; everything else becomes modal.mode=error. // the operator can escalate to force; everything else becomes modal.mode=error.
const mutation = useMutation({ const mutation = useTrackedMutation({
mutationFn: (input: { agent: Agent; force?: boolean; reason?: string }) => mutationFn: (input: { agent: Agent; force?: boolean; reason?: string }) =>
retireAgent(input.agent.id, { force: input.force, reason: input.reason }), retireAgent(input.agent.id, { force: input.force, reason: input.reason }),
invalidates: [['agents'], ['agents', 'retired']],
onSuccess: () => { onSuccess: () => {
qc.invalidateQueries({ queryKey: ['agents'] });
qc.invalidateQueries({ queryKey: ['agents', 'retired'] });
setModal({ kind: 'closed' }); setModal({ kind: 'closed' });
}, },
}); });
+4 -2
View File
@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { getCertificates, createCertificate, revokeCertificate, getOwners, getTeams, getRenewalPolicies, getProfiles, getIssuers, bulkRevokeCertificates, bulkRenewCertificates, bulkReassignCertificates } from '../api/client'; import { getCertificates, createCertificate, revokeCertificate, getOwners, getTeams, getRenewalPolicies, getProfiles, getIssuers, bulkRevokeCertificates, bulkRenewCertificates, bulkReassignCertificates } from '../api/client';
import { useAuth } from '../components/AuthProvider'; import { useAuth } from '../components/AuthProvider';
@@ -73,7 +74,7 @@ function CreateCertificateModal({ onClose, onSuccess }: { onClose: () => void; o
: `${Math.round(selectedProfile.max_ttl_seconds / 86400)}d` : `${Math.round(selectedProfile.max_ttl_seconds / 86400)}d`
: null; : null;
const mutation = useMutation({ const mutation = useTrackedMutation({
mutationFn: () => { mutationFn: () => {
const payload: Record<string, unknown> = { ...form }; const payload: Record<string, unknown> = { ...form };
// Convert comma-separated SANs to array // Convert comma-separated SANs to array
@@ -95,6 +96,7 @@ function CreateCertificateModal({ onClose, onSuccess }: { onClose: () => void; o
} }
return createCertificate(payload); return createCertificate(payload);
}, },
invalidates: [['certificates']],
onSuccess: () => onSuccess(), onSuccess: () => onSuccess(),
onError: (err: Error) => setError(err.message), onError: (err: Error) => setError(err.message),
}); });
+5 -2
View File
@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { useQuery, useMutation } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { previewDigest, sendDigest } from '../api/client'; import { previewDigest, sendDigest } from '../api/client';
import PageHeader from '../components/PageHeader'; import PageHeader from '../components/PageHeader';
import ErrorState from '../components/ErrorState'; import ErrorState from '../components/ErrorState';
@@ -13,8 +14,10 @@ export default function DigestPage() {
retry: false, retry: false,
}); });
const sendMutation = useMutation({ const sendMutation = useTrackedMutation({
mutationFn: sendDigest, mutationFn: sendDigest,
invalidates: 'noop',
noopReason: 'sendDigest dispatches an email server-side; no cached client query reflects digest-send state',
onSuccess: () => setShowConfirm(false), onSuccess: () => setShowConfirm(false),
}); });
+4 -3
View File
@@ -1,5 +1,6 @@
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTrackedMutation } from '../hooks/useTrackedMutation';
import { getIssuer, testIssuerConnection, getCertificates } from '../api/client'; import { getIssuer, testIssuerConnection, getCertificates } from '../api/client';
import PageHeader from '../components/PageHeader'; import PageHeader from '../components/PageHeader';
import StatusBadge from '../components/StatusBadge'; import StatusBadge from '../components/StatusBadge';
@@ -46,9 +47,9 @@ export default function IssuerDetailPage() {
enabled: !!id, enabled: !!id,
}); });
const testMutation = useMutation({ const testMutation = useTrackedMutation({
mutationFn: () => testIssuerConnection(id!), mutationFn: () => testIssuerConnection(id!),
onSuccess: () => refetch(), invalidates: [['issuer', id]],
}); });
if (error) { if (error) {