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 7e205e5da refactor(config-ui): the webhook page (#3891)
7e205e5da is described below

commit 7e205e5da7cee20237985061e63ddf65140e5848
Author: 青湛 <[email protected]>
AuthorDate: Fri Dec 9 15:28:56 2022 +0800

    refactor(config-ui): the webhook page (#3891)
    
    * feat(config-ui): add component create-dialog webhook plugin
    
    * feat(config-ui): add component delete-dialog for webhook plugin
    
    * feat(config-ui): add component view-or-edit-dialog for webhook plugin
    
    * feat(config-ui): simplify the use of component toast
    
    * feat(config-ui): support parameter style in dialog
    
    * refactor(config-ui): the webhook page
---
 config-ui/package-lock.json                        |   9 ++
 config-ui/package.json                             |   1 +
 config-ui/src/App.js                               |   8 +-
 config-ui/src/components/dialog/index.tsx          |   6 +-
 config-ui/src/components/toast2/index.tsx          |  19 ++-
 config-ui/src/pages/{ => connections}/index.ts     |   5 +-
 config-ui/src/pages/index.ts                       |   1 +
 config-ui/src/plugins/index.ts                     |   1 +
 .../webook/components/create-dialog/index.tsx      | 136 ++++++++++++++++++++
 .../webook/components/create-dialog/styled.ts}     |  36 +++++-
 .../webook/components/delete-dialog/index.tsx      |  62 +++++++++
 .../webook/components/delete-dialog/styled.ts}     |   5 +-
 .../{pages => plugins/webook/components}/index.ts  |   5 +-
 .../components/view-or-edit-dialog/index.tsx       | 139 +++++++++++++++++++++
 .../components/view-or-edit-dialog/styled.ts}      |  35 +++++-
 .../index.tsx => plugins/webook/connection/api.ts} |  30 ++++-
 config-ui/src/plugins/webook/connection/index.tsx  | 119 ++++++++++++++++++
 .../webook/connection/styled.ts}                   |  26 +++-
 .../plugins/webook/connection/use-connection.ts    |  94 ++++++++++++++
 config-ui/src/plugins/{ => webook}/index.ts        |   2 +-
 20 files changed, 711 insertions(+), 28 deletions(-)

diff --git a/config-ui/package-lock.json b/config-ui/package-lock.json
index 376ae19dc..750b1d06f 100644
--- a/config-ui/package-lock.json
+++ b/config-ui/package-lock.json
@@ -3228,6 +3228,15 @@
         "csstype": "^3.0.2"
       }
     },
