You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/02/13 08:26:21 UTC
[incubator-devlake] branch main updated: refactor(config-ui): adjust the connections (#4372)
This is an automated email from the ASF dual-hosted git repository.
likyh 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 115116b60 refactor(config-ui): adjust the connections (#4372)
115116b60 is described below
commit 115116b60b38a5ef69dc4a5b599ee998aa4314e8
Author: 青湛 <0x...@gmail.com>
AuthorDate: Mon Feb 13 16:26:17 2023 +0800
refactor(config-ui): adjust the connections (#4372)
* fix(config-ui): adjust the style for connection home page
* refactor(config-ui): adjust the configuration of the plugin
* feat(config-ui): encapsulate plugin connection form component
* refactor(config-ui): use plugin connection form to replace page
* fix(config-ui): the initialization value incorrect
---
.../connection/form/components/jira-auth/index.tsx | 115 -------------
.../form/components/rate-limit/index.tsx | 58 -------
config-ui/src/pages/connection/form/index.tsx | 190 +--------------------
config-ui/src/pages/connection/form/use-form.ts | 95 -----------
config-ui/src/pages/connection/home/styled.ts | 27 ++-
.../components/connection-form}/api.ts | 0
.../components/connection-form/fields/endpoint.tsx | 111 ++++++++++++
.../components/connection-form/fields/index.tsx | 113 ++++++++++++
.../components/connection-form/fields/name.tsx} | 38 ++---
.../components/connection-form/fields/password.tsx | 57 +++++++
.../components/connection-form/fields/proxy.tsx | 60 +++++++
.../connection-form/fields/rate-limit.tsx | 94 ++++++++++
.../components/connection-form/fields}/styled.ts | 27 ++-
.../components/connection-form/fields/token.tsx | 62 +++++++
.../components/connection-form/fields/username.tsx | 56 ++++++
.../plugins/components/connection-form/index.tsx | 80 +++++++++
.../components/connection-form/operate}/index.ts | 6 +-
.../components/connection-form/operate/save.tsx | 63 +++++++
.../components/connection-form/operate/test.tsx} | 42 +++--
.../components/connection-form}/styled.ts | 76 ++++-----
config-ui/src/plugins/components/index.ts | 1 +
config-ui/src/plugins/register/base/config.ts | 14 --
.../register/bitbucket/{config.ts => config.tsx} | 62 +++----
config-ui/src/plugins/register/github/api.ts | 2 +
.../register/github/{config.ts => config.tsx} | 50 +++---
.../register/github/connection-fields/graphql.tsx} | 41 ++---
.../register/github/connection-fields/index.ts} | 12 +-
.../register/github/connection-fields}/styled.ts | 52 +++---
.../register/github/connection-fields/token.tsx} | 91 +++++-----
config-ui/src/plugins/register/gitlab/config.ts | 60 -------
config-ui/src/plugins/register/gitlab/config.tsx | 74 ++++++++
config-ui/src/plugins/register/jenkins/config.ts | 46 +++--
.../register/jira/{config.ts => config.tsx} | 48 +++---
.../register/jira/connection-fields/auth.tsx | 126 ++++++++++++++
.../jira/connection-fields}/index.ts | 4 +-
.../register/jira/connection-fields}/styled.ts | 67 ++++----
config-ui/src/plugins/register/tapd/config.ts | 46 +++--
config-ui/src/plugins/register/zentao/config.ts | 43 ++---
config-ui/src/plugins/types.ts | 12 +-
39 files changed, 1288 insertions(+), 933 deletions(-)
diff --git a/config-ui/src/pages/connection/form/components/jira-auth/index.tsx b/config-ui/src/pages/connection/form/components/jira-auth/index.tsx
deleted file mode 100644
index 638b15324..000000000
--- a/config-ui/src/pages/connection/form/components/jira-auth/index.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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 React, { useState } from 'react';
-import { FormGroup, RadioGroup, Radio, InputGroup } from '@blueprintjs/core';
-
-type Method = 'BasicAuth' | 'AccessToken';
-
-type Value = {
- authMethod: string;
- username?: string;
- password?: string;
- token?: string;
-};
-
-interface Props {
- value: Value;
- onChange: (value: Value) => void;
-}
-
-export const JIRAAuth = ({ value, onChange }: Props) => {
- const [method, setMethod] = useState<Method>('BasicAuth');
-
- const handleChangeMethod = (e: React.FormEvent<HTMLInputElement>) => {
- const m = (e.target as HTMLInputElement).value as Method;
-
- setMethod(m);
- onChange({
- authMethod: m,
- username: m === 'BasicAuth' ? value.username : undefined,
- password: m === 'BasicAuth' ? value.password : undefined,
- token: m === 'AccessToken' ? value.token : undefined,
- });
- };
-
- const handleChangeUsername = (e: React.ChangeEvent<HTMLInputElement>) => {
- onChange({
- authMethod: 'BasicAuth',
- username: e.target.value,
- });
- };
-
- const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
- onChange({
- authMethod: 'BasicAuth',
- password: e.target.value,
- });
- };
-
- const handleChangeToken = (e: React.ChangeEvent<HTMLInputElement>) => {
- onChange({
- ...value,
- token: e.target.value,
- });
- };
-
- return (
- <div>
- <FormGroup inline label="Authentication Method" labelInfo="*">
- <RadioGroup selectedValue={method} onChange={handleChangeMethod}>
- <Radio value="BasicAuth">Basic Authentication</Radio>
- <Radio value="AccessToken">Using Personal Access Token</Radio>
- </RadioGroup>
- </FormGroup>
- {method === 'BasicAuth' && (
- <>
- <FormGroup inline label="Username/e-mail" labelInfo="*">
- <InputGroup
- placeholder="Your Username/e-mail"
- value={value.username || ''}
- onChange={handleChangeUsername}
- />
- </FormGroup>
- <FormGroup inline label="Password" labelInfo="*">
- <InputGroup
- type="password"
- placeholder="Your Password"
- value={value.password || ''}
- onChange={handleChangePassword}
- />
- </FormGroup>
- </>
- )}
- {method === 'AccessToken' && (
- <FormGroup inline label="Personal Access Token" labelInfo="*">
- <p>
- <a
- href="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html"
- target="_blank"
- rel="noreferrer"
- >
- Learn about how to create PAT
- </a>
- </p>
- <InputGroup type="password" placeholder="Your PAT" value={value.token || ''} onChange={handleChangeToken} />
- </FormGroup>
- )}
- </div>
- );
-};
diff --git a/config-ui/src/pages/connection/form/components/rate-limit/index.tsx b/config-ui/src/pages/connection/form/components/rate-limit/index.tsx
deleted file mode 100644
index 6b7fcffb1..000000000
--- a/config-ui/src/pages/connection/form/components/rate-limit/index.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 React, { useState, useEffect } from 'react';
-import { NumericInput, Switch } from '@blueprintjs/core';
-
-import * as S from './styled';
-
-interface Props {
- initialValue?: number;
- value?: number;
- onChange?: (value: number) => void;
-}
-
-export const RateLimit = ({ initialValue, value, onChange }: Props) => {
- const [show, setShow] = useState(false);
-
- useEffect(() => {
- setShow(value ? true : false);
- }, [value]);
-
- const handleChangeValue = (value: number) => {
- onChange?.(show ? value : 0);
- };
-
- const handleChangeShow = (e: React.FormEvent<HTMLInputElement>) => {
- const checked = (e.target as HTMLInputElement).checked;
- setShow(checked);
- if (!checked) {
- onChange?.(0);
- } else {
- onChange?.(initialValue ?? 0);
- }
- };
-
- return (
- <S.Wrapper>
- {show && <NumericInput value={value} onValueChange={handleChangeValue} />}
- <Switch checked={show} onChange={handleChangeShow} />
- <span>{show ? `Enabled - ${value} Requests/hr` : 'Disabled'}</span>
- </S.Wrapper>
- );
-};
diff --git a/config-ui/src/pages/connection/form/index.tsx b/config-ui/src/pages/connection/form/index.tsx
index dfb34be7b..387e9a338 100644
--- a/config-ui/src/pages/connection/form/index.tsx
+++ b/config-ui/src/pages/connection/form/index.tsx
@@ -16,170 +16,17 @@
*
*/
-import React, { useState, useEffect, useMemo } from 'react';
-import { useParams, useHistory } from 'react-router-dom';
-import { omit, pick } from 'lodash';
-import { FormGroup, InputGroup, Switch, ButtonGroup, Button, Icon, Intent, Position } from '@blueprintjs/core';
-import { Tooltip2 } from '@blueprintjs/popover2';
+import React, { useMemo } from 'react';
+import { useParams } from 'react-router-dom';
-import { PageHeader, Card, PageLoading } from '@/components';
-import type { PluginConfigConnectionType } from '@/plugins';
-import { PluginConfig } from '@/plugins';
-
-import { RateLimit, GitHubToken, GitLabToken, JIRAAuth } from './components';
-import { useForm } from './use-form';
-import * as S from './styled';
+import { PageHeader } from '@/components';
+import { PluginConfigConnectionType } from '@/plugins';
+import { PluginConfig, ConnectionForm } from '@/plugins';
export const ConnectionFormPage = () => {
- const [form, setForm] = useState<Record<string, any>>({});
-
- const history = useHistory();
const { plugin, cid } = useParams<{ plugin: string; cid?: string }>();
- const { loading, operating, connection, onTest, onCreate, onUpdate } = useForm({ plugin, id: cid });
-
- const {
- name,
- connection: { initialValues, fields },
- } = useMemo(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigConnectionType, [plugin]);
-
- useEffect(() => {
- setForm({
- ...form,
- ...omit(initialValues, 'rateLimitPerHour'),
- ...(connection ?? {}),
- });
- }, [initialValues, connection]);
-
- const error = useMemo(
- () =>
- fields.some((field) => {
- if (field.required) {
- return !form[field.key];
- }
-
- if (field.checkError) {
- return !field.checkError(form);
- }
-
- return false;
- }),
- [form, fields],
- );
-
- const handleTest = () =>
- onTest(pick(form, ['endpoint', 'token', 'username', 'password', 'app_id', 'secret_key', 'proxy', 'authMethod']));
-
- const handleCancel = () => history.push(`/connections/${plugin}`);
-
- const handleSave = () => (cid ? onUpdate(cid, form) : onCreate(form));
-
- const getFormItem = ({
- key,
- label,
- type,
- required,
- placeholder,
- tooltip,
- }: PluginConfigConnectionType['connection']['fields']['0']) => {
- if (type === 'jiraAuth') {
- return (
- <JIRAAuth
- key={key}
- value={{ authMethod: form.authMethod, username: form.username, password: form.password, token: form.token }}
- onChange={(value) => {
- setForm({
- ...form,
- ...value,
- });
- }}
- />
- );
- }
- return (
- <FormGroup
- key={key}
- inline
- label={
- <S.Label>
- <span>{label}</span>
- {tooltip && (
- <Tooltip2 position={Position.TOP} content={tooltip}>
- <Icon icon="help" size={12} />
- </Tooltip2>
- )}
- </S.Label>
- }
- labelFor={key}
- labelInfo={required ? '*' : ''}
- >
- {type === 'text' && (
- <InputGroup
- placeholder={placeholder}
- value={form[key] ?? ''}
- onChange={(e) => setForm({ ...form, [`${key}`]: e.target.value })}
- />
- )}
- {type === 'password' && (
- <InputGroup
- placeholder={placeholder}
- type="password"
- value={form[key] ?? ''}
- onChange={(e) => setForm({ ...form, [`${key}`]: e.target.value })}
- />
- )}
- {type === 'switch' && (
- <S.SwitchWrapper>
- <Switch
- checked={form[key] ?? false}
- onChange={(e) =>
- setForm({
- ...form,
- [key]: (e.target as HTMLInputElement).checked,
- })
- }
- />
- </S.SwitchWrapper>
- )}
- {type === 'rateLimit' && (
- <RateLimit
- initialValue={initialValues?.['rateLimitPerHour']}
- value={form.rateLimitPerHour}
- onChange={(value) =>
- setForm({
- ...form,
- rateLimitPerHour: value,
- })
- }
- />
- )}
- {type === 'githubToken' && (
- <GitHubToken
- form={form}
- value={form.token}
- onChange={(value) =>
- setForm({
- ...form,
- token: value,
- })
- }
- />
- )}
- {type === 'gitlabToken' && (
- <GitLabToken
- placeholder={placeholder}
- value={form.token}
- onChange={(value) =>
- setForm({
- ...form,
- token: value,
- })
- }
- />
- )}
- </FormGroup>
- );
- };
+ const { name } = useMemo(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigConnectionType, [plugin]);
return (
<PageHeader
@@ -187,33 +34,12 @@ export const ConnectionFormPage = () => {
{ name: 'Connections', path: '/connections' },
{ name, path: `/connections/${plugin}` },
{
- name: cid ? cid : 'Create',
+ name: cid ? cid : 'Create a New Connection',
path: `/connections/${plugin}/${cid ? cid : 'create'}`,
},
]}
>
- {loading ? (
- <PageLoading />
- ) : (
- <Card>
- <S.Wrapper>
- {fields.map((field) => getFormItem(field))}
- <div className="footer">
- <Button disabled={error} loading={operating} text="Test Connection" onClick={handleTest} />
- <ButtonGroup>
- <Button text="Cancel" onClick={handleCancel} />
- <Button
- disabled={error}
- loading={operating}
- intent={Intent.PRIMARY}
- text="Save Connection"
- onClick={handleSave}
- />
- </ButtonGroup>
- </div>
- </S.Wrapper>
- </Card>
- )}
+ <ConnectionForm plugin={plugin} connectionId={cid} />
</PageHeader>
);
};
diff --git a/config-ui/src/pages/connection/form/use-form.ts b/config-ui/src/pages/connection/form/use-form.ts
deleted file mode 100644
index 5c9431c66..000000000
--- a/config-ui/src/pages/connection/form/use-form.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 { useState, useMemo, useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-
-import { operator } from '@/utils';
-
-import * as API from './api';
-
-interface Props {
- plugin: string;
- id?: ID;
-}
-
-export const useForm = ({ plugin, id }: Props) => {
- const [loading, setLoading] = useState(false);
- const [operating, setOperating] = useState(false);
- const [connection, setConnection] = useState<any>();
-
- const history = useHistory();
-
- const getConnection = async () => {
- if (!id) return;
-
- setLoading(true);
- try {
- const res = await API.getConnection(plugin, id);
- setConnection(res);
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- getConnection();
- }, []);
-
- const handleTest = async (payload: any) => {
- const [success] = await operator(() => API.testConnection(plugin, payload), {
- setOperating,
- formatReason: () => 'Test Failed.Please check your configuration.',
- });
-
- if (success) {
- }
- };
-
- const handleCreate = async (payload: any) => {
- const [success] = await operator(() => API.createConnection(plugin, payload), {
- setOperating,
- });
-
- if (success) {
- history.push(`/connections/${plugin}`);
- }
- };
-
- const handleUpdate = async (id: ID, payload: any) => {
- const [success] = await operator(() => API.updateConnection(plugin, id, payload), {
- setOperating,
- });
-
- if (success) {
- history.push(`/connections/${plugin}`);
- }
- };
-
- return useMemo(
- () => ({
- loading,
- operating,
- connection,
- onTest: handleTest,
- onCreate: handleCreate,
- onUpdate: handleUpdate,
- }),
- [loading, operating, connection],
- );
-};
diff --git a/config-ui/src/pages/connection/home/styled.ts b/config-ui/src/pages/connection/home/styled.ts
index 534e8387e..a28976019 100644
--- a/config-ui/src/pages/connection/home/styled.ts
+++ b/config-ui/src/pages/connection/home/styled.ts
@@ -20,23 +20,13 @@ import styled from 'styled-components';
export const Wrapper = styled.div`
.block + .block {
- margin-top: 48px;
- }
-
- h2 {
- margin: 0 0 4px;
- }
-
- p {
- margin: 0 0 16px;
+ margin-top: 24px;
}
ul {
- margin: 0;
- padding: 0;
- list-style: none;
display: flex;
align-items: center;
+ flex-wrap: wrap;
}
li {
@@ -44,7 +34,10 @@ export const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
- padding: 8px 16px;
+ margin-right: 24px;
+ margin-bottom: 12px;
+ padding: 8px 0;
+ width: 120px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s linear;
@@ -55,6 +48,10 @@ export const Wrapper = styled.div`
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 16%), 0 0 2px 0 rgb(0 0 0 / 12%);
}
+ &:last-child {
+ margin-right: 0;
+ }
+
& > img {
width: 45px;
}
@@ -69,8 +66,4 @@ export const Wrapper = styled.div`
right: 4px;
}
}
-
- li + li {
- margin-left: 24px;
- }
`;
diff --git a/config-ui/src/pages/connection/form/api.ts b/config-ui/src/plugins/components/connection-form/api.ts
similarity index 100%
rename from config-ui/src/pages/connection/form/api.ts
rename to config-ui/src/plugins/components/connection-form/api.ts
diff --git a/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx b/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
new file mode 100644
index 000000000..68f509da0
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/endpoint.tsx
@@ -0,0 +1,111 @@
+/*
+ * 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.
+ *
+ */
+/*
+ * 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 React, { useState } from 'react';
+import { FormGroup, RadioGroup, Radio, InputGroup } from '@blueprintjs/core';
+
+import * as S from './styled';
+
+type VersionType = 'cloud' | 'server';
+
+interface Props {
+ subLabel?: string;
+ disabled?: boolean;
+ name: string;
+ multipleVersions?: Record<VersionType, string>;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export const ConnectionEndpoint = ({ subLabel, disabled = false, name, multipleVersions, value, onChange }: Props) => {
+ const [version, setVersion] = useState<VersionType>('cloud');
+
+ const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
+ const version = (e.target as HTMLInputElement).value as VersionType;
+ if (version === 'cloud') {
+ onChange(multipleVersions?.cloud ?? '');
+ }
+
+ if (version === 'server') {
+ onChange('');
+ }
+
+ setVersion(version);
+ };
+
+ const handleChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
+ onChange(e.target.value);
+ };
+
+ if (multipleVersions) {
+ return (
+ <FormGroup label={<S.Label>{name} Version</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
+ <RadioGroup inline selectedValue={version} onChange={handleChange}>
+ <Radio value="cloud">{name} Cloud</Radio>
+ <Radio value="server" disabled={multipleVersions.server === undefined}>
+ {name} Server
+ </Radio>
+ </RadioGroup>
+ {version === 'cloud' && (
+ <p>
+ If you are using {name} Cloud, you do not need to enter the endpoint URL, which is
+ {multipleVersions.cloud}.
+ </p>
+ )}
+ {version === 'server' && (
+ <FormGroup
+ label={<S.Label>Endpoint URL</S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={
+ <S.LabelDescription>If you are using {name} Server, please enter the endpoint URL.</S.LabelDescription>
+ }
+ >
+ <InputGroup placeholder="Your Endpoint URL" value={value} onChange={handleChangeValue} />
+ </FormGroup>
+ )}
+ </FormGroup>
+ );
+ }
+
+ return (
+ <FormGroup
+ label={<S.Label>Endpoint URL</S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={<S.LabelDescription>{subLabel ?? `Provide the ${name} instance API endpoint.`}</S.LabelDescription>}
+ >
+ <InputGroup disabled={disabled} placeholder="Your Endpoint URL" value={value} onChange={handleChange} />
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/plugins/components/connection-form/fields/index.tsx b/config-ui/src/plugins/components/connection-form/fields/index.tsx
new file mode 100644
index 000000000..a1cbc4b5d
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/index.tsx
@@ -0,0 +1,113 @@
+/*
+ * 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 React from 'react';
+
+import { ConnectionName } from './name';
+import { ConnectionEndpoint } from './endpoint';
+import { ConnectionUsername } from './username';
+import { ConnectionPassword } from './password';
+import { ConnectionToken } from './token';
+import { ConnectionProxy } from './proxy';
+import { ConnectionRateLimit } from './rate-limit';
+
+interface Props {
+ name: string;
+ fields: any[];
+ values: any;
+ setValues: (values: any) => void;
+ error: any;
+ setError: (error: any) => void;
+}
+
+export const Form = ({ name, fields, values, setValues, error, setError }: Props) => {
+ const generateForm = () => {
+ return fields.map((field) => {
+ if (typeof field === 'function') {
+ return field({ values, setValues, error, setError });
+ }
+
+ const key = typeof field === 'string' ? field : field.key;
+
+ switch (key) {
+ case 'name':
+ return (
+ <ConnectionName key={key} value={values.name ?? ''} onChange={(name) => setValues({ ...values, name })} />
+ );
+ case 'endpoint':
+ return (
+ <ConnectionEndpoint
+ {...field}
+ key={key}
+ name={name}
+ value={values.endpoint ?? ''}
+ onChange={(endpoint) => setValues({ ...values, endpoint })}
+ />
+ );
+ case 'username':
+ return (
+ <ConnectionUsername
+ key={key}
+ value={values.username ?? ''}
+ onChange={(username) => setValues({ ...values, username })}
+ />
+ );
+ case 'password':
+ return (
+ <ConnectionPassword
+ {...field}
+ key={key}
+ value={values.password ?? ''}
+ onChange={(password) => setValues({ ...values, password })}
+ />
+ );
+ case 'token':
+ return (
+ <ConnectionToken
+ {...field}
+ key={key}
+ value={values.token ?? ''}
+ onChange={(token) => setValues({ ...values, token })}
+ />
+ );
+ case 'proxy':
+ return (
+ <ConnectionProxy
+ key={key}
+ name={name}
+ value={values.proxy ?? ''}
+ onChange={(proxy) => setValues({ ...values, proxy })}
+ />
+ );
+ case 'rateLimitPerHour':
+ return (
+ <ConnectionRateLimit
+ {...field}
+ key={key}
+ value={values.rateLimitPerHour}
+ onChange={(rateLimitPerHour) => setValues({ ...values, rateLimitPerHour })}
+ />
+ );
+ default:
+ return null;
+ }
+ });
+ };
+
+ return <div>{generateForm()}</div>;
+};
diff --git a/config-ui/src/pages/connection/form/components/gitlab-token/index.tsx b/config-ui/src/plugins/components/connection-form/fields/name.tsx
similarity index 55%
copy from config-ui/src/pages/connection/form/components/gitlab-token/index.tsx
copy to config-ui/src/plugins/components/connection-form/fields/name.tsx
index 9c5579446..f207a2601 100644
--- a/config-ui/src/pages/connection/form/components/gitlab-token/index.tsx
+++ b/config-ui/src/plugins/components/connection-form/fields/name.tsx
@@ -17,31 +17,31 @@
*/
import React from 'react';
-import { InputGroup } from '@blueprintjs/core';
+import { FormGroup, InputGroup } from '@blueprintjs/core';
+
+import * as S from './styled';
interface Props {
- placeholder?: string;
- value?: string;
- onChange?: (value: string) => void;
+ value: string;
+ onChange: (value: string) => void;
}
-export const GitLabToken = ({ placeholder, value, onChange }: Props) => {
- const handleChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
- onChange?.(e.target.value);
+export const ConnectionName = ({ value, onChange }: Props) => {
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ onChange(e.target.value);
};
return (
- <div>
- <p>
- <a
- href="https://devlake.apache.org/docs/UserManuals/ConfigUI/GitLab/#auth-tokens"
- target="_blank"
- rel="noreferrer"
- >
- Learn about how to create a personal access token
- </a>
- </p>
- <InputGroup placeholder={placeholder} type="password" value={value} onChange={handleChangeValue} />
- </div>
+ <FormGroup
+ label={<S.Label>Connection Name</S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={
+ <S.LabelDescription>
+ Give your connection a unique name to help you identify it in the future.
+ </S.LabelDescription>
+ }
+ >
+ <InputGroup placeholder="Your Connection Name" value={value} onChange={handleChange} />
+ </FormGroup>
);
};
diff --git a/config-ui/src/plugins/components/connection-form/fields/password.tsx b/config-ui/src/plugins/components/connection-form/fields/password.tsx
new file mode 100644
index 000000000..5af8dfe11
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/password.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ *
+ */
+/*
+ * 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 React from 'react';
+import { FormGroup, InputGroup } from '@blueprintjs/core';
+
+import * as S from './styled';
+
+interface Props {
+ label?: string;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export const ConnectionPassword = ({ label, value, onChange }: Props) => {
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ onChange(e.target.value);
+ };
+
+ return (
+ <FormGroup label={<S.Label>{label ?? 'Password'}</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
+ <InputGroup placeholder="Your Password" value={value} onChange={handleChange} />
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/plugins/components/connection-form/fields/proxy.tsx b/config-ui/src/plugins/components/connection-form/fields/proxy.tsx
new file mode 100644
index 000000000..e400491f3
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/proxy.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ *
+ */
+/*
+ * 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 React from 'react';
+import { FormGroup, InputGroup } from '@blueprintjs/core';
+
+import * as S from './styled';
+
+interface Props {
+ name: string;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export const ConnectionProxy = ({ name, value, onChange }: Props) => {
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ onChange(e.target.value);
+ };
+
+ return (
+ <FormGroup
+ label={<S.Label>Proxy URL</S.Label>}
+ subLabel={<S.LabelDescription>Add a proxy if you cannot access {name} directly.</S.LabelDescription>}
+ >
+ <InputGroup placeholder="e.g http://proxy.localhost:8080" value={value} onChange={handleChange} />
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx b/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx
new file mode 100644
index 000000000..1e1b105b2
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/rate-limit.tsx
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ */
+/*
+ * 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 React, { useState, useEffect } from 'react';
+import { FormGroup, Switch, NumericInput } from '@blueprintjs/core';
+
+import { ExternalLink } from '@/components';
+
+import * as S from './styled';
+
+interface Props {
+ subLabel: string;
+ learnMore: string;
+ externalInfo: string;
+ defaultValue: number;
+ value: number;
+ onChange: (value: number) => void;
+}
+
+export const ConnectionRateLimit = ({ subLabel, learnMore, externalInfo, defaultValue, value, onChange }: Props) => {
+ const [checked, setChecked] = useState(true);
+
+ useEffect(() => {
+ setChecked(value ? true : false);
+ }, []);
+
+ const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
+ const checked = (e.target as HTMLInputElement).checked;
+ if (checked) {
+ onChange(defaultValue);
+ } else {
+ onChange(0);
+ }
+ setChecked(checked);
+ };
+
+ const handleChangeValue = (value: number) => {
+ onChange(value);
+ };
+
+ return (
+ <FormGroup
+ label={<S.Label>Custom Rate Limit</S.Label>}
+ subLabel={
+ <S.LabelDescription>
+ {subLabel} {learnMore && <ExternalLink link={learnMore}>Learn more</ExternalLink>}
+ </S.LabelDescription>
+ }
+ >
+ <S.RateLimit>
+ <Switch checked={checked} onChange={handleChange} />
+ {checked && (
+ <>
+ <NumericInput buttonPosition="none" min={0} value={value} onValueChange={handleChangeValue} />
+ <span>requests/hour</span>
+ </>
+ )}
+ </S.RateLimit>
+ {checked && externalInfo && <S.RateLimitInfo dangerouslySetInnerHTML={{ __html: externalInfo }} />}
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/pages/connection/form/components/rate-limit/styled.ts b/config-ui/src/plugins/components/connection-form/fields/styled.ts
similarity index 69%
copy from config-ui/src/pages/connection/form/components/rate-limit/styled.ts
copy to config-ui/src/plugins/components/connection-form/fields/styled.ts
index 808e8719f..d48455c0e 100644
--- a/config-ui/src/pages/connection/form/components/rate-limit/styled.ts
+++ b/config-ui/src/plugins/components/connection-form/fields/styled.ts
@@ -18,11 +18,32 @@
import styled from 'styled-components';
-export const Wrapper = styled.div`
+export const Label = styled.label`
+ font-size: 16px;
+ font-weight: 600;
+`;
+
+export const LabelInfo = styled.i`
+ color: #ff8b8b;
+`;
+
+export const LabelDescription = styled.p`
+ margin: 0;
+`;
+
+export const RateLimit = styled.div`
display: flex;
align-items: center;
- & > .bp4-numeric-input {
- margin-right: 8px;
+ .bp4-input-group {
+ width: 80px !important;
+ }
+
+ & > span {
+ margin-left: 8px;
}
`;
+
+export const RateLimitInfo = styled.p`
+ white-space: pre-line;
+`;
diff --git a/config-ui/src/plugins/components/connection-form/fields/token.tsx b/config-ui/src/plugins/components/connection-form/fields/token.tsx
new file mode 100644
index 000000000..d3a1c808d
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/token.tsx
@@ -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.
+ *
+ */
+/*
+ * 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 React from 'react';
+import { FormGroup, InputGroup } from '@blueprintjs/core';
+
+import * as S from './styled';
+
+interface Props {
+ label?: string;
+ subLabel?: string;
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export const ConnectionToken = ({ label, subLabel, value, onChange }: Props) => {
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ onChange(e.target.value);
+ };
+
+ return (
+ <FormGroup
+ label={<S.Label>{label ?? 'Token'}</S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={<S.LabelDescription>{subLabel}</S.LabelDescription>}
+ >
+ <InputGroup placeholder="Your Token" value={value} onChange={handleChange} />
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/plugins/components/connection-form/fields/username.tsx b/config-ui/src/plugins/components/connection-form/fields/username.tsx
new file mode 100644
index 000000000..717b51f11
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/fields/username.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ */
+/*
+ * 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 React from 'react';
+import { FormGroup, InputGroup } from '@blueprintjs/core';
+
+import * as S from './styled';
+
+interface Props {
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export const ConnectionUsername = ({ value, onChange }: Props) => {
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ onChange(e.target.value);
+ };
+
+ return (
+ <FormGroup label={<S.Label>Username</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
+ <InputGroup placeholder="Your Username" value={value} onChange={handleChange} />
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/plugins/components/connection-form/index.tsx b/config-ui/src/plugins/components/connection-form/index.tsx
new file mode 100644
index 000000000..acedfeafd
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/index.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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 React, { useState, useMemo } from 'react';
+import { ButtonGroup } from '@blueprintjs/core';
+
+import { PageLoading, ExternalLink } from '@/components';
+import { useRefreshData } from '@/hooks';
+import type { PluginConfigConnectionType } from '@/plugins';
+import { PluginConfig } from '@/plugins';
+
+import { Form } from './fields';
+import { Test, Save } from './operate';
+import * as API from './api';
+import * as S from './styled';
+
+interface Props {
+ plugin: string;
+ connectionId?: ID;
+}
+
+export const ConnectionForm = ({ plugin, connectionId }: Props) => {
+ const [form, setForm] = useState<Record<string, any>>({});
+ const [error, setError] = useState<Record<string, any>>({});
+
+ const {
+ name,
+ connection: { docLink, fields, initialValues },
+ } = useMemo(() => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigConnectionType, [plugin]);
+
+ const { ready, data } = useRefreshData(async () => {
+ if (!connectionId) {
+ return {};
+ }
+
+ return API.getConnection(plugin, connectionId);
+ }, [plugin, connectionId]);
+
+ if (connectionId && !ready) {
+ return <PageLoading />;
+ }
+
+ return (
+ <S.Wrapper>
+ <S.Tips>
+ If you run into any problems while creating a new connection for {name},{' '}
+ <ExternalLink link={docLink}>check out this doc</ExternalLink>.
+ </S.Tips>
+ <S.Form>
+ <Form
+ name={name}
+ fields={fields}
+ values={{ ...form, ...initialValues, ...data }}
+ setValues={setForm}
+ error={error}
+ setError={setError}
+ />
+ <ButtonGroup className="btns">
+ <Test plugin={plugin} form={form} error={error} />
+ <Save plugin={plugin} connectionId={connectionId} form={form} error={error} />
+ </ButtonGroup>
+ </S.Form>
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/pages/connection/form/components/index.ts b/config-ui/src/plugins/components/connection-form/operate/index.ts
similarity index 86%
rename from config-ui/src/pages/connection/form/components/index.ts
rename to config-ui/src/plugins/components/connection-form/operate/index.ts
index 448d0ba62..2dda77f3f 100644
--- a/config-ui/src/pages/connection/form/components/index.ts
+++ b/config-ui/src/plugins/components/connection-form/operate/index.ts
@@ -16,7 +16,5 @@
*
*/
-export * from './rate-limit';
-export * from './github-token';
-export * from './gitlab-token';
-export * from './jira-auth';
+export * from './test';
+export * from './save';
diff --git a/config-ui/src/plugins/components/connection-form/operate/save.tsx b/config-ui/src/plugins/components/connection-form/operate/save.tsx
new file mode 100644
index 000000000..8a3a45afa
--- /dev/null
+++ b/config-ui/src/plugins/components/connection-form/operate/save.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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 React, { useMemo } from 'react';
+import { useHistory } from 'react-router-dom';
+import { Button, Intent } from '@blueprintjs/core';
+
+import { useOperator } from '@/hooks';
+
+import * as API from '../api';
+
+interface Props {
+ plugin: string;
+ connectionId?: ID;
+ form: any;
+ error: any;
+}
+
+export const Save = ({ plugin, connectionId, form, error }: Props) => {
+ const history = useHistory();
+
+ const { operating, onSubmit } = useOperator(
+ (paylaod) =>
+ !connectionId ? API.createConnection(plugin, paylaod) : API.updateConnection(plugin, connectionId, paylaod),
+ {
+ callback: () => history.push(`/connections/${plugin}`),
+ },
+ );
+
+ const disabled = useMemo(() => {
+ return Object.values(error).some((value) => value);
+ }, [error]);
+
+ const handleSubmit = () => {
+ onSubmit(form);
+ };
+
+ return (
+ <Button
+ loading={operating}
+ disabled={disabled}
+ intent={Intent.PRIMARY}
+ outlined
+ text="Save Connection"
+ onClick={handleSubmit}
+ />
+ );
+};
diff --git a/config-ui/src/pages/connection/form/components/gitlab-token/index.tsx b/config-ui/src/plugins/components/connection-form/operate/test.tsx
similarity index 52%
copy from config-ui/src/pages/connection/form/components/gitlab-token/index.tsx
copy to config-ui/src/plugins/components/connection-form/operate/test.tsx
index 9c5579446..976fcb3fd 100644
--- a/config-ui/src/pages/connection/form/components/gitlab-token/index.tsx
+++ b/config-ui/src/plugins/components/connection-form/operate/test.tsx
@@ -16,32 +16,30 @@
*
*/
-import React from 'react';
-import { InputGroup } from '@blueprintjs/core';
+import React, { useMemo } from 'react';
+import { Button } from '@blueprintjs/core';
+import { pick } from 'lodash';
+
+import { useOperator } from '@/hooks';
+
+import * as API from '../api';
interface Props {
- placeholder?: string;
- value?: string;
- onChange?: (value: string) => void;
+ plugin: string;
+ form: any;
+ error: any;
}
-export const GitLabToken = ({ placeholder, value, onChange }: Props) => {
- const handleChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
- onChange?.(e.target.value);
+export const Test = ({ plugin, form, error }: Props) => {
+ const { operating, onSubmit } = useOperator((payload) => API.testConnection(plugin, payload));
+
+ const disabled = useMemo(() => {
+ return Object.values(error).some((value) => value);
+ }, [error]);
+
+ const handleSubmit = () => {
+ onSubmit(pick(form, ['endpoint', 'token', 'username', 'password', 'app_id', 'secret_key', 'proxy', 'authMethod']));
};
- return (
- <div>
- <p>
- <a
- href="https://devlake.apache.org/docs/UserManuals/ConfigUI/GitLab/#auth-tokens"
- target="_blank"
- rel="noreferrer"
- >
- Learn about how to create a personal access token
- </a>
- </p>
- <InputGroup placeholder={placeholder} type="password" value={value} onChange={handleChangeValue} />
- </div>
- );
+ return <Button loading={operating} disabled={disabled} outlined text="Test Connection" onClick={handleSubmit} />;
};
diff --git a/config-ui/src/pages/connection/home/styled.ts b/config-ui/src/plugins/components/connection-form/styled.ts
similarity index 53%
copy from config-ui/src/pages/connection/home/styled.ts
copy to config-ui/src/plugins/components/connection-form/styled.ts
index 534e8387e..09c870e4d 100644
--- a/config-ui/src/pages/connection/home/styled.ts
+++ b/config-ui/src/plugins/components/connection-form/styled.ts
@@ -18,59 +18,51 @@
import styled from 'styled-components';
-export const Wrapper = styled.div`
- .block + .block {
- margin-top: 48px;
- }
+export const Wrapper = styled.div``;
+
+export const Tips = styled.div`
+ margin-bottom: 36px;
+ padding: 24px;
+ color: #3c5088;
+ background: #f0f4fe;
+ border: 1px solid #bdcefb;
+ border-radius: 4px;
+`;
+
+export const Form = styled.div`
+ padding: 24px;
+ background: #ffffff;
+ box-shadow: 0px 2.4px 4.8px -0.8px rgba(0, 0, 0, 0.1), 0px 1.6px 8px rgba(0, 0, 0, 0.07);
+ border-radius: 8px;
- h2 {
- margin: 0 0 4px;
+ .bp4-form-group label.bp4-label {
+ margin: 0 0 8px 0;
}
- p {
- margin: 0 0 16px;
+ .bp4-form-group .bp4-form-group-sub-label {
+ margin: 0 0 8px 0;
}
- ul {
- margin: 0;
- padding: 0;
- list-style: none;
- display: flex;
- align-items: center;
+ .bp4-input-group {
+ width: 386px;
}
- li {
- position: relative;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 8px 16px;
- border: 2px solid transparent;
- cursor: pointer;
- transition: all 0.2s linear;
+ .bp4-input {
+ border: 1px solid #dbe4fd;
+ box-shadow: none;
+ border-radius: 4px;
- &:hover {
- background-color: #eeeeee;
- border-color: #7497f7;
- box-shadow: 0 2px 2px 0 rgb(0 0 0 / 16%), 0 0 2px 0 rgb(0 0 0 / 12%);
- }
-
- & > img {
- width: 45px;
+ &::placeholder {
+ color: #b8b8bf;
}
+ }
- & > span {
- margin-top: 4px;
- }
+ .btns {
+ display: flex;
+ justify-content: end;
- & > .bp4-tag {
- position: absolute;
- top: -4px;
- right: 4px;
+ .bp4-button + .bp4-button {
+ margin-left: 8px;
}
}
-
- li + li {
- margin-left: 24px;
- }
`;
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/components/index.ts
index 1fdd73324..e0d0014d6 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/components/index.ts
@@ -16,6 +16,7 @@
*
*/
+export * from './connection-form';
export * from './data-scope-list';
export * from './data-scope';
export * from './transformation';
diff --git a/config-ui/src/plugins/register/base/config.ts b/config-ui/src/plugins/register/base/config.ts
index 684ee1970..2de8f19e4 100644
--- a/config-ui/src/plugins/register/base/config.ts
+++ b/config-ui/src/plugins/register/base/config.ts
@@ -20,20 +20,6 @@ import { PluginType } from '../../types';
import Icon from './assets/icon.svg';
-export const BaseConnectionConfig = {
- type: PluginType.Connection,
- plugin: undefined,
- name: undefined,
- icon: Icon,
- isBeta: undefined,
- connection: {
- initialValues: [],
- fields: [],
- },
- entities: [],
- transformation: {},
-} as const;
-
export const BasePipelineConfig = {
type: PluginType.Pipeline,
plugin: undefined,
diff --git a/config-ui/src/plugins/register/bitbucket/config.ts b/config-ui/src/plugins/register/bitbucket/config.tsx
similarity index 51%
rename from config-ui/src/plugins/register/bitbucket/config.ts
rename to config-ui/src/plugins/register/bitbucket/config.tsx
index 77225837c..3582ee64b 100644
--- a/config-ui/src/plugins/register/bitbucket/config.ts
+++ b/config-ui/src/plugins/register/bitbucket/config.tsx
@@ -17,47 +17,47 @@
*/
import type { PluginConfigType } from '@/plugins';
-
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionUsername,
- ConnectionPassword,
- ConnectionProxy,
- ConnectionRatelimit,
-} from '../base';
+import { PluginType } from '@/plugins';
import Icon from './assets/icon.svg';
export const BitBucketConfig: PluginConfigType = {
- ...BaseConnectionConfig,
+ type: PluginType.Connection,
plugin: 'bitbucket',
name: 'BitBucket',
icon: Icon,
- isBeta: true,
+ sort: 5,
connection: {
- initialValues: {
- name: 'BitBucket',
- endpoint: 'https://api.bitbucket.org/2.0/',
- rateLimitPerHour: 10000,
- },
+ docLink: 'https://devlake.apache.org/docs/Configuration/BitBucket',
fields: [
- ConnectionName({
- placeholder: 'eg. BitBucket',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://api.bitbucket.org/2.0/',
- }),
- ConnectionUsername(),
- ConnectionPassword({
+ 'name',
+ {
+ key: 'endpoint',
+ multipleVersions: {
+ cloud: 'https://api.bitbucket.org/2.0/',
+ },
+ },
+ 'username',
+ {
+ key: 'password',
label: 'App Password',
- placeholder: 'App Password',
- }),
- ConnectionProxy(),
- ConnectionRatelimit(),
+ },
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses dynamic rate limit for optimized data collection for BitBucket. But you can adjust the collection speed by entering a fixed value.',
+ learnMore: 'https://devlake.apache.org/docs/Configuration/BitBucket#fixed-rate-limit-optional',
+ externalInfo:
+ 'The maximum rate limit for different entities in BitBucket Cloud is 60,000 or 1,000 requests/hour.',
+ defaultValue: 10000,
+ },
],
},
- entities: [],
- transformation: {},
+ entities: ['TICKET', 'CROSS'],
+ transformation: {
+ storyPointField: '',
+ remotelinkCommitShaPattern: '',
+ typeMappings: {},
+ },
};
diff --git a/config-ui/src/plugins/register/github/api.ts b/config-ui/src/plugins/register/github/api.ts
index d174fe6b9..dddf94570 100644
--- a/config-ui/src/plugins/register/github/api.ts
+++ b/config-ui/src/plugins/register/github/api.ts
@@ -52,3 +52,5 @@ export const searchRepo = (prefix: string, params: SearchRepoParams) =>
method: 'get',
data: params,
});
+
+export const testConnection = (payload: any) => request('/plugins/github/test', { method: 'post', data: payload });
diff --git a/config-ui/src/plugins/register/github/config.ts b/config-ui/src/plugins/register/github/config.tsx
similarity index 59%
rename from config-ui/src/plugins/register/github/config.ts
rename to config-ui/src/plugins/register/github/config.tsx
index bad965268..d3298d205 100644
--- a/config-ui/src/plugins/register/github/config.ts
+++ b/config-ui/src/plugins/register/github/config.tsx
@@ -16,43 +16,47 @@
*
*/
-import type { PluginConfigType } from '@/plugins';
+import React from 'react';
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionGitHubToken,
- ConnectionGitHubGraphql,
- ConnectionProxy,
- ConnectionRatelimit,
-} from '../base';
+import type { PluginConfigType } from '@/plugins';
+import { PluginType } from '@/plugins';
import Icon from './assets/icon.svg';
+import { Token, Graphql } from './connection-fields';
export const GitHubConfig: PluginConfigType = {
- ...BaseConnectionConfig,
+ type: PluginType.Connection,
plugin: 'github',
name: 'GitHub',
icon: Icon,
+ sort: 1,
connection: {
+ docLink: 'https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub',
initialValues: {
- name: 'GitHub',
endpoint: 'https://api.github.com/',
enableGraphql: true,
- rateLimitPerHour: 4500,
},
fields: [
- ConnectionName({
- placeholder: 'eg. GitHub',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://api.github.com/',
- }),
- ConnectionGitHubToken(),
- ConnectionGitHubGraphql(),
- ConnectionProxy(),
- ConnectionRatelimit(),
+ 'name',
+ {
+ key: 'endpoint',
+ multipleVersions: {
+ cloud: 'https://api.github.com/',
+ server: '',
+ },
+ },
+ (props: any) => <Token key="token" {...props} />,
+ 'proxy',
+ (props: any) => <Graphql key="graphql" {...props} />,
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses dynamic rate limit for optimized data collection for GitHub. But you can adjust the collection speed by entering a fixed value. Learn more',
+ learnMore: 'https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub/#fixed-rate-limit-optional',
+ externalInfo:
+ 'Rate Limit Value Reference\nGitHub: 0-5,000 requests/hour\nGitHub Enterprise: 0-15,000 requests/hour',
+ defaultValue: 4500,
+ },
],
},
entities: ['CODE', 'TICKET', 'CODEREVIEW', 'CROSS', 'CICD'],
diff --git a/config-ui/src/pages/connection/form/components/gitlab-token/index.tsx b/config-ui/src/plugins/register/github/connection-fields/graphql.tsx
similarity index 54%
rename from config-ui/src/pages/connection/form/components/gitlab-token/index.tsx
rename to config-ui/src/plugins/register/github/connection-fields/graphql.tsx
index 9c5579446..24ebecba5 100644
--- a/config-ui/src/pages/connection/form/components/gitlab-token/index.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/graphql.tsx
@@ -17,31 +17,34 @@
*/
import React from 'react';
-import { InputGroup } from '@blueprintjs/core';
+import { FormGroup, Switch } from '@blueprintjs/core';
+
+import * as S from './styled';
interface Props {
- placeholder?: string;
- value?: string;
- onChange?: (value: string) => void;
+ values: any;
+ setValues: any;
}
-export const GitLabToken = ({ placeholder, value, onChange }: Props) => {
- const handleChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => {
- onChange?.(e.target.value);
+export const Graphql = ({ values, setValues }: Props) => {
+ const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
+ const enableGraphql = (e.target as HTMLInputElement).checked;
+ setValues({
+ ...values,
+ enableGraphql,
+ });
};
return (
- <div>
- <p>
- <a
- href="https://devlake.apache.org/docs/UserManuals/ConfigUI/GitLab/#auth-tokens"
- target="_blank"
- rel="noreferrer"
- >
- Learn about how to create a personal access token
- </a>
- </p>
- <InputGroup placeholder={placeholder} type="password" value={value} onChange={handleChangeValue} />
- </div>
+ <FormGroup
+ label={<S.Label>Use GraphQL APIs</S.Label>}
+ subLabel={
+ <S.LabelDescription>
+ GraphQL APIs are 10+ times faster than REST APIs, but they may not be supported in GitHub Server.
+ </S.LabelDescription>
+ }
+ >
+ <Switch checked={values.enableGraphql ?? false} onChange={handleChange} />
+ </FormGroup>
);
};
diff --git a/config-ui/src/pages/connection/form/components/rate-limit/styled.ts b/config-ui/src/plugins/register/github/connection-fields/index.ts
similarity index 82%
rename from config-ui/src/pages/connection/form/components/rate-limit/styled.ts
rename to config-ui/src/plugins/register/github/connection-fields/index.ts
index 808e8719f..fc074058f 100644
--- a/config-ui/src/pages/connection/form/components/rate-limit/styled.ts
+++ b/config-ui/src/plugins/register/github/connection-fields/index.ts
@@ -16,13 +16,5 @@
*
*/
-import styled from 'styled-components';
-
-export const Wrapper = styled.div`
- display: flex;
- align-items: center;
-
- & > .bp4-numeric-input {
- margin-right: 8px;
- }
-`;
+export * from './token';
+export * from './graphql';
diff --git a/config-ui/src/pages/connection/form/components/github-token/styled.ts b/config-ui/src/plugins/register/github/connection-fields/styled.ts
similarity index 66%
rename from config-ui/src/pages/connection/form/components/github-token/styled.ts
rename to config-ui/src/plugins/register/github/connection-fields/styled.ts
index ffb01932d..062843441 100644
--- a/config-ui/src/pages/connection/form/components/github-token/styled.ts
+++ b/config-ui/src/plugins/register/github/connection-fields/styled.ts
@@ -19,38 +19,44 @@
import { Colors } from '@blueprintjs/core';
import styled from 'styled-components';
-export const Wrapper = styled.div`
+export const Label = styled.label`
+ font-size: 16px;
+ font-weight: 600;
+`;
+
+export const LabelInfo = styled.i`
+ color: #ff8b8b;
+`;
+
+export const LabelDescription = styled.p`
+ margin: 0;
+`;
+
+export const Endpoint = styled.div`
p {
- margin: 0 0 8px;
+ margin: 10px 0;
}
+`;
- h3 {
- margin: 0 0 8px;
- padding: 0;
- font-size: 14px;
- }
+export const Token = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
- .token {
+ .input {
display: flex;
align-items: center;
- justify-content: space-between;
- margin-bottom: 8px;
-
- .input {
- display: flex;
- align-items: center;
+ }
- & > span {
- margin-left: 4px;
+ .info {
+ margin-left: 4px;
- &.error {
- color: ${Colors.RED3};
- }
+ span.error {
+ color: ${Colors.RED3};
+ }
- &.success {
- color: ${Colors.GREEN3};
- }
- }
+ span.success {
+ color: ${Colors.GREEN3};
}
}
`;
diff --git a/config-ui/src/pages/connection/form/components/github-token/index.tsx b/config-ui/src/plugins/register/github/connection-fields/token.tsx
similarity index 58%
rename from config-ui/src/pages/connection/form/components/github-token/index.tsx
rename to config-ui/src/plugins/register/github/connection-fields/token.tsx
index e6943fb13..4b5b8bfb5 100644
--- a/config-ui/src/pages/connection/form/components/github-token/index.tsx
+++ b/config-ui/src/plugins/register/github/connection-fields/token.tsx
@@ -17,10 +17,12 @@
*/
import React, { useEffect, useState } from 'react';
-import { InputGroup, Button, Intent } from '@blueprintjs/core';
+import { FormGroup, InputGroup, Button, Intent } from '@blueprintjs/core';
import { pick } from 'lodash';
-import * as API from '../../api';
+import { ExternalLink } from '@/components';
+
+import * as API from '../api';
import * as S from './styled';
@@ -31,18 +33,19 @@ type TokenItem = {
};
interface Props {
- form: any;
- value?: string;
- onChange?: (value: string) => void;
+ values: any;
+ setValues: any;
+ error: any;
+ setError: any;
}
-export const GitHubToken = ({ form, value, onChange }: Props) => {
+export const Token = ({ values, setValues, error, setError }: Props) => {
const [tokens, setTokens] = useState<TokenItem[]>([{ value: '', status: 'idle' }]);
const testToken = async (token: string): Promise<TokenItem> => {
try {
- const res = await API.testConnection('github', {
- ...pick(form, ['endpoint', 'proxy']),
+ const res = await API.testConnection({
+ ...pick(values, ['endpoint', 'proxy']),
token,
});
return {
@@ -64,30 +67,26 @@ export const GitHubToken = ({ form, value, onChange }: Props) => {
};
useEffect(() => {
- if (value) {
- checkTokens(value);
+ if (values.token) {
+ checkTokens(values.token);
}
}, []);
useEffect(() => {
- onChange?.(tokens.map((it) => it.value).join(','));
+ const token = tokens.map((it) => it.value).join(',');
+ setValues({ ...values, token });
+ setError({ ...error, token: tokens.every((it) => it.value && it.status === 'valid') ? '' : 'error' });
}, [tokens]);
- const handleCreateToken = () => {
- setTokens([...tokens, { value: '', status: 'idle' }]);
- };
+ const handleCreateToken = () => setTokens([...tokens, { value: '', status: 'idle' }]);
- const handleRemoveToken = (key: number) => {
- setTokens(tokens.filter((_, i) => (i === key ? false : true)));
- };
+ const handleRemoveToken = (key: number) => setTokens(tokens.filter((_, i) => (i === key ? false : true)));
- const handleChangeToken = (key: number, value: string) => {
+ const handleChangeToken = (key: number, value: string) =>
setTokens(tokens.map((it, i) => (i === key ? { value, status: 'idle' } : it)));
- };
const handleTestToken = async (key: number) => {
const token = tokens.find((_, i) => i === key) as TokenItem;
-
if (token.status === 'idle' && token.value) {
const res = await testToken(token.value);
setTokens((tokens) => tokens.map((it, i) => (i === key ? res : it)));
@@ -95,40 +94,38 @@ export const GitHubToken = ({ form, value, onChange }: Props) => {
};
return (
- <S.Wrapper>
- <p>
- Add one or more personal token(s) for authentication from you and your organization members. Multiple tokens can
- help speed up the data collection process.{' '}
- </p>
- <p>
- <a
- href="https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub/#auth-tokens"
- target="_blank"
- rel="noreferrer"
- >
- Learn about how to create a personal access token
- </a>
- </p>
- <h3>Personal Access Token(s)</h3>
+ <FormGroup
+ label={<S.Label>Personal Access Token(s) </S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={
+ <S.LabelDescription>
+ Add one or more personal token(s) for authentication from you and your organization members. Multiple tokens
+ can help speed up the data collection process.{' '}
+ <ExternalLink link="https://devlake.apache.org/docs/UserManuals/ConfigUI/GitHub/#auth-tokens">
+ Learn how to create a personal access token
+ </ExternalLink>
+ </S.LabelDescription>
+ }
+ >
{tokens.map(({ value, status, from }, i) => (
- <div className="token" key={i}>
- <div className="input">
- <InputGroup
- placeholder="token"
- type="password"
- value={value ?? ''}
- onChange={(e) => handleChangeToken(i, e.target.value)}
- onBlur={() => handleTestToken(i)}
- />
+ <S.Token key={i}>
+ <InputGroup
+ placeholder="Token"
+ type="password"
+ value={value ?? ''}
+ onChange={(e) => handleChangeToken(i, e.target.value)}
+ onBlur={() => handleTestToken(i)}
+ />
+ <Button minimal icon="cross" onClick={() => handleRemoveToken(i)} />
+ <div className="info">
{status === 'invalid' && <span className="error">Invalid</span>}
{status === 'valid' && <span className="success">Valid From: {from}</span>}
</div>
- <Button minimal icon="cross" onClick={() => handleRemoveToken(i)} />
- </div>
+ </S.Token>
))}
<div className="action">
<Button outlined small intent={Intent.PRIMARY} text="Another Token" icon="plus" onClick={handleCreateToken} />
</div>
- </S.Wrapper>
+ </FormGroup>
);
};
diff --git a/config-ui/src/plugins/register/gitlab/config.ts b/config-ui/src/plugins/register/gitlab/config.ts
deleted file mode 100644
index 728679052..000000000
--- a/config-ui/src/plugins/register/gitlab/config.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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 type { PluginConfigType } from '@/plugins';
-
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionGitLabToken,
- ConnectionProxy,
- ConnectionRatelimit,
-} from '../base';
-
-import Icon from './assets/icon.svg';
-
-export const GitLabConfig: PluginConfigType = {
- ...BaseConnectionConfig,
- plugin: 'gitlab',
- name: 'GitLab',
- icon: Icon,
- connection: {
- initialValues: {
- name: 'GitLab',
- endpoint: 'https://gitlab.com/api/v4/',
- rateLimitPerHour: 5000,
- },
- fields: [
- ConnectionName({
- placeholder: 'eg. GitLab',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://gitlab.com/api/v4/',
- }),
- ConnectionGitLabToken(),
- ConnectionProxy(),
- ConnectionRatelimit(),
- ],
- },
- entities: ['CODE', 'TICKET', 'CODEREVIEW', 'CROSS', 'CICD'],
- transformation: {
- productionPattern: '',
- deploymentPattern: '',
- },
-};
diff --git a/config-ui/src/plugins/register/gitlab/config.tsx b/config-ui/src/plugins/register/gitlab/config.tsx
new file mode 100644
index 000000000..82cac3353
--- /dev/null
+++ b/config-ui/src/plugins/register/gitlab/config.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 React from 'react';
+
+import type { PluginConfigType } from '@/plugins';
+import { PluginType } from '@/plugins';
+
+import { ExternalLink } from '@/components';
+
+import Icon from './assets/icon.svg';
+
+export const GitLabConfig: PluginConfigType = {
+ type: PluginType.Connection,
+ plugin: 'gitlab',
+ name: 'GitLab',
+ icon: Icon,
+ sort: 2,
+ connection: {
+ docLink: 'https://devlake.apache.org/docs/Configuration/GitLab',
+ initialValues: {
+ endpoint: 'https://gitlab.com/api/v4/',
+ },
+ fields: [
+ 'name',
+ {
+ key: 'endpoint',
+ multipleVersions: {
+ cloud: 'https://gitlab.com/api/v4/',
+ server: '',
+ },
+ },
+ {
+ key: 'token',
+ label: 'Personal Access Token',
+ subLabel: (
+ <ExternalLink link="https://devlake.apache.org/docs/UserManuals/ConfigUI/GitLab/#auth-tokens">
+ Learn how to create a personal access token
+ </ExternalLink>
+ ),
+ },
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses dynamic rate limit around 12,000 requests/hour for optimized data collection for GitLab. But you can adjust the collection speed by entering a fixed value.',
+ learnMore: 'https://devlake.apache.org/docs/Configuration/GitLab#fixed-rate-limit-optional',
+ externalInfo:
+ 'The maximum rate limit for GitLab Cloud is 120,000 requests/hour. Tokens under the same IP address share the rate limit, so the actual rate limit for your token will be lower than this number.',
+ defaultValue: 12000,
+ },
+ ],
+ },
+ entities: ['CODE', 'TICKET', 'CODEREVIEW', 'CROSS', 'CICD'],
+ transformation: {
+ productionPattern: '',
+ deploymentPattern: '',
+ },
+};
diff --git a/config-ui/src/plugins/register/jenkins/config.ts b/config-ui/src/plugins/register/jenkins/config.ts
index 14bb3fbcc..58dd0b7b7 100644
--- a/config-ui/src/plugins/register/jenkins/config.ts
+++ b/config-ui/src/plugins/register/jenkins/config.ts
@@ -17,41 +17,35 @@
*/
import type { PluginConfigType } from '@/plugins';
-
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionUsername,
- ConnectionPassword,
- ConnectionProxy,
- ConnectionRatelimit,
-} from '../base';
+import { PluginType } from '@/plugins';
import Icon from './assets/icon.svg';
export const JenkinsConfig: PluginConfigType = {
- ...BaseConnectionConfig,
+ type: PluginType.Connection,
plugin: 'jenkins',
name: 'Jenkins',
icon: Icon,
+ sort: 4,
connection: {
- initialValues: {
- name: 'Jenkins',
- endpoint: 'https://api.jenkins.io/',
- rateLimitPerHour: 10000,
- },
+ docLink: 'https://devlake.apache.org/docs/Configuration/Jenkins',
fields: [
- ConnectionName({
- placeholder: 'eg. Jenkins',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://api.jenkins.io/',
- }),
- ConnectionUsername(),
- ConnectionPassword(),
- ConnectionProxy(),
- ConnectionRatelimit(),
+ 'name',
+ {
+ key: 'endpoint',
+ subLabel: 'Provide the Jenkins instance API endpoint. E.g. https://api.jenkins.io',
+ },
+ 'username',
+ 'password',
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses 10,000 requests/hour for data collection for Jenkins. But you can adjust the collection speed by setting up your desirable rate limit.',
+ learnMore: 'https://devlake.apache.org/docs/Configuration/Jenkins/#fixed-rate-limit-optional',
+ externalInfo: 'Jenkins does not specify a maximum value of rate limit.',
+ defaultValue: 10000,
+ },
],
},
entities: ['CICD'],
diff --git a/config-ui/src/plugins/register/jira/config.ts b/config-ui/src/plugins/register/jira/config.tsx
similarity index 54%
rename from config-ui/src/plugins/register/jira/config.ts
rename to config-ui/src/plugins/register/jira/config.tsx
index 77dcbea3e..0eb528b48 100644
--- a/config-ui/src/plugins/register/jira/config.ts
+++ b/config-ui/src/plugins/register/jira/config.tsx
@@ -16,40 +16,40 @@
*
*/
-import type { PluginConfigType } from '@/plugins';
+import React from 'react';
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionProxy,
- ConnectionRatelimit,
- ConnectionJIRAAuth,
-} from '../base';
+import type { PluginConfigType } from '@/plugins';
+import { PluginType } from '@/plugins';
import Icon from './assets/icon.svg';
+import { Auth } from './connection-fields';
export const JIRAConfig: PluginConfigType = {
- ...BaseConnectionConfig,
+ type: PluginType.Connection,
plugin: 'jira',
name: 'JIRA',
icon: Icon,
+ sort: 3,
connection: {
- initialValues: {
- name: 'JIRA',
- endpoint: 'https://your-domain.atlassian.net/rest/',
- rateLimitPerHour: 3000,
- },
+ docLink: 'https://devlake.apache.org/docs/Configuration/Jira',
fields: [
- ConnectionName({
- placeholder: 'eg. JIRA',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://your-domain.atlassian.net/rest/',
- }),
- ConnectionJIRAAuth(),
- ConnectionProxy(),
- ConnectionRatelimit(),
+ 'name',
+ {
+ key: 'endpoint',
+ subLabel:
+ 'Provide the Jira instance API endpoint. For Jira Cloud, e.g. https://your-company.atlassian.net/rest/',
+ },
+ (props: any) => <Auth {...props} />,
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses dynamic rate limit for optimized data collection. But you can adjust the collection speed by setting up your desirable rate limit.',
+ learnMore: 'https://devlake.apache.org/docs/Configuration/Jira/#fixed-rate-limit-optional',
+ externalInfo:
+ 'Jira Cloud does not specify a maximum value of rate limit. For Jira Server, please contact your admin for more information.',
+ defaultValue: 10000,
+ },
],
},
entities: ['TICKET', 'CROSS'],
diff --git a/config-ui/src/plugins/register/jira/connection-fields/auth.tsx b/config-ui/src/plugins/register/jira/connection-fields/auth.tsx
new file mode 100644
index 000000000..71d179ea7
--- /dev/null
+++ b/config-ui/src/plugins/register/jira/connection-fields/auth.tsx
@@ -0,0 +1,126 @@
+/*
+ * 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 React, { useState } from 'react';
+import { FormGroup, RadioGroup, Radio, InputGroup } from '@blueprintjs/core';
+
+import { ExternalLink } from '@/components';
+
+import * as S from './styled';
+
+type Method = 'BasicAuth' | 'AccessToken';
+
+interface Props {
+ values: any;
+ setValues: (value: any) => void;
+}
+
+export const Auth = ({ values, setValues }: Props) => {
+ const [method, setMethod] = useState<Method>('BasicAuth');
+
+ const handleChangeMethod = (e: React.FormEvent<HTMLInputElement>) => {
+ const m = (e.target as HTMLInputElement).value as Method;
+
+ setMethod(m);
+ setValues({
+ ...values,
+ authMethod: m,
+ username: m === 'BasicAuth' ? values.username : undefined,
+ password: m === 'BasicAuth' ? values.password : undefined,
+ token: m === 'AccessToken' ? values.token : undefined,
+ });
+ };
+
+ const handleChangeUsername = (e: React.ChangeEvent<HTMLInputElement>) => {
+ setValues({
+ ...values,
+ authMethod: 'BasicAuth',
+ username: e.target.value,
+ });
+ };
+
+ const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
+ setValues({
+ ...values,
+ authMethod: 'BasicAuth',
+ password: e.target.value,
+ });
+ };
+
+ const handleChangeToken = (e: React.ChangeEvent<HTMLInputElement>) => {
+ setValues({
+ ...values,
+ token: e.target.value,
+ });
+ };
+
+ return (
+ <FormGroup label={<S.Label>Authentication Method</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
+ <RadioGroup inline selectedValue={method} onChange={handleChangeMethod}>
+ <Radio value="BasicAuth">Basic Authentication</Radio>
+ <Radio value="AccessToken">Using Personal Access Token</Radio>
+ </RadioGroup>
+ {method === 'BasicAuth' && (
+ <>
+ <FormGroup label={<S.Label>Username/e-mail</S.Label>} labelInfo={<S.LabelInfo>*</S.LabelInfo>}>
+ <InputGroup
+ placeholder="Your Username/e-mail"
+ value={values.username || ''}
+ onChange={handleChangeUsername}
+ />
+ </FormGroup>
+ <FormGroup
+ label={<S.Label>Password</S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={
+ <S.LabelDescription>
+ For Jira Cloud, please enter your{' '}
+ <ExternalLink link="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html">
+ Personal Access Token
+ </ExternalLink>{' '}
+ For Jira Server v8+, please enter the password of your Jira account.
+ </S.LabelDescription>
+ }
+ >
+ <InputGroup
+ type="password"
+ placeholder="Your Token/Password"
+ value={values.password || ''}
+ onChange={handleChangePassword}
+ />
+ </FormGroup>
+ </>
+ )}
+ {method === 'AccessToken' && (
+ <FormGroup
+ label={<S.Label>Personal Access Token</S.Label>}
+ labelInfo={<S.LabelInfo>*</S.LabelInfo>}
+ subLabel={
+ <S.LabelDescription>
+ <ExternalLink link="https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html">
+ Learn about how to create PAT
+ </ExternalLink>
+ </S.LabelDescription>
+ }
+ >
+ <InputGroup type="password" placeholder="Your PAT" value={values.token || ''} onChange={handleChangeToken} />
+ </FormGroup>
+ )}
+ </FormGroup>
+ );
+};
diff --git a/config-ui/src/plugins/components/index.ts b/config-ui/src/plugins/register/jira/connection-fields/index.ts
similarity index 89%
copy from config-ui/src/plugins/components/index.ts
copy to config-ui/src/plugins/register/jira/connection-fields/index.ts
index 1fdd73324..7a88dcdb2 100644
--- a/config-ui/src/plugins/components/index.ts
+++ b/config-ui/src/plugins/register/jira/connection-fields/index.ts
@@ -16,6 +16,4 @@
*
*/
-export * from './data-scope-list';
-export * from './data-scope';
-export * from './transformation';
+export * from './auth';
diff --git a/config-ui/src/pages/connection/form/styled.ts b/config-ui/src/plugins/register/jira/connection-fields/styled.ts
similarity index 59%
rename from config-ui/src/pages/connection/form/styled.ts
rename to config-ui/src/plugins/register/jira/connection-fields/styled.ts
index 7140b12f3..062843441 100644
--- a/config-ui/src/pages/connection/form/styled.ts
+++ b/config-ui/src/plugins/register/jira/connection-fields/styled.ts
@@ -19,51 +19,44 @@
import { Colors } from '@blueprintjs/core';
import styled from 'styled-components';
-export const Wrapper = styled.div`
- .bp4-form-group {
- display: flex;
- align-items: start;
- justify-content: space-between;
-
- .bp4-label {
- flex: 0 0 200px;
- font-weight: 600;
-
- .bp4-popover2-target {
- display: inline;
- margin: 0;
- line-height: 1;
- margin-left: 4px;
+export const Label = styled.label`
+ font-size: 16px;
+ font-weight: 600;
+`;
- & > .bp4-icon {
- display: block;
- }
- }
+export const LabelInfo = styled.i`
+ color: #ff8b8b;
+`;
- .bp4-text-muted {
- color: ${Colors.RED3};
- }
- }
+export const LabelDescription = styled.p`
+ margin: 0;
+`;
- .bp4-form-content {
- flex: auto;
- }
+export const Endpoint = styled.div`
+ p {
+ margin: 10px 0;
}
+`;
- .footer {
+export const Token = styled.div`
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+
+ .input {
display: flex;
align-items: center;
- justify-content: space-between;
- margin-top: 32px;
}
-`;
-export const Label = styled.span`
- display: inline-flex;
- align-items: center;
-`;
+ .info {
+ margin-left: 4px;
-export const SwitchWrapper = styled.div`
- display: flex;
- align-items: center;
+ span.error {
+ color: ${Colors.RED3};
+ }
+
+ span.success {
+ color: ${Colors.GREEN3};
+ }
+ }
`;
diff --git a/config-ui/src/plugins/register/tapd/config.ts b/config-ui/src/plugins/register/tapd/config.ts
index 7dd81e831..9bcb20c9c 100644
--- a/config-ui/src/plugins/register/tapd/config.ts
+++ b/config-ui/src/plugins/register/tapd/config.ts
@@ -17,42 +17,40 @@
*/
import type { PluginConfigType } from '@/plugins';
-
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionUsername,
- ConnectionPassword,
- ConnectionProxy,
- ConnectionRatelimit,
-} from '../base';
+import { PluginType } from '@/plugins';
import Icon from './assets/icon.svg';
export const TAPDConfig: PluginConfigType = {
- ...BaseConnectionConfig,
+ type: PluginType.Connection,
plugin: 'tapd',
name: 'TAPD',
isBeta: true,
icon: Icon,
+ sort: 6,
connection: {
+ docLink: 'https://devlake.apache.org/docs/Configuration/Tapd',
initialValues: {
- name: 'TAPD',
- endpoint: 'https://api.tapd.cn/',
- rateLimitPerHour: 3000,
+ endpoint: 'https://api.tapd.cn',
},
fields: [
- ConnectionName({
- placeholder: 'eg. TAPD',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://api.tapd.cn/',
- }),
- ConnectionUsername(),
- ConnectionPassword(),
- ConnectionProxy(),
- ConnectionRatelimit(),
+ 'name',
+ {
+ key: 'endpoint',
+ subLabel: 'You do not need to enter the endpoint URL, because all versions use the same URL.',
+ disabled: true,
+ },
+ 'username',
+ 'password',
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses 3,000 requests/hour for data collection for TAPD. But you can adjust the collection speed by setting up your desirable rate limit.',
+ learnMore: 'https://devlake.apache.org/docs/Configuration/Tapdt#fixed-rate-limit-optional',
+ externalInfo: 'The maximum rate limit of TAPD is 3,600 requests/hour.',
+ defaultValue: 3000,
+ },
],
},
entities: ['TICKET'],
diff --git a/config-ui/src/plugins/register/zentao/config.ts b/config-ui/src/plugins/register/zentao/config.ts
index 692141ee8..47a54faab 100644
--- a/config-ui/src/plugins/register/zentao/config.ts
+++ b/config-ui/src/plugins/register/zentao/config.ts
@@ -17,42 +17,33 @@
*/
import type { PluginConfigType } from '@/plugins';
-
-import {
- BaseConnectionConfig,
- ConnectionName,
- ConnectionEndpoint,
- ConnectionUsername,
- ConnectionPassword,
- ConnectionProxy,
- ConnectionRatelimit,
-} from '../base';
+import { PluginType } from '@/plugins';
import Icon from './assets/icon.svg';
export const ZenTaoConfig: PluginConfigType = {
- ...BaseConnectionConfig,
+ type: PluginType.Connection,
plugin: 'zentao',
name: 'ZenTao',
isBeta: true,
icon: Icon,
+ sort: 7,
connection: {
- initialValues: {
- name: 'ZenTao',
- endpoint: 'https://your-domain:port/api.php/v1/',
- rateLimitPerHour: 10000,
- },
+ docLink: 'https://devlake.apache.org/docs/Configuration/Zentao',
fields: [
- ConnectionName({
- placeholder: 'eg. ZenTao',
- }),
- ConnectionEndpoint({
- placeholder: 'eg. https://your-domain:port/api.php/v1/',
- }),
- ConnectionUsername(),
- ConnectionPassword(),
- ConnectionProxy(),
- ConnectionRatelimit(),
+ 'name',
+ 'endpoint',
+ 'username',
+ 'password',
+ 'proxy',
+ {
+ key: 'rateLimitPerHour',
+ subLabel:
+ 'By default, DevLake uses 10,000 requests/hour for data collection for ZenTao. But you can adjust the collection speed by setting up your desirable rate limit.',
+ learnMore: 'https://devlake.apache.org/docs/Configuration/Zentao/#custom-rate-limit-optional',
+ externalInfo: 'Jenkins does not specify a maximum value of rate limit.',
+ defaultValue: 10000,
+ },
],
},
entities: ['TICKET'],
diff --git a/config-ui/src/plugins/types.ts b/config-ui/src/plugins/types.ts
index f91389553..84e0fd1dd 100644
--- a/config-ui/src/plugins/types.ts
+++ b/config-ui/src/plugins/types.ts
@@ -27,18 +27,12 @@ export type PluginConfigConnectionType = {
plugin: string;
name: string;
icon: string;
+ sort: number;
isBeta?: boolean;
connection: {
+ docLink: string;
initialValues?: Record<string, any>;
- fields: Array<{
- key: string;
- type: 'text' | 'password' | 'switch' | 'rateLimit' | 'githubToken' | 'gitlabToken' | 'jiraAuth';
- label?: string;
- required?: boolean;
- placeholder?: string;
- tooltip?: string;
- checkError?: (value: any) => boolean;
- }>;
+ fields: any[];
};
entities: string[];
transformation: any;