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'