+    "@types/react-copy-to-clipboard": {
+      "version": "5.0.4",
+      "resolved": 
"https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz";,
+      "integrity": 
"sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==",
+      "dev": true,
+      "requires": {
+        "@types/react": "*"
+      }
+    },
     "@types/react-router": {
       "version": "5.1.19",
       "resolved": 
"https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz";,
diff --git a/config-ui/package.json b/config-ui/package.json
index aab5e607d..d6cd4a282 100644
--- a/config-ui/package.json
+++ b/config-ui/package.json
@@ -52,6 +52,7 @@
     "@testing-library/react": "^12.1.1",
     "@testing-library/user-event": "^13.2.1",
     "@types/react": "^18.0.24",
+    "@types/react-copy-to-clipboard": "^5.0.4",
     "@types/react-router-dom": "^5.3.3",
     "@types/styled-components": "^5.1.26",
     "autoprefixer": "^9.8.6",
diff --git a/config-ui/src/App.js b/config-ui/src/App.js
index cfa31264d..e498742e7 100644
--- a/config-ui/src/App.js
+++ b/config-ui/src/App.js
@@ -36,7 +36,11 @@ import '@fontsource/inter/variable-full.css'
 import useDatabaseMigrations from '@/hooks/useDatabaseMigrations'
 
 import { BaseLayout } from '@/layouts'
-import { ProjectHomePage, CreateBlueprintPage } from '@/pages'
+import {
+  ProjectHomePage,
+  CreateBlueprintPage,
+  WebHookConnectionPage
+} from '@/pages'
 import ErrorBoundary from '@/components/ErrorBoundary'
 import Integration from '@/pages/configure/integration/index'
 import ManageIntegration from '@/pages/configure/integration/manage'
@@ -116,7 +120,7 @@ function App(props) {
               <Route
                 exact
                 path='/connections/incoming-webhook'
-                component={() => <IncomingWebhookConnection />}
+                component={() => <WebHookConnectionPage />}
               />
               <Route
                 exact
diff --git a/config-ui/src/components/dialog/index.tsx 
b/config-ui/src/components/dialog/index.tsx
index 5b50d5fc2..0ae8570eb 100644
--- a/config-ui/src/components/dialog/index.tsx
+++ b/config-ui/src/components/dialog/index.tsx
@@ -24,6 +24,7 @@ import * as S from './styled'
 interface Props {
   isOpen: boolean
   children: React.ReactNode
+  style?: React.CSSProperties
   title?: string
   cancelText?: string
   okText?: string
@@ -35,8 +36,9 @@ interface Props {
 
 export const Dialog = ({
   isOpen,
-  title,
   children,
+  style,
+  title,
   cancelText = 'Cancel',
   okText = 'OK',
   okDisabled,
@@ -45,7 +47,7 @@ export const Dialog = ({
   onOk
 }: Props) => {
   return (
-    <S.Container isOpen={isOpen}>
+    <S.Container isOpen={isOpen} style={style}>
       {title && (
         <S.Header className={Classes.DIALOG_HEADER}>
           <h2>{title}</h2>
diff --git a/config-ui/src/components/toast2/index.tsx 
b/config-ui/src/components/toast2/index.tsx
index 009407768..4a8f208e2 100644
--- a/config-ui/src/components/toast2/index.tsx
+++ b/config-ui/src/components/toast2/index.tsx
@@ -16,8 +16,25 @@
  *
  */
 
-import { Toaster, Position } from '@blueprintjs/core'
+import { Toaster, Position, Intent } from '@blueprintjs/core'
 
 export const Toast = Toaster.create({
   position: Position.TOP
 })
+
+export const toast = {
+  success(message: string) {
+    Toast.show({
+      message,
+      intent: Intent.SUCCESS,
+      icon: 'endorsed'
+    })
+  },
+  error(message: string) {
+    Toast.show({
+      message,
+      intent: Intent.DANGER,
+      icon: 'error'
+    })
+  }
+}
diff --git a/config-ui/src/pages/index.ts 
b/config-ui/src/pages/connections/index.ts
similarity index 88%
copy from config-ui/src/pages/index.ts
copy to config-ui/src/pages/connections/index.ts
index 9b1511822..66ed7d6f5 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/pages/connections/index.ts
@@ -16,5 +16,6 @@
  *
  */
 
-export * from './project'
-export * from './blueprint'
+import { WebHookConnection } from '@/plugins'
+
+export const WebHookConnectionPage = WebHookConnection
diff --git a/config-ui/src/pages/index.ts b/config-ui/src/pages/index.ts
index 9b1511822..89e794d51 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/pages/index.ts
@@ -18,3 +18,4 @@
 
 export * from './project'
 export * from './blueprint'
+export * from './connections'
diff --git a/config-ui/src/plugins/index.ts b/config-ui/src/plugins/index.ts
index 4a9427b7a..c784ec481 100644
--- a/config-ui/src/plugins/index.ts
+++ b/config-ui/src/plugins/index.ts
@@ -17,3 +17,4 @@
  */
 
 export * from './common'
+export * from './webook'
diff --git a/config-ui/src/plugins/webook/components/create-dialog/index.tsx 
b/config-ui/src/plugins/webook/components/create-dialog/index.tsx
new file mode 100644
index 000000000..3dd430255
--- /dev/null
+++ b/config-ui/src/plugins/webook/components/create-dialog/index.tsx
@@ -0,0 +1,136 @@
+/*
+ * 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, useCallback } from 'react'
+import { InputGroup, Icon } from '@blueprintjs/core'
+import { CopyToClipboard } from 'react-copy-to-clipboard'
+
+import { Dialog, toast } from '@/components'
+
+import * as S from './styled'
+
+const prefix = `${window.location.origin}/api`
+
+interface Props {
+  isOpen: boolean
+  saving: boolean
+  onSubmit: (name: string) => Promise<any>
+  onCancel: () => void
+}
+
+export const CreateDialog = ({ isOpen, saving, onSubmit, onCancel }: Props) => 
{
+  const [step, setStep] = useState(1)
+  const [name, setName] = useState('')
+  const [record, setRecord] = useState({
+    postIssuesEndpoint: '',
+    closeIssuesEndpoint: '',
+    postDeploymentsCurl: ''
+  })
+
+  const [okText, okDisabled] = useMemo(
+    () => [step === 1 ? 'Generate POST URL' : 'Done', step === 1 && !name],
+    [step, name]
+  )
+
+  const handleSubmit = useCallback(async () => {
+    if (step === 1) {
+      const res = await onSubmit(name)
+      setStep(2)
+      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\\"
+      }"`
+      })
+    } else {
+      onCancel()
+    }
+  }, [step, name])
+
+  return (
+    <Dialog
+      isOpen={isOpen}
+      title='Add a New Incoming Webhook'
+      style={{ width: 600, top: -100 }}
+      okText={okText}
+      okDisabled={okDisabled}
+      okLoading={saving}
+      onCancel={onCancel}
+      onOk={handleSubmit}
+    >
+      {step === 1 && (
+        <S.Wrapper>
+          <h3>Incoming Webhook Name *</h3>
+          <p>
+            Give your Incoming Webhook a unique name to help you identify it in
+            the future.
+          </p>
+          <InputGroup value={name} onChange={(e) => setName(e.target.value)} />
+        </S.Wrapper>
+      )}
+      {step === 2 && (
+        <S.Wrapper>
+          <h2>
+            <Icon icon='endorsed' size={30} />
+            <span>POST URL Generated!</span>
+          </h2>
+          <h3>POST URL</h3>
+          <p>
+            Copy the following URLs to your issue tracking tool for Incidents
+            and CI tool for Deployments by making a POST to DevLake.
+          </p>
+          <h3>Incident</h3>
+          <p>POST to register an incident</p>
+          <div className='block'>
+            <span>{record.postIssuesEndpoint}</span>
+            <CopyToClipboard
+              text={record.postIssuesEndpoint}
+              onCopy={() => toast.success('Copy successfully.')}
+            >
+              <Icon icon='clipboard' />
+            </CopyToClipboard>
+          </div>
+          <p>POST to close a registered incident</p>
+          <div className='block'>
+            <span>{record.closeIssuesEndpoint}</span>
+            <CopyToClipboard
+              text={record.closeIssuesEndpoint}
+              onCopy={() => toast.success('Copy successfully.')}
+            >
+              <Icon icon='clipboard' />
+            </CopyToClipboard>
+          </div>
+          <h3>Deployment</h3>
+          <p>POST to register a deployment</p>
+          <div className='block'>
+            <span>{record.postDeploymentsCurl}</span>
+            <CopyToClipboard
+              text={record.postDeploymentsCurl}
+              onCopy={() => toast.success('Copy successfully.')}
+            >
+              <Icon icon='clipboard' />
+            </CopyToClipboard>
+          </div>
+        </S.Wrapper>
+      )}
+    </Dialog>
+  )
+}
diff --git a/config-ui/src/components/toast2/index.tsx 
b/config-ui/src/plugins/webook/components/create-dialog/styled.ts
similarity index 58%
copy from config-ui/src/components/toast2/index.tsx
copy to config-ui/src/plugins/webook/components/create-dialog/styled.ts
index 009407768..99d20448d 100644
--- a/config-ui/src/components/toast2/index.tsx
+++ b/config-ui/src/plugins/webook/components/create-dialog/styled.ts
@@ -16,8 +16,36 @@
  *
  */
 
-import { Toaster, Position } from '@blueprintjs/core'
+import styled from 'styled-components'
+import { Colors } from '@blueprintjs/core'
 
-export const Toast = Toaster.create({
-  position: Position.TOP
-})
+export const Wrapper = styled.div`
+  h2 {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0;
+    margin-bottom: 16px;
+    padding: 0;
+    font-weight: 600;
+    color: ${Colors.GREEN5};
+
+    .bp3-icon {
+      margin-right: 8px;
+    }
+  }
+
+  h3 {
+    margin: 0;
+    padding: 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/webook/components/delete-dialog/index.tsx 
b/config-ui/src/plugins/webook/components/delete-dialog/index.tsx
new file mode 100644
index 000000000..89bfbc8cd
--- /dev/null
+++ b/config-ui/src/plugins/webook/components/delete-dialog/index.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.
+ *
+ */
+
+import React from 'react'
+
+import { Dialog } from '@/components'
+
+import * as S from './styled'
+
+interface Props {
+  initialValues: any
+  isOpen: boolean
+  saving: boolean
+  onSubmit: (id: ID) => Promise<any>
+  onCancel: () => void
+}
+
+export const DeleteDialog = ({
+  initialValues,
+  isOpen,
+  saving,
+  onSubmit,
+  onCancel
+}: Props) => {
+  const handleSubmit = () => {
+    onSubmit(initialValues.id)
+    onCancel()
+  }
+
+  return (
+    <Dialog
+      isOpen={isOpen}
+      title='Delete this Incoming Webhook?'
+      style={{ width: 600, top: -100 }}
+      okText='Confirm'
+      okLoading={saving}
+      onCancel={onCancel}
+      onOk={handleSubmit}
+    >
+      <S.Wrapper>
+        <div className='message'>
+          <p>This Incoming Webhook cannot be recovered once it’s deleted.</p>
+        </div>
+      </S.Wrapper>
+    </Dialog>
+  )
+}
diff --git a/config-ui/src/pages/index.ts 
b/config-ui/src/plugins/webook/components/delete-dialog/styled.ts
similarity index 91%
copy from config-ui/src/pages/index.ts
copy to config-ui/src/plugins/webook/components/delete-dialog/styled.ts
index 9b1511822..9a54e477e 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/plugins/webook/components/delete-dialog/styled.ts
@@ -16,5 +16,6 @@
  *
  */
 
-export * from './project'
-export * from './blueprint'
+import styled from 'styled-components'
+
+export const Wrapper = styled.div``
diff --git a/config-ui/src/pages/index.ts 
b/config-ui/src/plugins/webook/components/index.ts
similarity index 88%
copy from config-ui/src/pages/index.ts
copy to config-ui/src/plugins/webook/components/index.ts
index 9b1511822..83796a8aa 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/plugins/webook/components/index.ts
@@ -16,5 +16,6 @@
  *
  */
 
-export * from './project'
-export * from './blueprint'
+export * from './create-dialog'
+export * from './delete-dialog'
+export * from './view-or-edit-dialog'
diff --git 
a/config-ui/src/plugins/webook/components/view-or-edit-dialog/index.tsx 
b/config-ui/src/plugins/webook/components/view-or-edit-dialog/index.tsx
new file mode 100644
index 000000000..2a933f877
--- /dev/null
+++ b/config-ui/src/plugins/webook/components/view-or-edit-dialog/index.tsx
@@ -0,0 +1,139 @@
+/*
+ * 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, useCallback } from 'react'
+import { InputGroup, Icon } from '@blueprintjs/core'
+import { CopyToClipboard } from 'react-copy-to-clipboard'
+
+import { Dialog, toast } from '@/components'
+
+import * as S from './styled'
+
+const prefix = `${window.location.origin}/api`
+
+interface Props {
+  type: 'edit' | 'show'
+  initialValues: any
+  isOpen: boolean
+  saving: boolean
+  onSubmit: (id: ID, name: string) => Promise<any>
+  onCancel: () => void
+}
+
+export const ViewOrEditDialog = ({
+  type,
+  initialValues,
+  isOpen,
+  saving,
+  onSubmit,
+  onCancel
+}: Props) => {
+  const [name, setName] = useState('')
+  const [record, setRecord] = useState({
+    postIssuesEndpoint: '',
+    closeIssuesEndpoint: '',
+    postDeploymentsCurl: ''
+  })
+
+  useEffect(() => {
+    setName(initialValues.name)
+    setRecord({
+      postIssuesEndpoint: `${prefix}${initialValues.postIssuesEndpoint}`,
+      closeIssuesEndpoint: `${prefix}${initialValues.closeIssuesEndpoint}`,
+      postDeploymentsCurl: `curl 
${prefix}${initialValues.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\\"
+}"`
+    })
+  }, [initialValues])
+
+  const handleSubmit = useCallback(async () => {
+    if (type === 'edit') {
+      await onSubmit(initialValues.id, name)
+    }
+
+    onCancel()
+  }, [name])
+
+  return (
+    <Dialog
+      isOpen={isOpen}
+      title='View or Edit Incoming Webhook'
+      style={{ width: 600, top: -100 }}
+      okText={type === 'edit' ? 'Save' : 'Done'}
+      okDisabled={!name}
+      okLoading={saving}
+      onCancel={onCancel}
+      onOk={handleSubmit}
+    >
+      <S.Wrapper>
+        <h3>Incoming Webhook Name *</h3>
+        <p>
+          Give your Incoming Webhook a unique name to help you identify it in
+          the future.
+        </p>
+        <InputGroup
+          disabled={type !== 'edit'}
+          value={name}
+          onChange={(e) => setName(e.target.value)}
+        />
+        <h2>
+          <Icon icon='endorsed' size={30} />
+          <span>POST URL </span>
+        </h2>
+        <p>
+          Copy the following URLs to your issue tracking tool for Incidents and
+          CI tool for Deployments by making a POST to DevLake.
+        </p>
+        <h3>Incident</h3>
+        <p>POST to register an incident </p>
+        <div className='block'>
+          <span>{record.postIssuesEndpoint}</span>
+          <CopyToClipboard
+            text={record.postIssuesEndpoint}
+            onCopy={() => toast.success('Copy successfully.')}
+          >
+            <Icon icon='clipboard' />
+          </CopyToClipboard>
+        </div>
+        <p>POST to close a registered incident</p>
+        <div className='block'>
+          <span>{record.closeIssuesEndpoint}</span>
+          <CopyToClipboard
+            text={record.closeIssuesEndpoint}
+            onCopy={() => toast.success('Copy successfully.')}
+          >
+            <Icon icon='clipboard' />
+          </CopyToClipboard>
+        </div>
+        <h3>Deployment</h3>
+        <p>POST to register a deployment</p>
+        <div className='block'>
+          <span>{record.postDeploymentsCurl}</span>
+          <CopyToClipboard
+            text={record.postDeploymentsCurl}
+            onCopy={() => toast.success('Copy successfully.')}
+          >
+            <Icon icon='clipboard' />
+          </CopyToClipboard>
+        </div>
+      </S.Wrapper>
+    </Dialog>
+  )
+}
diff --git a/config-ui/src/components/toast2/index.tsx 
b/config-ui/src/plugins/webook/components/view-or-edit-dialog/styled.ts
similarity index 59%
copy from config-ui/src/components/toast2/index.tsx
copy to config-ui/src/plugins/webook/components/view-or-edit-dialog/styled.ts
index 009407768..661a33f23 100644
--- a/config-ui/src/components/toast2/index.tsx
+++ b/config-ui/src/plugins/webook/components/view-or-edit-dialog/styled.ts
@@ -16,8 +16,35 @@
  *
  */
 
-import { Toaster, Position } from '@blueprintjs/core'
+import styled from 'styled-components'
+import { Colors } from '@blueprintjs/core'
 
-export const Toast = Toaster.create({
-  position: Position.TOP
-})
+export const Wrapper = styled.div`
+  h2 {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 16px 0;
+    padding: 0;
+    font-weight: 600;
+    color: ${Colors.GREEN5};
+
+    .bp3-icon {
+      margin-right: 8px;
+    }
+  }
+
+  h3 {
+    margin: 0;
+    padding: 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/components/toast2/index.tsx 
b/config-ui/src/plugins/webook/connection/api.ts
similarity index 54%
copy from config-ui/src/components/toast2/index.tsx
copy to config-ui/src/plugins/webook/connection/api.ts
index 009407768..afae3a921 100644
--- a/config-ui/src/components/toast2/index.tsx
+++ b/config-ui/src/plugins/webook/connection/api.ts
@@ -16,8 +16,30 @@
  *
  */
 
-import { Toaster, Position } from '@blueprintjs/core'
+import request from '@/components/utils/request'
 
-export const Toast = Toaster.create({
-  position: Position.TOP
-})
+export const getConnections = () => request('/plugins/webhook/connections')
+
+export const getConnection = (id: ID) =>
+  request(`/plugins/webhook/connections/${id}`)
+
+type Paylod = {
+  name: string
+}
+
+export const createConnection = (payload: Paylod) =>
+  request('/plugins/webhook/connections', {
+    method: 'post',
+    data: payload
+  })
+
+export const updateConnection = (id: ID, payload: Paylod) =>
+  request(`/plugins/webhook/connections/${id}`, {
+    method: 'patch',
+    data: payload
+  })
+
+export const deleteConnection = (id: ID) =>
+  request(`/plugins/webhook/connections/${id}`, {
+    method: 'delete'
+  })
diff --git a/config-ui/src/plugins/webook/connection/index.tsx 
b/config-ui/src/plugins/webook/connection/index.tsx
new file mode 100644
index 000000000..bd9330867
--- /dev/null
+++ b/config-ui/src/plugins/webook/connection/index.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 React, { useState } from 'react'
+import { ButtonGroup, Button, Icon, Intent } from '@blueprintjs/core'
+
+import { Table, ColumnType } from '@/components'
+
+import { CreateDialog, ViewOrEditDialog, DeleteDialog } from '../components'
+
+import type { ConnectionItem } from './use-connection'
+import { useConnection } from './use-connection'
+import * as S from './styled'
+
+type Type = 'add' | 'edit' | 'show' | 'delete'
+
+export const WebHookConnection = () => {
+  const [type, setType] = useState<Type>()
+  const [record, setRecord] = useState<ConnectionItem>()
+
+  const { loading, saving, connections, onCreate, onUpdate, onDelete } =
+    useConnection()
+
+  const handleHideDialog = () => {
+    setType(undefined)
+    setRecord(undefined)
+  }
+
+  const handleShowDialog = (t: Type, r?: ConnectionItem) => {
+    setType(t)
+    setRecord(r)
+  }
+
+  const columns: ColumnType<ConnectionItem> = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      key: 'id'
+    },
+    {
+      title: 'Incoming Webhook Name',
+      dataIndex: 'name',
+      key: 'name',
+      render: (name, row) => (
+        <span onClick={() => handleShowDialog('show', row)}>{name}</span>
+      )
+    },
+    {
+      title: '',
+      dataIndex: '',
+      key: 'action',
+      align: 'center',
+      render: (_, row) => (
+        <S.Action>
+          <Icon icon='edit' onClick={() => handleShowDialog('edit', row)} />
+          <Icon icon='trash' onClick={() => handleShowDialog('delete', row)} />
+        </S.Action>
+      )
+    }
+  ]
+
+  return (
+    <S.Wrapper>
+      <ButtonGroup>
+        <Button
+          icon='plus'
+          text='Add a Webhook'
+          intent={Intent.PRIMARY}
+          onClick={() => handleShowDialog('add')}
+        />
+      </ButtonGroup>
+      <S.Inner>
+        <Table loading={loading} columns={columns} dataSource={connections} />
+      </S.Inner>
+      {type === 'add' && (
+        <CreateDialog
+          isOpen
+          saving={saving}
+          onSubmit={onCreate}
+          onCancel={handleHideDialog}
+        />
+      )}
+      {(type === 'edit' || type === 'show') && (
+        <ViewOrEditDialog
+          type={type}
+          initialValues={record}
+          isOpen
+          saving={saving}
+          onSubmit={onUpdate}
+          onCancel={handleHideDialog}
+        />
+      )}
+      {type === 'delete' && (
+        <DeleteDialog
+          initialValues={record}
+          isOpen
+          saving={saving}
+          onSubmit={onDelete}
+          onCancel={handleHideDialog}
+        />
+      )}
+    </S.Wrapper>
+  )
+}
diff --git a/config-ui/src/components/toast2/index.tsx 
b/config-ui/src/plugins/webook/connection/styled.ts
similarity index 66%
copy from config-ui/src/components/toast2/index.tsx
copy to config-ui/src/plugins/webook/connection/styled.ts
index 009407768..9ff4886f1 100644
--- a/config-ui/src/components/toast2/index.tsx
+++ b/config-ui/src/plugins/webook/connection/styled.ts
@@ -16,8 +16,26 @@
  *
  */
 
-import { Toaster, Position } from '@blueprintjs/core'
+import styled from 'styled-components'
 
-export const Toast = Toaster.create({
-  position: Position.TOP
-})
+export const Wrapper = styled.div``
+
+export const Inner = styled.div`
+  margin-top: 16px;
+  background-color: #fff;
+  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;
+`
+
+export const Action = styled.div`
+  color: #7497f7;
+
+  span {
+    cursor: pointer;
+  }
+
+  span + span {
+    margin-left: 8px;
+  }
+`
diff --git a/config-ui/src/plugins/webook/connection/use-connection.ts 
b/config-ui/src/plugins/webook/connection/use-connection.ts
new file mode 100644
index 000000000..348aff788
--- /dev/null
+++ b/config-ui/src/plugins/webook/connection/use-connection.ts
@@ -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.
+ *
+ */
+
+import { useState, useEffect, useMemo } from 'react'
+
+import { operator } from '@/utils'
+
+import * as API from './api'
+
+export type ConnectionItem = {
+  id: ID
+  name: string
+}
+
+export const useConnection = () => {
+  const [loading, setLoading] = useState(false)
+  const [saving, setSaving] = useState(false)
+  const [connections, setConnections] = useState<ConnectionItem[]>([])
+
+  const getConnections = async () => {
+    setLoading(true)
+    try {
+      const res = await API.getConnections()
+      setConnections(res)
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  useEffect(() => {
+    getConnections()
+  }, [])
+
+  const handleCreate = async (name: string) => {
+    const [success, res] = await operator(
+      () => API.createConnection({ name }),
+      {
+        setOperating: setSaving
+      }
+    )
+
+    if (success) {
+      getConnections()
+      return API.getConnection(res.id)
+    }
+  }
+
+  const handleUpdate = async (id: ID, name: string) => {
+    const [success] = await operator(() => API.updateConnection(id, { name }), 
{
+      setOperating: setSaving
+    })
+
+    if (success) {
+      getConnections()
+    }
+  }
+
+  const handleDelete = async (id: ID) => {
+    const [success] = await operator(() => API.deleteConnection(id), {
+      setOperating: setSaving
+    })
+
+    if (success) {
+      getConnections()
+    }
+  }
+
+  return useMemo(
+    () => ({
+      loading,
+      saving,
+      connections,
+      onCreate: handleCreate,
+      onUpdate: handleUpdate,
+      onDelete: handleDelete
+    }),
+    [loading, saving, connections]
+  )
+}
diff --git a/config-ui/src/plugins/index.ts 
b/config-ui/src/plugins/webook/index.ts
similarity index 96%
copy from config-ui/src/plugins/index.ts
copy to config-ui/src/plugins/webook/index.ts
index 4a9427b7a..de78e5c49 100644
--- a/config-ui/src/plugins/index.ts
+++ b/config-ui/src/plugins/webook/index.ts
@@ -16,4 +16,4 @@
  *
  */
 
-export * from './common'
+export * from './connection'

Reply via email to