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 94b96f838 refactor(config-ui): the pipeline (#4196)
94b96f838 is described below
commit 94b96f838a9779034e69be2e5407404d79611f4e
Author: 青湛 <[email protected]>
AuthorDate: Mon Jan 16 17:51:41 2023 +0800
refactor(config-ui): the pipeline (#4196)
* feat(config-ui): update global styles
* refactor(config-ui): move components/delete-button to
components/action/delete-button
* feat(config-ui): add new action component icon-button
* feat(config-ui): add new component text-tooltip
* feat(config-ui): add new hook use-operator
* feat(config-ui): add pipeline component context
* feat(config-ui): add pipeline component cancel and rerun
* refactor(config-ui): use pipeline status component to replace misc
* refactor(config-ui): move pipeline/detail/components/task to
pipeline/components/task
* refactor(config-ui): remove the pages/pipeline/detail
* feat(config-ui): add pipeline component info and tasks
* refactor(config-ui): optimize pipeline historical with new components
* refactor(config-ui): optimize blueprint detail status with new components
* fix(config-ui): optimize run now failure prompt
---
.../{ => action}/delete-button/index.tsx | 0
.../action/icon-button/index.tsx} | 23 +--
.../pipeline/detail => components/action}/index.ts | 3 +-
config-ui/src/components/index.ts | 3 +-
.../text-tooltip/index.tsx} | 47 ++---
config-ui/src/hooks/index.ts | 1 +
.../detail/use-detail.ts => hooks/use-operator.ts} | 51 ++----
config-ui/src/index.css | 36 ++++
.../src/pages/blueprint/detail/panel/status.tsx | 30 +++-
config-ui/src/pages/blueprint/detail/use-detail.ts | 1 +
config-ui/src/pages/pipeline/api.ts | 19 ++
.../cancel/index.tsx} | 25 ++-
.../api.ts => components/context/index.tsx} | 32 ++--
.../pages/pipeline/components/historical/index.tsx | 68 ++++---
config-ui/src/pages/pipeline/components/index.ts | 4 +-
.../src/pages/pipeline/components/info/index.tsx | 97 ++++++++++
.../components/{historical => info}/styled.ts | 44 +++--
.../src/pages/pipeline/components/rerun/index.tsx | 51 ++++++
.../{misc.ts => components/status/index.tsx} | 28 ++-
.../components/{historical => status}/styled.ts | 9 +-
.../{detail => }/components/task/index.tsx | 54 +++---
.../{detail => }/components/task/styled.ts | 9 +-
.../src/pages/pipeline/components/tasks/index.tsx | 125 +++++++++++++
.../{detail => components/tasks}/styled.ts | 68 +------
.../src/pages/pipeline/detail/components/index.ts | 19 --
.../src/pages/pipeline/detail/pipeline-detail.tsx | 195 ---------------------
config-ui/src/pages/pipeline/index.ts | 2 -
27 files changed, 565 insertions(+), 479 deletions(-)
diff --git a/config-ui/src/components/delete-button/index.tsx
b/config-ui/src/components/action/delete-button/index.tsx
similarity index 100%
rename from config-ui/src/components/delete-button/index.tsx
rename to config-ui/src/components/action/delete-button/index.tsx
diff --git a/config-ui/src/pages/pipeline/detail/pipeline-info.tsx
b/config-ui/src/components/action/icon-button/index.tsx
similarity index 63%
copy from config-ui/src/pages/pipeline/detail/pipeline-info.tsx
copy to config-ui/src/components/action/icon-button/index.tsx
index 13e4ced79..7a40dc80d 100644
--- a/config-ui/src/pages/pipeline/detail/pipeline-info.tsx
+++ b/config-ui/src/components/action/icon-button/index.tsx
@@ -17,19 +17,20 @@
*/
import React from 'react';
-
-import { Card } from '@/components';
-
-import { PipelineDetail } from './pipeline-detail';
+import { Button, Intent, Position, IconName } from '@blueprintjs/core';
+import { Tooltip2 } from '@blueprintjs/popover2';
interface Props {
- id?: ID;
+ icon: IconName;
+ tooltip: string;
+ loading?: boolean;
+ onClick?: () => void;
}
-export const PipelineInfo = ({ id }: Props) => {
- if (!id) {
- return <Card>There is no current run for this blueprint.</Card>;
- }
-
- return <PipelineDetail id={id} />;
+export const IconButton = ({ icon, tooltip, loading, onClick }: Props) => {
+ return (
+ <Tooltip2 intent={Intent.PRIMARY} position={Position.TOP}
content={tooltip}>
+ <Button loading={loading} minimal intent={Intent.PRIMARY} icon={icon}
onClick={onClick} />
+ </Tooltip2>
+ );
};
diff --git a/config-ui/src/pages/pipeline/detail/index.ts
b/config-ui/src/components/action/index.ts
similarity index 92%
rename from config-ui/src/pages/pipeline/detail/index.ts
rename to config-ui/src/components/action/index.ts
index d0223bac2..06dc0b92e 100644
--- a/config-ui/src/pages/pipeline/detail/index.ts
+++ b/config-ui/src/components/action/index.ts
@@ -16,4 +16,5 @@
*
*/
-export * from './pipeline-info';
+export * from './delete-button';
+export * from './icon-button';
diff --git a/config-ui/src/components/index.ts
b/config-ui/src/components/index.ts
index 50248a71d..d46332c87 100644
--- a/config-ui/src/components/index.ts
+++ b/config-ui/src/components/index.ts
@@ -26,4 +26,5 @@ export * from './toast';
export * from './logo';
export * from './card';
export * from './inspector';
-export * from './delete-button';
+export * from './action';
+export * from './text-tooltip';
diff --git a/config-ui/src/pages/pipeline/components/historical/styled.ts
b/config-ui/src/components/text-tooltip/index.tsx
similarity index 56%
copy from config-ui/src/pages/pipeline/components/historical/styled.ts
copy to config-ui/src/components/text-tooltip/index.tsx
index 21362c88b..03d90e766 100644
--- a/config-ui/src/pages/pipeline/components/historical/styled.ts
+++ b/config-ui/src/components/text-tooltip/index.tsx
@@ -16,31 +16,34 @@
*
*/
-import { Colors } from '@blueprintjs/core';
+import React from 'react';
+import type { IntentProps } from '@blueprintjs/core';
+import { Position } from '@blueprintjs/core';
+import { Tooltip2 } from '@blueprintjs/popover2';
import styled from 'styled-components';
-export const StatusColumn = styled.div`
- display: flex;
- align-items: center;
+const Wrapper = styled.div`
+ width: 100%;
- .bp4-icon {
- margin-right: 4px;
- }
-
- &.ready,
- &.cancel {
- color: #94959f;
- }
-
- &.loading {
- color: #7497f7;
+ & > .bp4-popover2-target {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
+`;
- &.success {
- color: ${Colors.GREEN3};
- }
+interface Props extends IntentProps {
+ content: string;
+ children: React.ReactNode;
+}
- &.error {
- color: ${Colors.RED3};
- }
-`;
+export const TextTooltip = ({ intent, content, children }: Props) => {
+ return (
+ <Wrapper>
+ <Tooltip2 intent={intent} position={Position.TOP} content={content}>
+ {children}
+ </Tooltip2>
+ </Wrapper>
+ );
+};
diff --git a/config-ui/src/hooks/index.ts b/config-ui/src/hooks/index.ts
index bd3a94b28..bdf49dea4 100644
--- a/config-ui/src/hooks/index.ts
+++ b/config-ui/src/hooks/index.ts
@@ -17,3 +17,4 @@
*/
export * from './use-auto-refresh';
+export * from './use-operator';
diff --git a/config-ui/src/pages/pipeline/detail/use-detail.ts
b/config-ui/src/hooks/use-operator.ts
similarity index 55%
rename from config-ui/src/pages/pipeline/detail/use-detail.ts
rename to config-ui/src/hooks/use-operator.ts
index 00f7eb582..1dba1f79c 100644
--- a/config-ui/src/pages/pipeline/detail/use-detail.ts
+++ b/config-ui/src/hooks/use-operator.ts
@@ -20,54 +20,33 @@ import { useState, useMemo } from 'react';
import { operator } from '@/utils';
-import * as API from './api';
-
-export interface Props {
- id: ID;
-}
-
-export const useDetail = ({ id }: Props) => {
- const [version, setVersion] = useState(0);
+export const useOperator = <T>(
+ request: (paylod?: any) => Promise<T>,
+ options?: {
+ callback?: () => void;
+ formatReason?: () => string;
+ formatMessage?: () => string;
+ },
+) => {
const [operating, setOperating] = useState(false);
- const handlePipelineCancel = async () => {
- const [success] = await operator(() => API.deletePipeline(id), {
- setOperating,
- });
-
- if (success) {
- setVersion(version + 1);
- }
- };
-
- const handlePipelineRerun = async () => {
- const [success] = await operator(() => API.pipelineRerun(id), {
- setOperating,
- });
-
- if (success) {
- setVersion(version + 1);
- }
- };
-
- const handleTaskRertun = async (id: ID) => {
- const [success] = await operator(() => API.taskRerun(id), {
+ const handleSubmit = async (paylod?: any) => {
+ const [success] = await operator(() => request(paylod), {
setOperating,
+ formatReason: options?.formatReason,
+ formatMessage: options?.formatMessage,
});
if (success) {
- setVersion(version + 1);
+ options?.callback?.();
}
};
return useMemo(
() => ({
- version,
operating,
- onCancel: handlePipelineCancel,
- onRerun: handlePipelineRerun,
- onRerunTask: handleTaskRertun,
+ onSubmit: handleSubmit,
}),
- [version, operating],
+ [operating],
);
};
diff --git a/config-ui/src/index.css b/config-ui/src/index.css
index fb1a71a35..352b633b2 100644
--- a/config-ui/src/index.css
+++ b/config-ui/src/index.css
@@ -22,3 +22,39 @@
@import '~@blueprintjs/select/lib/css/blueprint-select.css';
@import '~@blueprintjs/popover2/lib/css/blueprint-popover2.css';
@import '~@blueprintjs/datetime/lib/css/blueprint-datetime.css';
+
+h1 {
+ font-size: 20px;
+}
+
+h2 {
+ font-size: 18px;
+}
+
+h3 {
+ font-size: 16px;
+}
+
+h4 {
+ font-size: 14px;
+}
+
+h5 {
+ font-size: 12px;
+}
+
+h6 {
+ font-size: 10px;
+}
+
+ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+p {
+ margin: 0 0 8px 0;
+ font-size: 12px;
+ color: #94959f;
+}
diff --git a/config-ui/src/pages/blueprint/detail/panel/status.tsx
b/config-ui/src/pages/blueprint/detail/panel/status.tsx
index b3d10dc28..cb01d53ea 100644
--- a/config-ui/src/pages/blueprint/detail/panel/status.tsx
+++ b/config-ui/src/pages/blueprint/detail/panel/status.tsx
@@ -20,7 +20,8 @@ import React, { useMemo } from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { getCron } from '@/config';
-import { PipelineInfo, PipelineHistorical } from '@/pages';
+import { Card } from '@/components';
+import { PipelineContextProvider, PipelineInfo, PipelineTasks,
PipelineHistorical } from '@/pages';
import { formatTime } from '@/utils';
import type { BlueprintType } from '../../types';
@@ -54,14 +55,25 @@ export const Status = ({ blueprint, pipelineId, operating,
onRun }: Props) => {
/>
</span>
</div>
- <div className="block">
- <h3>Current Pipeline</h3>
- <PipelineInfo id={pipelineId} />
- </div>
- <div className="block">
- <h3>Historical Pipelines</h3>
- <PipelineHistorical blueprintId={blueprint.id} />
- </div>
+ <PipelineContextProvider>
+ <div className="block">
+ <h3>Current Pipeline</h3>
+ {!pipelineId ? (
+ <Card>There is no current run for this blueprint.</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>
</S.StatusPanel>
);
};
diff --git a/config-ui/src/pages/blueprint/detail/use-detail.ts
b/config-ui/src/pages/blueprint/detail/use-detail.ts
index dda5c7aca..344ffc522 100644
--- a/config-ui/src/pages/blueprint/detail/use-detail.ts
+++ b/config-ui/src/pages/blueprint/detail/use-detail.ts
@@ -61,6 +61,7 @@ export const useDetail = ({ id }: UseDetailProps) => {
const handleRun = async () => {
const [success] = await operator(() => API.runBlueprint(id), {
setOperating,
+ formatReason: (err) => (err as any).response?.data?.message,
});
if (success) {
diff --git a/config-ui/src/pages/pipeline/api.ts
b/config-ui/src/pages/pipeline/api.ts
index fc0fdaa03..f00fbf623 100644
--- a/config-ui/src/pages/pipeline/api.ts
+++ b/config-ui/src/pages/pipeline/api.ts
@@ -18,6 +18,25 @@
import { request } from '@/utils';
+export const getPipeline = (id: ID) => request(`/pipelines/${id}`);
+
+export const getPipelineTasks = (id: ID) => request(`/pipelines/${id}/tasks`);
+
+export const deletePipeline = (id: ID) =>
+ request(`/pipelines/${id}`, {
+ method: 'delete',
+ });
+
export const getPipelineHistorical = (id: ID) =>
request(`/blueprints/${id}/pipelines`);
+export const pipelineRerun = (id: ID) =>
+ request(`/pipelines/${id}/rerun`, {
+ method: 'post',
+ });
+
+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/detail/pipeline-info.tsx
b/config-ui/src/pages/pipeline/components/cancel/index.tsx
similarity index 56%
rename from config-ui/src/pages/pipeline/detail/pipeline-info.tsx
rename to config-ui/src/pages/pipeline/components/cancel/index.tsx
index 13e4ced79..aae3a30d2 100644
--- a/config-ui/src/pages/pipeline/detail/pipeline-info.tsx
+++ b/config-ui/src/pages/pipeline/components/cancel/index.tsx
@@ -18,18 +18,29 @@
import React from 'react';
-import { Card } from '@/components';
+import { IconButton } from '@/components';
+import { useOperator } from '@/hooks';
-import { PipelineDetail } from './pipeline-detail';
+import { StatusEnum } from '../../types';
+import * as API from '../../api';
+
+import { usePipeline } from '../context';
interface Props {
- id?: ID;
+ id: ID;
+ status: StatusEnum;
}
-export const PipelineInfo = ({ id }: Props) => {
- if (!id) {
- return <Card>There is no current run for this blueprint.</Card>;
+export const PipelineCancel = ({ id, status }: Props) => {
+ const { setVersion } = usePipeline();
+
+ const { operating, onSubmit } = useOperator(() => API.deletePipeline(id), {
+ callback: () => setVersion((v) => v + 1),
+ });
+
+ if (![StatusEnum.ACTIVE, StatusEnum.RUNNING,
StatusEnum.RERUN].includes(status)) {
+ return null;
}
- return <PipelineDetail id={id} />;
+ return <IconButton loading={operating} icon="disable" tooltip="Cancel"
onClick={onSubmit} />;
};
diff --git a/config-ui/src/pages/pipeline/detail/api.ts
b/config-ui/src/pages/pipeline/components/context/index.tsx
similarity index 59%
rename from config-ui/src/pages/pipeline/detail/api.ts
rename to config-ui/src/pages/pipeline/components/context/index.tsx
index 2b4a9bc25..77b883559 100644
--- a/config-ui/src/pages/pipeline/detail/api.ts
+++ b/config-ui/src/pages/pipeline/components/context/index.tsx
@@ -16,23 +16,23 @@
*
*/
-import { request } from '@/utils';
+import React, { useContext, useState } from 'react';
-export const getPipeline = (id: ID) => request(`/pipelines/${id}`);
+const PipelineContext = React.createContext<{
+ version: number;
+ setVersion: React.Dispatch<React.SetStateAction<number>>;
+}>({
+ version: 0,
+ setVersion: () => {},
+});
-export const getPipelineTasks = (id: ID) => request(`/pipelines/${id}/tasks`);
+interface Props {
+ children: React.ReactNode;
+}
-export const deletePipeline = (id: ID) =>
- request(`/pipelines/${id}`, {
- method: 'delete',
- });
+export const PipelineContextProvider = ({ children }: Props) => {
+ const [version, setVersion] = useState(0);
+ return <PipelineContext.Provider value={{ version, setVersion
}}>{children}</PipelineContext.Provider>;
+};
-export const pipelineRerun = (id: ID) =>
- request(`/pipelines/${id}/rerun`, {
- method: 'post',
- });
-
-export const taskRerun = (id: ID) =>
- request(`/tasks/${id}/rerun`, {
- method: 'post',
- });
+export const usePipeline = () => useContext(PipelineContext);
diff --git a/config-ui/src/pages/pipeline/components/historical/index.tsx
b/config-ui/src/pages/pipeline/components/historical/index.tsx
index ef6b0d8ca..1c8b8c7cf 100644
--- a/config-ui/src/pages/pipeline/components/historical/index.tsx
+++ b/config-ui/src/pages/pipeline/components/historical/index.tsx
@@ -17,39 +17,41 @@
*/
import React, { useState, useMemo } from 'react';
-import { Icon, ButtonGroup, Button, Position, Intent, IconName } from
'@blueprintjs/core';
-import { Tooltip2 } from '@blueprintjs/popover2';
+import { ButtonGroup } from '@blueprintjs/core';
import { pick } from 'lodash';
import { saveAs } from 'file-saver';
import { DEVLAKE_ENDPOINT } from '@/config';
-import { Card, Loading, Table, ColumnType, Inspector } from '@/components';
+import type { ColumnType } from '@/components';
+import { Card, Loading, Table, Inspector, Dialog, IconButton } from
'@/components';
import { useAutoRefresh } from '@/hooks';
import { formatTime } from '@/utils';
import type { PipelineType } from '../../types';
import { StatusEnum } from '../../types';
-import { STATUS_ICON, STATUS_LABEL, STATUS_CLS } from '../../misc';
import * as API from '../../api';
+import { usePipeline } from '../context';
+import { PipelineStatus } from '../status';
import { PipelineDuration } from '../duration';
-
-import * as S from './styled';
+import { PipelineTasks } from '../tasks';
interface Props {
blueprintId: ID;
}
export const PipelineHistorical = ({ blueprintId }: Props) => {
- const [isOpen, setIsOpen] = useState(false);
- const [json, setJson] = useState<any>({});
+ const [JSON, setJSON] = useState<any>(null);
+ const [ID, setID] = useState<ID | null>(null);
+
+ const { version } = usePipeline();
const { loading, data } = useAutoRefresh<PipelineType[]>(
async () => {
const res = await API.getPipelineHistorical(blueprintId);
return res.pipelines;
},
- [],
+ [version],
{
cancel: (data) =>
!!(
@@ -61,6 +63,10 @@ export const PipelineHistorical = ({ blueprintId }: Props)
=> {
},
);
+ const handleShowJSON = (row: PipelineType) => {
+ setJSON(pick(row, ['id', 'name', 'plan', 'skipOnFail']));
+ };
+
const handleDownloadLog = async (id: ID) => {
const res = await API.getPipelineLog(id);
if (res) {
@@ -68,6 +74,10 @@ export const PipelineHistorical = ({ blueprintId }: Props)
=> {
}
};
+ const handleShowDetails = (id: ID) => {
+ setID(id);
+ };
+
const columns = useMemo(
() =>
[
@@ -75,33 +85,27 @@ export const PipelineHistorical = ({ blueprintId }: Props)
=> {
title: 'Status',
dataIndex: 'status',
key: 'status',
- render: (val: StatusEnum) => (
- <S.StatusColumn className={STATUS_CLS(val)}>
- {STATUS_ICON[val] === 'loading' ? (
- <Loading style={{ marginRight: 4 }} size={14} />
- ) : (
- <Icon style={{ marginRight: 4 }} icon={STATUS_ICON[val] as
IconName} />
- )}
- <span>{STATUS_LABEL[val]}</span>
- </S.StatusColumn>
- ),
+ render: (val) => <PipelineStatus status={val} />,
},
{
title: 'Started at',
dataIndex: 'beganAt',
key: 'beganAt',
+ align: 'center',
render: (val: string | null) => (val ? formatTime(val) : '-'),
},
{
title: 'Completed at',
dataIndex: 'finishedAt',
key: 'finishedAt',
+ align: 'center',
render: (val: string | null) => (val ? formatTime(val) : '-'),
},
{
title: 'Duration',
dataIndex: ['status', 'beganAt', 'finishedAt'],
key: 'duration',
+ align: 'center',
render: ({ status, beganAt, finishedAt }) => (
<PipelineDuration status={status} beganAt={beganAt}
finishedAt={finishedAt} />
),
@@ -110,23 +114,12 @@ export const PipelineHistorical = ({ blueprintId }:
Props) => {
title: '',
dataIndex: 'id',
key: 'action',
+ align: 'center',
render: (id: ID, row) => (
<ButtonGroup>
- <Tooltip2 position={Position.TOP} intent={Intent.PRIMARY}
content="View JSON">
- <Button
- minimal
- intent={Intent.PRIMARY}
- icon="code"
- onClick={() => {
- setIsOpen(true);
- setJson(pick(row, ['id', 'name', 'plan', 'skipOnFail']));
- }}
- />
- </Tooltip2>
- <Tooltip2 position={Position.TOP} intent={Intent.PRIMARY}
content="Download Logs">
- <Button minimal intent={Intent.PRIMARY} icon="document"
onClick={() => handleDownloadLog(id)} />
- </Tooltip2>
- {/* <Button minimal intent={Intent.PRIMARY} icon='chevron-right'
/> */}
+ <IconButton icon="code" tooltip="View JSON" onClick={() =>
handleShowJSON(row)} />
+ <IconButton icon="document" tooltip="Download Logs" onClick={()
=> handleDownloadLog(id)} />
+ <IconButton icon="chevron-right" tooltip="View Details"
onClick={() => handleShowDetails(id)} />
</ButtonGroup>
),
},
@@ -149,7 +142,12 @@ export const PipelineHistorical = ({ blueprintId }: Props)
=> {
return (
<div>
<Table columns={columns} dataSource={data} />
- <Inspector isOpen={isOpen} title={`Pipeline ${json?.id}`} data={json}
onClose={() => setIsOpen(false)} />
+ {JSON && <Inspector isOpen title={`Pipeline ${JSON?.id}`} data={JSON}
onClose={() => setJSON(null)} />}
+ {ID && (
+ <Dialog style={{ width: 720 }} isOpen title={`Pipeline ${ID}`}
footer={null} onCancel={() => setID(null)}>
+ <PipelineTasks id={ID} />
+ </Dialog>
+ )}
</div>
);
};
diff --git a/config-ui/src/pages/pipeline/components/index.ts
b/config-ui/src/pages/pipeline/components/index.ts
index 294a8fde5..f1374d327 100644
--- a/config-ui/src/pages/pipeline/components/index.ts
+++ b/config-ui/src/pages/pipeline/components/index.ts
@@ -16,5 +16,7 @@
*
*/
+export * from './context';
export * from './historical';
-export * from './duration';
+export * from './info';
+export * from './tasks';
diff --git a/config-ui/src/pages/pipeline/components/info/index.tsx
b/config-ui/src/pages/pipeline/components/info/index.tsx
new file mode 100644
index 000000000..146cfa793
--- /dev/null
+++ b/config-ui/src/pages/pipeline/components/info/index.tsx
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react';
+
+import { Loading } from '@/components';
+import { useAutoRefresh } from '@/hooks';
+import { formatTime } from '@/utils';
+
+import type { PipelineType } from '../../types';
+import { StatusEnum } from '../../types';
+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';
+
+interface Props {
+ id: ID;
+ style?: React.CSSProperties;
+}
+
+export const PipelineInfo = ({ id, style }: Props) => {
+ const { version } = usePipeline();
+
+ const { loading, data } = useAutoRefresh<PipelineType>(() =>
API.getPipeline(id), [version], {
+ cancel: (data) => {
+ return !!(
+ data &&
+ [StatusEnum.COMPLETED, StatusEnum.PARTIAL, StatusEnum.FAILED,
StatusEnum.CANCELLED].includes(data.status)
+ );
+ },
+ });
+
+ if (loading || !data) {
+ return <Loading />;
+ }
+
+ const { status, beganAt, finishedAt, stage, finishedTasks, totalTasks,
message } = data;
+
+ return (
+ <S.Wrapper style={style}>
+ <ul>
+ <li>
+ <span>Status</span>
+ <strong>
+ <PipelineStatus status={status} />
+ </strong>
+ </li>
+ <li>
+ <span>Started at</span>
+ <strong>{formatTime(beganAt)}</strong>
+ </li>
+ <li>
+ <span>Duration</span>
+ <strong>
+ <PipelineDuration status={status} beganAt={beganAt}
finishedAt={finishedAt} />
+ </strong>
+ </li>
+ <li>
+ <span>Current Stage</span>
+ <strong>{stage}</strong>
+ </li>
+ <li>
+ <span>Tasks Completed</span>
+ <strong>
+ {finishedTasks}/{totalTasks}
+ </strong>
+ </li>
+ <li>
+ <PipelineCancel id={id} status={status} />
+ <PipelineRerun type="pipeline" id={id} status={status} />
+ </li>
+ </ul>
+ {StatusEnum.FAILED === status && <p className="'message'">{message}</p>}
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/pages/pipeline/components/historical/styled.ts
b/config-ui/src/pages/pipeline/components/info/styled.ts
similarity index 63%
copy from config-ui/src/pages/pipeline/components/historical/styled.ts
copy to config-ui/src/pages/pipeline/components/info/styled.ts
index 21362c88b..6760c1f22 100644
--- a/config-ui/src/pages/pipeline/components/historical/styled.ts
+++ b/config-ui/src/pages/pipeline/components/info/styled.ts
@@ -19,28 +19,42 @@
import { Colors } from '@blueprintjs/core';
import styled from 'styled-components';
-export const StatusColumn = styled.div`
- display: flex;
- align-items: center;
+import { Card } from '@/components';
- .bp4-icon {
- margin-right: 4px;
+export const Wrapper = styled(Card)`
+ ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ display: flex;
+ align-items: center;
}
- &.ready,
- &.cancel {
- color: #94959f;
- }
+ li {
+ flex: 5;
+ display: flex;
+ flex-direction: column;
- &.loading {
- color: #7497f7;
- }
+ &:last-child {
+ flex: 1;
+ }
+
+ & > span {
+ font-size: 12px;
+ color: #94959f;
+ text-align: center;
+ }
- &.success {
- color: ${Colors.GREEN3};
+ & > strong {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-top: 8px;
+ }
}
- &.error {
+ 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
new file mode 100644
index 000000000..89f0200f1
--- /dev/null
+++ b/config-ui/src/pages/pipeline/components/rerun/index.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 React from 'react';
+
+import { IconButton } from '@/components';
+import { useOperator } from '@/hooks';
+
+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 { setVersion } = usePipeline();
+
+ const { operating, onSubmit } = useOperator(() => (type === 'task' ?
API.taskRerun(id) : API.pipelineRerun(id)), {
+ callback: () => setVersion((v) => v + 1),
+ });
+
+ if (![StatusEnum.COMPLETED, StatusEnum.PARTIAL, StatusEnum.FAILED,
StatusEnum.CANCELLED].includes(status)) {
+ return null;
+ }
+
+ if (type === 'task') {
+ return <IconButton loading={operating} icon="repeat" tooltip="Rerun task"
onClick={onSubmit} />;
+ }
+
+ return <IconButton loading={operating} icon="repeat" tooltip="Rerun failed
tasks" onClick={onSubmit} />;
+};
diff --git a/config-ui/src/pages/pipeline/misc.ts
b/config-ui/src/pages/pipeline/components/status/index.tsx
similarity index 74%
rename from config-ui/src/pages/pipeline/misc.ts
rename to config-ui/src/pages/pipeline/components/status/index.tsx
index f96499cab..f93355252 100644
--- a/config-ui/src/pages/pipeline/misc.ts
+++ b/config-ui/src/pages/pipeline/components/status/index.tsx
@@ -16,9 +16,15 @@
*
*/
+import React from 'react';
+import { Icon, IconName } from '@blueprintjs/core';
import classNames from 'classnames';
-import { StatusEnum } from './types';
+import { Loading } from '@/components';
+
+import { StatusEnum } from '../../types';
+
+import * as S from './styled';
export const STATUS_ICON = {
[StatusEnum.CREATED]: 'stopwatch',
@@ -44,11 +50,27 @@ export const STATUS_LABEL = {
[StatusEnum.CANCELLED]: 'Cancelled',
};
-export const STATUS_CLS = (status: StatusEnum) =>
- classNames({
+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/historical/styled.ts
b/config-ui/src/pages/pipeline/components/status/styled.ts
similarity index 89%
rename from config-ui/src/pages/pipeline/components/historical/styled.ts
rename to config-ui/src/pages/pipeline/components/status/styled.ts
index 21362c88b..87ed2db8c 100644
--- a/config-ui/src/pages/pipeline/components/historical/styled.ts
+++ b/config-ui/src/pages/pipeline/components/status/styled.ts
@@ -19,14 +19,7 @@
import { Colors } from '@blueprintjs/core';
import styled from 'styled-components';
-export const StatusColumn = styled.div`
- display: flex;
- align-items: center;
-
- .bp4-icon {
- margin-right: 4px;
- }
-
+export const Wrapper = styled.div`
&.ready,
&.cancel {
color: #94959f;
diff --git a/config-ui/src/pages/pipeline/detail/components/task/index.tsx
b/config-ui/src/pages/pipeline/components/task/index.tsx
similarity index 64%
rename from config-ui/src/pages/pipeline/detail/components/task/index.tsx
rename to config-ui/src/pages/pipeline/components/task/index.tsx
index f8b868dd7..8ec91ee9a 100644
--- a/config-ui/src/pages/pipeline/detail/components/task/index.tsx
+++ b/config-ui/src/pages/pipeline/components/task/index.tsx
@@ -17,26 +17,26 @@
*/
import React, { useMemo } from 'react';
-import { Icon, Intent, Position, Colors } from '@blueprintjs/core';
-import { Tooltip2 } from '@blueprintjs/popover2';
+import { Intent } from '@blueprintjs/core';
+import { TextTooltip } from '@/components';
import type { PluginConfigType } from '@/plugins';
import { Plugins, PluginConfig } from '@/plugins';
-import { PipelineDuration } from '../../../components';
-import { StatusEnum, TaskType } from '../../../types';
-import { STATUS_CLS } from '../../../misc';
+import type { TaskType } from '../../types';
+import { StatusEnum } from '../../types';
+
+import { PipelineDuration } from '../duration';
+import { PipelineRerun } from '../rerun';
import * as S from './styled';
interface Props {
task: TaskType;
- operating: boolean;
- onRerun: (id: ID) => void;
}
-export const Task = ({ task, operating, onRerun }: Props) => {
- const { beganAt, finishedAt, status, message, progressDetail } = task;
+export const PipelineTask = ({ task }: Props) => {
+ const { id, beganAt, finishedAt, status, message, progressDetail } = task;
const [icon, name] = useMemo(() => {
const config = PluginConfig.find((p) => p.plugin === task.plugin) as
PluginConfigType;
@@ -57,32 +57,28 @@ export const Task = ({ task, operating, onRerun }: Props)
=> {
case Plugins.GitLab === config.plugin:
name = `${name}:projectId:${options.projectId}`;
break;
+ case [Plugins.JIRA, Plugins.Jenkins].includes(config.plugin):
+ name = `${name}:${options.scopeId}`;
+ break;
}
return [config.icon, name];
}, [task]);
- const statusCls = STATUS_CLS(status);
-
- const handleRerun = () => {
- if (operating) return;
- onRerun(task.id);
- };
-
return (
<S.Wrapper>
<S.Info>
<div className="title">
<img src={icon} alt="" />
<strong>Task{task.id}</strong>
- <span title={name}>{name}</span>
+ <span>
+ <TextTooltip content={name}>{name}</TextTooltip>
+ </span>
</div>
- {[status === StatusEnum.CREATED, StatusEnum.PENDING].includes(status)
&& (
- <p className={statusCls}>Subtasks pending</p>
- )}
+ {[status === StatusEnum.CREATED, StatusEnum.PENDING].includes(status)
&& <p>Subtasks pending</p>}
{[StatusEnum.ACTIVE, StatusEnum.RUNNING].includes(status) && (
- <p className={statusCls}>
+ <p>
Subtasks running
<strong style={{ marginLeft: 8 }}>
{progressDetail?.finishedSubTasks}/{progressDetail?.totalSubTasks}
@@ -90,23 +86,19 @@ export const Task = ({ task, operating, onRerun }: Props)
=> {
</p>
)}
- {status === StatusEnum.COMPLETED && <p className={statusCls}>All
Subtasks completed</p>}
+ {status === StatusEnum.COMPLETED && <p>All Subtasks completed</p>}
{status === StatusEnum.FAILED && (
- <Tooltip2 content={message} intent={Intent.DANGER}>
- <p className={statusCls}>Task failed: hover to view the reason</p>
- </Tooltip2>
+ <TextTooltip intent={Intent.DANGER} content={message}>
+ <p className="error">Task failed: hover to view the reason</p>
+ </TextTooltip>
)}
- {status === StatusEnum.CANCELLED && <p className={statusCls}>Subtasks
canceled</p>}
+ {status === StatusEnum.CANCELLED && <p>Subtasks canceled</p>}
</S.Info>
<S.Duration>
- {[StatusEnum.COMPLETED, StatusEnum.FAILED,
StatusEnum.CANCELLED].includes(status) && (
- <Tooltip2 position={Position.TOP} content="Rerun task">
- <Icon icon="repeat" style={{ color: Colors.BLUE3 }}
onClick={handleRerun} />
- </Tooltip2>
- )}
<PipelineDuration status={status} beganAt={beganAt}
finishedAt={finishedAt} />
+ <PipelineRerun type="task" id={id} status={status} />
</S.Duration>
</S.Wrapper>
);
diff --git a/config-ui/src/pages/pipeline/detail/components/task/styled.ts
b/config-ui/src/pages/pipeline/components/task/styled.ts
similarity index 94%
rename from config-ui/src/pages/pipeline/detail/components/task/styled.ts
rename to config-ui/src/pages/pipeline/components/task/styled.ts
index f15f0fa40..35b153954 100644
--- a/config-ui/src/pages/pipeline/detail/components/task/styled.ts
+++ b/config-ui/src/pages/pipeline/components/task/styled.ts
@@ -38,18 +38,17 @@ export const Info = styled.div`
align-items: center;
margin-bottom: 8px;
- img {
+ & > img {
width: 20px;
}
- strong {
+ & > strong {
margin: 0 4px;
}
- span {
+ & > span {
+ flex: auto;
overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
}
}
diff --git a/config-ui/src/pages/pipeline/components/tasks/index.tsx
b/config-ui/src/pages/pipeline/components/tasks/index.tsx
new file mode 100644
index 000000000..82eb0d4d2
--- /dev/null
+++ b/config-ui/src/pages/pipeline/components/tasks/index.tsx
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React, { useEffect, useState } from 'react';
+import { Icon, Collapse, Button } from '@blueprintjs/core';
+import { sortBy, groupBy } 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 { usePipeline } from '../context';
+import { PipelineTask } from '../task';
+
+import * as S from './styled';
+
+interface Props {
+ id: ID;
+ style?: React.CSSProperties;
+}
+
+export const PipelineTasks = ({ id, style }: Props) => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const { version } = usePipeline();
+
+ const { loading, data } = useAutoRefresh<TaskType[]>(
+ 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))
+ );
+ },
+ },
+ );
+
+ if (loading) {
+ return <Loading />;
+ }
+
+ const stages = groupBy(sortBy(data, 'id'), 'pipelineRow');
+
+ const handleToggleOpen = () => setIsOpen(!isOpen);
+
+ return (
+ <S.Wrapper style={style}>
+ <S.Inner>
+ <S.Header>
+ {Object.keys(stages).map((key) => {
+ let status;
+
+ switch (true) {
+ case !!stages[key].find((task) => [StatusEnum.ACTIVE,
StatusEnum.RUNNING].includes(task.status)):
+ status = 'loading';
+ break;
+ case stages[key].every((task) => task.status ===
StatusEnum.COMPLETED):
+ status = 'success';
+ break;
+ case !!stages[key].find((task) => task.status ===
StatusEnum.FAILED):
+ status = 'error';
+ break;
+ case !!stages[key].find((task) => task.status ===
StatusEnum.CANCELLED):
+ status = 'cancel';
+ break;
+ default:
+ status = 'ready';
+ break;
+ }
+
+ return (
+ <li key={key} className={status}>
+ <strong>Stage {key}</strong>
+ {status === 'loading' && <Loading size={14} />}
+ {status === 'success' && <Icon icon="tick-circle" />}
+ {status === 'error' && <Icon icon="cross-circle" />}
+ {status === 'cancel' && <Icon icon="disable" />}
+ </li>
+ );
+ })}
+ </S.Header>
+ <Collapse isOpen={isOpen}>
+ <S.Tasks>
+ {Object.keys(stages).map((key) => (
+ <li key={key}>
+ {stages[key].map((task) => (
+ <PipelineTask key={task.id} task={task} />
+ ))}
+ </li>
+ ))}
+ </S.Tasks>
+ </Collapse>
+ </S.Inner>
+ <Button
+ className="collapse-control"
+ minimal
+ icon={isOpen ? 'chevron-down' : 'chevron-up'}
+ onClick={handleToggleOpen}
+ />
+ </S.Wrapper>
+ );
+};
diff --git a/config-ui/src/pages/pipeline/detail/styled.ts
b/config-ui/src/pages/pipeline/components/tasks/styled.ts
similarity index 66%
rename from config-ui/src/pages/pipeline/detail/styled.ts
rename to config-ui/src/pages/pipeline/components/tasks/styled.ts
index 1aa2ef89f..1d1f7b3da 100644
--- a/config-ui/src/pages/pipeline/detail/styled.ts
+++ b/config-ui/src/pages/pipeline/components/tasks/styled.ts
@@ -16,72 +16,16 @@
*
*/
-import { Colors } from '@blueprintjs/core';
import styled from 'styled-components';
export const Wrapper = styled.div`
- .card + .card {
- margin-top: 16px;
- }
-
- .card:last-child {
- position: relative;
- padding: 24px 48px 24px 24px;
-
- .collapse-control {
- position: absolute;
- right: 12px;
- top: 24px;
- }
- }
-
- p.message {
- margin: 8px 0 0;
-
- &.error {
- color: ${Colors.RED3};
- }
- }
-`;
-
-export const Pipeline = styled.ul`
- margin: 0;
- padding: 0;
- list-style: none;
- display: flex;
- align-items: center;
-
- li {
- flex: 1;
- display: flex;
- flex-direction: column;
-
- &.success {
- color: ${Colors.GREEN3};
-
- .bp4-icon {
- color: ${Colors.GREEN3};
- }
- }
+ position: relative;
+ padding-right: 36px;
- &.error {
- color: ${Colors.RED3};
-
- .bp4-icon {
- color: ${Colors.RED3};
- }
- }
-
- & > span {
- font-size: 12px;
- color: #94959f;
- }
-
- & > strong {
- display: flex;
- align-items: center;
- margin-top: 8px;
- }
+ .collapse-control {
+ position: absolute;
+ right: 0;
+ top: 0;
}
`;
diff --git a/config-ui/src/pages/pipeline/detail/components/index.ts
b/config-ui/src/pages/pipeline/detail/components/index.ts
deleted file mode 100644
index 7636f4330..000000000
--- a/config-ui/src/pages/pipeline/detail/components/index.ts
+++ /dev/null
@@ -1,19 +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.
- *
- */
-
-export * from './task';
diff --git a/config-ui/src/pages/pipeline/detail/pipeline-detail.tsx
b/config-ui/src/pages/pipeline/detail/pipeline-detail.tsx
deleted file mode 100644
index 25ff9377a..000000000
--- a/config-ui/src/pages/pipeline/detail/pipeline-detail.tsx
+++ /dev/null
@@ -1,195 +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 React, { useState } from 'react';
-import { Icon, Button, Collapse, Intent, IconName } from '@blueprintjs/core';
-import { groupBy } from 'lodash';
-import classNames from 'classnames';
-
-import { Card, Loading } from '@/components';
-import { useAutoRefresh } from '@/hooks';
-import { formatTime } from '@/utils';
-
-import { PipelineDuration } from '../components';
-import type { PipelineType, TaskType } from '../types';
-import { StatusEnum } from '../types';
-import { STATUS_ICON, STATUS_LABEL, STATUS_CLS } from '../misc';
-
-import { useDetail } from './use-detail';
-import { Task } from './components';
-import * as API from './api';
-import * as S from './styled';
-
-interface Props {
- id: ID;
-}
-
-export const PipelineDetail = ({ id }: Props) => {
- const [isOpen, setIsOpen] = useState(true);
-
- const { version, operating, onCancel, onRerun, onRerunTask } = useDetail({
id });
-
- const { loading, data } = useAutoRefresh<{
- pipeline: PipelineType;
- tasks: TaskType[];
- }>(
- async () => {
- const [pipeRes, taskRes] = await Promise.all([API.getPipeline(id),
API.getPipelineTasks(id)]);
-
- return {
- pipeline: pipeRes,
- tasks: taskRes.tasks,
- };
- },
- [version],
- {
- cancel: (data) => {
- const { pipeline } = data ?? {};
- return !!(
- pipeline &&
- [StatusEnum.COMPLETED, StatusEnum.PARTIAL, StatusEnum.FAILED,
StatusEnum.CANCELLED].includes(pipeline.status)
- );
- },
- },
- );
-
- if (loading || !data) {
- return <Loading />;
- }
-
- const { pipeline, tasks } = data;
-
- const { status, beganAt, finishedAt, stage, finishedTasks, totalTasks,
message } = pipeline;
- const stages = groupBy(tasks, 'pipelineRow');
-
- const handleToggleOpen = () => {
- setIsOpen(!isOpen);
- };
-
- const statusCls = STATUS_CLS(status);
-
- return (
- <S.Wrapper>
- <Card className="card">
- <S.Pipeline>
- <li className={statusCls}>
- <span>Status</span>
- <strong>
- {STATUS_ICON[status] === 'loading' ? (
- <Loading style={{ marginRight: 4 }} size={14} />
- ) : (
- <Icon style={{ marginRight: 4 }} icon={STATUS_ICON[status] as
IconName} />
- )}
- {STATUS_LABEL[status]}
- </strong>
- </li>
- <li>
- <span>Started at</span>
- <strong>{formatTime(beganAt)}</strong>
- </li>
- <li>
- <span>Duration</span>
- <strong>
- <PipelineDuration status={status} beganAt={beganAt}
finishedAt={finishedAt} />
- </strong>
- </li>
- <li>
- <span>Current Stage</span>
- <strong>{stage}</strong>
- </li>
- <li>
- <span>Tasks Completed</span>
- <strong>
- {finishedTasks}/{totalTasks}
- </strong>
- </li>
- <li>
- {[StatusEnum.CREATED, StatusEnum.PENDING, StatusEnum.ACTIVE,
StatusEnum.RUNNING].includes(status) && (
- <Button loading={operating} outlined intent={Intent.PRIMARY}
text="Cancel" onClick={onCancel} />
- )}
-
- {[StatusEnum.FAILED, StatusEnum.PARTIAL].includes(status) && (
- <Button
- loading={operating}
- outlined
- intent={Intent.PRIMARY}
- text="Rerun failed tasks"
- onClick={onRerun}
- />
- )}
- </li>
- </S.Pipeline>
- {StatusEnum.FAILED === status && <p className={classNames('message',
statusCls)}>{message}</p>}
- </Card>
- <Card className="card">
- <S.Inner>
- <S.Header>
- {Object.keys(stages).map((key) => {
- let status;
-
- switch (true) {
- case !!stages[key].find((task) => [StatusEnum.ACTIVE,
StatusEnum.RUNNING].includes(task.status)):
- status = 'loading';
- break;
- case stages[key].every((task) => [StatusEnum.COMPLETED,
StatusEnum.PARTIAL].includes(task.status)):
- status = 'success';
- break;
- case !!stages[key].find((task) => task.status ===
StatusEnum.FAILED):
- status = 'error';
- break;
- case !!stages[key].find((task) => task.status ===
StatusEnum.CANCELLED):
- status = 'cancel';
- break;
- default:
- status = 'ready';
- break;
- }
-
- return (
- <li key={key} className={status}>
- <strong>Stage {key}</strong>
- {status === 'loading' && <Loading size={14} />}
- {status === 'success' && <Icon icon="tick-circle" />}
- {status === 'error' && <Icon icon="cross-circle" />}
- {status === 'cancel' && <Icon icon="disable" />}
- </li>
- );
- })}
- </S.Header>
- <Collapse isOpen={isOpen}>
- <S.Tasks>
- {Object.keys(stages).map((key) => (
- <li key={key}>
- {stages[key].map((task) => (
- <Task key={task.id} task={task} operating={operating}
onRerun={onRerunTask} />
- ))}
- </li>
- ))}
- </S.Tasks>
- </Collapse>
- </S.Inner>
- <Button
- className="collapse-control"
- minimal
- icon={isOpen ? 'chevron-down' : 'chevron-up'}
- onClick={handleToggleOpen}
- />
- </Card>
- </S.Wrapper>
- );
-};
diff --git a/config-ui/src/pages/pipeline/index.ts
b/config-ui/src/pages/pipeline/index.ts
index 5c45114f7..eecb8b531 100644
--- a/config-ui/src/pages/pipeline/index.ts
+++ b/config-ui/src/pages/pipeline/index.ts
@@ -17,6 +17,4 @@
*/
export * from './types';
-export * from './misc';
export * from './components';
-export * from './detail';