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

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

commit 3ad660599f04260c04cb58a436f0a36d7224a39e
Author: mintsweet <[email protected]>
AuthorDate: Mon Sep 4 14:20:10 2023 +1200

    refactor(config-ui): the content about pipeline
---
 .../src/pages/blueprint/detail/status-panel.tsx    |  68 +++++--
 config-ui/src/pages/index.ts                       |   1 -
 .../src/pages/pipeline/components/cancel/index.tsx |  54 ------
 .../src/pages/pipeline/components/info/styled.ts   |  57 ------
 .../src/pages/pipeline/components/rerun/index.tsx  |  58 ------
 .../src/pages/pipeline/components/status/index.tsx |  75 --------
 .../src/pages/pipeline/components/status/styled.ts |  39 ----
 .../src/pages/pipeline/components/task/styled.ts   |  80 --------
 .../src/pages/pipeline/components/tasks/styled.ts  |  87 ---------
 config-ui/src/pages/project/home/api.ts            |   4 +-
 config-ui/src/pages/project/home/index.tsx         |   2 +-
 config-ui/src/{pages => routes}/pipeline/api.ts    |  16 +-
 .../pipeline/components/duration.tsx}              |  14 +-
 .../{pages => routes}/pipeline/components/index.ts |   5 +-
 .../pipeline/components/info.tsx}                  |  74 +++++---
 .../src/routes/pipeline/components/status.tsx      |  51 ++++++
 .../pipeline/components/table.tsx}                 |  85 ++++-----
 .../pipeline/components/task.tsx}                  |  62 ++++---
 .../pipeline/components/tasks.tsx}                 |  47 ++---
 config-ui/src/routes/pipeline/constant.ts          |  43 +++++
 config-ui/src/{pages => routes}/pipeline/index.ts  |   3 +-
 .../index.tsx => routes/pipeline/pipeline.tsx}     |  40 ++--
 config-ui/src/routes/pipeline/pipelines.tsx        |  56 ++++++
 config-ui/src/routes/pipeline/styled.ts            | 202 +++++++++++++++++++++
 config-ui/src/{pages => routes}/pipeline/types.ts  |  10 +-
 25 files changed, 608 insertions(+), 625 deletions(-)

diff --git a/config-ui/src/pages/blueprint/detail/status-panel.tsx 
b/config-ui/src/pages/blueprint/detail/status-panel.tsx
index 506380545..11320384f 100644
--- a/config-ui/src/pages/blueprint/detail/status-panel.tsx
+++ b/config-ui/src/pages/blueprint/detail/status-panel.tsx
@@ -23,7 +23,9 @@ import { Tooltip2 } from '@blueprintjs/popover2';
 
 import { Card, IconButton, Dialog } from '@/components';
 import { getCron } from '@/config';
-import { PipelineContextProvider, PipelineInfo, PipelineTasks, 
PipelineHistorical } from '@/pages';
+import { useAutoRefresh } from '@/hooks';
+import * as PipelineT from '@/routes/pipeline/types';
+import { PipelineInfo, PipelineTasks, PipelineTable } from '@/routes/pipeline';
 import { formatTime, operator } from '@/utils';
 
 import { BlueprintType, FromEnum } from '../types';
@@ -46,6 +48,28 @@ export const StatusPanel = ({ from, blueprint, pipelineId, 
onRefresh }: Props) =
 
   const cron = useMemo(() => getCron(blueprint.isManual, 
blueprint.cronConfig), [blueprint]);
 
+  const { loading, data } = useAutoRefresh<PipelineT.Pipeline[]>(
+    async () => {
+      const res = await API.getBlueprintPipelines(blueprint.id);
+      return res.pipelines;
+    },
+    [],
+    {
+      cancel: (data) =>
+        !!(
+          data &&
+          data.every((it) =>
+            [
+              PipelineT.PipelineStatus.COMPLETED,
+              PipelineT.PipelineStatus.PARTIAL,
+              PipelineT.PipelineStatus.CANCELLED,
+              PipelineT.PipelineStatus.FAILED,
+            ].includes(it.status),
+          )
+        ),
+    },
+  );
+
   const handleShowDeleteDialog = () => {
     setIsOpen(true);
   };
@@ -143,25 +167,31 @@ export const StatusPanel = ({ from, blueprint, 
pipelineId, onRefresh }: Props) =
         </S.BlueprintAction>
       )}
 
-      <PipelineContextProvider>
-        <div className="block">
-          <h3>Current Pipeline</h3>
-          {!pipelineId ? (
-            <Card>There is no current run for this blueprint.</Card>
-          ) : (
-            <>
+      {/* <PipelineContextProvider> */}
+      <div className="block">
+        <h3>Current Pipeline</h3>
+        {!pipelineId ? (
+          <Card>There is no current run for this blueprint.</Card>
+        ) : (
+          <>
+            <Card>
               <PipelineInfo id={pipelineId} />
-              <Card style={{ marginTop: 16 }}>
-                <PipelineTasks id={pipelineId} />
-              </Card>
-            </>
-          )}
-        </div>
-        <div className="block">
-          <h3>Historical Pipelines</h3>
-          <PipelineHistorical blueprintId={blueprint.id} />
-        </div>
-      </PipelineContextProvider>
+            </Card>
+            <Card>
+              <PipelineTasks id={pipelineId} />
+            </Card>
+          </>
+        )}
+      </div>
+      <div className="block">
+        <h3>Historical Pipelines</h3>
+        {!data?.length ? (
+          <Card>There are no historical runs associated with this 
blueprint.</Card>
+        ) : (
+          <PipelineTable loading={loading} dataSource={data} />
+        )}
+      </div>
+      {/* </PipelineContextProvider> */}
 
       <Dialog
         isOpen={isOpen}
diff --git a/config-ui/src/pages/index.ts b/config-ui/src/pages/index.ts
index 3554c4b95..555e0869f 100644
--- a/config-ui/src/pages/index.ts
+++ b/config-ui/src/pages/index.ts
@@ -18,5 +18,4 @@
 
 export * from './blueprint';
 export * from './connection';
-export * from './pipeline';
 export * from './project';
