1+ /*
2+ * Licensed to the Apache Software Foundation (ASF) under one or more
3+ * contributor license agreements. See the NOTICE file distributed with
4+ * this work for additional information regarding copyright ownership.
5+ * The ASF licenses this file to You under the Apache License, Version 2.0
6+ * (the "License"); you may not use this file except in compliance with
7+ * the License. You may obtain a copy of the License at
8+ *
9+ * http://www.apache.org/licenses/LICENSE-2.0
10+ *
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
16+ *
17+ */
18+
19+ import { useState } from 'react' ;
20+ import { Button , Alert , Space } from 'antd' ;
21+ import { CheckCircleOutlined , ExclamationCircleOutlined , LoadingOutlined } from '@ant-design/icons' ;
22+
23+ import API from '@/api' ;
24+ import { operator } from '@/utils' ;
25+
26+ interface Props {
27+ plugin : string ;
28+ connectionId ?: ID ;
29+ values : any ;
30+ initialValues : any ;
31+ disabled ?: boolean ;
32+ }
33+
34+ interface TestResult {
35+ success : boolean ;
36+ message : string ;
37+ details ?: {
38+ s3Access ?: boolean ;
39+ identityCenterAccess ?: boolean ;
40+ } ;
41+ }
42+
43+ export const QDevConnectionTest = ( { plugin, connectionId, values, initialValues, disabled } : Props ) => {
44+ const [ testing , setTesting ] = useState ( false ) ;
45+ const [ testResult , setTestResult ] = useState < TestResult | null > ( null ) ;
46+
47+ const handleTest = async ( ) => {
48+ setTesting ( true ) ;
49+ setTestResult ( null ) ;
50+
51+ try {
52+ const [ success , result ] = await operator (
53+ ( ) => {
54+ if ( connectionId ) {
55+ // Test existing connection with only changed values
56+ return API . connection . test ( plugin , connectionId , {
57+ authType : values . authType !== initialValues . authType ? values . authType : undefined ,
58+ accessKeyId : values . accessKeyId !== initialValues . accessKeyId ? values . accessKeyId : undefined ,
59+ secretAccessKey : values . secretAccessKey !== initialValues . secretAccessKey ? values . secretAccessKey : undefined ,
60+ region : values . region !== initialValues . region ? values . region : undefined ,
61+ bucket : values . bucket !== initialValues . bucket ? values . bucket : undefined ,
62+ identityStoreId : values . identityStoreId !== initialValues . identityStoreId ? values . identityStoreId : undefined ,
63+ identityStoreRegion : values . identityStoreRegion !== initialValues . identityStoreRegion ? values . identityStoreRegion : undefined ,
64+ rateLimitPerHour : values . rateLimitPerHour !== initialValues . rateLimitPerHour ? values . rateLimitPerHour : undefined ,
65+ proxy : values . proxy !== initialValues . proxy ? values . proxy : undefined ,
66+ } ) ;
67+ } else {
68+ // Test new connection with all values
69+ return API . connection . testOld ( plugin , {
70+ name : values . name || '' ,
71+ authType : values . authType || 'access_key' ,
72+ accessKeyId : values . accessKeyId || '' ,
73+ secretAccessKey : values . secretAccessKey || '' ,
74+ region : values . region || '' ,
75+ bucket : values . bucket || '' ,
76+ identityStoreId : values . identityStoreId || '' ,
77+ identityStoreRegion : values . identityStoreRegion || '' ,
78+ rateLimitPerHour : values . rateLimitPerHour || 20000 ,
79+ proxy : values . proxy || '' ,
80+ endpoint : '' , // Not used by Q Developer
81+ authMethod : '' , // Not used by Q Developer
82+ username : '' , // Not used by Q Developer
83+ password : '' , // Not used by Q Developer
84+ token : '' , // Not used by Q Developer
85+ appId : '' , // Not used by Q Developer
86+ secretKey : '' , // Not used by Q Developer
87+ dbUrl : '' , // Not used by Q Developer
88+ organization : '' , // Not used by Q Developer
89+ } ) ;
90+ }
91+ } ,
92+ {
93+ setOperating : ( ) => { } , // We handle loading state ourselves
94+ hideToast : true , // We show our own success/error messages
95+ } ,
96+ ) ;
97+
98+ if ( success && result ) {
99+ setTestResult ( {
100+ success : true ,
101+ message : 'Connection test successful! AWS credentials and S3 access verified.' ,
102+ details : {
103+ s3Access : true ,
104+ identityCenterAccess : values . identityStoreId ? true : undefined ,
105+ } ,
106+ } ) ;
107+ } else {
108+ setTestResult ( {
109+ success : false ,
110+ message : 'Connection test failed. Please check your configuration.' ,
111+ } ) ;
112+ }
113+ } catch ( error : any ) {
114+ let errorMessage = 'Connection test failed. Please check your configuration.' ;
115+
116+ if ( error ?. response ?. data ?. message ) {
117+ errorMessage = error . response . data . message ;
118+ } else if ( error ?. message ) {
119+ errorMessage = error . message ;
120+ }
121+
122+ // Provide more specific error messages based on common issues
123+ if ( errorMessage . includes ( 'InvalidAccessKeyId' ) || errorMessage . includes ( 'SignatureDoesNotMatch' ) ) {
124+ errorMessage = 'Invalid AWS credentials. Please check your Access Key ID and Secret Access Key.' ;
125+ } else if ( errorMessage . includes ( 'NoSuchBucket' ) ) {
126+ errorMessage = 'S3 bucket not found. Please check the bucket name and region.' ;
127+ } else if ( errorMessage . includes ( 'AccessDenied' ) ) {
128+ errorMessage = 'Access denied. Please check your AWS permissions for S3 and IAM Identity Center.' ;
129+ } else if ( errorMessage . includes ( 'InvalidBucketName' ) ) {
130+ errorMessage = 'Invalid S3 bucket name. Please check the bucket name format.' ;
131+ } else if ( errorMessage . includes ( 'NoCredentialsError' ) ) {
132+ errorMessage = 'AWS credentials not found. Please provide valid Access Key ID and Secret Access Key, or ensure IAM role is properly configured.' ;
133+ }
134+
135+ setTestResult ( {
136+ success : false ,
137+ message : errorMessage ,
138+ } ) ;
139+ } finally {
140+ setTesting ( false ) ;
141+ }
142+ } ;
143+
144+ const getAlertType = ( ) => {
145+ if ( ! testResult ) return undefined ;
146+ return testResult . success ? 'success' : 'error' ;
147+ } ;
148+
149+ const getAlertIcon = ( ) => {
150+ if ( testing ) return < LoadingOutlined /> ;
151+ if ( ! testResult ) return undefined ;
152+ return testResult . success ? < CheckCircleOutlined /> : < ExclamationCircleOutlined /> ;
153+ } ;
154+
155+ return (
156+ < Space direction = "vertical" style = { { width : '100%' } } >
157+ < Button
158+ type = "default"
159+ loading = { testing }
160+ disabled = { disabled || testing }
161+ onClick = { handleTest }
162+ style = { { marginTop : 16 } }
163+ >
164+ { testing ? 'Testing Connection...' : 'Test Connection' }
165+ </ Button >
166+
167+ { ( testResult || testing ) && (
168+ < Alert
169+ type = { getAlertType ( ) }
170+ icon = { getAlertIcon ( ) }
171+ message = { testing ? 'Testing connection to AWS S3 and IAM Identity Center...' : testResult ?. message }
172+ description = {
173+ testResult ?. success && testResult . details ? (
174+ < div >
175+ < div > ✓ S3 Access: Verified</ div >
176+ { testResult . details . identityCenterAccess && (
177+ < div > ✓ IAM Identity Center: Configured</ div >
178+ ) }
179+ { ! values . identityStoreId && (
180+ < div style = { { marginTop : 8 , color : '#faad14' } } >
181+ ⚠️ IAM Identity Center not configured - user display names will show as user IDs
182+ </ div >
183+ ) }
184+ </ div >
185+ ) : undefined
186+ }
187+ showIcon
188+ style = { { marginTop : 8 } }
189+ />
190+ ) }
191+ </ Space >
192+ ) ;
193+ } ;
0 commit comments