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], - ); -};
