This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 09607368f feat:add q dev ui (#8587)
09607368f is described below
commit 09607368f50182f91e85f8b7f11c8bbe8c9fd0e2
Author: Warren Chen <[email protected]>
AuthorDate: Tue Sep 23 16:42:06 2025 +0800
feat:add q dev ui (#8587)
---
.../migrationscripts/20250320_modify_file_meta.go | 2 +-
config-ui/src/plugins/register/index.ts | 2 +
.../src/plugins/register/q-dev/assets/icon.svg | 62 ++++++++++
config-ui/src/plugins/register/q-dev/config.tsx | 82 +++++++++++++
.../q-dev/connection-fields/aws-credentials.tsx | 130 +++++++++++++++++++++
.../connection-fields/identity-center-config.tsx | 100 ++++++++++++++++
.../register/q-dev/connection-fields/index.ts | 21 ++++
.../register/q-dev/connection-fields/s3-config.tsx | 64 ++++++++++
config-ui/src/plugins/register/q-dev/index.ts | 19 +++
9 files changed, 481 insertions(+), 1 deletion(-)
diff --git
a/backend/plugins/q_dev/models/migrationscripts/20250320_modify_file_meta.go
b/backend/plugins/q_dev/models/migrationscripts/20250320_modify_file_meta.go
index 9744f6d09..83311cf07 100644
--- a/backend/plugins/q_dev/models/migrationscripts/20250320_modify_file_meta.go
+++ b/backend/plugins/q_dev/models/migrationscripts/20250320_modify_file_meta.go
@@ -51,7 +51,7 @@ func (*modifyFileMetaTable) Up(basicRes context.BasicRes)
errors.Error {
return errors.Default.Wrap(err, "failed to load column
metadata for _tool_q_dev_s3_file_meta.processed_time")
}
if len(cols) == 0 {
- // If column is not visible in metadata, treat as no
processing needed
+ // If column is not visible in metadata, treat as no
processing needed
return nil
}
if nullable, ok := cols[0].Nullable(); ok {
diff --git a/config-ui/src/plugins/register/index.ts
b/config-ui/src/plugins/register/index.ts
index 36f87f701..8fb38ee29 100644
--- a/config-ui/src/plugins/register/index.ts
+++ b/config-ui/src/plugins/register/index.ts
@@ -33,6 +33,7 @@ import { TAPDConfig } from './tapd';
import { WebhookConfig } from './webhook';
import { ZenTaoConfig } from './zentao';
import { OpsgenieConfig } from './opsgenie';
+import { QDevConfig } from './q-dev';
import { TeambitionConfig } from './teambition';
import { TestmoConfig } from './testmo';
import { SlackConfig } from './slack/config';
@@ -50,6 +51,7 @@ export const pluginConfigs: IPluginConfig[] = [
JiraConfig,
PagerDutyConfig,
SlackConfig,
+ QDevConfig,
SonarQubeConfig,
TAPDConfig,
TestmoConfig,
diff --git a/config-ui/src/plugins/register/q-dev/assets/icon.svg
b/config-ui/src/plugins/register/q-dev/assets/icon.svg
new file mode 100644
index 000000000..503114f14
--- /dev/null
+++ b/config-ui/src/plugins/register/q-dev/assets/icon.svg
@@ -0,0 +1,62 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<svg width="100" height="100" viewBox="0 0 36 36"
xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Hex logo with pointer
to edge">
+ <defs>
+ <linearGradient id="g" x1="7" y1="6" x2="29" y2="30"
gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#5AA4FF"/>
+ <stop offset="0.55" stop-color="#5C72FF"/>
+ <stop offset="1" stop-color="#7B3BFF"/>
+ </linearGradient>
+ <linearGradient id="glow" x1="8" y1="7" x2="28" y2="29"
gradientUnits="userSpaceOnUse">
+ <stop offset="0" stop-color="#FFF" stop-opacity="0.95"/>
+ <stop offset="1" stop-color="#FFF" stop-opacity="0.85"/>
+ </linearGradient>
+ </defs>
+
+ <path fill="url(#g)" d="
+ M 18 6
+ L 26.6 10.9
+ a 2.9 2.9 0 0 1 1.45 2.52
+ v 9.16
+ a 2.9 2.9 0 0 1 -1.45 2.52
+ L 18 30
+ L 9.4 25.09
+ A 2.9 2.9 0 0 1 7.95 22.57
+ v -9.16
+ A 2.9 2.9 0 0 1 9.4 10.9
+ L 18 6
+ Z"/>
+
+ <path id="inner" fill="none" stroke="url(#glow)" stroke-width="1.7" d="
+ M 18 8.2
+ L 25.6 12.3
+ a 1.9 1.9 0 0 1 0.95 1.64
+ v 7.92
+ a 1.9 1.9 0 0 1 -0.95 1.64
+ L 18 27.8
+ L 10.4 23.7
+ a 1.9 1.9 0 0 1 -0.95 -1.64
+ v -7.92
+ a 1.9 1.9 0 0 1 0.95 -1.64
+ L 18 8.2
+ Z"/>
+
+ <g stroke="#FFF" stroke-width="2.0" stroke-linecap="round"
stroke-linejoin="round" fill="none">
+ <path d="M 18.1 17.0 L 24.9 20.3"/>
+ <circle cx="18.1" cy="17.0" r="1.7" fill="#FFF" stroke="none"/>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/config-ui/src/plugins/register/q-dev/config.tsx
b/config-ui/src/plugins/register/q-dev/config.tsx
new file mode 100644
index 000000000..ad3045203
--- /dev/null
+++ b/config-ui/src/plugins/register/q-dev/config.tsx
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { IPluginConfig } from '@/types';
+
+import Icon from './assets/icon.svg?react';
+
+export const QDevConfig: IPluginConfig = {
+ plugin: 'q_dev',
+ name: 'Q Developer',
+ icon: ({ color }) => <Icon fill={color} />,
+ sort: 20,
+ connection: {
+ docLink: '', // TODO: 添加文档链接
+ initialValues: {
+ accessKeyId: '',
+ secretAccessKey: '',
+ region: 'us-east-1',
+ bucket: '',
+ identityStoreId: '',
+ identityStoreRegion: 'us-east-1',
+ rateLimitPerHour: 20000,
+ },
+ fields: [
+ 'name',
+ {
+ key: 'accessKeyId',
+ label: 'AWS Access Key ID',
+ subLabel: '请输入您的AWS Access Key ID',
+ },
+ {
+ key: 'secretAccessKey',
+ label: 'AWS Secret Access Key',
+ subLabel: '请输入您的AWS Secret Access Key',
+ },
+ {
+ key: 'region',
+ label: 'AWS区域',
+ subLabel: '请输入AWS区域,例如:us-east-1',
+ },
+ {
+ key: 'bucket',
+ label: 'S3存储桶名称',
+ subLabel: '请输入存储Q Developer数据的S3存储桶名称',
+ },
+ {
+ key: 'identityStoreId',
+ label: 'IAM Identity Store ID',
+ subLabel: '请输入Identity Store ID,格式:d-xxxxxxxxxx',
+ },
+ {
+ key: 'identityStoreRegion',
+ label: 'IAM Identity Center区域',
+ subLabel: '请输入IAM Identity Center所在的AWS区域',
+ },
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel: '设置每小时的API请求限制,用于控制数据收集速度',
+ defaultValue: 20000,
+ },
+ ],
+ },
+ dataScope: {
+ title: 'Data Sources',
+ },
+};
\ No newline at end of file
diff --git
a/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx
b/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx
new file mode 100644
index 000000000..58e627b2b
--- /dev/null
+++ b/config-ui/src/plugins/register/q-dev/connection-fields/aws-credentials.tsx
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { ChangeEvent, useEffect } from 'react';
+import { Input } from 'antd';
+
+import { Block } from '@/components';
+
+interface Props {
+ accessKeyId: string;
+ secretAccessKey: string;
+ region: string;
+ errors: {
+ accessKeyId?: string;
+ secretAccessKey?: string;
+ region?: string;
+ };
+ setValues: (values: any) => void;
+ setErrors: (errors: any) => void;
+}
+
+export const AwsCredentials = ({
+ accessKeyId,
+ secretAccessKey,
+ region,
+ errors,
+ setValues,
+ setErrors
+}: Props) => {
+
+ const validateAccessKeyId = (value: string) => {
+ if (!value) {
+ return 'AWS Access Key ID是必填项';
+ }
+ if (!/^[A-Z0-9]{20}$/.test(value)) {
+ return 'AWS Access Key ID格式不正确';
+ }
+ return '';
+ };
+
+ const validateSecretAccessKey = (value: string) => {
+ if (!value) {
+ return 'AWS Secret Access Key是必填项';
+ }
+ if (value.length < 40) {
+ return 'AWS Secret Access Key长度不足';
+ }
+ return '';
+ };
+
+ const validateRegion = (value: string) => {
+ if (!value) {
+ return 'AWS区域是必填项';
+ }
+ if (!/^[a-z0-9-]+$/.test(value)) {
+ return 'AWS区域格式不正确';
+ }
+ return '';
+ };
+
+ const handleAccessKeyIdChange = (e: ChangeEvent<HTMLInputElement>) => {
+ const value = e.target.value;
+ setValues({ accessKeyId: value });
+ setErrors({ accessKeyId: validateAccessKeyId(value) });
+ };
+
+ const handleSecretAccessKeyChange = (e: ChangeEvent<HTMLInputElement>) => {
+ const value = e.target.value;
+ setValues({ secretAccessKey: value });
+ setErrors({ secretAccessKey: validateSecretAccessKey(value) });
+ };
+
+ const handleRegionChange = (e: ChangeEvent<HTMLInputElement>) => {
+ const value = e.target.value;
+ setValues({ region: value });
+ setErrors({ region: validateRegion(value) });
+ };
+
+ return (
+ <>
+ <Block title="AWS Access Key ID" description="请输入您的AWS Access Key ID"
required>
+ <Input
+ style={{ width: 386 }}
+ placeholder="AKIAIOSFODNN7EXAMPLE"
+ value={accessKeyId}
+ onChange={handleAccessKeyIdChange}
+ status={errors.accessKeyId ? 'error' : ''}
+ />
+ {errors.accessKeyId && <div style={{ color: 'red', fontSize: '12px',
marginTop: '4px' }}>{errors.accessKeyId}</div>}
+ </Block>
+
+ <Block title="AWS Secret Access Key" description="请输入您的AWS Secret Access
Key" required>
+ <Input.Password
+ style={{ width: 386 }}
+ placeholder="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
+ value={secretAccessKey}
+ onChange={handleSecretAccessKeyChange}
+ status={errors.secretAccessKey ? 'error' : ''}
+ />
+ {errors.secretAccessKey && <div style={{ color: 'red', fontSize:
'12px', marginTop: '4px' }}>{errors.secretAccessKey}</div>}
+ </Block>
+
+ <Block title="AWS区域" description="请输入AWS区域,例如:us-east-1" required>
+ <Input
+ style={{ width: 386 }}
+ placeholder="us-east-1"
+ value={region}
+ onChange={handleRegionChange}
+ status={errors.region ? 'error' : ''}
+ />
+ {errors.region && <div style={{ color: 'red', fontSize: '12px',
marginTop: '4px' }}>{errors.region}</div>}
+ </Block>
+ </>
+ );
+};
\ No newline at end of file
diff --git
a/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx
b/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx
new file mode 100644
index 000000000..4f0de4666
--- /dev/null
+++
b/config-ui/src/plugins/register/q-dev/connection-fields/identity-center-config.tsx
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { ChangeEvent } from 'react';
+import { Input } from 'antd';
+
+import { Block } from '@/components';
+
+interface Props {
+ identityStoreId: string;
+ identityStoreRegion: string;
+ errors: {
+ identityStoreId?: string;
+ identityStoreRegion?: string;
+ };
+ setValues: (values: any) => void;
+ setErrors: (errors: any) => void;
+}
+
+export const IdentityCenterConfig = ({
+ identityStoreId,
+ identityStoreRegion,
+ errors,
+ setValues,
+ setErrors
+}: Props) => {
+
+ const validateIdentityStoreId = (value: string) => {
+ if (!value) {
+ return 'Identity Store ID是必填项';
+ }
+ if (!/^d-[a-z0-9]{10}$/.test(value)) {
+ return 'Identity Store ID格式不正确,应为:d-xxxxxxxxxx';
+ }
+ return '';
+ };
+
+ const validateIdentityStoreRegion = (value: string) => {
+ if (!value) {
+ return 'Identity Center区域是必填项';
+ }
+ if (!/^[a-z0-9-]+$/.test(value)) {
+ return 'Identity Center区域格式不正确';
+ }
+ return '';
+ };
+
+ const handleIdentityStoreIdChange = (e: ChangeEvent<HTMLInputElement>) => {
+ const value = e.target.value;
+ setValues({ identityStoreId: value });
+ setErrors({ identityStoreId: validateIdentityStoreId(value) });
+ };
+
+ const handleIdentityStoreRegionChange = (e: ChangeEvent<HTMLInputElement>)
=> {
+ const value = e.target.value;
+ setValues({ identityStoreRegion: value });
+ setErrors({ identityStoreRegion: validateIdentityStoreRegion(value) });
+ };
+
+ return (
+ <>
+ <Block title="IAM Identity Store ID" description="请输入Identity Store
ID,格式:d-xxxxxxxxxx" required>
+ <Input
+ style={{ width: 386 }}
+ placeholder="d-1234567890"
+ value={identityStoreId}
+ onChange={handleIdentityStoreIdChange}
+ status={errors.identityStoreId ? 'error' : ''}
+ />
+ {errors.identityStoreId && <div style={{ color: 'red', fontSize:
'12px', marginTop: '4px' }}>{errors.identityStoreId}</div>}
+ </Block>
+
+ <Block title="IAM Identity Center区域" description="请输入IAM Identity
Center所在的AWS区域" required>
+ <Input
+ style={{ width: 386 }}
+ placeholder="us-east-1"
+ value={identityStoreRegion}
+ onChange={handleIdentityStoreRegionChange}
+ status={errors.identityStoreRegion ? 'error' : ''}
+ />
+ {errors.identityStoreRegion && <div style={{ color: 'red', fontSize:
'12px', marginTop: '4px' }}>{errors.identityStoreRegion}</div>}
+ </Block>
+ </>
+ );
+};
\ No newline at end of file
diff --git a/config-ui/src/plugins/register/q-dev/connection-fields/index.ts
b/config-ui/src/plugins/register/q-dev/connection-fields/index.ts
new file mode 100644
index 000000000..c01b72f0b
--- /dev/null
+++ b/config-ui/src/plugins/register/q-dev/connection-fields/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+export * from './aws-credentials';
+export * from './s3-config';
+export * from './identity-center-config';
\ No newline at end of file
diff --git
a/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx
b/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx
new file mode 100644
index 000000000..9cdfa8f3c
--- /dev/null
+++ b/config-ui/src/plugins/register/q-dev/connection-fields/s3-config.tsx
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { ChangeEvent } from 'react';
+import { Input } from 'antd';
+
+import { Block } from '@/components';
+
+interface Props {
+ bucket: string;
+ error?: string;
+ setValue: (value: string) => void;
+ setError: (error: string) => void;
+}
+
+export const S3Config = ({ bucket, error, setValue, setError }: Props) => {
+
+ const validateBucket = (value: string) => {
+ if (!value) {
+ return 'S3存储桶名称是必填项';
+ }
+ if (!/^[a-z0-9.-]+$/.test(value)) {
+ return 'S3存储桶名称格式不正确,只能包含小写字母、数字、点和连字符';
+ }
+ if (value.length < 3 || value.length > 63) {
+ return 'S3存储桶名称长度必须在3-63个字符之间';
+ }
+ return '';
+ };
+
+ const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
+ const value = e.target.value;
+ setValue(value);
+ setError(validateBucket(value));
+ };
+
+ return (
+ <Block title="S3存储桶名称" description="请输入存储Q Developer数据的S3存储桶名称" required>
+ <Input
+ style={{ width: 386 }}
+ placeholder="my-qdev-data-bucket"
+ value={bucket}
+ onChange={handleChange}
+ status={error ? 'error' : ''}
+ />
+ {error && <div style={{ color: 'red', fontSize: '12px', marginTop: '4px'
}}>{error}</div>}
+ </Block>
+ );
+};
\ No newline at end of file
diff --git a/config-ui/src/plugins/register/q-dev/index.ts
b/config-ui/src/plugins/register/q-dev/index.ts
new file mode 100644
index 000000000..257c4bc7d
--- /dev/null
+++ b/config-ui/src/plugins/register/q-dev/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+export * from './config';
\ No newline at end of file