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

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

commit 68d2f977b025286ffe04564e50125ea304fd73d5
Author: mintsweet <[email protected]>
AuthorDate: Mon Sep 11 22:00:24 2023 +1200

    feat(config-ui): improve the api key in webhook
---
 config-ui/src/plugins/register/webook/api.ts       |   2 +
 .../index.tsx => components/create-dialog.tsx}     | 105 +++++++++--------
 .../use-delete.ts => components/delete-dialog.tsx} |  43 ++++---
 .../register/webook/components/edit-dialog.tsx     |  73 ++++++++++++
 .../plugins/register/webook/components/index.ts    |   6 +-
 .../webook/components/miller-columns/index.tsx     |  66 -----------
 .../miller-columns/use-miller-columns.ts           |  58 ---------
 .../register/webook/components/selector-dialog.tsx |  98 +++++++++++++++
 .../register/webook/components/view-dialog.tsx     | 131 +++++++++++++++++++++
 .../{connection/index.tsx => connection.tsx}       |  53 +++------
 .../plugins/register/webook/connection/styled.ts   |  37 ------
 .../register/webook/delete-dialog/index.tsx        |  55 ---------
 .../register/webook/delete-dialog/styled.ts        |  21 ----
 config-ui/src/plugins/register/webook/index.ts     |   6 +-
 .../register/webook/selector-dialog/index.tsx      |  61 ----------
 .../register/webook/selector-dialog/styled.ts      |  33 ------
 .../register/webook/{create-dialog => }/styled.ts  |  30 ++++-
 .../register/webook/view-or-edit-dialog/index.tsx  |  81 -------------
 .../register/webook/view-or-edit-dialog/styled.ts  |  34 ------
 .../webook/view-or-edit-dialog/use-view-or-edit.ts |  77 ------------
 20 files changed, 435 insertions(+), 635 deletions(-)

diff --git a/config-ui/src/plugins/register/webook/api.ts 
b/config-ui/src/plugins/register/webook/api.ts
index 15c4de061..fa35f40e6 100644
--- a/config-ui/src/plugins/register/webook/api.ts
+++ b/config-ui/src/plugins/register/webook/api.ts
@@ -42,3 +42,5 @@ export const deleteConnection = (id: ID) =>
   request(`/plugins/webhook/connections/${id}`, {
     method: 'delete',
   });
+
+export const renewApiKey = (id: ID) => request(`/api-keys/${id}`, { method: 
'put' });
diff --git a/config-ui/src/plugins/register/webook/create-dialog/index.tsx 
b/config-ui/src/plugins/register/webook/components/create-dialog.tsx
similarity index 60%
rename from config-ui/src/plugins/register/webook/create-dialog/index.tsx
rename to config-ui/src/plugins/register/webook/components/create-dialog.tsx
index 180bb16ac..5d4424eb1 100644
--- a/config-ui/src/plugins/register/webook/create-dialog/index.tsx
+++ b/config-ui/src/plugins/register/webook/components/create-dialog.tsx
@@ -19,12 +19,12 @@
 import { useState, useMemo } from 'react';
 import { InputGroup, Icon } from '@blueprintjs/core';
 
-import { Dialog, FormItem, CopyText, Message } from '@/components';
+import { Dialog, FormItem, CopyText } from '@/components';
+import { useConnections } from '@/hooks';
 import { operator } from '@/utils';
 
 import * as API from '../api';
-
-import * as S from './styled';
+import * as S from '../styled';
 
 interface Props {
   isOpen: boolean;
@@ -32,7 +32,7 @@ interface Props {
   onSubmitAfter?: (id: ID) => void;
 }
 
-export const WebhookCreateDialog = ({ isOpen, onCancel, onSubmitAfter }: 
Props) => {
+export const CreateDialog = ({ isOpen, onCancel, onSubmitAfter }: Props) => {
   const [operating, setOperating] = useState(false);
   const [step, setStep] = useState(1);
   const [name, setName] = useState('');
@@ -44,49 +44,46 @@ export const WebhookCreateDialog = ({ isOpen, onCancel, 
onSubmitAfter }: Props)
     apiKey: '',
   });
 
+  const { onRefresh } = useConnections();
+
   const prefix = useMemo(() => `${window.location.origin}/api`, []);
 
   const handleSubmit = async () => {
-    if (step === 1) {
-      const [success, res] = await operator(
-        async () => {
-          const { id, apiKey } = await API.createConnection({ name });
-          const { postIssuesEndpoint, closeIssuesEndpoint, 
postPipelineDeployTaskEndpoint } = await API.getConnection(
-            id,
-          );
-          return {
-            id,
-            apiKey: apiKey.apiKey,
-            postIssuesEndpoint,
-            closeIssuesEndpoint,
-            postPipelineDeployTaskEndpoint,
-          };
-        },
-        {
-          setOperating,
-          hideToast: true,
-        },
-      );
+    const [success, res] = await operator(
+      async () => {
+        const { id, apiKey } = await API.createConnection({ name });
+        const { postIssuesEndpoint, closeIssuesEndpoint, 
postPipelineDeployTaskEndpoint } = await API.getConnection(id);
+        return {
+          id,
+          apiKey: apiKey.apiKey,
+          postIssuesEndpoint,
+          closeIssuesEndpoint,
+          postPipelineDeployTaskEndpoint,
+        };
+      },
+      {
+        setOperating,
+        hideToast: true,
+      },
+    );
 
-      if (success) {
-        setStep(2);
-        setRecord({
-          id: res.id,
-          postIssuesEndpoint: `${prefix}${res.postIssuesEndpoint}?key={KEY}`,
-          closeIssuesEndpoint: `${prefix}${res.closeIssuesEndpoint}?key={KEY}`,
-          postDeploymentsCurl: `curl 
${prefix}${res.postPipelineDeployTaskEndpoint} -X 'POST'
-            \\ -H 'Authorization: Bearer {KEY}'
+    if (success) {
+      setStep(2);
+      setRecord({
+        id: res.id,
+        postIssuesEndpoint: 
`${prefix}${res.postIssuesEndpoint}?api_key=${res.apiKey}`,
+        closeIssuesEndpoint: 
`${prefix}${res.closeIssuesEndpoint}?api_key=${res.apiKey}`,
+        postDeploymentsCurl: `curl 
${prefix}${res.postPipelineDeployTaskEndpoint} -X 'POST'
+            \\ -H 'Authorization: Bearer ${res.apiKey}'
             \\ -d '{
             \\"commit_sha\\":\\"the sha of deployment commit\\",
             \\"repo_url\\":\\"the repo URL of the deployment commit\\",
             \\"start_time\\":\\"eg. 2020-01-01T12:00:00+00:00\\"
           }'`,
-          apiKey: res.apiKey,
-        });
-      }
-    } else {
-      onCancel();
-      onSubmitAfter?.(record.id);
+        apiKey: res.apiKey,
+      });
+      onRefresh('webhook');
+      onSubmitAfter?.(res.id);
     }
   };
 
