You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2023/06/19 01:43:25 UTC

[incubator-devlake] branch main updated: fix(config-ui): some bugs for jira dev panel (#5514)

This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 188df62ec fix(config-ui): some bugs for jira dev panel (#5514)
188df62ec is described below

commit 188df62ec2d162fdc2dc5351b2088e0f6d7a459a
Author: 青湛 <0x...@gmail.com>
AuthorDate: Mon Jun 19 09:43:21 2023 +0800

    fix(config-ui): some bugs for jira dev panel (#5514)
---
 config-ui/src/plugins/register/jira/api.ts         |  15 ++
 .../jira/transformation-fields/cross-domain.tsx    | 253 ++++-----------------
 .../jira/transformation-fields/dev-panel.tsx       | 210 +++++++++++++++++
 .../jira/transformation-fields/remote-link.tsx     | 119 ++++++++++
 .../register/jira/transformation-fields/styled.ts  |  20 +-
 config-ui/src/utils/operator.ts                    |   2 +-
 6 files changed, 409 insertions(+), 210 deletions(-)

diff --git a/config-ui/src/plugins/register/jira/api.ts b/config-ui/src/plugins/register/jira/api.ts
index cf1068b30..07f6fd8ef 100644
--- a/config-ui/src/plugins/register/jira/api.ts
+++ b/config-ui/src/plugins/register/jira/api.ts
@@ -37,3 +37,18 @@ export const getApplicationTypes = (connectionId: ID, query: { key: string }) =>
 
 export const getDevPanelCommits = (connectionId: ID, query: { key: string; applicationType: string }) =>
   request(`/plugins/jira/connections/${connectionId}/dev-panel-commits`, { data: query });
+
+export const generateRegex = (pattern: string) =>
+  request('/plugins/jira/generate-regex', {
+    method: 'post',
+    data: { pattern },
+  });
+
+export const applyRegex = (regex: string, urls: string[]) =>
+  request('/plugins/jira/apply-regex', {
+    method: 'post',
+    data: {
+      regex,
+      urls,
+    },
+  });
diff --git a/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx b/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
index bce16432d..fd8fc243f 100644
--- a/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
+++ b/config-ui/src/plugins/register/jira/transformation-fields/cross-domain.tsx
@@ -16,15 +16,14 @@
  *
  */
 
-import { useState, useEffect } from 'react';
-import { Radio, Icon, Collapse, InputGroup, Button, Intent, RadioGroup } from '@blueprintjs/core';
+import { useEffect, useState } from 'react';
+import { Radio, Icon, Collapse, Button } from '@blueprintjs/core';
 
-import { ExternalLink, IconButton, Dialog, FormItem } from '@/components';
+import { ExternalLink } from '@/components';
 import JiraIssueTipsImg from '@/images/jira-issue-tips.png';
-import { operator } from '@/utils';
-
-import * as API from '../api';
 
+import { RemoteLink } from './remote-link';
+import { DevPanel } from './dev-panel';
 import * as S from './styled';
 
 interface Props {
@@ -34,113 +33,25 @@ interface Props {
 }
 
 export const CrossDomain = ({ connectionId, transformation, setTransformation }: Props) => {
-  const [radio, setRadio] = useState<'repo' | 'commitSha'>('repo');
-  const [repoTips, setRepoTips] = useState(false);
-  const [repoLinks, setRepoLinks] = useState([]);
+  const [radio, setRadio] = useState<'remote-link' | 'dev-panel'>('remote-link');
+  const [showTip, setShowTip] = useState(false);
   const [isOpen, setIsOpen] = useState(false);
-  const [step, setStep] = useState(1);
-  const [issueKey, setIssueKey] = useState('');
-  const [searching, setSearching] = useState(false);
-  const [applicationTypes, setApplicationTypes] = useState<string[]>([]);
-  const [applicationType, setApplicationType] = useState<string>();
-  const [operating, setOperating] = useState(false);
-  const [devPanelCommits, setDevPanelCommits] = useState<string[]>([]);
 
   useEffect(() => {
-    if (transformation.remotelinkCommitShaPattern) {
-      setRadio('commitSha');
-    }
-
-    if (transformation.remotelinkRepoPattern) {
-      setRepoLinks(transformation.remotelinkRepoPattern);
-    }
-  }, [transformation]);
-
-  const handleChangeRadio = (r: 'repo' | 'commitSha') => {
-    if (r === 'repo') {
-      setTransformation({
-        ...transformation,
-        remotelinkCommitShaPattern: '',
-      });
-    }
-
-    if (r === 'commitSha') {
-      setTransformation({
-        ...transformation,
-        remotelinkRepoPattern: [],
-      });
+    if (transformation.applicationType) {
+      setRadio('dev-panel');
+    } else {
+      setRadio('remote-link');
     }
+  }, []);
 
-    setRadio(r);
-  };
-
-  const handleToggleRepoTips = () => setRepoTips(!repoTips);
-
-  const handleChangeRepoLinks = (index: number, value: string) => {
-    const newValue = repoLinks.map((link, i) => (index === i ? value : link));
+  const handleChangeRadio = (r: 'remote-link' | 'dev-panel') => {
     setTransformation({
       ...transformation,
-      remotelinkRepoPattern: newValue,
-    });
-  };
-
-  const handleAddRepoLinks = () => {
-    const newValue = [...repoLinks, ''];
-    setTransformation({
-      ...transformation,
-      remotelinkRepoPattern: newValue,
-    });
-  };
-
-  const handleDeleteRepoLinks = (index: number) => {
-    const newValue = repoLinks.filter((_, i) => i !== index);
-    setTransformation({
-      ...transformation,
-      remotelinkRepoPattern: newValue,
-    });
-  };
-
-  const handleShowDevPanel = () => setIsOpen(true);
-  const handleHideDevPanel = () => setIsOpen(false);
-
-  const handleSearch = async () => {
-    const [success, res] = await operator(() => API.getApplicationTypes(connectionId, { key: issueKey }), {
-      setOperating: setSearching,
-      hideToast: true,
+      applicationType: r === 'remote-link' ? '' : transformation.applicationType,
+      remotelinkRepoPattern: [],
     });
-
-    if (success) {
-      setApplicationTypes(res);
-    }
-  };
-
-  const handleSubmit = async () => {
-    if (step === 1 && applicationType) {
-      const [success, res] = await operator(
-        () => API.getDevPanelCommits(connectionId, { key: issueKey, applicationType }),
-        {
-          setOperating,
-          hideToast: true,
-        },
-      );
-
-      if (success) {
-        setStep(2);
-        setDevPanelCommits(res);
-        return;
-      }
-    }
-
-    handleHideDevPanel();
-  };
-
-  const handleCancel = () => {
-    if (step === 1) {
-      handleHideDevPanel();
-      return;
-    }
-
-    setStep(1);
+    setRadio(r);
   };
 
   return (
@@ -155,129 +66,55 @@ export const CrossDomain = ({ connectionId, transformation, setTransformation }:
       </p>
       <div className="radio">
         <div className="radio-item">
-          <Radio checked={radio === 'repo'} onChange={() => handleChangeRadio('repo')} />
+          <Radio checked={radio === 'remote-link'} onChange={() => handleChangeRadio('remote-link')} />
           <div className="content">
             <h5>Connect Jira issues and commits via Jira issues’ remote links that match the following pattern</h5>
-            <p onClick={handleToggleRepoTips}>
+            <p style={{ display: 'flex', alignItems: 'center' }} onClick={() => setShowTip(!showTip)}>
               Input pattern(s) to match and parse commits and repo identifiers from issue remote links. See examples{' '}
-              <Icon icon={!repoTips ? 'chevron-down' : 'chevron-up'} style={{ marginLeft: 8, cursor: 'pointer' }} />
+              <Icon icon={showTip ? 'chevron-down' : 'chevron-up'} style={{ cursor: 'pointer' }} />
             </p>
-            <Collapse isOpen={repoTips}>
+            <Collapse isOpen={showTip}>
               <img src={JiraIssueTipsImg} width="100%" alt="" />
             </Collapse>
-            {radio === 'repo' && (
-              <>
-                {repoLinks.map((link, i) => (
-                  <div className="input">
-                    <InputGroup
-                      key={i}
-                      placeholder="E.g. https://gitlab.com/{namespace}/{repo_name}/-/commit/{commit_sha}"
-                      value={link}
-                      onChange={(e) => handleChangeRepoLinks(i, e.target.value)}
-                    />
-                    {repoLinks.length > 1 && (
-                      <IconButton icon="cross" tooltip="Delete" onClick={() => handleDeleteRepoLinks(i)} />
-                    )}
-                  </div>
-                ))}
-                <Button
-                  outlined
-                  intent={Intent.PRIMARY}
-                  icon="add"
-                  text="Add a Pattern"
-                  onClick={() => handleAddRepoLinks()}
-                />
-              </>
+            {radio === 'remote-link' && (
+              <RemoteLink transformation={transformation} setTransformation={setTransformation} />
             )}
           </div>
         </div>
         <div className="radio-item">
-          <Radio checked={radio === 'commitSha'} onChange={() => handleChangeRadio('commitSha')} />
+          <Radio checked={radio === 'dev-panel'} onChange={() => handleChangeRadio('dev-panel')} />
           <div className="content">
             <h5>
               Connect Jira issues and commits via{' '}
-              <ExternalLink link="Links to: https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/">
+              <ExternalLink link="https://support.atlassian.com/jira-software-cloud/docs/view-development-information-for-an-issue/">
                 development panel
               </ExternalLink>
             </h5>
             <p>Finish the configuration so DevLake can get your Git data from your Jira development panel.</p>
-            {radio === 'commitSha' && <Button text="Configure" onClick={handleShowDevPanel} />}
+            {radio === 'dev-panel' && (
+              <>
+                {transformation.applicationType && (
+                  <div className="application">
+                    <span>{transformation.applicationType}</span>
+                    <span>{transformation.remotelinkRepoPattern[0]?.pattern}</span>
+                  </div>
+                )}
+                <Button
+                  text={!transformation.applicationType ? 'Configure' : 'Edit Configuration'}
+                  onClick={() => setIsOpen(true)}
+                />
+                <DevPanel
+                  connectionId={connectionId}
+                  transformation={transformation}
+                  setTransformation={setTransformation}
+                  isOpen={isOpen}
+                  onCancel={() => setIsOpen(false)}
+                />
+              </>
+            )}
           </div>
         </div>
       </div>
-      <Dialog
-        style={{ width: 820 }}
-        isOpen={isOpen}
-        title="Configure the `Application Type` and `Commit Pattern` by sending request(s) to Jira."
-        okText={step === 1 ? 'Next' : 'Save'}
-        okDisabled={(step === 1 && !applicationType) || (step === 2 && !transformation.remotelinkCommitShaPattern)}
-        okLoading={operating}
-        cancelText={step === 2 ? 'Prev' : 'Cancel'}
-        onOk={handleSubmit}
-        onCancel={handleCancel}
-      >
-        <S.DialogBody>
-          {step === 1 && (
-            <>
-              <FormItem
-                label="Jira Issue Key"
-                subLabel="Input any issue key that has connected commit(s) in the development panel"
-                required
-              >
-                <div className="search">
-                  <InputGroup
-                    placeholder="Please enter..."
-                    value={issueKey}
-                    onChange={(e) => setIssueKey(e.target.value)}
-                  />
-                  <Button loading={searching} disabled={!issueKey} text="See Results" onClick={handleSearch} />
-                </div>
-              </FormItem>
-              {applicationTypes.length > 0 && (
-                <FormItem label="Application Type" subLabel="Please choose an application type." required>
-                  <RadioGroup
-                    selectedValue={applicationType}
-                    onChange={(e) => setApplicationType((e.target as HTMLInputElement).value)}
-                  >
-                    {applicationTypes.map((at) => (
-                      <Radio key={at} value={at} label={at} />
-                    ))}
-                  </RadioGroup>
-                </FormItem>
-              )}
-            </>
-          )}
-          {step === 2 && (
-            <>
-              <FormItem label="Jira Issue Key">{issueKey}</FormItem>
-              <FormItem label="Application Type">{applicationType}</FormItem>
-              <FormItem label="Commit Url Preview" subLabel="The latest five commit(s) associated with the issue.">
-                <ul>
-                  {devPanelCommits.map((commit) => (
-                    <li key={commit}>{commit}</li>
-                  ))}
-                </ul>
-              </FormItem>
-              <FormItem
-                label="Commit Pattern"
-                subLabel={
-                  <>
-                    Input pattern(s) to match and parse commits and repo identifiers from above commit URLs. See
-                    examples
-                  </>
-                }
-                required
-              >
-                <InputGroup
-                  placeholder="eg. https://gitlab.com/{namespace}/{repo_name}/commit/{commit_sha}"
-                  value={transformation.remotelinkCommitShaPattern}
-                  onChange={(e) => setTransformation({ ...transformation, remotelinkCommitShaPattern: e.target.value })}
-                />
-              </FormItem>
-            </>
-          )}
-        </S.DialogBody>
-      </Dialog>
     </S.CrossDomain>
   );
 };
diff --git a/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx b/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx
new file mode 100644
index 000000000..c469fd734
--- /dev/null
+++ b/config-ui/src/plugins/register/jira/transformation-fields/dev-panel.tsx
@@ -0,0 +1,210 @@
+/*
+ * 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 { useEffect, useState } from 'react';
+import { InputGroup, Button, RadioGroup, Radio, Icon, Collapse } from '@blueprintjs/core';
+
+import { Dialog, FormItem } from '@/components';
+import JiraIssueTipsImg from '@/images/jira-issue-tips.png';
+import { operator } from '@/utils';
+
+import * as API from '../api';
+import * as S from './styled';
+
+interface Props {
+  connectionId: ID;
+  transformation: any;
+  setTransformation: React.Dispatch<React.SetStateAction<any>>;
+  isOpen: boolean;
+  onCancel: () => void;
+}
+
+export const DevPanel = ({ connectionId, transformation, setTransformation, isOpen, onCancel }: Props) => {
+  const [step, setStep] = useState(1);
+  const [issueKey, setIssueKey] = useState('');
+  const [searching, setSearching] = useState(false);
+  const [applicationTypes, setApplicationTypes] = useState<string[]>([]);
+  const [applicationType, setApplicationType] = useState<string>();
+  const [showTip, setShowTip] = useState(false);
+  const [operating, setOperating] = useState(false);
+  const [devPanelCommits, setDevPanelCommits] = useState<string[]>([]);
+  const [pattern, setPattern] = useState('');
+  const [regex, setRegex] = useState('');
+  const [preview, setPreview] = useState([]);
+
+  const getRegex = async () => {
+    if (!pattern) return;
+
+    const [success, res] = await operator(
+      async () => {
+        const { regex } = await API.generateRegex(pattern);
+        const preview = await API.applyRegex(regex, devPanelCommits);
+        return {
+          regex,
+          preview,
+        };
+      },
+      {
+        setOperating,
+        hideToast: true,
+      },
+    );
+
+    if (success) {
+      setRegex(res.regex);
+      setPreview(res.preview);
+      setTransformation({
+        ...transformation,
+        applicationType,
+        remotelinkRepoPattern: [{ pattern, regex: res.regex }],
+      });
+    }
+  };
+
+  useEffect(() => {
+    const timer = setTimeout(getRegex, 500);
+    return () => clearTimeout(timer);
+  }, [pattern]);
+
+  const handleSearch = async () => {
+    const [success, res] = await operator(() => API.getApplicationTypes(connectionId, { key: issueKey }), {
+      setOperating: setSearching,
+      hideToast: true,
+    });
+
+    if (success) {
+      setApplicationTypes(res);
+      setApplicationType(res[0]);
+    }
+  };
+
+  const handleSubmit = async () => {
+    if (step === 1 && applicationType) {
+      const [success, res] = await operator(
+        () => API.getDevPanelCommits(connectionId, { key: issueKey, applicationType }),
+        {
+          setOperating,
+          hideToast: true,
+        },
+      );
+
+      if (success) {
+        setStep(2);
+        setDevPanelCommits(res);
+        return;
+      }
+    }
+
+    onCancel();
+  };
+
+  const handleCancel = () => {
+    if (step === 1) {
+      onCancel();
+      return;
+    }
+
+    setStep(1);
+  };
+
+  return (
+    <Dialog
+      style={{ width: 820 }}
+      isOpen={isOpen}
+      title="Configure the `Application Type` and `Commit Pattern` by sending request(s) to Jira."
+      okText={step === 1 ? 'Next' : 'Save'}
+      okDisabled={(step === 1 && !applicationType) || (step === 2 && (!pattern || !regex))}
+      okLoading={operating}
+      cancelText={step === 2 ? 'Prev' : 'Cancel'}
+      onOk={handleSubmit}
+      onCancel={handleCancel}
+    >
+      <S.DialogBody>
+        {step === 1 && (
+          <>
+            <FormItem
+              label="Jira Issue Key"
+              subLabel="Input any issue key that has connected commit(s) in the development panel"
+              required
+            >
+              <div className="search">
+                <InputGroup
+                  placeholder="Please enter..."
+                  value={issueKey}
+                  onChange={(e) => setIssueKey(e.target.value)}
+                />
+                <Button loading={searching} disabled={!issueKey} text="See Results" onClick={handleSearch} />
+              </div>
+            </FormItem>
+            {applicationTypes.length > 0 && (
+              <FormItem label="Application Type" subLabel="Please choose an application type." required>
+                <RadioGroup
+                  selectedValue={applicationType}
+                  onChange={(e) => setApplicationType((e.target as HTMLInputElement).value)}
+                >
+                  {applicationTypes.map((at) => (
+                    <Radio key={at} value={at} label={at} />
+                  ))}
+                </RadioGroup>
+              </FormItem>
+            )}
+          </>
+        )}
+        {step === 2 && (
+          <>
+            <FormItem label="Jira Issue Key">{issueKey}</FormItem>
+            <FormItem label="Application Type">{applicationType}</FormItem>
+            <FormItem label="Commit Url Preview" subLabel="The latest five commit(s) associated with the issue.">
+              <ul>
+                {devPanelCommits.map((commit) => (
+                  <li key={commit}>{commit}</li>
+                ))}
+              </ul>
+            </FormItem>
+            <FormItem
+              label="Commit Pattern"
+              subLabel={
+                <>
+                  <p style={{ display: 'flex', alignItems: 'center' }} onClick={() => setShowTip(!showTip)}>
+                    Input pattern(s) to match and parse commits and repo identifiers from above commit URLs. See
+                    examples <Icon icon={showTip ? 'chevron-down' : 'chevron-up'} style={{ cursor: 'pointer' }} />
+                  </p>
+                  <Collapse isOpen={showTip}>
+                    <img src={JiraIssueTipsImg} width="100%" alt="" />
+                  </Collapse>
+                </>
+              }
+              required
+            >
+              <InputGroup
+                placeholder="eg. https://gitlab.com/{namespace}/{repo_name}/commit/{commit_sha}"
+                value={pattern}
+                onChange={(e) => setPattern(e.target.value)}
+              />
+            </FormItem>
+            <FormItem label="Configuration Results Preview">
+              <code>
+                <pre>{JSON.stringify(preview, null, '  ')}</pre>
+              </code>
+            </FormItem>
+          </>
+        )}
+      </S.DialogBody>
+    </Dialog>
+  );
+};
diff --git a/config-ui/src/plugins/register/jira/transformation-fields/remote-link.tsx b/config-ui/src/plugins/register/jira/transformation-fields/remote-link.tsx
new file mode 100644
index 000000000..a9117381d
--- /dev/null
+++ b/config-ui/src/plugins/register/jira/transformation-fields/remote-link.tsx
@@ -0,0 +1,119 @@
+/*
+ * 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 { useEffect, useState } from 'react';
+import { InputGroup, Button, Intent } from '@blueprintjs/core';
+
+import { IconButton } from '@/components';
+import { operator } from '@/utils';
+
+import * as API from '../api';
+import * as S from './styled';
+
+interface Props {
+  transformation: any;
+  setTransformation: React.Dispatch<React.SetStateAction<any>>;
+}
+
+export const RemoteLink = ({ transformation, setTransformation }: Props) => {
+  const [links, setLinks] = useState<Array<{ pattern: string; regex: string }>>(
+    transformation.remotelinkRepoPattern ?? [],
+  );
+  const [generating, setGenerating] = useState(false);
+
+  const generateRegex = async (pattern: string) => {
+    try {
+      const res = await API.generateRegex(pattern);
+      return { pattern, regex: res.regex };
+    } catch {
+      return { pattern, regex: '' };
+    }
+  };
+
+  const getRegex = async () => {
+    const [success, res] = await operator(
+      () =>
+        Promise.all(
+          links.map((link) => {
+            if (!link.pattern || link.regex) {
+              return link;
+            }
+            return generateRegex(link.pattern);
+          }),
+        ),
+      {
+        setOperating: setGenerating,
+        hideToast: true,
+      },
+    );
+
+    if (success) {
+      setTransformation({
+        ...transformation,
+        remotelinkRepoPattern: res.filter((it: any) => it.regex),
+      });
+    }
+  };
+
+  useEffect(() => {
+    const timer = setTimeout(getRegex, 1000);
+    return () => clearTimeout(timer);
+  }, [links]);
+
+  const handleChangeLinks = (index: number, value: string) => {
+    const newValue = links.map((link, i) => (index === i ? { pattern: value, regex: link.regex } : link));
+    setLinks(newValue);
+  };
+
+  const handleAddLink = () => {
+    const newValue = [...links, { pattern: '', regex: '' }];
+    setLinks(newValue);
+  };
+
+  const handleDeleteLink = (index: number) => {
+    const newValue = links.filter((_, i) => i !== index);
+    setLinks(newValue);
+  };
+
+  return (
+    <S.RemoteLinkWrapper>
+      {links.map((link, i) => (
+        <div key={i} className="input">
+          <InputGroup
+            key={i}
+            placeholder="E.g. https://gitlab.com/{namespace}/{repo_name}/-/commit/{commit_sha}"
+            value={link.pattern}
+            onChange={(e) => handleChangeLinks(i, e.target.value)}
+            // onBlur={() => handleGenerateRegex(i, link.pattern)}
+          />
+          {links.length > 1 && (
+            <IconButton disabled={generating} icon="cross" tooltip="Delete" onClick={() => handleDeleteLink(i)} />
+          )}
+        </div>
+      ))}
+      <Button
+        outlined
+        disabled={generating}
+        intent={Intent.PRIMARY}
+        icon="add"
+        text="Add a Pattern"
+        onClick={() => handleAddLink()}
+      />
+    </S.RemoteLinkWrapper>
+  );
+};
diff --git a/config-ui/src/plugins/register/jira/transformation-fields/styled.ts b/config-ui/src/plugins/register/jira/transformation-fields/styled.ts
index 2ea1165bb..0e575dd8d 100644
--- a/config-ui/src/plugins/register/jira/transformation-fields/styled.ts
+++ b/config-ui/src/plugins/register/jira/transformation-fields/styled.ts
@@ -27,6 +27,21 @@ export const CrossDomain = styled.div`
     margin-top: 24px;
   }
 
+  .application {
+    margin-bottom: 8px;
+
+    span {
+      padding: 4px 8px;
+      background-color: #efefef;
+    }
+
+    span + span {
+      margin-left: 8px;
+    }
+  }
+`;
+
+export const RemoteLinkWrapper = styled.div`
   .input {
     display: flex;
     align-items: center;
@@ -40,8 +55,11 @@ export const CrossDomain = styled.div`
 `;
 
 export const DialogBody = styled.div`
-  ul {
+  ul,
+  pre {
     padding: 8px 16px;
+    max-height: 240px;
+    overflow-y: auto;
     background: #efefef;
   }
 
diff --git a/config-ui/src/utils/operator.ts b/config-ui/src/utils/operator.ts
index d1aeb1d4c..45fb15b91 100644
--- a/config-ui/src/utils/operator.ts
+++ b/config-ui/src/utils/operator.ts
@@ -34,7 +34,7 @@ export type OperateConfig = {
  * @param config.formatReason -> Show the reason for the failure
  * @returns
  */
-export const operator = async <T>(request: () => Promise<T>, config?: OperateConfig) => {
+export const operator = async <T>(request: () => Promise<T>, config?: OperateConfig): Promise<[boolean, any?]> => {
   const { setOperating, formatMessage, formatReason } = config || {};
 
   try {