Show bridge errors in connector cards

Display SCIM bridge state directly on Google Workspace and Microsoft
365 connector cards so admins can quickly identify failing bridges.

When a bridge is failed, render an inline error callout and surface the
latest provisioning event error message to make troubleshooting visible
without leaving the connector section.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
Cursor Agent
2026-05-27 23:33:41 +00:00
committed by Bryan Frimin
parent 4d4f4fa526
commit f02c3b88b7
2 changed files with 306 additions and 220 deletions

View File

@@ -23,6 +23,7 @@ import {
DialogFooter, DialogFooter,
Google, Google,
IconSettingsGear2, IconSettingsGear2,
IconWarning,
Input, Input,
useDialogRef, useDialogRef,
useToast, useToast,
@@ -38,9 +39,18 @@ import { useOrganizationId } from "#/hooks/useOrganizationId";
const googleWorkspaceConnectorFragment = graphql` const googleWorkspaceConnectorFragment = graphql`
fragment GoogleWorkspaceConnectorFragment on SCIMConfiguration { fragment GoogleWorkspaceConnectorFragment on SCIMConfiguration {
id id
events(first: 1) {
edges {
node {
id
errorMessage
}
}
}
bridge { bridge {
id id
type type
state
excludedUserNames excludedUserNames
connector { connector {
id id
@@ -83,9 +93,26 @@ export function GoogleWorkspaceConnector(props: {
const connector = bridge?.connector; const connector = bridge?.connector;
const scimConfigurationId = data?.id; const scimConfigurationId = data?.id;
const bridgeId = bridge?.id; const bridgeId = bridge?.id;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __, dateTimeFormat } = useTranslate(); const { __, dateTimeFormat } = useTranslate();
const bridgeState = bridge?.state ?? null;
const latestBridgeError = data?.events?.edges?.[0]?.node?.errorMessage ?? null;
const isBridgeFailed = bridgeState === "FAILED";
const isBridgePending = bridgeState === "PENDING";
const bridgeStatusBadgeVariant = isBridgeFailed
? "danger"
: isBridgePending
? "warning"
: "success";
const bridgeStatusLabel = isBridgeFailed
? __("Error")
: isBridgePending
? __("Syncing")
: __("Connected");
const bridgeErrorMessage = latestBridgeError
?? __(
"The bridge is currently failing to sync. Check the provisioning event history for more details.",
);
const { toast } = useToast(); const { toast } = useToast();
const dialogRef = useDialogRef(); const dialogRef = useDialogRef();
const excludedUserNamesDialogRef = useDialogRef(); const excludedUserNamesDialogRef = useDialogRef();
@@ -225,7 +252,8 @@ export function GoogleWorkspaceConnector(props: {
// Connected state // Connected state
return ( return (
<Card padded className="flex items-center gap-3"> <Card padded className="space-y-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center bg-subtle rounded"> <div className="w-10 h-10 flex items-center justify-center bg-subtle rounded">
<Google className="w-6 h-6" /> <Google className="w-6 h-6" />
</div> </div>
@@ -235,8 +263,8 @@ export function GoogleWorkspaceConnector(props: {
{sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))} {sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))}
</p> </p>
</div> </div>
<Badge variant="success" size="md"> <Badge variant={bridgeStatusBadgeVariant} size="md">
{__("Connected")} {bridgeStatusLabel}
</Badge> </Badge>
<Dialog <Dialog
ref={excludedUserNamesDialogRef} ref={excludedUserNamesDialogRef}
@@ -337,6 +365,21 @@ export function GoogleWorkspaceConnector(props: {
</Button> </Button>
</DialogFooter> </DialogFooter>
</Dialog> </Dialog>
</div>
{isBridgeFailed && (
<div className="flex items-start gap-2 rounded-lg bg-bg-warning/10 border border-border-warning p-3">
<IconWarning size={16} className="text-txt-danger shrink-0 mt-0.5" />
<div className="space-y-1">
<p className="text-sm font-medium text-txt-danger">
{__("Bridge is in an error state")}
</p>
<p className="text-sm text-txt-secondary whitespace-pre-wrap break-all">
{bridgeErrorMessage}
</p>
</div>
</div>
)}
</Card> </Card>
); );
} }

View File

@@ -22,6 +22,7 @@ import {
DialogContent, DialogContent,
DialogFooter, DialogFooter,
IconSettingsGear2, IconSettingsGear2,
IconWarning,
Input, Input,
Microsoft, Microsoft,
useDialogRef, useDialogRef,
@@ -38,9 +39,18 @@ import { useOrganizationId } from "#/hooks/useOrganizationId";
const microsoft365ConnectorFragment = graphql` const microsoft365ConnectorFragment = graphql`
fragment Microsoft365ConnectorFragment on SCIMConfiguration { fragment Microsoft365ConnectorFragment on SCIMConfiguration {
id id
events(first: 1) {
edges {
node {
id
errorMessage
}
}
}
bridge { bridge {
id id
type type
state
excludedUserNames excludedUserNames
connector { connector {
id id
@@ -83,9 +93,26 @@ export function Microsoft365Connector(props: {
const connector = bridge?.connector; const connector = bridge?.connector;
const scimConfigurationId = data?.id; const scimConfigurationId = data?.id;
const bridgeId = bridge?.id; const bridgeId = bridge?.id;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __, dateTimeFormat } = useTranslate(); const { __, dateTimeFormat } = useTranslate();
const bridgeState = bridge?.state ?? null;
const latestBridgeError = data?.events?.edges?.[0]?.node?.errorMessage ?? null;
const isBridgeFailed = bridgeState === "FAILED";
const isBridgePending = bridgeState === "PENDING";
const bridgeStatusBadgeVariant = isBridgeFailed
? "danger"
: isBridgePending
? "warning"
: "success";
const bridgeStatusLabel = isBridgeFailed
? __("Error")
: isBridgePending
? __("Syncing")
: __("Connected");
const bridgeErrorMessage = latestBridgeError
?? __(
"The bridge is currently failing to sync. Check the provisioning event history for more details.",
);
const { toast } = useToast(); const { toast } = useToast();
const dialogRef = useDialogRef(); const dialogRef = useDialogRef();
const excludedUserNamesDialogRef = useDialogRef(); const excludedUserNamesDialogRef = useDialogRef();
@@ -223,7 +250,8 @@ export function Microsoft365Connector(props: {
} }
return ( return (
<Card padded className="flex items-center gap-3"> <Card padded className="space-y-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center bg-subtle rounded"> <div className="w-10 h-10 flex items-center justify-center bg-subtle rounded">
<Microsoft className="w-6 h-6" /> <Microsoft className="w-6 h-6" />
</div> </div>
@@ -233,8 +261,8 @@ export function Microsoft365Connector(props: {
{sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))} {sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))}
</p> </p>
</div> </div>
<Badge variant="success" size="md"> <Badge variant={bridgeStatusBadgeVariant} size="md">
{__("Connected")} {bridgeStatusLabel}
</Badge> </Badge>
<Dialog <Dialog
ref={excludedUserNamesDialogRef} ref={excludedUserNamesDialogRef}
@@ -335,6 +363,21 @@ export function Microsoft365Connector(props: {
</Button> </Button>
</DialogFooter> </DialogFooter>
</Dialog> </Dialog>
</div>
{isBridgeFailed && (
<div className="flex items-start gap-2 rounded-lg bg-bg-warning/10 border border-border-warning p-3">
<IconWarning size={16} className="text-txt-danger shrink-0 mt-0.5" />
<div className="space-y-1">
<p className="text-sm font-medium text-txt-danger">
{__("Bridge is in an error state")}
</p>
<p className="text-sm text-txt-secondary whitespace-pre-wrap break-all">
{bridgeErrorMessage}
</p>
</div>
</div>
)}
</Card> </Card>
); );
} }