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 : }
|