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/01 14:15:41 UTC

[incubator-devlake] branch main updated: feat(config-ui): support bearer authentication for jira (#4287)

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 e5157e854 feat(config-ui): support bearer authentication for jira (#4287)
e5157e854 is described below

commit e5157e854aa5da68680de729ee77cbc33ee8d91e
Author: 青湛 <0x...@gmail.com>
AuthorDate: Wed Feb 1 22:15:36 2023 +0800

    feat(config-ui): support bearer authentication for jira (#4287)
---
 .../src/pages/connection/form/components/index.ts  |   1 +
 .../connection/form/components/jira-auth/index.tsx | 115 +++++++++++++++++++++
 config-ui/src/pages/connection/form/index.tsx      |  32 +++++-
 .../src/plugins/register/base/connection-fields.ts |   8 ++
 config-ui/src/plugins/register/jira/config.ts      |  10 +-
 config-ui/src/plugins/types.ts                     |   5 +-
 6 files changed, 158 insertions(+), 13 deletions(-)

diff --git a/config-ui/src/pages/connection/form/components/index.ts b/config-ui/src/pages/connection/form/components/index.ts
index 3fc2e5b52..448d0ba62 100644
--- a/config-ui/src/pages/connection/form/components/index.ts
+++ b/config-ui/src/pages/connection/form/components/index.ts
@@ -19,3 +19,4 @@
 export * from './rate-limit';
 export * from './github-token';
 export * from './gitlab-token';
+export * from './jira-auth';
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
new file mode 100644
index 000000000..638b15324
--- /dev/null
+++ b/config-ui/src/pages/connection/form/components/jira-auth/index.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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/index.tsx b/config-ui/src/pages/connection/form/index.tsx
index a5d0504ae..630c36ce2 100644
--- a/config-ui/src/pages/connection/form/index.tsx
+++ b/config-ui/src/pages/connection/form/index.tsx
@@ -26,7 +26,7 @@ import { PageHeader, Card, PageLoading } from '@/components';
 import type { PluginConfigConnectionType } from '@/plugins';
 import { PluginConfig } from '@/plugins';
 
-import { RateLimit, GitHubToken, GitLabToken } from './components';
+import { RateLimit, GitHubToken, GitLabToken, JIRAAuth } from './components';
 import { useForm } from './use-form';
 import * as S from './styled';
 
@@ -51,12 +51,23 @@ export const ConnectionFormPage = () => {
   }, [initialValues, connection]);
 
   const error = useMemo(
-    () => !!(fields.filter((field) => field.required) ?? []).find((field) => !form[field.key]),
+    () =>
+      fields.every((field) => {
+        if (field.required) {
+          return !!form[field.key];
+        }
+
+        if (field.checkError) {
+          return !field.checkError(form);
+        }
+
+        return true;
+      }),
     [form, fields],
   );
 
   const handleTest = () =>
-    onTest(pick(form, ['endpoint', 'token', 'username', 'password', 'app_id', 'secret_key', 'proxy']));
+    onTest(pick(form, ['endpoint', 'token', 'username', 'password', 'app_id', 'secret_key', 'proxy', 'authMethod']));
 
   const handleCancel = () => history.push(`/connections/${plugin}`);
 
@@ -70,6 +81,21 @@ export const ConnectionFormPage = () => {
     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}
diff --git a/config-ui/src/plugins/register/base/connection-fields.ts b/config-ui/src/plugins/register/base/connection-fields.ts
index 56b4701b1..0a6493bdf 100644
--- a/config-ui/src/plugins/register/base/connection-fields.ts
+++ b/config-ui/src/plugins/register/base/connection-fields.ts
@@ -98,3 +98,11 @@ export const ConnectionGitLabToken = () => ({
   required: true,
   placeholder: 'eg. ff9d1ad0e5c04f1f98fa',
 });
+
+export const ConnectionJIRAAuth = () => ({
+  key: 'auth',
+  type: 'jiraAuth' as const,
+  checkError: (form: any) => {
+    return !!(form.username && form.password) || !!form.token;
+  },
+});
diff --git a/config-ui/src/plugins/register/jira/config.ts b/config-ui/src/plugins/register/jira/config.ts
index eb6289cc4..77dcbea3e 100644
--- a/config-ui/src/plugins/register/jira/config.ts
+++ b/config-ui/src/plugins/register/jira/config.ts
@@ -22,10 +22,9 @@ import {
   BaseConnectionConfig,
   ConnectionName,
   ConnectionEndpoint,
-  ConnectionUsername,
-  ConnectionPassword,
   ConnectionProxy,
   ConnectionRatelimit,
+  ConnectionJIRAAuth,
 } from '../base';
 
 import Icon from './assets/icon.svg';
@@ -48,12 +47,7 @@ export const JIRAConfig: PluginConfigType = {
       ConnectionEndpoint({
         placeholder: 'eg. https://your-domain.atlassian.net/rest/',
       }),
-      ConnectionUsername({
-        label: 'Username / E-mail',
-      }),
-      ConnectionPassword({
-        tooltip: 'If you are using JIRA Cloud or JIRA Server,\nyour API Token should be used as password.',
-      }),
+      ConnectionJIRAAuth(),
       ConnectionProxy(),
       ConnectionRatelimit(),
     ],
diff --git a/config-ui/src/plugins/types.ts b/config-ui/src/plugins/types.ts
index 94282845c..f91389553 100644
--- a/config-ui/src/plugins/types.ts
+++ b/config-ui/src/plugins/types.ts
@@ -32,11 +32,12 @@ export type PluginConfigConnectionType = {
     initialValues?: Record<string, any>;
     fields: Array<{
       key: string;
-      type: 'text' | 'password' | 'switch' | 'rateLimit' | 'githubToken' | 'gitlabToken';
-      label: string;
+      type: 'text' | 'password' | 'switch' | 'rateLimit' | 'githubToken' | 'gitlabToken' | 'jiraAuth';
+      label?: string;
       required?: boolean;
       placeholder?: string;
       tooltip?: string;
+      checkError?: (value: any) => boolean;
     }>;
   };
   entities: string[];