@@ -95,6 +92,7 @@ export const WebhookCreateDialog = ({ isOpen, onCancel, 
onSubmitAfter }: Props)
       isOpen={isOpen}
       title="Add a New Webhook"
       style={{ width: 820 }}
+      footer={step === 2 ? null : undefined}
       okText={step === 1 ? 'Generate POST URL' : 'Done'}
       okDisabled={step === 1 && !name}
       okLoading={operating}
@@ -102,14 +100,18 @@ export const WebhookCreateDialog = ({ isOpen, onCancel, 
onSubmitAfter }: Props)
       onOk={handleSubmit}
     >
       {step === 1 && (
-        <S.Detail>
-          <h3>Webhook Name *</h3>
-          <p>Give your Webhook a unique name to help you identify it in the 
future.</p>
-          <InputGroup value={name} onChange={(e) => setName(e.target.value)} />
-        </S.Detail>
+        <S.Wrapper>
+          <FormItem
+            label="Webhook Name"
+            subLabel="Give your Webhook a unique name to help you identify it 
in the future."
+            required
+          >
+            <InputGroup placeholder="Webhook Name" value={name} onChange={(e) 
=> setName(e.target.value)} />
+          </FormItem>
+        </S.Wrapper>
       )}
       {step === 2 && (
-        <S.Detail>
+        <S.Wrapper>
           <h2>
             <Icon icon="endorsed" size={30} />
             <span>POST URL Generated!</span>
@@ -118,6 +120,10 @@ export const WebhookCreateDialog = ({ isOpen, onCancel, 
onSubmitAfter }: Props)
             Copy the following POST URLs to your issue tracking or CI tools to 
push `Incidents` and `Deployments` by
             making a POST to DevLake.
           </p>
+          <p>
+            An API key is automatically generated for the authentication of 
this webhook. This key does not expire. You
+            can revoke it in the webhook page at any time.
+          </p>
           <FormItem label="Incident">
             <h5>Post to register an incident</h5>
             <CopyText content={record.postIssuesEndpoint} />
@@ -128,15 +134,10 @@ export const WebhookCreateDialog = ({ isOpen, onCancel, 
onSubmitAfter }: Props)
             <h5>Post to register a deployment</h5>
             <CopyText content={record.postDeploymentsCurl} />
           </FormItem>
-          <FormItem label="API Key">
-            <Message
-              style={{ marginBottom: 8 }}
-              content="Please make sure to copy your API key now. You will not 
be able to see it again."
-            />
-            <CopyText content={record.apiKey} />
-          </FormItem>
-        </S.Detail>
+        </S.Wrapper>
       )}
     </Dialog>
   );
 };
+
+export default CreateDialog;
diff --git a/config-ui/src/plugins/register/webook/delete-dialog/use-delete.ts 
b/config-ui/src/plugins/register/webook/components/delete-dialog.tsx
similarity index 55%
rename from config-ui/src/plugins/register/webook/delete-dialog/use-delete.ts
rename to config-ui/src/plugins/register/webook/components/delete-dialog.tsx
index 71d959f06..404ba9a97 100644
--- a/config-ui/src/plugins/register/webook/delete-dialog/use-delete.ts
+++ b/config-ui/src/plugins/register/webook/components/delete-dialog.tsx
@@ -16,35 +16,48 @@
  *
  */
 
-import { useState, useMemo } from 'react';
+import { useState } from 'react';
 
+import { Dialog, Message } from '@/components';
+import { useConnections } from '@/hooks';
 import { operator } from '@/utils';
 
 import * as API from '../api';
 
