You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2023/05/30 02:00:59 UTC

[incubator-devlake] branch main updated: feat(config-ui): support delete blueprint and project (#5305)

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 f03cec04f feat(config-ui): support delete blueprint and project (#5305)
f03cec04f is described below

commit f03cec04f2023e681739c102f37f945cc4690515
Author: 青湛 <0x...@gmail.com>
AuthorDate: Tue May 30 10:00:54 2023 +0800

    feat(config-ui): support delete blueprint and project (#5305)
    
    * feat(config-ui): support params align in buttons component
    
    * feat(config-ui): support delete project
    
    * feat(config-ui): support delete blueprint
---
 config-ui/src/components/buttons/index.tsx         | 22 +++++--
 .../pages/blueprint/detail/blueprint-detail.tsx    | 46 ++++++++++++-
 config-ui/src/pages/blueprint/detail/styled.ts     | 10 +++
 config-ui/src/pages/project/detail/api.ts          |  5 ++
 .../src/pages/project/detail/settings-panel.tsx    | 75 +++++++++++++++++-----
 config-ui/src/pages/project/detail/styled.ts       | 10 +++
 6 files changed, 146 insertions(+), 22 deletions(-)

diff --git a/config-ui/src/components/buttons/index.tsx b/config-ui/src/components/buttons/index.tsx
index 8734aa563..ac5dd647e 100644
--- a/config-ui/src/components/buttons/index.tsx
+++ b/config-ui/src/components/buttons/index.tsx
@@ -18,21 +18,35 @@
 
 import styled from 'styled-components';
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<{ align: 'left' | 'right' | 'center' }>`
   display: flex;
   align-items: center;
-  justify-content: end;
   margin-top: 24px;
 
+  ${({ align }) => {
+    if (align === 'left') {
+      return 'justify-content: flex-start;';
+    }
+
+    if (align === 'right') {
+      return 'justify-content: flex-end;';
+    }
+
+    if (align === 'center') {
+      return 'justify-content: space-around;';
+    }
+  }}
+
   .bp4-button + .bp4-button {
     margin-left: 8px;
   }
 `;
 
 interface Props {
+  align?: 'left' | 'right' | 'center';
   children: React.ReactNode;
 }
 
-export const Buttons = ({ children }: Props) => {
-  return <Wrapper>{children}</Wrapper>;
+export const Buttons = ({ align = 'right', children }: Props) => {
+  return <Wrapper align={align}>{children}</Wrapper>;
 };
diff --git a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
index dc8faff31..36cf81e99 100644
--- a/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
+++ b/config-ui/src/pages/blueprint/detail/blueprint-detail.tsx
@@ -17,10 +17,11 @@
  */
 
 import { useState } from 'react';
+import { useHistory } from 'react-router-dom';
 import type { TabId } from '@blueprintjs/core';
-import { Tabs, Tab, Switch } from '@blueprintjs/core';
+import { Tabs, Tab, Switch, Button, Icon, Intent } from '@blueprintjs/core';
 
-import { PageLoading } from '@/components';
+import { PageLoading, Dialog } from '@/components';
 import { useRefreshData } from '@/hooks';
 import { operator } from '@/utils';
 
@@ -37,6 +38,9 @@ export const BlueprintDetail = ({ id }: Props) => {
   const [activeTab, setActiveTab] = useState<TabId>('configuration');
   const [version, setVersion] = useState(1);
   const [operating, setOperating] = useState(false);
+  const [isOpen, setIsOpen] = useState(false);
+
+  const history = useHistory();
 
   const { ready, data } = useRefreshData(
     async () => Promise.all([API.getBlueprint(id), API.getBlueprintPipelines(id)]),
@@ -77,6 +81,25 @@ export const BlueprintDetail = ({ id }: Props) => {
     }
   };
 
+  const handleShowDeleteDialog = () => {
+    setIsOpen(true);
+  };
+
+  const handleHideDeleteDialog = () => {
+    setIsOpen(false);
+  };
+
+  const handleDelete = async () => {
+    const [success] = await operator(() => API.deleteBluprint(id), {
+      setOperating,
+      formatMessage: () => 'Delete blueprint successful.',
+    });
+
+    if (success) {
+      history.push('/blueprints');
+    }
+  };
+
   return (
     <S.Wrapper>
       <Tabs selectedTabId={activeTab} onChange={(at) => setActiveTab(at)}>
@@ -99,7 +122,26 @@ export const BlueprintDetail = ({ id }: Props) => {
           checked={blueprint.enable}
           onChange={(e) => handleUpdate({ enable: (e.target as HTMLInputElement).checked })}
         />
+        <Button intent={Intent.DANGER} text="Delete Blueprint" onClick={handleShowDeleteDialog} />
       </Tabs>
+      <Dialog
+        isOpen={isOpen}
+        style={{ width: 820 }}
+        title="Are you sure you want to delete this Blueprint?"
+        okText="Confirm"
+        okLoading={operating}
+        onCancel={handleHideDeleteDialog}
+        onOk={handleDelete}
+      >
+        <S.DialogBody>
+          <Icon icon="warning-sign" />
+          <span>
+            Please note: deleting the Blueprint will not delete the historical data of the Data Scopes in this
+            Blueprint. If you would like to delete the historical data of Data Scopes, please visit the Connection page
+            and do so.
+          </span>
+        </S.DialogBody>
+      </Dialog>
     </S.Wrapper>
   );
 };
diff --git a/config-ui/src/pages/blueprint/detail/styled.ts b/config-ui/src/pages/blueprint/detail/styled.ts
index 31af92f98..a7caedb95 100644
--- a/config-ui/src/pages/blueprint/detail/styled.ts
+++ b/config-ui/src/pages/blueprint/detail/styled.ts
@@ -22,6 +22,16 @@ export const Wrapper = styled.div`
   padding-bottom: 24px;
 `;
 
+export const DialogBody = styled.div`
+  display: flex;
+  align-items: center;
+
+  .bp4-icon {
+    margin-right: 8px;
+    color: #f4be55;
+  }
+`;
+
 export const ConfigurationPanel = styled.div`
   .block + .block {
     margin-top: 36px;
diff --git a/config-ui/src/pages/project/detail/api.ts b/config-ui/src/pages/project/detail/api.ts
index 2c7c65581..a222dda7f 100644
--- a/config-ui/src/pages/project/detail/api.ts
+++ b/config-ui/src/pages/project/detail/api.ts
@@ -30,3 +30,8 @@ export const updateProject = (name: string, payload: Omit<ProjectType, 'blueprin
 
 export const updateBlueprint = (id: ID, payload: BlueprintType) =>
   request(`/blueprints/${id}`, { method: 'patch', data: payload });
+
+export const deleteProject = (name: string) =>
+  request(`/projects/${name}`, {
+    method: 'delete',
+  });
diff --git a/config-ui/src/pages/project/detail/settings-panel.tsx b/config-ui/src/pages/project/detail/settings-panel.tsx
index a271b05f7..701be104c 100644
--- a/config-ui/src/pages/project/detail/settings-panel.tsx
+++ b/config-ui/src/pages/project/detail/settings-panel.tsx
@@ -18,15 +18,16 @@
 
 import { useEffect, useState } from 'react';
 import { useHistory } from 'react-router-dom';
-import { InputGroup, Checkbox, Button, Intent } from '@blueprintjs/core';
+import { InputGroup, Checkbox, Button, Icon, Intent } from '@blueprintjs/core';
 
-import { Card, FormItem, Buttons, toast } from '@/components';
+import { Card, FormItem, Buttons, toast, Dialog } from '@/components';
 import { operator } from '@/utils';
 
 import type { ProjectType } from '../types';
 import { validName } from '../utils';
 
 import * as API from './api';
+import * as S from './styled';
 
 interface Props {
   project: ProjectType;
@@ -36,6 +37,7 @@ export const SettingsPanel = ({ project }: Props) => {
   const [name, setName] = useState('');
   const [enableDora, setEnableDora] = useState(false);
   const [operating, setOperating] = useState(false);
+  const [isOpen, setIsOpen] = useState(false);
 
   const history = useHistory();
 
@@ -75,21 +77,62 @@ export const SettingsPanel = ({ project }: Props) => {
     }
   };
 
+  const handleShowDeleteDialog = () => {
+    setIsOpen(true);
+  };
+
+  const handleHideDeleteDialog = () => {
+    setIsOpen(false);
+  };
+
+  const handleDelete = async () => {
+    const [success] = await operator(() => API.deleteProject(project.name), {
+      setOperating,
+      formatMessage: () => 'Delete project successful.',
+    });
+
+    if (success) {
+      history.push(`/projects`);
+    }
+  };
+
   return (
-    <Card>
-      <FormItem label="Project Name" subLabel="Edit your project name with letters, numbers, -, _ or /" required>
-        <InputGroup style={{ width: 386 }} value={name} onChange={(e) => setName(e.target.value)} />
-      </FormItem>
-      <FormItem subLabel="DORA metrics are four widely-adopted metrics for measuring software delivery performance.">
-        <Checkbox
-          label="Enable DORA Metrics"
-          checked={enableDora}
-          onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
-        />
-      </FormItem>
-      <Buttons>
-        <Button text="Save" loading={operating} disabled={!name} intent={Intent.PRIMARY} onClick={handleUpdate} />
+    <>
+      <Card>
+        <FormItem label="Project Name" subLabel="Edit your project name with letters, numbers, -, _ or /" required>
+          <InputGroup style={{ width: 386 }} value={name} onChange={(e) => setName(e.target.value)} />
+        </FormItem>
+        <FormItem subLabel="DORA metrics are four widely-adopted metrics for measuring software delivery performance.">
+          <Checkbox
+            label="Enable DORA Metrics"
+            checked={enableDora}
+            onChange={(e) => setEnableDora((e.target as HTMLInputElement).checked)}
+          />
+        </FormItem>
+        <Buttons align="left">
+          <Button text="Save" loading={operating} disabled={!name} intent={Intent.PRIMARY} onClick={handleUpdate} />
+        </Buttons>
+      </Card>
+      <Buttons align="center">
+        <Button intent={Intent.DANGER} text="Delete Project" onClick={handleShowDeleteDialog} />
       </Buttons>
-    </Card>
+      <Dialog
+        isOpen={isOpen}
+        style={{ width: 820 }}
+        title="Are you sure you want to delete this Project?"
+        okText="Confirm"
+        okLoading={operating}
+        onCancel={handleHideDeleteDialog}
+        onOk={handleDelete}
+      >
+        <S.DialogBody>
+          <Icon icon="warning-sign" />
+          <span>
+            This operation cannot be undone. Deleting a Data Connection will delete all data that have been collected in
+            this Connection.
+          </span>
+        </S.DialogBody>
+      </Dialog>
+    </>
   );
 };
diff --git a/config-ui/src/pages/project/detail/styled.ts b/config-ui/src/pages/project/detail/styled.ts
index 248651df2..cca7675d9 100644
--- a/config-ui/src/pages/project/detail/styled.ts
+++ b/config-ui/src/pages/project/detail/styled.ts
@@ -24,3 +24,13 @@ export const Wrapper = styled.div`
     justify-content: center;
   }
 `;
+
+export const DialogBody = styled.div`
+  display: flex;
+  align-items: center;
+
+  .bp4-icon {
+    margin-right: 8px;
+    color: #f4be55;
+  }
+`;