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+ } as any ) ;
67+ } else {
68+ // Test new connection with all values
69+ return API . connection . testOld ( plugin , {
70+ authType : values . authType || 'access_key' ,
71+ accessKeyId : values . accessKeyId || '' ,
72+ secretAccessKey : values . secretAccessKey || '' ,
73+ region : values . region || '' ,
74+ bucket : values . bucket || '' ,
75+ identityStoreId : values . identityStoreId || '' ,
76+ identityStoreRegion : values . identityStoreRegion || '' ,
77+ rateLimitPerHour : values . rateLimitPerHour || 20000 ,
78+ proxy : values . proxy || '' ,
79+ endpoint : '' , // Not used by Q Developer
80+ token : '' , // Not used by Q Developer
81+ } as any ) ;
82+ }
83+ } ,
84+ {
85+ setOperating : ( ) => { } , // We handle loading state ourselves
86+ hideToast : true , // We show our own success/error messages
87+ } ,
88+ ) ;
89+
90+ if ( success && result ) {
91+ setTestResult ( {
92+ success : true ,
93+ message : 'Connection test successful! AWS credentials and S3 access verified.' ,
94+ details : {
95+ s3Access : true ,
96+ identityCenterAccess : values . identityStoreId ? true : undefined ,
97+ } ,
98+ } ) ;
99+ } else {
100+ setTestResult ( {
101+ success : false ,
102+ message : 'Connection test failed. Please check your configuration.' ,
103+ } ) ;
104+ }
105+ } catch ( error : any ) {
106+ let errorMessage = 'Connection test failed. Please check your configuration.' ;
107+
108+ if ( error ?. response ?. data ?. message ) {
109+ errorMessage = error . response . data . message ;
110+ } else if ( error ?. message ) {
111+ errorMessage = error . message ;
112+ }
113+
114+ // Provide more specific error messages based on common issues
115+ if ( errorMessage . includes ( 'InvalidAccessKeyId' ) || errorMessage . includes ( 'SignatureDoesNotMatch' ) ) {
116+ errorMessage = 'Invalid AWS credentials. Please check your Access Key ID and Secret Access Key.' ;
117+ } else if ( errorMessage . includes ( 'NoSuchBucket' ) ) {
118+ errorMessage = 'S3 bucket not found. Please check the bucket name and region.' ;
119+ } else if ( errorMessage . includes ( 'AccessDenied' ) ) {
120+ errorMessage = 'Access denied. Please check your AWS permissions for S3 and IAM Identity Center.' ;
121+ } else if ( errorMessage . includes ( 'InvalidBucketName' ) ) {
122+ errorMessage = 'Invalid S3 bucket name. Please check the bucket name format.' ;
123+ } else if ( errorMessage . includes ( 'NoCredentialsError' ) ) {
124+ errorMessage = 'AWS credentials not found. Please provide valid Access Key ID and Secret Access Key, or ensure IAM role is properly configured.' ;
125+ }
126+
127+ setTestResult ( {
128+ success : false ,
129+ message : errorMessage ,
130+ } ) ;
131+ } finally {
132+ setTesting ( false ) ;
133+ }
134+ } ;
135+
136+ const getAlertType = ( ) => {
137+ if ( ! testResult ) return undefined ;
138+ return testResult . success ? 'success' : 'error' ;
139+ } ;
140+
141+ const getAlertIcon = ( ) => {
142+ if ( testing ) return < LoadingOutlined /> ;
143+ if ( ! testResult ) return undefined ;
144+ return testResult . success ? < CheckCircleOutlined /> : < ExclamationCircleOutlined /> ;
145+ } ;
146+
147+ return (
148+ < Space direction = "vertical" style = { { width : '100%' } } >
149+ < Button
150+ type = "default"
151+ loading = { testing }
152+ disabled = { disabled || testing }
153+ onClick = { handleTest }
154+ style = { { marginTop : 16 } }
155+ >
156+ { testing ? 'Testing Connection...' : 'Test Connection' }
157+ </ Button >
158+
159+ { ( testResult || testing ) && (
160+ < Alert
161+ type = { getAlertType ( ) }
162+ icon = { getAlertIcon ( ) }
163+ message = { testing ? 'Testing connection to AWS S3 and IAM Identity Center...' : testResult ?. message }
164+ description = {
165+ testResult ?. success && testResult . details ? (
166+ < div >
167+ < div > ✓ S3 Access: Verified</ div >
168+ { testResult . details . identityCenterAccess && (
169+ < div > ✓ IAM Identity Center: Configured</ div >
170+ ) }
171+ { ! values . identityStoreId && (
172+ < div style = { { marginTop : 8 , color : '#faad14' } } >
173+ ⚠️ IAM Identity Center not configured - user display names will show as user IDs
174+ </ div >
175+ ) }
176+ </ div >
177+ ) : undefined
178+ }
179+ showIcon
180+ style = { { marginTop : 8 } }
181+ />
182+ ) }
183+ </ Space >
184+ ) ;
185+ } ;
0 commit comments