-export interface UseDeleteProps {
-  initialID: ID;
+interface Props {
+  initialId: ID;
+  onCancel: () => void;
   onSubmitAfter?: (id: ID) => void;
 }
 
-export const useDelete = ({ initialID, onSubmitAfter }: UseDeleteProps) => {
-  const [saving, setSaving] = useState(false);
+export const DeleteDialog = ({ initialId, onCancel, onSubmitAfter }: Props) => 
{
+  const [operating, setOperating] = useState(false);
 
-  const handleDelete = async () => {
-    const [success] = await operator(() => API.deleteConnection(initialID), {
-      setOperating: setSaving,
+  const { onRefresh } = useConnections();
+
+  const handleSubmit = async () => {
+    const [success] = await operator(() => API.deleteConnection(initialId), {
+      setOperating,
     });
 
     if (success) {
-      onSubmitAfter?.(initialID);
+      onRefresh('webhook');
+      onSubmitAfter?.(initialId);
+      onCancel();
     }
   };
 
-  return useMemo(
-    () => ({
-      saving,
-      onSubmit: handleDelete,
-    }),
-    [saving],
+  return (
+    <Dialog
+      isOpen
+      title="Delete this Webhook?"
+      // style={{ width: 600 }}
+      okText="Confirm"
+      okLoading={operating}
+      onCancel={onCancel}
+      onOk={handleSubmit}
+    >
+      <Message content="This Webhook cannot be recovered once it’s deleted." />
+    </Dialog>
   );
 };
diff --git a/config-ui/src/plugins/register/webook/components/edit-dialog.tsx 
b/config-ui/src/plugins/register/webook/components/edit-dialog.tsx
new file mode 100644
index 000000000..e66b554d1
--- /dev/null
+++ b/config-ui/src/plugins/register/webook/components/edit-dialog.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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, useEffect } from 'react';
+import { InputGroup } from '@blueprintjs/core';
+
+import { Dialog, FormItem } from '@/components';
+import { useConnections } from '@/hooks';
+import { operator } from '@/utils';
+
+import * as API from '../api';
+
+interface Props {
+  initialId: ID;
+  onCancel: () => void;
+}
+
+export const EditDialog = ({ initialId, onCancel }: Props) => {
+  const [name, setName] = useState('');
+  const [operating, setOperating] = useState(false);
+
+  const { onRefresh } = useConnections({ plugin: 'webhook' });
+
+  useEffect(() => {
+    (async () => {
+      const res = await API.getConnection(initialId);
+      setName(res.name);
+    })();
+  }, [initialId]);
+
+  const handleSubmit = async () => {
+    const [success] = await operator(() => API.updateConnection(initialId, { 
name }), {
+      setOperating,
+    });
+
+    if (success) {
+      onRefresh('webhook');
+      onCancel();
+    }
+  };
+
+  return (
+    <Dialog
+      style={{ width: 820 }}
+      isOpen
+      title="Edit Webhook Name"
+      okLoading={operating}
+      okDisabled={!name}
+      okText="Save"
+      onCancel={onCancel}
+      onOk={handleSubmit}
+    >
+      <FormItem label="Name" required>
+        <InputGroup value={name} onChange={(e) => setName(e.target.value)} />
+      </FormItem>
+    </Dialog>
+  );
+};
diff --git a/config-ui/src/plugins/register/webook/components/index.ts 
b/config-ui/src/plugins/register/webook/components/index.ts
index 30c0d1fa0..8703633a7 100644
--- a/config-ui/src/plugins/register/webook/components/index.ts
+++ b/config-ui/src/plugins/register/webook/components/index.ts
@@ -16,4 +16,8 @@
  *
  */
 
-export * from './miller-columns';
+export * from './create-dialog';
+export * from './delete-dialog';
+export * from './edit-dialog';
+export * from './selector-dialog';
+export * from './view-dialog';
diff --git 
a/config-ui/src/plugins/register/webook/components/miller-columns/index.tsx 
b/config-ui/src/plugins/register/webook/components/miller-columns/index.tsx
deleted file mode 100644
index 4812f8d98..000000000
--- a/config-ui/src/plugins/register/webook/components/miller-columns/index.tsx
+++ /dev/null
@@ -1,66 +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, useEffect } from 'react';
-import MillerColumnsSelect from 'miller-columns-select';
-
-import { Loading } from '@/components';
-
-import { useMillerColumns } from './use-miller-columns';
-
-interface Props {
-  selectedItems: any[];
-  onChangeItems: (selectedItems: any[]) => void;
-}
-
-export const MillerColumns = ({ selectedItems, onChangeItems }: Props) => {
-  const [seletedIds, setSelectedIds] = useState<ID[]>([]);
-
-  const { items, getHasMore } = useMillerColumns();
-
-  useEffect(() => {
-    setSelectedIds(selectedItems.map((it) => it.boardId));
-  }, []);
-
-  useEffect(() => {
-    onChangeItems(
-      items
-        .filter((it) => seletedIds.includes(it.id))
-        .map((it) => ({
-          id: it.id,
-          name: it.name,
-        })),
-    );
-  }, [seletedIds]);
-
-  const renderLoading = () => {
-    return <Loading size={20} style={{ padding: '4px 12px' }} />;
-  };
-
-  return (
-    <MillerColumnsSelect
-      columnCount={1}
-      columnHeight={160}
-      getHasMore={getHasMore}
-      renderLoading={renderLoading}
-      items={items}
-      selectedIds={seletedIds}
-      onSelectItemIds={setSelectedIds}
-    />
-  );
-};
diff --git 
a/config-ui/src/plugins/register/webook/components/miller-columns/use-miller-columns.ts
 
b/config-ui/src/plugins/register/webook/components/miller-columns/use-miller-columns.ts
deleted file mode 100644
index 95b79124e..000000000
--- 
a/config-ui/src/plugins/register/webook/components/miller-columns/use-miller-columns.ts
+++ /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 { useState, useEffect, useMemo } from 'react';
-import type { McsItem } from 'miller-columns-select';
-
-import * as API from '../../api';
-
-type WebhookItemType = McsItem<{
-  id: ID;
-  name: string;
-}>;
-
-export const useMillerColumns = () => {
-  const [items, setItems] = useState<WebhookItemType[]>([]);
-  const [isLast, setIsLast] = useState(false);
-
-  const updateItems = (arr: any) =>
-    arr.map((it: any) => ({
-      parentId: null,
-      id: it.id,
-      title: it.name,
-      name: it.name,
-    }));
-
-  useEffect(() => {
-    (async () => {
-      const res = await API.getConnections();
-      setItems([...updateItems(res)]);
-      setIsLast(true);
-    })();
-  }, []);
-
-  return useMemo(
-    () => ({
-      items,
-      getHasMore() {
-        return !isLast;
-      },
-    }),
-    [items, isLast],
-  );
-};
diff --git 
a/config-ui/src/plugins/register/webook/components/selector-dialog.tsx 
b/config-ui/src/plugins/register/webook/components/selector-dialog.tsx
new file mode 100644
index 000000000..09fe03945
--- /dev/null
+++ b/config-ui/src/plugins/register/webook/components/selector-dialog.tsx
@@ -0,0 +1,98 @@
+/*
+ * 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, useEffect } from 'react';
+import type { McsItem } from 'miller-columns-select';
+import MillerColumnsSelect from 'miller-columns-select';
+
+import { Dialog, FormItem, Loading } from '@/components';
+
+import * as T from '../types';
+import * as API from '../api';
+import * as S from '../styled';
+
+interface Props {
+  isOpen: boolean;
+  saving: boolean;
+  onCancel: () => void;
+  onSubmit: (items: T.WebhookItemType[]) => void;
+}
+
+export const SelectorDialog = ({ isOpen, saving, onCancel, onSubmit }: Props) 
=> {
+  const [items, setItems] = useState<McsItem<T.WebhookItemType>[]>([]);
+  const [selectedItems, setSelectedItems] = useState<T.WebhookItemType[]>([]);
+  const [isLast, setIsLast] = useState(false);
+
+  const updateItems = (arr: any) =>
+    arr.map((it: any) => ({
+      parentId: null,
+      id: it.id,
+      title: it.name,
+      name: it.name,
+    }));
+
+  useEffect(() => {
+    (async () => {
+      const res = await API.getConnections();
+      setItems([...updateItems(res)]);
+      setIsLast(true);
+    })();
+  }, []);
+
+  const handleSubmit = () => onSubmit(selectedItems);
+
+  return (
+    <Dialog
+      isOpen={isOpen}
+      title="Select Existing Webhooks"
+      style={{
+        width: 820,
+      }}
+      okText="Confrim"
+      okLoading={saving}
+      okDisabled={!selectedItems.length}
+      onCancel={onCancel}
+      onOk={handleSubmit}
+    >
+      <S.Wrapper>
+        <FormItem label="Webhooks" subLabel="Select an existing Webhook to 
import to the current project.">
+          <MillerColumnsSelect
+            columnCount={1}
+            columnHeight={160}
+            getHasMore={() => !isLast}
+            renderLoading={() => <Loading size={20} style={{ padding: '4px 
12px' }} />}
+            items={items}
+            selectedIds={selectedItems.map((it) => it.id)}
+            onSelectItemIds={(seletedIds: ID[]) =>
+              setSelectedItems(
+                items
+                  .filter((it) => seletedIds.includes(it.id))
+                  .map((it) => ({
+                    id: it.id,
+                    name: it.name,
+                  })),
+              )
+            }
+          />
+        </FormItem>
+      </S.Wrapper>
+    </Dialog>
+  );
+};
+
+export default SelectorDialog;
diff --git a/config-ui/src/plugins/register/webook/components/view-dialog.tsx 
b/config-ui/src/plugins/register/webook/components/view-dialog.tsx
new file mode 100644
index 000000000..0b7ec81e5
--- /dev/null
+++ b/config-ui/src/plugins/register/webook/components/view-dialog.tsx
@@ -0,0 +1,131 @@
+/*
+ * 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, useEffect, useMemo } from 'react';
+import { Button, Icon, Intent } from '@blueprintjs/core';
+
+import { Dialog, FormItem, CopyText } from '@/components';
+import { operator } from '@/utils';
+
+import * as API from '../api';
+import * as S from '../styled';
+
+interface Props {
+  initialId: ID;
+  onCancel: () => void;
+}
+
+export const ViewDialog = ({ initialId, onCancel }: Props) => {
+  const [record, setRecord] = useState({
+    apiKeyId: '',
+    postIssuesEndpoint: '',
+    closeIssuesEndpoint: '',
+    postDeploymentsCurl: '',
+  });
+  const [operating, setOperating] = useState(false);
+  const [apiKey, setApiKey] = useState('');
+
+  const prefix = useMemo(() => `${window.location.origin}/api`, []);
+
+  useEffect(() => {
+    (async () => {
+      const res = await API.getConnection(initialId);
+      setRecord({
+        apiKeyId: res.apiKey.id,
+        postIssuesEndpoint: `${prefix}${res.postIssuesEndpoint}`,
+        closeIssuesEndpoint: `${prefix}${res.closeIssuesEndpoint}`,
+        postDeploymentsCurl: `curl 
${prefix}${res.postPipelineDeployTaskEndpoint} -X 'POST' -d "{
+      \\"commit_sha\\":\\"the sha of deployment commit\\",
+      \\"repo_url\\":\\"the repo URL of the deployment commit\\",
+      \\"start_time\\":\\"Optional, eg. 2020-01-01T12:00:00+00:00\\"
+      }"`,
+      });
+    })();
+  }, [initialId]);
+
+  const handleGenerateNewKey = async () => {
+    const [success, res] = await operator(() => 
API.renewApiKey(record.apiKeyId), {
+      setOperating,
+    });
+
+    if (success) {
+      setApiKey(res.apiKey);
+      setRecord({
+        ...record,
+        postIssuesEndpoint: 
`${prefix}${res.postIssuesEndpoint}?api_key=${res.apiKey}`,
+        closeIssuesEndpoint: 
`${prefix}${res.closeIssuesEndpoint}?api_key=${res.apiKey}`,
+        postDeploymentsCurl: `curl 
${prefix}${res.postPipelineDeployTaskEndpoint} -X 'POST' -d "{
+          \\  -H 'Authorization: Bearer ${res.apiKey}'
+          \\ "commit_sha\\":\\"the sha of deployment commit\\",
+          \\ "repo_url\\":\\"the repo URL of the deployment commit\\",
+          \\ "start_time\\":\\"Optional, eg. 2020-01-01T12:00:00+00:00\\"
+      }"`,
+      });
+    }
+  };
+
+  return (
+    <Dialog style={{ width: 820 }} isOpen title="View Webhook" footer={null} 
onCancel={onCancel}>
+      <S.Wrapper>
+        {apiKey && (
+          <h2>
+            <Icon icon="endorsed" size={30} />
+            <span>POST URL Generated!</span>
+          </h2>
+        )}
+        <p>
+          Copy the following POST URLs to your issue tracking or CI tools to 
push `Incidents` and `Deployments` by
+          making a POST to DevLake. Please replace the API_key in the 
following URLs.
+        </p>
+        <FormItem label="Incidents">
+          <h5>Post to register an incident</h5>
+          <CopyText content={record.postIssuesEndpoint} />
+          <h5>Post to close a registered incident</h5>
+          <CopyText content={record.closeIssuesEndpoint} />
+        </FormItem>
+        <FormItem label="Deployment">
+          <h5>Post to register a deployment</h5>
+          <CopyText content={record.postDeploymentsCurl} />
+        </FormItem>
+        <FormItem
+          label="API Key"
+          subLabel="If you have forgotten your API key, you can revoke the 
previous key and generate a new one as a replacement."
+        >
+          {!apiKey ? (
+            <Button
+              intent={Intent.PRIMARY}
+              loading={operating}
+              text="Revoke and generate a new key"
+              onClick={handleGenerateNewKey}
+            />
+          ) : (
+            <>
+              <S.ApiKey>
+                <CopyText content={apiKey} />
+                <span>No Expiration</span>
+              </S.ApiKey>
+              <S.Tips>
+                <strong>Please copy your key now. You will not be able to see 
it again.</strong>
+              </S.Tips>
+            </>
+          )}
+        </FormItem>
+      </S.Wrapper>
+    </Dialog>
+  );
+};
diff --git a/config-ui/src/plugins/register/webook/connection/index.tsx 
b/config-ui/src/plugins/register/webook/connection.tsx
similarity index 66%
rename from config-ui/src/plugins/register/webook/connection/index.tsx
rename to config-ui/src/plugins/register/webook/connection.tsx
index 3f0802e1b..b21707fa6 100644
--- a/config-ui/src/plugins/register/webook/connection/index.tsx
+++ b/config-ui/src/plugins/register/webook/connection.tsx
@@ -17,16 +17,14 @@
  */
 
 import { useState } from 'react';
-import { ButtonGroup, Button, Intent } from '@blueprintjs/core';
+import { Button, Intent } from '@blueprintjs/core';
 
-import { Table, ColumnType, ExternalLink, IconButton } from '@/components';
+import { Buttons, Table, ColumnType, ExternalLink, IconButton } from 
'@/components';
 import { useConnections } from '@/hooks';
 import { DOC_URL } from '@/release';
 
-import type { WebhookItemType } from '../types';
-import { WebhookCreateDialog } from '../create-dialog';
-import { WebhookDeleteDialog } from '../delete-dialog';
-import { WebhookViewOrEditDialog } from '../view-or-edit-dialog';
+import { CreateDialog, ViewDialog, EditDialog, DeleteDialog } from 
'./components';
+import * as T from './types';
 
 import * as S from './styled';
 
@@ -42,19 +40,19 @@ export const WebHookConnection = ({ filterIds, 
onCreateAfter, onDeleteAfter }: P
   const [type, setType] = useState<Type>();
   const [currentID, setCurrentID] = useState<ID>();
 
-  const { connections, onRefresh } = useConnections({ plugin: 'webhook' });
+  const { connections } = useConnections({ plugin: 'webhook' });
 
   const handleHideDialog = () => {
     setType(undefined);
     setCurrentID(undefined);
   };
 
-  const handleShowDialog = (t: Type, r?: WebhookItemType) => {
+  const handleShowDialog = (t: Type, r?: T.WebhookItemType) => {
     setType(t);
     setCurrentID(r?.id);
   };
 
-  const columns: ColumnType<WebhookItemType> = [
+  const columns: ColumnType<T.WebhookItemType> = [
     {
       title: 'ID',
       dataIndex: 'id',
@@ -73,7 +71,8 @@ export const WebHookConnection = ({ filterIds, onCreateAfter, 
onDeleteAfter }: P
       align: 'center',
       render: (_, row) => (
         <S.Action>
-          <IconButton icon="edit" tooltip="Edit" onClick={() => 
handleShowDialog('edit', row)} />
+          <IconButton icon="array" tooltip="View" onClick={() => 
handleShowDialog('show', row)} />
+          <IconButton icon="annotation" tooltip="Edit" onClick={() => 
handleShowDialog('edit', row)} />
           <IconButton icon="trash" tooltip="Delete" onClick={() => 
handleShowDialog('delete', row)} />
         </S.Action>
       ),
@@ -82,9 +81,9 @@ export const WebHookConnection = ({ filterIds, onCreateAfter, 
onDeleteAfter }: P
 
   return (
     <S.Wrapper>
-      <ButtonGroup>
+      <Buttons>
         <Button icon="plus" text="Add a Webhook" intent={Intent.PRIMARY} 
onClick={() => handleShowDialog('add')} />
-      </ButtonGroup>
+      </Buttons>
       <Table
         columns={columns}
         dataSource={connections.filter((cs) => (filterIds ? 
filterIds.includes(cs.id) : true))}
@@ -100,34 +99,12 @@ export const WebHookConnection = ({ filterIds, 
onCreateAfter, onDeleteAfter }: P
         }}
       />
       {type === 'add' && (
-        <WebhookCreateDialog
-          isOpen
-          onCancel={handleHideDialog}
-          onSubmitAfter={(id) => {
-            onRefresh();
-            onCreateAfter?.(id);
-          }}
-        />
+        <CreateDialog isOpen onCancel={handleHideDialog} onSubmitAfter={(id) 
=> onCreateAfter?.(id)} />
       )}
+      {type === 'show' && currentID && <ViewDialog initialId={currentID} 
onCancel={handleHideDialog} />}
+      {type === 'edit' && currentID && <EditDialog initialId={currentID} 
onCancel={handleHideDialog} />}
       {type === 'delete' && currentID && (
-        <WebhookDeleteDialog
-          isOpen
-          initialID={currentID}
-          onCancel={handleHideDialog}
-          onSubmitAfter={(id) => {
-            onRefresh();
-            onDeleteAfter?.(id);
-          }}
-        />
-      )}
-      {(type === 'edit' || type === 'show') && currentID && (
-        <WebhookViewOrEditDialog
-          type={type}
-          isOpen
-          initialID={currentID}
-          onCancel={handleHideDialog}
-          onSubmitAfter={() => onRefresh()}
-        />
+        <DeleteDialog initialId={currentID} onCancel={handleHideDialog} 
onSubmitAfter={(id) => onDeleteAfter?.(id)} />
       )}
     </S.Wrapper>
   );
diff --git a/config-ui/src/plugins/register/webook/connection/styled.ts 
b/config-ui/src/plugins/register/webook/connection/styled.ts
deleted file mode 100644
index 83d21157a..000000000
--- a/config-ui/src/plugins/register/webook/connection/styled.ts
+++ /dev/null
@@ -1,37 +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 styled from 'styled-components';
-
-export const Wrapper = styled.div`
-  .bp4-button-group {
-    margin-bottom: 16px;
-  }
-`;
-
-export const Action = styled.div`
-  color: #7497f7;
-
-  span {
-    cursor: pointer;
-  }
-
-  span + span {
-    margin-left: 8px;
-  }
-`;
diff --git a/config-ui/src/plugins/register/webook/delete-dialog/index.tsx 
b/config-ui/src/plugins/register/webook/delete-dialog/index.tsx
deleted file mode 100644
index 4fbe0433b..000000000
--- a/config-ui/src/plugins/register/webook/delete-dialog/index.tsx
+++ /dev/null
@@ -1,55 +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 { Dialog } from '@/components';
-
-import type { UseDeleteProps } from './use-delete';
-import { useDelete } from './use-delete';
-import * as S from './styled';
-
-interface Props extends UseDeleteProps {
-  isOpen: boolean;
-  onCancel: () => void;
-}
-
-export const WebhookDeleteDialog = ({ isOpen, onCancel, ...props }: Props) => {
-  const { saving, onSubmit } = useDelete({ ...props });
-
-  const handleSubmit = () => {
-    onSubmit();
-    onCancel();
-  };
-
-  return (
-    <Dialog
-      isOpen={isOpen}
-      title="Delete this Webhook?"
-      style={{ width: 600 }}
-      okText="Confirm"
-      okLoading={saving}
-      onCancel={onCancel}
-      onOk={handleSubmit}
-    >
-      <S.Wrapper>
-        <div className="message">
-          <p>This Webhook cannot be recovered once it’s deleted.</p>
-        </div>
-      </S.Wrapper>
-    </Dialog>
-  );
-};
diff --git a/config-ui/src/plugins/register/webook/delete-dialog/styled.ts 
b/config-ui/src/plugins/register/webook/delete-dialog/styled.ts
deleted file mode 100644
index 4bf74a53c..000000000
--- a/config-ui/src/plugins/register/webook/delete-dialog/styled.ts
+++ /dev/null
@@ -1,21 +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 styled from 'styled-components';
-
-export const Wrapper = styled.div``;
diff --git a/config-ui/src/plugins/register/webook/index.ts 
b/config-ui/src/plugins/register/webook/index.ts
index fa62a00e3..3d084a606 100644
--- a/config-ui/src/plugins/register/webook/index.ts
+++ b/config-ui/src/plugins/register/webook/index.ts
@@ -19,7 +19,5 @@
 export * from './types';
 export * from './config';
 export * from './connection';
-export * from './create-dialog';
-export * from './delete-dialog';
-export * from './view-or-edit-dialog';
-export * from './selector-dialog';
+export { default as WebhookCreateDialog } from './components/create-dialog';
+export { default as WebhookSelectorDialog } from 
'./components/selector-dialog';
diff --git a/config-ui/src/plugins/register/webook/selector-dialog/index.tsx 
b/config-ui/src/plugins/register/webook/selector-dialog/index.tsx
deleted file mode 100644
index 2de2d0ae8..000000000
--- a/config-ui/src/plugins/register/webook/selector-dialog/index.tsx
+++ /dev/null
@@ -1,61 +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 } from 'react';
-
-import { Dialog } from '@/components';
-
-import type { WebhookItemType } from '../types';
-
-import { MillerColumns } from '../components';
-
-import * as S from './styled';
-
-interface Props {
-  isOpen: boolean;
-  saving: boolean;
-  onCancel: () => void;
-  onSubmit: (items: WebhookItemType[]) => void;
-}
-
-export const WebhookSelectorDialog = ({ isOpen, saving, onCancel, onSubmit }: 
Props) => {
-  const [selectedItems, setSelectedItems] = useState<WebhookItemType[]>([]);
-
-  const handleSubmit = () => onSubmit(selectedItems);
-
-  return (
-    <Dialog
-      isOpen={isOpen}
-      title="Select Existing Webhooks"
-      style={{
-        width: 820,
-      }}
-      okText="Confrim"
-      okLoading={saving}
-      okDisabled={!selectedItems.length}
-      onCancel={onCancel}
-      onOk={handleSubmit}
-    >
-      <S.Wrapper>
-        <h3>Webhooks</h3>
-        <p>Select an existing Webhook to import to the current project.</p>
-        <MillerColumns selectedItems={selectedItems} 
onChangeItems={setSelectedItems} />
-      </S.Wrapper>
-    </Dialog>
-  );
-};
diff --git a/config-ui/src/plugins/register/webook/selector-dialog/styled.ts 
b/config-ui/src/plugins/register/webook/selector-dialog/styled.ts
deleted file mode 100644
index c51774c44..000000000
--- a/config-ui/src/plugins/register/webook/selector-dialog/styled.ts
+++ /dev/null
@@ -1,33 +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 styled from 'styled-components';
-
-export const Wrapper = styled.div`
-  padding: 0 8px;
-
-  h3 {
-    margin: 8px 0;
-    font-size: 14px;
-  }
-
-  p {
-    margin: 0 0 8px;
-    font-size: 12px;
-  }
-`;
diff --git a/config-ui/src/plugins/register/webook/create-dialog/styled.ts 
b/config-ui/src/plugins/register/webook/styled.ts
similarity index 76%
rename from config-ui/src/plugins/register/webook/create-dialog/styled.ts
rename to config-ui/src/plugins/register/webook/styled.ts
index 8d4f16106..4c5ad12d1 100644
--- a/config-ui/src/plugins/register/webook/create-dialog/styled.ts
+++ b/config-ui/src/plugins/register/webook/styled.ts
@@ -16,10 +16,10 @@
  *
  */
 
-import styled from 'styled-components';
 import { Colors } from '@blueprintjs/core';
+import styled from 'styled-components';
 
-export const Detail = styled.div`
+export const Wrapper = styled.div`
   h2 {
     display: flex;
     align-items: center;
@@ -39,3 +39,29 @@ export const Detail = styled.div`
     margin: 8px 0;
   }
 `;
+
+export const Action = styled.div`
+  color: #7497f7;
+
+  span {
+    cursor: pointer;
+  }
+
+  span + span {
+    margin-left: 8px;
+  }
+`;
+
+export const ApiKey = styled.div`
+  display: flex;
+  align-items: center;
+
+  & > div {
+    max-width: 50%;
+    margin-right: 8px;
+  }
+`;
+
+export const Tips = styled.div`
+  margin-top: 8px;
+`;
diff --git 
a/config-ui/src/plugins/register/webook/view-or-edit-dialog/index.tsx 
b/config-ui/src/plugins/register/webook/view-or-edit-dialog/index.tsx
deleted file mode 100644
index 1b9437b4f..000000000
--- a/config-ui/src/plugins/register/webook/view-or-edit-dialog/index.tsx
+++ /dev/null
@@ -1,81 +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 { InputGroup } from '@blueprintjs/core';
-
-import { Dialog, FormItem, CopyText } from '@/components';
-
-import type { UseViewOrEditProps } from './use-view-or-edit';
-import { useViewOrEdit } from './use-view-or-edit';
-import * as S from './styled';
-
-interface Props extends UseViewOrEditProps {
-  type: 'edit' | 'show';
-  isOpen: boolean;
-  onCancel: () => void;
-}
-
-export const WebhookViewOrEditDialog = ({ type, isOpen, onCancel, ...props }: 
Props) => {
-  const { saving, name, record, onChangeName, onSubmit } = useViewOrEdit({
-    ...props,
-  });
-
-  const handleSubmit = () => {
-    if (type === 'edit') {
-      onSubmit();
-    }
-
-    onCancel();
-  };
-
-  return (
-    <Dialog
-      isOpen={isOpen}
-      title="View/Edit Webhook"
-      style={{ width: 820 }}
-      okText={type === 'edit' ? 'Save' : 'Done'}
-      okDisabled={!name}
-      okLoading={saving}
-      onCancel={onCancel}
-      onOk={handleSubmit}
-    >
-      <S.Wrapper>
-        <h3>Webhook Name *</h3>
-        <p>
-          Copy the following POST URLs to your issue tracking or CI tools to 
push `Incidents` and `Deployments` by
-          making a POST to DevLake.
-        </p>
-        <InputGroup disabled={type !== 'edit'} value={name} onChange={(e) => 
onChangeName(e.target.value)} />
-        <p>
-          Copy the following URLs to your issue tracking tool for Incidents 
and CI tool for Deployments by making a POST
-          to DevLake.
-        </p>
-        <FormItem label="Incidents">
-          <h5>Post to register an incident</h5>
-          <CopyText content={record.postIssuesEndpoint} />
-          <h5>Post to close a registered incident</h5>
-          <CopyText content={record.closeIssuesEndpoint} />
-        </FormItem>
-        <FormItem label="Deployment">
-          <h5>Post to register a deployment</h5>
-          <CopyText content={record.postDeploymentsCurl} />
-        </FormItem>
-      </S.Wrapper>
-    </Dialog>
-  );
-};
diff --git 
a/config-ui/src/plugins/register/webook/view-or-edit-dialog/styled.ts 
b/config-ui/src/plugins/register/webook/view-or-edit-dialog/styled.ts
deleted file mode 100644
index 969380e28..000000000
--- a/config-ui/src/plugins/register/webook/view-or-edit-dialog/styled.ts
+++ /dev/null
@@ -1,34 +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 styled from 'styled-components';
-
-export const Wrapper = styled.div`
-  h5 {
-    margin: 8px 0;
-  }
-
-  .block {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    margin-bottom: 8px;
-    padding: 10px 16px;
-    background: #f0f4fe;
-  }
-`;
diff --git 
a/config-ui/src/plugins/register/webook/view-or-edit-dialog/use-view-or-edit.ts 
b/config-ui/src/plugins/register/webook/view-or-edit-dialog/use-view-or-edit.ts
deleted file mode 100644
index e7d14b18c..000000000
--- 
a/config-ui/src/plugins/register/webook/view-or-edit-dialog/use-view-or-edit.ts
+++ /dev/null
@@ -1,77 +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 { operator } from '@/utils';
-
-import * as API from '../api';
-
-export interface UseViewOrEditProps {
-  initialID: ID;
-  onSubmitAfter?: (id: ID) => void;
-}
-
-export const useViewOrEdit = ({ initialID, onSubmitAfter }: 
UseViewOrEditProps) => {
-  const [saving, setSaving] = useState(false);
-  const [name, setName] = useState('');
-  const [record, setRecord] = useState({
-    postIssuesEndpoint: '',
-    closeIssuesEndpoint: '',
-    postDeploymentsCurl: '',
-  });
-
-  const prefix = useMemo(() => `${window.location.origin}/api`, []);
-
-  useEffect(() => {
-    (async () => {
-      const res = await API.getConnection(initialID);
-      setName(res.name);
-      setRecord({
-        postIssuesEndpoint: `${prefix}${res.postIssuesEndpoint}`,
-        closeIssuesEndpoint: `${prefix}${res.closeIssuesEndpoint}`,
-        postDeploymentsCurl: `curl 
${prefix}${res.postPipelineDeployTaskEndpoint} -X 'POST' -d "{
-      \\"commit_sha\\":\\"the sha of deployment commit\\",
-      \\"repo_url\\":\\"the repo URL of the deployment commit\\",
-      \\"start_time\\":\\"Optional, eg. 2020-01-01T12:00:00+00:00\\"
-      }"`,
-      });
-    })();
-  }, [initialID]);
-
-  const handleUpdate = async () => {
-    const [success] = await operator(() => API.updateConnection(initialID, { 
name }), {
-      setOperating: setSaving,
-    });
-
-    if (success) {
-      onSubmitAfter?.(initialID);
-    }
-  };
-
-  return useMemo(
-    () => ({
-      saving,
-      name,
-      record,
-      onChangeName: setName,
-      onSubmit: handleUpdate,
-    }),
-    [saving, name, record],
-  );
-};


Reply via email to