(props: {
readonly connection: Connection;
/** The integration declares scopes this connection was not granted — it must
* reconnect to grant the newly-needed access (e.g. after a service was added). */
readonly needsReconsent: boolean;
readonly showOwnerLabel: boolean;
readonly onEdit: () => void;
readonly onReconnect: () => void;
readonly onRemove: () => void;
})
| 61 | const OWNERS: readonly Owner[] = ["org", "user"]; |
| 62 | |
| 63 | function AccountRow(props: { |
| 64 | readonly connection: Connection; |
| 65 | /** The integration declares scopes this connection was not granted — it must |
| 66 | * reconnect to grant the newly-needed access (e.g. after a service was added). */ |
| 67 | readonly needsReconsent: boolean; |
| 68 | readonly showOwnerLabel: boolean; |
| 69 | readonly onEdit: () => void; |
| 70 | readonly onReconnect: () => void; |
| 71 | readonly onRemove: () => void; |
| 72 | }) { |
| 73 | const { connection, needsReconsent } = props; |
| 74 | const [checking, setChecking] = useState(false); |
| 75 | |
| 76 | // The status renders WITHOUT any clicking: every checkHealth run persists its |
| 77 | // verdict on the connection row, so the list answers "has this expired?" at a |
| 78 | // glance. A live check from this session takes precedence. We deliberately do |
| 79 | // NOT derive expiry from the stored `expiresAt`: that's the access-token |
| 80 | // lifetime, which refreshes, so a passive countdown means nothing. |
| 81 | // |
| 82 | // Health checks are AUTOMATIC: the hook revalidates on mount, stale-while- |
| 83 | // revalidate style (shared with the integrations-list summary), and the |
| 84 | // persisted verdict renders instantly while the probe corrects it in place. |
| 85 | const { probe, status, runCheck } = useConnectionHealth(connection); |
| 86 | const indicator = HEALTH_INDICATOR_COLOR[status]; |
| 87 | |
| 88 | // Prefer a probed identity (the live account), then the stored label, then the |
| 89 | // connection name. The probe is the whole point: it shows WHICH account this is. |
| 90 | const identity = |
| 91 | (probe?.identity && probe.identity.length > 0 ? probe.identity : null) ?? |
| 92 | (connection.identityLabel && connection.identityLabel.length > 0 |
| 93 | ? connection.identityLabel |
| 94 | : null); |
| 95 | const displayLabel = identity ?? String(connection.name); |
| 96 | |
| 97 | const expired = status === "expired"; |
| 98 | |
| 99 | const handleCheck = async () => { |
| 100 | if (checking) return; |
| 101 | setChecking(true); |
| 102 | const exit = await runCheck(); |
| 103 | setChecking(false); |
| 104 | if (Exit.isFailure(exit)) { |
| 105 | toast.error(messageFromExit(exit, "Health check failed")); |
| 106 | return; |
| 107 | } |
| 108 | // The hook already folded the fresh probe into the live state. |
| 109 | if (exit.value.status === "healthy") { |
| 110 | toast.success( |
| 111 | exit.value.identity ? `Healthy: ${exit.value.identity}` : "Connection is healthy", |
| 112 | ); |
| 113 | } else if (exit.value.status === "expired") { |
| 114 | toast.error("Connection expired, reconnect to restore access"); |
| 115 | } else if (exit.value.status === "degraded") { |
| 116 | toast.warning(exit.value.detail ?? "Connection check returned an error"); |
| 117 | } else { |
| 118 | toast.message("No health check is configured for this integration"); |
| 119 | } |
| 120 | }; |
nothing calls this directly
no test coverage detected