(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;
})
| 81 | }; |
| 82 | |
| 83 | function AccountRow(props: { |
| 84 | readonly connection: Connection; |
| 85 | /** The integration declares scopes this connection was not granted — it must |
| 86 | * reconnect to grant the newly-needed access (e.g. after a service was added). */ |
| 87 | readonly needsReconsent: boolean; |
| 88 | readonly showOwnerLabel: boolean; |
| 89 | readonly onEdit: () => void; |
| 90 | readonly onReconnect: () => void; |
| 91 | readonly onRemove: () => void; |
| 92 | }) { |
| 93 | const { connection, needsReconsent } = props; |
| 94 | const [checking, setChecking] = useState(false); |
| 95 | |
| 96 | // The status renders WITHOUT any clicking: every checkHealth run persists its |
| 97 | // verdict on the connection row, so the list answers "has this expired?" at a |
| 98 | // glance. A live check from this session takes precedence. We deliberately do |
| 99 | // NOT derive expiry from the stored `expiresAt`: that's the access-token |
| 100 | // lifetime, which refreshes, so a passive countdown means nothing. |
| 101 | // |
| 102 | // Health checks are AUTOMATIC: the hook revalidates on mount, stale-while- |
| 103 | // revalidate style (shared with the integrations-list summary), and the |
| 104 | // persisted verdict renders instantly while the probe corrects it in place. |
| 105 | const { probe, status, runCheck } = useConnectionHealth(connection); |
| 106 | const indicator = HEALTH_INDICATOR_COLOR[status]; |
| 107 | |
| 108 | // Prefer the stored label from the connection row, then a probed identity, |
| 109 | // then the connection name. OAuth labels come from the grant's OIDC claims, |
| 110 | // while health identities remain useful for non-OAuth probes. |
| 111 | const identity = |
| 112 | (connection.identityLabel && connection.identityLabel.length > 0 |
| 113 | ? connection.identityLabel |
| 114 | : null) ?? (probe?.identity && probe.identity.length > 0 ? probe.identity : null); |
| 115 | const displayLabel = identity ?? String(connection.name); |
| 116 | |
| 117 | const expired = status === "expired"; |
| 118 | const needsHealthAttention = status === "expired" || status === "degraded"; |
| 119 | const healthDetail = needsHealthAttention ? probe?.detail : undefined; |
| 120 | const missingOAuthScopes = connection.missingOAuthScopes ?? []; |
| 121 | |
| 122 | const handleCheck = async () => { |
| 123 | if (checking) return; |
| 124 | setChecking(true); |
| 125 | const exit = await runCheck(); |
| 126 | setChecking(false); |
| 127 | if (Exit.isFailure(exit)) { |
| 128 | toast.error(messageFromExit(exit, "Health check failed")); |
| 129 | return; |
| 130 | } |
| 131 | // The hook already folded the fresh probe into the live state. |
| 132 | if (exit.value.status === "healthy") { |
| 133 | toast.success( |
| 134 | exit.value.identity ? `Healthy: ${exit.value.identity}` : "Connection is healthy", |
| 135 | ); |
| 136 | } else if (exit.value.status === "expired") { |
| 137 | toast.error("Connection expired, reconnect to restore access"); |
| 138 | } else if (exit.value.status === "degraded") { |
| 139 | toast.warning(exit.value.detail ?? "Connection check returned an error"); |
| 140 | } else { |
nothing calls this directly
no test coverage detected