Add a per-row Test button to the Integrations list

The add/edit forms already had "Test connection", backed by existing
POST /api/integrations/:id/test (admin-only, pings with the stored
config) and POST /api/integrations/test (for a not-yet-saved one) --
but there was no way to re-test an already-configured integration
without opening its edit form. Add a "Test" button next to Edit/
Delete on each row, showing an OK/latency or Failed/error badge
inline once it returns. Pure frontend wiring to the existing endpoint,
no server changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 20:31:04 +02:00
co-authored by Claude Sonnet 5
parent d5b6c55383
commit 7564796c39
+28 -1
View File
@@ -29,6 +29,8 @@ export default function Integrations({ user }: { user: CurrentUser }) {
const [editingIntegration, setEditingIntegration] = useState<IntegrationSummary | null>(null);
const [error, setError] = useState<string | null>(null);
const [integrationColors, setIntegrationColors] = useState<Record<string, string>>({});
const [testingId, setTestingId] = useState<number | null>(null);
const [testResults, setTestResults] = useState<Record<number, { ok: boolean; latencyMs?: number; error?: string }>>({});
useEffect(() => {
api.settings.integrationColors().then((res) => setIntegrationColors(res.integrationColors)).catch(() => {});
@@ -52,6 +54,18 @@ export default function Integrations({ user }: { user: CurrentUser }) {
}
}
async function testIntegration(i: IntegrationSummary) {
setTestingId(i.id);
try {
const result = await api.integrations.testExisting(i.id, {});
setTestResults((prev) => ({ ...prev, [i.id]: result }));
} catch (err) {
setTestResults((prev) => ({ ...prev, [i.id]: { ok: false, error: err instanceof Error ? err.message : String(err) } }));
} finally {
setTestingId(null);
}
}
async function removeIntegration(i: IntegrationSummary) {
if (!confirm(`Delete integration "${i.name}"?`)) return;
try {
@@ -159,7 +173,17 @@ export default function Integrations({ user }: { user: CurrentUser }) {
</td>
{isAdmin && (
<td>
<div className="btn-list flex-nowrap">
<div className="btn-list flex-nowrap align-items-center">
<button className="btn btn-sm" onClick={() => testIntegration(i)} disabled={testingId === i.id}>
{testingId === i.id ? "Testing…" : "Test"}
</button>
{testResults[i.id] && testingId !== i.id && (
<span className={`badge ${testResults[i.id].ok ? "bg-green-lt text-green" : "bg-red-lt text-red"}`}>
{testResults[i.id].ok
? `OK${testResults[i.id].latencyMs !== undefined ? ` (${testResults[i.id].latencyMs}ms)` : ""}`
: "Failed"}
</span>
)}
<button className="btn btn-sm" onClick={() => setEditingIntegration(i)}>
Edit
</button>
@@ -167,6 +191,9 @@ export default function Integrations({ user }: { user: CurrentUser }) {
Delete
</button>
</div>
{testResults[i.id] && !testResults[i.id].ok && testingId !== i.id && (
<div className="text-red small mt-1">{testResults[i.id].error}</div>
)}
</td>
)}
</tr>