feat: pipeline-task details
This commit is contained in:
parent
c88e9e6785
commit
73b1c6a40d
@ -1,13 +1,41 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Link, useResponse } from '@curi/react-dom';
|
import { Link, useResponse, useRouter } from '@curi/react-dom';
|
||||||
import { faPlayCircle, faVial } from '@fortawesome/free-solid-svg-icons';
|
import { faPlayCircle, faVial } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { CircularProgress, Collapse, IconButton, LinearProgress, List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, makeStyles, useTheme } from '@material-ui/core';
|
import {
|
||||||
import { Cancel, CheckCircle, CloudDownload, ShoppingCart, Timer } from '@material-ui/icons';
|
CircularProgress,
|
||||||
import { format } from 'date-fns';
|
Collapse,
|
||||||
import React, { FC, Fragment, ReactNode, useState } from 'react';
|
IconButton,
|
||||||
import { Commit, Pipeline, PipelineTask, TaskStatuses } from '../generated/graphql';
|
LinearProgress,
|
||||||
import { COMMITS } from './queries';
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
makeStyles,
|
||||||
|
useTheme,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import {
|
||||||
|
Cancel,
|
||||||
|
CheckCircle,
|
||||||
|
CloudDownload,
|
||||||
|
ShoppingCart,
|
||||||
|
Timer,
|
||||||
|
} from "@material-ui/icons";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { equals, find, propEq, takeWhile } from "ramda";
|
||||||
|
import React, { FC, Fragment, ReactNode, useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
Commit,
|
||||||
|
CreatePipelineTaskInput,
|
||||||
|
Pipeline,
|
||||||
|
PipelineTask,
|
||||||
|
TaskStatuses,
|
||||||
|
WorkUnit,
|
||||||
|
PipelineUnits,
|
||||||
|
} from "../generated/graphql";
|
||||||
|
import { CREATE_PIPELINE_TASK } from "./muations";
|
||||||
|
import { COMMITS } from "./queries";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
pipeline: Pipeline;
|
pipeline: Pipeline;
|
||||||
@ -22,7 +50,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const CommitList: FC<Props> = ({pipeline}) => {
|
export const CommitList: FC<Props> = ({ pipeline }) => {
|
||||||
const { data, loading } = useQuery<{ commits: Commit[] }>(COMMITS, {
|
const { data, loading } = useQuery<{ commits: Commit[] }>(COMMITS, {
|
||||||
variables: {
|
variables: {
|
||||||
pipelineId: pipeline.id,
|
pipelineId: pipeline.id,
|
||||||
@ -41,20 +69,84 @@ export const CommitList: FC<Props> = ({pipeline}) => {
|
|||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
{data?.commits.map((commit) => (
|
{data?.commits.map((commit) => (
|
||||||
<Item key={commit.hash} commit={commit} />
|
<Item key={commit.hash} commit={commit} pipeline={pipeline} />
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const Item: FC<{commit: Commit}> = ({commit}) => {
|
const unitActionPairs: Array<[PipelineUnits, ReactNode, string]> = [
|
||||||
|
[PipelineUnits.Checkout, <ShoppingCart />, "checkout"],
|
||||||
|
[
|
||||||
|
PipelineUnits.InstallDependencies,
|
||||||
|
<CloudDownload />,
|
||||||
|
"install dependencies",
|
||||||
|
],
|
||||||
|
[PipelineUnits.Test, <FontAwesomeIcon icon={faVial} />, "test"],
|
||||||
|
[PipelineUnits.Deploy, <FontAwesomeIcon icon={faPlayCircle} />, "deploy"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const Item: FC<{ commit: Commit; pipeline: Pipeline }> = ({
|
||||||
|
commit,
|
||||||
|
pipeline,
|
||||||
|
}) => {
|
||||||
const [isOpen, setOpen] = useState(() => false);
|
const [isOpen, setOpen] = useState(() => false);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const {response} = useResponse();
|
const [createTask, { loading }] = useMutation<
|
||||||
|
{ createPipelineTask: PipelineTask },
|
||||||
|
{ task: CreatePipelineTaskInput }
|
||||||
|
>(CREATE_PIPELINE_TASK);
|
||||||
|
|
||||||
|
const units = useMemo(
|
||||||
|
() => pipeline.workUnitMetadata.units.map((unit) => unit.type),
|
||||||
|
[pipeline]
|
||||||
|
);
|
||||||
|
|
||||||
|
const {navigate, url} = useRouter();
|
||||||
|
const { response } = useResponse();
|
||||||
|
|
||||||
|
const handleCreateTask = (unit: PipelineUnits) => {
|
||||||
|
const _units = [...takeWhile(equals(unit), units), unit];
|
||||||
|
createTask({
|
||||||
|
variables: {
|
||||||
|
task: {
|
||||||
|
units: _units,
|
||||||
|
pipelineId: pipeline.id,
|
||||||
|
commit: commit.hash,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(({data}) => {
|
||||||
|
navigate({
|
||||||
|
url: url({
|
||||||
|
name: "pipeline-task-detail",
|
||||||
|
params: { ...response.params, taskId: data?.createPipelineTask.id},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = useMemo(
|
||||||
|
() =>
|
||||||
|
units.map((unit) => {
|
||||||
|
const pair = find(propEq(0, unit), unitActionPairs);
|
||||||
|
return (
|
||||||
|
pair && (
|
||||||
|
<IconButton
|
||||||
|
aria-label={pair[2]}
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => handleCreateTask(unit)}
|
||||||
|
>
|
||||||
|
{pair[1]}
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
[units]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ListItem button onClick={() => setOpen(!isOpen)}>
|
<ListItem button onClick={() => setOpen(!isOpen)}>
|
||||||
@ -62,21 +154,9 @@ const Item: FC<{commit: Commit}> = ({commit}) => {
|
|||||||
primary={commit.message}
|
primary={commit.message}
|
||||||
secondary={format(commit.date, "yyyy-MM-dd HH:mm:ss")}
|
secondary={format(commit.date, "yyyy-MM-dd HH:mm:ss")}
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>{actions}</ListItemSecondaryAction>
|
||||||
<IconButton edge="end" aria-label="delete">
|
|
||||||
<ShoppingCart />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton edge="end" aria-label="delete">
|
|
||||||
<CloudDownload />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton edge="end" aria-label="delete">
|
|
||||||
<FontAwesomeIcon icon={faVial} />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton edge="end" aria-label="delete">
|
|
||||||
<FontAwesomeIcon icon={faPlayCircle} />
|
|
||||||
</IconButton>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
{loading && <LinearProgress color="secondary" />}
|
||||||
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
||||||
<List component="div" disablePadding>
|
<List component="div" disablePadding>
|
||||||
{commit.tasks.map((task) => (
|
{commit.tasks.map((task) => (
|
||||||
@ -92,9 +172,9 @@ const Item: FC<{commit: Commit}> = ({commit}) => {
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const TaskItem: FC<{task: PipelineTask}> = ({task}) => {
|
const TaskItem: FC<{ task: PipelineTask }> = ({ task }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const statusIcon: ReactNode = (() => {
|
const statusIcon: ReactNode = (() => {
|
||||||
@ -106,11 +186,11 @@ const TaskItem: FC<{task: PipelineTask}> = ({task}) => {
|
|||||||
case TaskStatuses.Failed:
|
case TaskStatuses.Failed:
|
||||||
return <Cancel style={{ color: theme.palette.error.main }} />;
|
return <Cancel style={{ color: theme.palette.error.main }} />;
|
||||||
case TaskStatuses.Working:
|
case TaskStatuses.Working:
|
||||||
return <CircularProgress style={{ color: theme.palette.secondary.main }} />;
|
return (
|
||||||
|
<CircularProgress style={{ color: theme.palette.secondary.main }} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
)()
|
|
||||||
return (
|
return (
|
||||||
<ListItem button className={classes.nested}>
|
<ListItem button className={classes.nested}>
|
||||||
<ListItemIcon>{statusIcon}</ListItemIcon>
|
<ListItemIcon>{statusIcon}</ListItemIcon>
|
||||||
|
12
src/commits/muations.ts
Normal file
12
src/commits/muations.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const CREATE_PIPELINE_TASK = gql`
|
||||||
|
mutation CreatePipelineTask($task: CreatePipelineTaskInput!) {
|
||||||
|
createPipelineTask(task: $task) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
startedAt
|
||||||
|
endedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
11
src/commons/fallbacks/error-page.tsx
Normal file
11
src/commons/fallbacks/error-page.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Typography } from '@material-ui/core';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
export const ErrorPage: FC = ({children}) => {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<Typography component="h2">Something is wrong :(</Typography>
|
||||||
|
<Typography variant="body1" component="div">{children}</Typography>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -1710,7 +1710,7 @@
|
|||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "findPipelineTask",
|
"name": "pipelineTask",
|
||||||
"description": null,
|
"description": null,
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
|
@ -186,7 +186,7 @@ export type Query = {
|
|||||||
pipeline: Pipeline;
|
pipeline: Pipeline;
|
||||||
commits: Array<Commit>;
|
commits: Array<Commit>;
|
||||||
listPipelineTaskByPipelineId: Array<PipelineTask>;
|
listPipelineTaskByPipelineId: Array<PipelineTask>;
|
||||||
findPipelineTask: PipelineTask;
|
pipelineTask: PipelineTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -215,7 +215,7 @@ export type QueryListPipelineTaskByPipelineIdArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryFindPipelineTaskArgs = {
|
export type QueryPipelineTaskArgs = {
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,92 @@
|
|||||||
import { FC } from 'react'
|
import { gql, useQuery } from "@apollo/client";
|
||||||
|
import { LinearProgress, makeStyles, Typography } from "@material-ui/core";
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { ErrorPage } from "../commons/fallbacks/error-page";
|
||||||
|
import { PipelineTask, PipelineTaskLogs } from "../generated/graphql";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
taskId: string;
|
taskId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PipelineTaskDetail: FC<Props> = ({taskId}) => {
|
const PIPELINE_TASK = gql`
|
||||||
return <div>{taskId} detail</div>
|
query FindPipelineTask($taskId: String!) {
|
||||||
}
|
pipelineTask(id: $taskId) {
|
||||||
|
id
|
||||||
|
units
|
||||||
|
commit
|
||||||
|
status
|
||||||
|
startedAt
|
||||||
|
endedAt
|
||||||
|
logs {
|
||||||
|
unit
|
||||||
|
logs
|
||||||
|
status
|
||||||
|
startedAt
|
||||||
|
endedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
},
|
||||||
|
groupTitle: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
fontWeight: 500,
|
||||||
|
borderTop: '1px solid #eee',
|
||||||
|
fontSize: '16px',
|
||||||
|
padding: '12px',
|
||||||
|
marginLeft: '1px',
|
||||||
|
},
|
||||||
|
logText: {
|
||||||
|
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
border: 'none',
|
||||||
|
margin: '6px 12px',
|
||||||
|
display: "block"
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PipelineTaskDetail: FC<Props> = ({ taskId }) => {
|
||||||
|
const { data, loading, error } = useQuery<{ pipelineTask: PipelineTask }>(
|
||||||
|
PIPELINE_TASK,
|
||||||
|
{
|
||||||
|
variables: { taskId },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ErrorPage>{error.message}</ErrorPage>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LinearProgress color="secondary" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Typography variant="h4" component="h2">
|
||||||
|
{taskId} detail
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{data?.pipelineTask.logs.map((logs) => (
|
||||||
|
<LogGroup key={logs.unit} logs={logs} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LogGroup: FC<{ logs: PipelineTaskLogs }> = ({ logs }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={classes.groupTitle}>
|
||||||
|
{logs.unit} {format(logs.startedAt, 'yyyy-MM-dd HH:mm:ss')} {logs.status}
|
||||||
|
</div>
|
||||||
|
<code className={classes.logText}>{logs.logs}</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -60,12 +60,12 @@ export const ProjectDetail: FC<Props> = ({ project, children }) => {
|
|||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
className={classes.root}
|
className={classes.root}
|
||||||
>
|
>
|
||||||
<Grid item xs={3} style={{ height: "100%", display: "flex" }}>
|
<Grid item lg={1} style={{ height: "100%", display: "flex" }}>
|
||||||
<Paper className={classes.pipelineListContainer}>
|
<Paper className={classes.pipelineListContainer}>
|
||||||
<PipelineList projectId={project.id} />
|
<PipelineList projectId={project.id} />
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={9} style={{ height: "100%", display: "flex", overflowY: 'auto' }}>
|
<Grid item xs={10} lg={11} style={{ height: "100%", display: "flex", overflowY: 'auto' }}>
|
||||||
{children}
|
{children}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
Loading…
Reference in New Issue
Block a user