diff --git a/config-ui/src/pages/pipeline/components/cancel/index.tsx 
b/config-ui/src/pages/pipeline/components/cancel/index.tsx
deleted file mode 100644
index 3dc22308a..000000000
--- a/config-ui/src/pages/pipeline/components/cancel/index.tsx
+++ /dev/null
@@ -1,54 +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 { IconButton } from '@/components';
-import { operator } from '@/utils';
-
-import { StatusEnum } from '../../types';
-import * as API from '../../api';
-
-import { usePipeline } from '../context';
-
-interface Props {
-  id: ID;
-  status: StatusEnum;
-}
-
-export const PipelineCancel = ({ id, status }: Props) => {
-  const [canceling, setCanceling] = useState(false);
-
-  const { setVersion } = usePipeline();
-
-  const handleSubmit = async () => {
-    const [success] = await operator(() => API.deletePipeline(id), {
-      setOperating: setCanceling,
-    });
-
-    if (success) {
-      setVersion((v) => v + 1);
-    }
-  };
-
-  if (![StatusEnum.ACTIVE, StatusEnum.RUNNING, 
StatusEnum.RERUN].includes(status)) {
-    return null;
-  }
-
-  return <IconButton loading={canceling} icon="disable" tooltip="Cancel" 
onClick={handleSubmit} />;
-};
diff --git a/config-ui/src/pages/pipeline/components/info/styled.ts 
b/config-ui/src/pages/pipeline/components/info/styled.ts
deleted file mode 100644
index de9a3e7f3..000000000
--- a/config-ui/src/pages/pipeline/components/info/styled.ts
+++ /dev/null
@@ -1,57 +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 { Colors } from '@blueprintjs/core';
-import styled from 'styled-components';
-
-import { Card } from '@/components';
-
-export const Wrapper = styled(Card)`
-  ul {
-    display: flex;
-    align-items: center;
-  }
-
-  li {
-    flex: 5;
-    display: flex;
-    flex-direction: column;
-
-    &:last-child {
-      flex: 1;
-    }
-
-    & > span {
-      font-size: 12px;
-      color: #94959f;
-      text-align: center;
-    }
-
-    & > strong {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      margin-top: 8px;
-    }
-  }
-
-  p.message {
-    margin: 8px 0 0;
-    color: ${Colors.RED3};
-  }
-`;
diff --git a/config-ui/src/pages/pipeline/components/rerun/index.tsx 
b/config-ui/src/pages/pipeline/components/rerun/index.tsx
deleted file mode 100644
index 7f1eb1445..000000000
--- a/config-ui/src/pages/pipeline/components/rerun/index.tsx
+++ /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 } from 'react';
-
-import { IconButton } from '@/components';
-import { operator } from '@/utils';
-
-import { StatusEnum } from '../../types';
-import * as API from '../../api';
-
-import { usePipeline } from '../context';
-
-interface Props {
-  type: 'pipeline' | 'task';
-  id: ID;
-  status: StatusEnum;
-}
-
-export const PipelineRerun = ({ type, id, status }: Props) => {
-  const [reruning, setReruning] = useState(false);
-
-  const { setVersion } = usePipeline();
-
-  const handleSubmit = async () => {
-    const [success] = await operator(() => (type === 'task' ? 
API.taskRerun(id) : API.pipelineRerun(id)), {
-      setOperating: setReruning,
-    });
-
-    if (success) {
-      setVersion((v) => v + 1);
-    }
-  };
-
-  if (![StatusEnum.COMPLETED, StatusEnum.PARTIAL, StatusEnum.FAILED, 
StatusEnum.CANCELLED].includes(status)) {
-    return null;
-  }
-
-  if (type === 'task') {
-    return <IconButton loading={reruning} icon="repeat" tooltip="Rerun task" 
onClick={handleSubmit} />;
-  }
-
-  return <IconButton loading={reruning} icon="repeat" tooltip="Rerun failed 
tasks" onClick={handleSubmit} />;
-};
diff --git a/config-ui/src/pages/pipeline/components/status/index.tsx 
b/config-ui/src/pages/pipeline/components/status/index.tsx
deleted file mode 100644
index 72ced128b..000000000
--- a/config-ui/src/pages/pipeline/components/status/index.tsx
+++ /dev/null
@@ -1,75 +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 { Icon, IconName } from '@blueprintjs/core';
-import classNames from 'classnames';
-
-import { Loading } from '@/components';
-
-import { StatusEnum } from '../../types';
-
-import * as S from './styled';
-
-export const STATUS_ICON = {
-  [StatusEnum.CREATED]: 'stopwatch',
-  [StatusEnum.PENDING]: 'stopwatch',
-  [StatusEnum.ACTIVE]: 'loading',
-  [StatusEnum.RUNNING]: 'loading',
-  [StatusEnum.RERUN]: 'loading',
-  [StatusEnum.COMPLETED]: 'tick-circle',
-  [StatusEnum.PARTIAL]: 'tick-circle',
-  [StatusEnum.FAILED]: 'delete',
-  [StatusEnum.CANCELLED]: 'undo',
-};
-
-export const STATUS_LABEL = {
-  [StatusEnum.CREATED]: 'Created (Pending)',
-  [StatusEnum.PENDING]: 'Created (Pending)',
-  [StatusEnum.ACTIVE]: 'In Progress',
-  [StatusEnum.RUNNING]: 'In Progress',
-  [StatusEnum.RERUN]: 'In Progress',
-  [StatusEnum.COMPLETED]: 'Succeeded',
-  [StatusEnum.PARTIAL]: 'Partial Success',
-  [StatusEnum.FAILED]: 'Failed',
-  [StatusEnum.CANCELLED]: 'Cancelled',
-};
-
-interface Props {
-  status: StatusEnum;
-}
-
-export const PipelineStatus = ({ status }: Props) => {
-  const statusCls = classNames({
-    ready: [StatusEnum.CREATED, StatusEnum.PENDING].includes(status),
-    loading: [StatusEnum.ACTIVE, StatusEnum.RUNNING, 
StatusEnum.RERUN].includes(status),
-    success: [StatusEnum.COMPLETED, StatusEnum.PARTIAL].includes(status),
-    error: status === StatusEnum.FAILED,
-    cancel: status === StatusEnum.CANCELLED,
-  });
-
-  return (
-    <S.Wrapper className={statusCls}>
-      {STATUS_ICON[status] === 'loading' ? (
-        <Loading style={{ marginRight: 4 }} size={14} />
-      ) : (
-        <Icon style={{ marginRight: 4 }} icon={STATUS_ICON[status] as 
IconName} />
-      )}
-      <span>{STATUS_LABEL[status]}</span>
-    </S.Wrapper>
-  );
-};
diff --git a/config-ui/src/pages/pipeline/components/status/styled.ts 
b/config-ui/src/pages/pipeline/components/status/styled.ts
deleted file mode 100644
index 87ed2db8c..000000000
--- a/config-ui/src/pages/pipeline/components/status/styled.ts
+++ /dev/null
@@ -1,39 +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 { Colors } from '@blueprintjs/core';
-import styled from 'styled-components';
-
-export const Wrapper = styled.div`
-  &.ready,
-  &.cancel {
-    color: #94959f;
-  }
-
-  &.loading {
-    color: #7497f7;
-  }
-
-  &.success {
-    color: ${Colors.GREEN3};
-  }
-
-  &.error {
-    color: ${Colors.RED3};
-  }
-`;
diff --git a/config-ui/src/pages/pipeline/components/task/styled.ts 
b/config-ui/src/pages/pipeline/components/task/styled.ts
deleted file mode 100644
index 35b153954..000000000
--- a/config-ui/src/pages/pipeline/components/task/styled.ts
+++ /dev/null
@@ -1,80 +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 { Colors } from '@blueprintjs/core';
-import styled from 'styled-components';
-
-export const Wrapper = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 16px 0;
-  height: 80px;
-  border-bottom: 1px solid #dbe4fd;
-  box-sizing: border-box;
-`;
-
-export const Info = styled.div`
-  flex: auto;
-  overflow: hidden;
-
-  .title {
-    display: flex;
-    align-items: center;
-    margin-bottom: 8px;
-
-    & > img {
-      width: 20px;
-    }
-
-    & > strong {
-      margin: 0 4px;
-    }
-
-    & > span {
-      flex: auto;
-      overflow: hidden;
-    }
-  }
-
-  p {
-    padding-left: 26px;
-    margin: 0;
-    font-size: 12px;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-
-    &.error {
-      color: ${Colors.RED3};
-    }
-  }
-`;
-
-export const Duration = styled.div`
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  flex: 0 0 80px;
-  text-align: right;
-
-  .bp4-icon {
-    margin-top: 4px;
-    cursor: pointer;
-  }
-`;
diff --git a/config-ui/src/pages/pipeline/components/tasks/styled.ts 
b/config-ui/src/pages/pipeline/components/tasks/styled.ts
deleted file mode 100644
index d6def19bb..000000000
--- a/config-ui/src/pages/pipeline/components/tasks/styled.ts
+++ /dev/null
@@ -1,87 +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`
-  position: relative;
-  padding-right: 36px;
-
-  .collapse-control {
-    position: absolute;
-    right: 0;
-    top: 0;
-  }
-`;
-
-export const Inner = styled.div`
-  overflow: auto;
-`;
-
-export const Header = styled.ul`
-  display: flex;
-  align-items: center;
-
-  li {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    flex: 0 0 30%;
-    padding: 8px 12px;
-
-    &.ready,
-    &.cancel {
-      color: #94959f;
-      background-color: #f9f9fa;
-    }
-
-    &.loading {
-      color: #7497f7;
-      background-color: #e9efff;
-    }
-
-    &.success {
-      color: #4db764;
-      background-color: #edfbf0;
-    }
-
-    &.error {
-      color: #e34040;
-      background-color: #feefef;
-    }
-  }
-
-  li + li {
-    margin-left: 16px;
-  }
-`;
-
-export const Tasks = styled.ul`
-  display: flex;
-  align-items: flex-start;
-
-  li {
-    flex: 0 0 30%;
-    padding-bottom: 8px;
-    overflow: hidden;
-  }
-
-  li + li {
-    margin-left: 16px;
-  }
-`;
diff --git a/config-ui/src/pages/project/home/api.ts 
b/config-ui/src/pages/project/home/api.ts
index 539e91b38..9a9a7d09b 100644
--- a/config-ui/src/pages/project/home/api.ts
+++ b/config-ui/src/pages/project/home/api.ts
@@ -19,7 +19,7 @@
 import { request } from '@/utils';
 
 import type { BlueprintType } from '@/pages/blueprint';
-import type { PipelineType } from '@/pages/pipeline';
+import * as T from '@/routes/pipeline/types';
 
 type GetProjectsParams = {
   page: number;
@@ -31,7 +31,7 @@ type GetProjectsResponse = {
     name: string;
     createdAt: string;
     blueprint: BlueprintType;
-    lastPipeline: PipelineType;
+    lastPipeline: T.Pipeline;
   }>;
   count: number;
 };
diff --git a/config-ui/src/pages/project/home/index.tsx 
b/config-ui/src/pages/project/home/index.tsx
index c9dd39005..79dca269c 100644
--- a/config-ui/src/pages/project/home/index.tsx
+++ b/config-ui/src/pages/project/home/index.tsx
@@ -26,7 +26,7 @@ import { getCron, cronPresets } from '@/config';
 import { useConnections, useRefreshData } from '@/hooks';
 import { DOC_URL } from '@/release';
 import { formatTime, operator } from '@/utils';
-import { PipelineStatus } from '@/pages/pipeline';
+import { PipelineStatus } from '@/routes/pipeline';
 
 import { validName, encodeName } from '../utils';
 import { BlueprintType, ModeEnum } from '../../blueprint';
diff --git a/config-ui/src/pages/pipeline/api.ts 
b/config-ui/src/routes/pipeline/api.ts
similarity index 88%
rename from config-ui/src/pages/pipeline/api.ts
rename to config-ui/src/routes/pipeline/api.ts
index f00fbf623..261e04a99 100644
--- a/config-ui/src/pages/pipeline/api.ts
+++ b/config-ui/src/routes/pipeline/api.ts
@@ -18,25 +18,27 @@
 
 import { request } from '@/utils';
 
-export const getPipeline = (id: ID) => request(`/pipelines/${id}`);
+import * as T from './types';
 
-export const getPipelineTasks = (id: ID) => request(`/pipelines/${id}/tasks`);
+export const getPipelines = (): Promise<{ count: number; pipelines: 
T.Pipeline[] }> => request('/pipelines');
+
+export const getPipeline = (id: ID) => request(`/pipelines/${id}`);
 
 export const deletePipeline = (id: ID) =>
   request(`/pipelines/${id}`, {
     method: 'delete',
   });
 
-export const getPipelineHistorical = (id: ID) => 
request(`/blueprints/${id}/pipelines`);
-
-export const pipelineRerun = (id: ID) =>
+export const rerunPipeline = (id: ID) =>
   request(`/pipelines/${id}/rerun`, {
     method: 'post',
   });
 
+export const getPipelineLog = (id: ID) => 
request(`/pipelines/${id}/logging.tar.gz`);
+
+export const getPipelineTasks = (id: ID) => request(`/pipelines/${id}/tasks`);
+
 export const taskRerun = (id: ID) =>
   request(`/tasks/${id}/rerun`, {
     method: 'post',
   });
-
-export const getPipelineLog = (id: ID) => 
request(`/pipelines/${id}/logging.tar.gz`);
diff --git a/config-ui/src/pages/pipeline/components/duration/index.tsx 
b/config-ui/src/routes/pipeline/components/duration.tsx
similarity index 83%
rename from config-ui/src/pages/pipeline/components/duration/index.tsx
rename to config-ui/src/routes/pipeline/components/duration.tsx
index f154e5a19..829cb548c 100644
--- a/config-ui/src/pages/pipeline/components/duration/index.tsx
+++ b/config-ui/src/routes/pipeline/components/duration.tsx
@@ -16,13 +16,12 @@
  *
  */
 
-import React from 'react';
 import dayjs from 'dayjs';
 
-import { StatusEnum } from '../../types';
+import * as T from '../types';
 
 interface Props {
-  status: StatusEnum;
+  status: T.PipelineStatus;
   beganAt: string | null;
   finishedAt: string | null;
 }
@@ -32,7 +31,14 @@ export const PipelineDuration = ({ status, beganAt, 
finishedAt }: Props) => {
     return <span>-</span>;
   }
 
-  if (![StatusEnum.CANCELLED, StatusEnum.COMPLETED, StatusEnum.PARTIAL, 
StatusEnum.FAILED].includes(status)) {
+  if (
+    ![
+      T.PipelineStatus.CANCELLED,
+      T.PipelineStatus.COMPLETED,
+      T.PipelineStatus.PARTIAL,
+      T.PipelineStatus.FAILED,
+    ].includes(status)
+  ) {
     return <span>{dayjs(beganAt).toNow(true)}</span>;
   }
 
diff --git a/config-ui/src/pages/pipeline/components/index.ts 
b/config-ui/src/routes/pipeline/components/index.ts
similarity index 93%
rename from config-ui/src/pages/pipeline/components/index.ts
rename to config-ui/src/routes/pipeline/components/index.ts
index 8d5dc2b64..fca3387b9 100644
--- a/config-ui/src/pages/pipeline/components/index.ts
+++ b/config-ui/src/routes/pipeline/components/index.ts
@@ -16,8 +16,7 @@
  *
  */
 
-export * from './context';
-export * from './historical';
-export * from './info';
 export * from './status';
+export * from './table';
+export * from './info';
 export * from './tasks';
diff --git a/config-ui/src/pages/pipeline/components/info/index.tsx 
b/config-ui/src/routes/pipeline/components/info.tsx
similarity index 51%
rename from config-ui/src/pages/pipeline/components/info/index.tsx
rename to config-ui/src/routes/pipeline/components/info.tsx
index 0ee64a87d..7399b98ad 100644
--- a/config-ui/src/pages/pipeline/components/info/index.tsx
+++ b/config-ui/src/routes/pipeline/components/info.tsx
@@ -16,41 +16,60 @@
  *
  */
 
-import React from 'react';
+import { useState } from 'react';
 
-import { Loading } from '@/components';
+import { Loading, IconButton } from '@/components';
 import { useAutoRefresh } from '@/hooks';
-import { formatTime } from '@/utils';
+import { formatTime, operator } from '@/utils';
 
-import type { PipelineType } from '../../types';
-import { StatusEnum } from '../../types';
-import * as API from '../../api';
+import * as T from '../types';
+import * as S from '../styled';
+import * as API from '../api';
 
-import { usePipeline } from '../context';
-import { PipelineStatus } from '../status';
-import { PipelineDuration } from '../duration';
-import { PipelineCancel } from '../cancel';
-import { PipelineRerun } from '../rerun';
-
-import * as S from './styled';
+import { PipelineStatus } from './status';
+import { PipelineDuration } from './duration';
 
 interface Props {
   id: ID;
-  style?: React.CSSProperties;
 }
 
-export const PipelineInfo = ({ id, style }: Props) => {
-  const { version } = usePipeline();
+export const PipelineInfo = ({ id }: Props) => {
+  const [operating, setOperating] = useState(false);
 
-  const { data } = useAutoRefresh<PipelineType>(() => API.getPipeline(id), 
[version], {
+  const { data } = useAutoRefresh<T.Pipeline>(() => API.getPipeline(id), [], {
     cancel: (data) => {
       return !!(
         data &&
-        [StatusEnum.COMPLETED, StatusEnum.PARTIAL, StatusEnum.FAILED, 
StatusEnum.CANCELLED].includes(data.status)
+        [
+          T.PipelineStatus.COMPLETED,
+          T.PipelineStatus.PARTIAL,
+          T.PipelineStatus.FAILED,
+          T.PipelineStatus.CANCELLED,
+        ].includes(data.status)
       );
     },
   });
 
+  const handleCancel = async () => {
+    const [success] = await operator(() => API.deletePipeline(id), {
+      setOperating,
+    });
+
+    //   if (success) {
+    //     setVersion((v) => v + 1);
+    //   }
+  };
+
+  const handleRerun = async () => {
+    const [success] = await operator(() => API.rerunPipeline(id), {
+      setOperating,
+    });
+
+    // if (success) {
+    //   setVersion((v) => v + 1);
+    // }
+  };
+
   if (!data) {
     return <Loading />;
   }
@@ -58,7 +77,7 @@ export const PipelineInfo = ({ id, style }: Props) => {
   const { status, beganAt, finishedAt, stage, finishedTasks, totalTasks, 
message } = data;
 
   return (
-    <S.Wrapper style={style}>
+    <S.Info>
       <ul>
         <li>
           <span>Status</span>
@@ -87,11 +106,20 @@ export const PipelineInfo = ({ id, style }: Props) => {
           </strong>
         </li>
         <li>
-          <PipelineCancel id={id} status={status} />
-          <PipelineRerun type="pipeline" id={id} status={status} />
+          {[T.PipelineStatus.ACTIVE, T.PipelineStatus.RUNNING, 
T.PipelineStatus.RERUN].includes(status) && (
+            <IconButton loading={operating} icon="disable" tooltip="Cancel" 
onClick={handleCancel} />
+          )}
+          {[
+            T.PipelineStatus.COMPLETED,
+            T.PipelineStatus.PARTIAL,
+            T.PipelineStatus.FAILED,
+            T.PipelineStatus.CANCELLED,
+          ].includes(status) && (
+            <IconButton loading={operating} icon="repeat" tooltip="Rerun 
failed tasks" onClick={handleRerun} />
+          )}
         </li>
       </ul>
-      {StatusEnum.FAILED === status && <p className="'message'">{message}</p>}
-    </S.Wrapper>
+      {T.PipelineStatus.FAILED === status && <p 
className="'message'">{message}</p>}
+    </S.Info>
   );
 };
diff --git a/config-ui/src/routes/pipeline/components/status.tsx 
b/config-ui/src/routes/pipeline/components/status.tsx
new file mode 100644
index 000000000..b07226672
--- /dev/null
+++ b/config-ui/src/routes/pipeline/components/status.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 { Icon, IconName } from '@blueprintjs/core';
+import classNames from 'classnames';
+
+import { Loading } from '@/components';
+
+import * as T from '../types';
+import * as S from '../styled';
+import * as C from '../constant';
+
+interface Props {
+  status: T.PipelineStatus;
+}
+
+export const PipelineStatus = ({ status }: Props) => {
+  const cls = classNames({
+    ready: [T.PipelineStatus.CREATED, 
T.PipelineStatus.PENDING].includes(status),
+    loading: [T.PipelineStatus.ACTIVE, T.PipelineStatus.RUNNING, 
T.PipelineStatus.RERUN].includes(status),
+    success: [T.PipelineStatus.COMPLETED, 
T.PipelineStatus.PARTIAL].includes(status),
+    error: status === T.PipelineStatus.FAILED,
+    cancel: status === T.PipelineStatus.CANCELLED,
+  });
+
+  return (
+    <S.StatusWrapper className={cls}>
+      {C.PipeLineStatusIcon[status] === 'loading' ? (
+        <Loading style={{ marginRight: 4 }} size={14} />
+      ) : (
+        <Icon style={{ marginRight: 4 }} icon={C.PipeLineStatusIcon[status] as 
IconName} />
+      )}
+      <span>{C.PipeLineStatusLabel[status]}</span>
+    </S.StatusWrapper>
+  );
+};
diff --git a/config-ui/src/pages/pipeline/components/historical/index.tsx 
b/config-ui/src/routes/pipeline/components/table.tsx
similarity index 65%
rename from config-ui/src/pages/pipeline/components/historical/index.tsx
rename to config-ui/src/routes/pipeline/components/table.tsx
index 570d80665..c40f19627 100644
--- a/config-ui/src/pages/pipeline/components/historical/index.tsx
+++ b/config-ui/src/routes/pipeline/components/table.tsx
@@ -22,48 +22,37 @@ import { pick } from 'lodash';
 import { saveAs } from 'file-saver';
 
 import { DEVLAKE_ENDPOINT } from '@/config';
-import type { ColumnType } from '@/components';
-import { Card, Table, Inspector, Dialog, IconButton } from '@/components';
-import { useAutoRefresh } from '@/hooks';
+import { Table, ColumnType, IconButton, Inspector, Dialog } from 
'@/components';
 import { formatTime } from '@/utils';
 
-import type { PipelineType } from '../../types';
-import { StatusEnum } from '../../types';
-import * as API from '../../api';
+import * as T from '../types';
+import * as API from '../api';
 
-import { usePipeline } from '../context';
-import { PipelineStatus } from '../status';
-import { PipelineDuration } from '../duration';
-import { PipelineTasks } from '../tasks';
+import { PipelineStatus } from './status';
+import { PipelineDuration } from './duration';
+import { PipelineTasks } from './tasks';
 
 interface Props {
-  blueprintId: ID;
+  loading: boolean;
+  dataSource: T.Pipeline[];
+  pagination?: {
+    total: number;
+    page: number;
+    pageSize: number;
+    onChange: (page: number) => void;
+  };
+  noData?: {
+    text?: React.ReactNode;
+    btnText?: string;
+    onCreate?: () => void;
+  };
 }
 
-export const PipelineHistorical = ({ blueprintId }: Props) => {
+export const PipelineTable = ({ dataSource, pagination, noData }: Props) => {
   const [JSON, setJSON] = useState<any>(null);
-  const [ID, setID] = useState<ID | null>(null);
-
-  const { version } = usePipeline();
-
-  const { data } = useAutoRefresh<PipelineType[]>(
-    async () => {
-      const res = await API.getPipelineHistorical(blueprintId);
-      return res.pipelines;
-    },
-    [version],
-    {
-      cancel: (data) =>
-        !!(
-          data &&
-          data.every((it) =>
-            [StatusEnum.COMPLETED, StatusEnum.PARTIAL, StatusEnum.CANCELLED, 
StatusEnum.FAILED].includes(it.status),
-          )
-        ),
-    },
-  );
+  const [id, setId] = useState<ID | null>(null);
 
-  const handleShowJSON = (row: PipelineType) => {
+  const handleShowJSON = (row: T.Pipeline) => {
     setJSON(pick(row, ['id', 'name', 'plan', 'skipOnFail']));
   };
 
@@ -75,12 +64,17 @@ export const PipelineHistorical = ({ blueprintId }: Props) 
=> {
   };
 
   const handleShowDetails = (id: ID) => {
-    setID(id);
+    setId(id);
   };
 
   const columns = useMemo(
     () =>
       [
+        {
+          title: 'ID',
+          dataIndex: 'id',
+          key: 'id',
+        },
         {
           title: 'Status',
           dataIndex: 'status',
@@ -92,20 +86,19 @@ export const PipelineHistorical = ({ blueprintId }: Props) 
=> {
           dataIndex: 'beganAt',
           key: 'beganAt',
           align: 'center',
-          render: (val: string | null) => (val ? formatTime(val) : '-'),
+          render: (val) => formatTime(val),
         },
         {
           title: 'Completed at',
           dataIndex: 'finishedAt',
           key: 'finishedAt',
           align: 'center',
-          render: (val: string | null) => (val ? formatTime(val) : '-'),
+          render: (val) => formatTime(val),
         },
         {
           title: 'Duration',
           dataIndex: ['status', 'beganAt', 'finishedAt'],
           key: 'duration',
-          align: 'center',
           render: ({ status, beganAt, finishedAt }) => (
             <PipelineDuration status={status} beganAt={beganAt} 
finishedAt={finishedAt} />
           ),
@@ -123,23 +116,19 @@ export const PipelineHistorical = ({ blueprintId }: 
Props) => {
             </ButtonGroup>
           ),
         },
-      ] as ColumnType<PipelineType>,
+      ] as ColumnType<T.Pipeline>,
     [],
   );
 
-  if (!data) {
-    return <Card>There are no historical runs associated with this 
blueprint.</Card>;
-  }
-
   return (
-    <div>
-      <Table columns={columns} dataSource={data} />
+    <>
+      <Table columns={columns} dataSource={dataSource} pagination={pagination} 
noData={noData} />
       {JSON && <Inspector isOpen title={`Pipeline ${JSON?.id}`} data={JSON} 
onClose={() => setJSON(null)} />}
-      {ID && (
-        <Dialog style={{ width: 820 }} isOpen title={`Pipeline ${ID}`} 
footer={null} onCancel={() => setID(null)}>
-          <PipelineTasks id={ID} />
+      {id && (
+        <Dialog style={{ width: 820 }} isOpen title={`Pipeline ${id}`} 
footer={null} onCancel={() => setId(null)}>
+          <PipelineTasks id={id} />
         </Dialog>
       )}
-    </div>
+    </>
   );
 };
diff --git a/config-ui/src/pages/pipeline/components/task/index.tsx 
b/config-ui/src/routes/pipeline/components/task.tsx
similarity index 70%
rename from config-ui/src/pages/pipeline/components/task/index.tsx
rename to config-ui/src/routes/pipeline/components/task.tsx
index 297a6a57b..6ecaf61af 100644
--- a/config-ui/src/pages/pipeline/components/task/index.tsx
+++ b/config-ui/src/routes/pipeline/components/task.tsx
@@ -16,25 +16,26 @@
  *
  */
 
-import { useMemo } from 'react';
+import { useState, useMemo } from 'react';
 import { Intent } from '@blueprintjs/core';
 
-import { TextTooltip } from '@/components';
+import { TextTooltip, IconButton } from '@/components';
 import { getPluginConfig } from '@/plugins';
+import { operator } from '@/utils';
 
-import type { TaskType } from '@/pages';
-import { StatusEnum } from '@/pages';
+import * as T from '../types';
+import * as S from '../styled';
+import * as API from '../api';
 
-import { PipelineDuration } from '../duration';
-import { PipelineRerun } from '../rerun';
-
-import * as S from './styled';
+import { PipelineDuration } from './duration';
 
 interface Props {
-  task: TaskType;
+  task: T.PipelineTask;
 }
 
 export const PipelineTask = ({ task }: Props) => {
+  const [operating, setOperating] = useState(false);
+
   const { id, beganAt, finishedAt, status, message, progressDetail } = task;
 
   const [icon, name] = useMemo(() => {
@@ -86,19 +87,29 @@ export const PipelineTask = ({ task }: Props) => {
     return [config.icon, name];
   }, [task]);
 
+  const handleRerun = async () => {
+    const [success] = await operator(() => API.taskRerun(id), {
+      setOperating,
+    });
+
+    // if (success) {
+    //   setVersion((v) => v + 1);
+    // }
+  };
+
   return (
-    <S.Wrapper>
-      <S.Info>
+    <S.Task>
+      <div className="info">
         <div className="title">
           <img src={icon} alt="" />
-          <strong>Task{task.id}</strong>
+          <strong>Task{id}</strong>
           <span>
             <TextTooltip content={name}>{name}</TextTooltip>
           </span>
         </div>
-        {[status === StatusEnum.CREATED, StatusEnum.PENDING].includes(status) 
&& <p>Subtasks pending</p>}
+        {[status === T.PipelineStatus.CREATED, 
T.PipelineStatus.PENDING].includes(status) && <p>Subtasks pending</p>}
 
-        {[StatusEnum.ACTIVE, StatusEnum.RUNNING].includes(status) && (
+        {[T.PipelineStatus.ACTIVE, T.PipelineStatus.RUNNING].includes(status) 
&& (
           <p>
             Subtasks running
             <strong style={{ marginLeft: 8 }}>
@@ -107,20 +118,27 @@ export const PipelineTask = ({ task }: Props) => {
           </p>
         )}
 
-        {status === StatusEnum.COMPLETED && <p>All Subtasks completed</p>}
+        {status === T.PipelineStatus.COMPLETED && <p>All Subtasks 
completed</p>}
 
-        {status === StatusEnum.FAILED && (
+        {status === T.PipelineStatus.FAILED && (
           <TextTooltip intent={Intent.DANGER} content={message}>
             <p className="error">Task failed: hover to view the reason</p>
           </TextTooltip>
         )}
 
-        {status === StatusEnum.CANCELLED && <p>Subtasks canceled</p>}
-      </S.Info>
-      <S.Duration>
+        {status === T.PipelineStatus.CANCELLED && <p>Subtasks canceled</p>}
+      </div>
+      <div className="duration">
         <PipelineDuration status={status} beganAt={beganAt} 
finishedAt={finishedAt} />
-        <PipelineRerun type="task" id={id} status={status} />
-      </S.Duration>
-    </S.Wrapper>
+        {[
+          T.PipelineStatus.COMPLETED,
+          T.PipelineStatus.PARTIAL,
+          T.PipelineStatus.FAILED,
+          T.PipelineStatus.CANCELLED,
+        ].includes(status) && (
+          <IconButton loading={operating} icon="repeat" tooltip="Rerun task" 
onClick={handleRerun} />
+        )}
+      </div>
+    </S.Task>
   );
 };
diff --git a/config-ui/src/pages/pipeline/components/tasks/index.tsx 
b/config-ui/src/routes/pipeline/components/tasks.tsx
similarity index 77%
rename from config-ui/src/pages/pipeline/components/tasks/index.tsx
rename to config-ui/src/routes/pipeline/components/tasks.tsx
index 3380aa455..a4b0cdcec 100644
--- a/config-ui/src/pages/pipeline/components/tasks/index.tsx
+++ b/config-ui/src/routes/pipeline/components/tasks.tsx
@@ -23,14 +23,11 @@ import { groupBy, sortBy } from 'lodash';
 import { Loading } from '@/components';
 import { useAutoRefresh } from '@/hooks';
 
-import type { TaskType } from '../../types';
-import { StatusEnum } from '../../types';
-import * as API from '../../api';
+import * as T from '../types';
+import * as S from '../styled';
+import * as API from '../api';
 
-import { usePipeline } from '../context';
-import { PipelineTask } from '../task';
-
-import * as S from './styled';
+import { PipelineTask } from './task';
 
 interface Props {
   id: ID;
@@ -40,19 +37,21 @@ interface Props {
 export const PipelineTasks = ({ id, style }: Props) => {
   const [isOpen, setIsOpen] = useState(true);
 
-  const { version } = usePipeline();
+  // const { version } = usePipeline();
 
-  const { data } = useAutoRefresh<TaskType[]>(
+  const { data } = useAutoRefresh<T.PipelineTask[]>(
     async () => {
       const taskRes = await API.getPipelineTasks(id);
       return taskRes.tasks;
     },
-    [version],
+    [],
     {
       cancel: (data) => {
         return !!(
           data &&
-          data.every((task) => [StatusEnum.COMPLETED, StatusEnum.FAILED, 
StatusEnum.CANCELLED].includes(task.status))
+          data.every((task) =>
+            [T.PipelineStatus.COMPLETED, T.PipelineStatus.FAILED, 
T.PipelineStatus.CANCELLED].includes(task.status),
+          )
         );
       },
     },
@@ -63,23 +62,25 @@ export const PipelineTasks = ({ id, style }: Props) => {
   const handleToggleOpen = () => setIsOpen(!isOpen);
 
   return (
-    <S.Wrapper style={style}>
-      <S.Inner>
-        <S.Header>
+    <S.Tasks>
+      <div className="inner">
+        <S.TasksHeader>
           {Object.keys(stages).map((key) => {
             let status;
 
             switch (true) {
-              case !!stages[key].find((task) => [StatusEnum.ACTIVE, 
StatusEnum.RUNNING].includes(task.status)):
+              case !!stages[key].find((task) =>
+                [T.PipelineStatus.ACTIVE, 
T.PipelineStatus.RUNNING].includes(task.status),
+              ):
                 status = 'loading';
                 break;
-              case stages[key].every((task) => task.status === 
StatusEnum.COMPLETED):
+              case stages[key].every((task) => task.status === 
T.PipelineStatus.COMPLETED):
                 status = 'success';
                 break;
-              case !!stages[key].find((task) => task.status === 
StatusEnum.FAILED):
+              case !!stages[key].find((task) => task.status === 
T.PipelineStatus.FAILED):
                 status = 'error';
                 break;
-              case !!stages[key].find((task) => task.status === 
StatusEnum.CANCELLED):
+              case !!stages[key].find((task) => task.status === 
T.PipelineStatus.CANCELLED):
                 status = 'cancel';
                 break;
               default:
@@ -97,9 +98,9 @@ export const PipelineTasks = ({ id, style }: Props) => {
               </li>
             );
           })}
-        </S.Header>
+        </S.TasksHeader>
         <Collapse isOpen={isOpen}>
-          <S.Tasks>
+          <S.TasksList>
             {Object.keys(stages).map((key) => (
               <li key={key}>
                 {stages[key].map((task) => (
@@ -107,15 +108,15 @@ export const PipelineTasks = ({ id, style }: Props) => {
                 ))}
               </li>
             ))}
-          </S.Tasks>
+          </S.TasksList>
         </Collapse>
-      </S.Inner>
+      </div>
       <Button
         className="collapse-control"
         minimal
         icon={isOpen ? 'chevron-down' : 'chevron-up'}
         onClick={handleToggleOpen}
       />
-    </S.Wrapper>
+    </S.Tasks>
   );
 };
diff --git a/config-ui/src/routes/pipeline/constant.ts 
b/config-ui/src/routes/pipeline/constant.ts
new file mode 100644
index 000000000..79782789b
--- /dev/null
+++ b/config-ui/src/routes/pipeline/constant.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 * as T from './types';
+
+export const PipeLineStatusIcon = {
+  [T.PipelineStatus.CREATED]: 'stopwatch',
+  [T.PipelineStatus.PENDING]: 'stopwatch',
+  [T.PipelineStatus.ACTIVE]: 'loading',
+  [T.PipelineStatus.RUNNING]: 'loading',
+  [T.PipelineStatus.RERUN]: 'loading',
+  [T.PipelineStatus.COMPLETED]: 'tick-circle',
+  [T.PipelineStatus.PARTIAL]: 'tick-circle',
+  [T.PipelineStatus.FAILED]: 'delete',
+  [T.PipelineStatus.CANCELLED]: 'undo',
+};
+
+export const PipeLineStatusLabel = {
+  [T.PipelineStatus.CREATED]: 'Created (Pending)',
+  [T.PipelineStatus.PENDING]: 'Created (Pending)',
+  [T.PipelineStatus.ACTIVE]: 'In Progress',
+  [T.PipelineStatus.RUNNING]: 'In Progress',
+  [T.PipelineStatus.RERUN]: 'In Progress',
+  [T.PipelineStatus.COMPLETED]: 'Succeeded',
+  [T.PipelineStatus.PARTIAL]: 'Partial Success',
+  [T.PipelineStatus.FAILED]: 'Failed',
+  [T.PipelineStatus.CANCELLED]: 'Cancelled',
+};
diff --git a/config-ui/src/pages/pipeline/index.ts 
b/config-ui/src/routes/pipeline/index.ts
similarity index 93%
rename from config-ui/src/pages/pipeline/index.ts
rename to config-ui/src/routes/pipeline/index.ts
index eecb8b531..2e0c4e264 100644
--- a/config-ui/src/pages/pipeline/index.ts
+++ b/config-ui/src/routes/pipeline/index.ts
@@ -16,5 +16,6 @@
  *
  */
 
-export * from './types';
 export * from './components';
+export * from './pipelines';
+export * from './pipeline';
diff --git a/config-ui/src/pages/pipeline/components/context/index.tsx 
b/config-ui/src/routes/pipeline/pipeline.tsx
similarity index 56%
rename from config-ui/src/pages/pipeline/components/context/index.tsx
rename to config-ui/src/routes/pipeline/pipeline.tsx
index 77b883559..1cf17f9b8 100644
--- a/config-ui/src/pages/pipeline/components/context/index.tsx
+++ b/config-ui/src/routes/pipeline/pipeline.tsx
@@ -15,24 +15,32 @@
  * limitations under the License.
  *
  */
+import { useParams } from 'react-router-dom';
 
-import React, { useContext, useState } from 'react';
+import { PageHeader, Card } from '@/components';
 
-const PipelineContext = React.createContext<{
-  version: number;
-  setVersion: React.Dispatch<React.SetStateAction<number>>;
-}>({
-  version: 0,
-  setVersion: () => {},
-});
+import { PipelineInfo, PipelineTasks } from './components';
 
-interface Props {
-  children: React.ReactNode;
-}
+export const Pipeline = () => {
+  const { id } = useParams();
 
-export const PipelineContextProvider = ({ children }: Props) => {
-  const [version, setVersion] = useState(0);
-  return <PipelineContext.Provider value={{ version, setVersion 
}}>{children}</PipelineContext.Provider>;
+  return (
+    <PageHeader
+      breadcrumbs={[
+        { name: 'Advanced', path: '/blueprints' },
+        { name: 'Pipelines', path: '/pipelines' },
+        {
+          name: id as string,
+          path: `/pipelines/${id}`,
+        },
+      ]}
+    >
+      <Card>
+        <PipelineInfo id={id as string} />
+      </Card>
+      <Card>
+        <PipelineTasks id={id as string} />
+      </Card>
+    </PageHeader>
+  );
 };
-
-export const usePipeline = () => useContext(PipelineContext);
diff --git a/config-ui/src/routes/pipeline/pipelines.tsx 
b/config-ui/src/routes/pipeline/pipelines.tsx
new file mode 100644
index 000000000..b990054b8
--- /dev/null
+++ b/config-ui/src/routes/pipeline/pipelines.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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 } from 'react';
+
+import { PageHeader } from '@/components';
+import { useRefreshData } from '@/hooks';
+
+import { PipelineTable } from './components';
+import * as API from './api';
+
+export const Pipelines = () => {
+  const [page, setPage] = useState(1);
+  const [pageSize] = useState(20);
+
+  const { ready, data } = useRefreshData(() => API.getPipelines());
+
+  const [dataSource, total] = useMemo(() => [(data?.pipelines ?? []).map((it) 
=> it), data?.count ?? 0], [data]);
+
+  return (
+    <PageHeader
+      breadcrumbs={[
+        { name: 'Advanced', path: '/blueprints' },
+        { name: 'Pipelines', path: '/pipelines' },
+      ]}
+    >
+      <PipelineTable
+        loading={!ready}
+        dataSource={dataSource}
+        pagination={{
+          total,
+          page,
+          pageSize,
+          onChange: setPage,
+        }}
+        noData={{
+          text: 'Add new projects to see engineering metrics based on 
projects.',
+        }}
+      />
+    </PageHeader>
+  );
+};
diff --git a/config-ui/src/routes/pipeline/styled.ts 
b/config-ui/src/routes/pipeline/styled.ts
new file mode 100644
index 000000000..bcda461dd
--- /dev/null
+++ b/config-ui/src/routes/pipeline/styled.ts
@@ -0,0 +1,202 @@
+/*
+ * 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 { Colors } from '@blueprintjs/core';
+import styled from 'styled-components';
+
+export const StatusWrapper = styled.div`
+  &.ready,
+  &.cancel {
+    color: #94959f;
+  }
+
+  &.loading {
+    color: #7497f7;
+  }
+
+  &.success {
+    color: ${Colors.GREEN3};
+  }
+
+  &.error {
+    color: ${Colors.RED3};
+  }
+`;
+
+export const Info = styled.div`
+  ul {
+    display: flex;
+    align-items: center;
+  }
+
+  li {
+    flex: 5;
+    display: flex;
+    flex-direction: column;
+
+    &:last-child {
+      flex: 1;
+    }
+
+    & > span {
+      font-size: 12px;
+      color: #94959f;
+      text-align: center;
+    }
+
+    & > strong {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 8px;
+    }
+  }
+
+  p.message {
+    margin: 8px 0 0;
+    color: ${Colors.RED3};
+  }
+`;
+
+export const Tasks = styled.div`
+  position: relative;
+  padding-right: 36px;
+
+  .inner {
+    overflow: auto;
+  }
+
+  .collapse-control {
+    position: absolute;
+    right: 0;
+    top: 0;
+  }
+`;
+
+export const TasksHeader = styled.ul`
+  display: flex;
+  align-items: center;
+
+  li {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    flex: 0 0 30%;
+    padding: 8px 12px;
+
+    &.ready,
+    &.cancel {
+      color: #94959f;
+      background-color: #f9f9fa;
+    }
+
+    &.loading {
+      color: #7497f7;
+      background-color: #e9efff;
+    }
+
+    &.success {
+      color: #4db764;
+      background-color: #edfbf0;
+    }
+
+    &.error {
+      color: #e34040;
+      background-color: #feefef;
+    }
+  }
+
+  li + li {
+    margin-left: 16px;
+  }
+`;
+
+export const TasksList = styled.ul`
+  display: flex;
+  align-items: flex-start;
+
+  li {
+    flex: 0 0 30%;
+    padding-bottom: 8px;
+    overflow: hidden;
+  }
+
+  li + li {
+    margin-left: 16px;
+  }
+`;
+
+export const Task = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 0;
+  height: 80px;
+  border-bottom: 1px solid #dbe4fd;
+  box-sizing: border-box;
+
+  .info {
+    flex: auto;
+    overflow: hidden;
+
+    .title {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+
+      & > img {
+        width: 20px;
+      }
+
+      & > strong {
+        margin: 0 4px;
+      }
+
+      & > span {
+        flex: auto;
+        overflow: hidden;
+      }
+    }
+
+    p {
+      padding-left: 26px;
+      margin: 0;
+      font-size: 12px;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+
+      &.error {
+        color: ${Colors.RED3};
+      }
+    }
+  }
+
+  .duration {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    flex: 0 0 80px;
+    text-align: right;
+
+    .bp4-icon {
+      margin-top: 4px;
+      cursor: pointer;
+    }
+  }
+`;
diff --git a/config-ui/src/pages/pipeline/types.ts 
b/config-ui/src/routes/pipeline/types.ts
similarity index 91%
rename from config-ui/src/pages/pipeline/types.ts
rename to config-ui/src/routes/pipeline/types.ts
index 275a230eb..cd4fecbd4 100644
--- a/config-ui/src/pages/pipeline/types.ts
+++ b/config-ui/src/routes/pipeline/types.ts
@@ -16,7 +16,7 @@
  *
  */
 
-export enum StatusEnum {
+export enum PipelineStatus {
   CREATED = 'TASK_CREATED',
   PENDING = 'TASK_PENDING',
   ACTIVE = 'TASK_ACTIVE',
@@ -28,9 +28,9 @@ export enum StatusEnum {
   CANCELLED = 'TASK_CANCELLED',
 }
 
-export type PipelineType = {
+export type Pipeline = {
   id: ID;
-  status: StatusEnum;
+  status: PipelineStatus;
   beganAt: string | null;
   finishedAt: string | null;
   stage: number;
@@ -39,10 +39,10 @@ export type PipelineType = {
   message: string;
 };
 
-export type TaskType = {
+export type PipelineTask = {
   id: ID;
   plugin: string;
-  status: StatusEnum;
+  status: PipelineStatus;
   pipelineRow: number;
   pipelineCol: number;
   beganAt: string | null;

Reply via email to