LCOV - code coverage report
Current view: top level - dashql-app/src/connections - connection_inline_header.tsx Coverage Total Hit
Test: DashQL 6e74948 Lines: 100.0 % 127 127
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : import * as React from 'react';
       2              : import * as connection from '@ankoh/dashql-jsonschema/connection.js';
       3              : import symbols from '@ankoh/dashql-svg-symbols';
       4              : 
       5              : import * as style from './connection_inline_header.module.css';
       6              : 
       7              : import { PlugIcon, XIcon } from '../ui/foundations/symbol_icon.js';
       8              : 
       9              : import { Button, ButtonVariant, IconButton } from '../ui/foundations/button.js';
      10              : import { ConnectionHealth, ConnectionState, isConnectionOpen } from './connection_state.js';
      11              : import { ConnectorInfo } from './connector_info.js';
      12              : import { IndicatorStatus, StatusIndicator } from '../ui/foundations/status_indicator.js';
      13              : import { SegmentedControl } from '../ui/foundations/segmented_control.js';
      14              : import { getConnectionError, getConnectionHealthIndicator, getConnectionStatusText } from './salesforce_connection_settings.js';
      15              : import { useLogger } from '../platform/logger/logger_provider.js';
      16              : import { CONNECTOR_ICON_STYLE, useConnectorIcon } from './connector_icon.js';
      17              : 
      18              : const LOG_CTX = "conn_inline_header";
      19              : 
      20              : interface Props {
      21              :     connector: ConnectorInfo;
      22              :     connection: ConnectionState | null;
      23              :     wrongPlatform: boolean;
      24              :     setupConnection?: () => void;
      25              :     cancelSetup?: () => void;
      26              :     resetSetup?: () => void;
      27              :     protocol?: connection.HyperProtocol;
      28              :     protocols?: connection.HyperProtocol[];
      29              :     onProtocolChange?: (protocol: connection.HyperProtocol) => void;
      30              :     freezeInput?: boolean;
      31              :     embedded?: boolean;
      32              :     onClose?: () => void;
      33              :     connectorNameAction?: React.ReactNode;
      34              :     statusText?: string;
      35              :     indicatorStatus?: IndicatorStatus;
      36              :     statusError?: string | null;
      37              :     connectionHealth?: ConnectionHealth;
      38              : }
      39              : 
      40            1 : const PROTOCOL_LABELS: Record<connection.HyperProtocol, string> = {
      41            1 :     WASM: "WASM",
      42            1 :     V3_GRPC: "gRPC",
      43            1 :     V3_HTTP: "HTTP",
      44            1 :     V3_HTTP_LINK: "HTTP link",
      45            1 : };
      46              : 
      47            1 : export function ConnectionInlineHeader(props: Props): React.ReactElement {
      48            1 :     const connectorIcon = useConnectorIcon();
      49            1 :     const logger = useLogger();
      50              : 
      51              :     // Get the action button.
      52              :     // Suppress the header button when no setup/cancel/reset handler is provided.
      53            1 :     const headerActionsProvided = props.setupConnection || props.cancelSetup || props.resetSetup;
      54            1 :     const connectionHealth = props.connectionHealth ?? props.connection?.connectionHealth;
      55            1 :     let connectButton: React.ReactElement = <div />;
      56            1 :     if (headerActionsProvided) {
      57            1 :         switch (connectionHealth) {
      58            1 :             case ConnectionHealth.NOT_STARTED:
      59            1 :             case ConnectionHealth.CANCELLED:
      60            1 :             case ConnectionHealth.FAILED:
      61            1 :                 connectButton = (
      62            1 :                     <Button
      63            1 :                         variant={ButtonVariant.Primary}
      64            1 :                         leadingVisual={props.embedded ? undefined : PlugIcon}
      65            1 :                         data-testid="connection-setup"
      66            1 :                         onClick={props.setupConnection}
      67            1 :                         disabled={props.wrongPlatform || !props.setupConnection}
      68              :                     >
      69            1 :                         {props.embedded ? "Select" : "Connect"}
      70            1 :                     </Button>
      71              :                 );
      72            1 :                 break;
      73            1 :             case ConnectionHealth.CONNECTING:
      74            1 :                 connectButton = (
      75            1 :                     <Button
      76            1 :                         variant={ButtonVariant.Danger}
      77            1 :                         leadingVisual={XIcon}
      78            1 :                         data-testid="connection-cancel"
      79            1 :                         onClick={props.cancelSetup}
      80            1 :                         disabled={!props.cancelSetup}
      81            1 :                     >
      82              :                         Cancel
      83            1 :                     </Button>
      84              :                 );
      85            1 :                 break;
      86            1 :             case ConnectionHealth.ONLINE:
      87            1 :                 connectButton = (
      88            1 :                     <Button
      89            1 :                         variant={ButtonVariant.Danger}
      90            1 :                         leadingVisual={XIcon}
      91            1 :                         data-testid="connection-reset"
      92            1 :                         onClick={props.resetSetup}
      93            1 :                         disabled={!props.resetSetup}
      94            1 :                     >
      95              :                         Disconnect
      96            1 :                     </Button>
      97              :                 );
      98              :                 break;
      99            1 :         }
     100              :     }
     101              : 
     102              :     // An open connection keeps its protocol, switching it would replace the settings page underneath it
     103            1 :     const protocolLocked = props.freezeInput || isConnectionOpen(connectionHealth);
     104              : 
     105              :     // Get the connection status
     106            1 :     let statusText: string = props.statusText ?? "";
     107            1 :     let indicatorStatus: IndicatorStatus = props.indicatorStatus ?? IndicatorStatus.None;
     108            1 :     if (props.wrongPlatform) {
     109            1 :         statusText = "Connector is unavailable on this platform";
     110            1 :         indicatorStatus = IndicatorStatus.Skip;
     111            1 :     } else if (props.statusText == null && props.indicatorStatus == null) {
     112            1 :         statusText = getConnectionStatusText(props.connection?.connectionStatus, logger);
     113            1 :         indicatorStatus = getConnectionHealthIndicator(props.connection?.connectionHealth ?? null);
     114            1 :     }
     115              : 
     116              :     // Get the connection error (if any)
     117            1 :     const connectionError = props.statusError ?? getConnectionError(props.connection?.details ?? null)?.message.toString();
     118              : 
     119            1 :     return (
     120            1 :         <div className={style.container}>
     121            1 :             <div className={style.header_row}>
     122            1 :                 <div className={style.connector_info}>
     123            1 :                     <div className={style.connector_icon}>
     124            1 :                         <svg width="20px" height="24px">
     125            1 :                             <use xlinkHref={`${symbols}#${connectorIcon(props.connector)}`} style={CONNECTOR_ICON_STYLE} />
     126            1 :                         </svg>
     127            1 :                     </div>
     128            1 :                     <div className={style.connector_name} data-testid="connector-name">
     129            1 :                         {props.connector.names.displayLong}
     130            1 :                     </div>
     131            1 :                     {props.connectorNameAction}
     132            1 :                 </div>
     133            1 :                 <div className={style.actions}>
     134            1 :                     {props.protocol !== undefined && props.onProtocolChange && props.protocols && props.protocols.length > 1 && (
     135            1 :                         <div title={protocolLocked ? "Disconnect to switch the protocol" : undefined}>
     136            1 :                             <SegmentedControl
     137            1 :                                 aria-label="Attached database protocol"
     138            1 :                                 onChange={(index) => {
     139            1 :                                     if (!protocolLocked) props.onProtocolChange!(props.protocols![index]);
     140            1 :                                 }}
     141              :                             >
     142            1 :                                 {props.protocols.map(p => (
     143            1 :                                     <SegmentedControl.Button
     144            1 :                                         key={p}
     145            1 :                                         selected={props.protocol === p}
     146            1 :                                         disabled={protocolLocked}
     147            1 :                                         data-testid="protocol-option"
     148            1 :                                         data-protocol={p}
     149              :                                     >
     150            1 :                                         {PROTOCOL_LABELS[p]}
     151            1 :                                     </SegmentedControl.Button>
     152            1 :                                 ))}
     153            1 :                             </SegmentedControl>
     154            1 :                         </div>
     155              :                     )}
     156            1 :                     {props.onClose && (
     157            1 :                         <IconButton
     158            1 :                             variant={ButtonVariant.Invisible}
     159            1 :                             aria-label="Close"
     160            1 :                             data-testid="connection-close"
     161            1 :                             onClick={props.onClose}
     162              :                         >
     163            1 :                             <XIcon />
     164            1 :                         </IconButton>
     165              :                     )}
     166            1 :                 </div>
     167            1 :             </div>
     168            1 :             <div className={style.status_row}>
     169            1 :                 {!props.embedded && (
     170            1 :                     <div className={style.status_left} data-testid="connection-status">
     171            1 :                         <div className={style.status_indicator}>
     172            1 :                             <StatusIndicator status={indicatorStatus} />
     173            1 :                         </div>
     174            1 :                         <div className={style.status_text}>
     175            1 :                             {statusText}
     176            1 :                         </div>
     177            1 :                         {connectionError && (
     178            1 :                             <div className={style.status_error}>
     179            1 :                                 {connectionError}
     180            1 :                             </div>
     181              :                         )}
     182            1 :                     </div>
     183              :                 )}
     184            1 :                 {props.embedded && (
     185            1 :                     <div className={style.embedded_label} data-testid="embedded-engine-label">
     186              :                         Embedded Engine
     187            1 :                     </div>
     188              :                 )}
     189            1 :                 <div className={style.status_right}>
     190            1 :                     {connectButton}
     191            1 :                 </div>
     192            1 :             </div>
     193            1 :         </div>
     194              :     );
     195            1 : }
        

Generated by: LCOV version 2.0-1