2021-06-05 19:17:45 +08:00
|
|
|
import { useMutation, useQuery, useSubscription } from "@apollo/client";
|
|
|
|
import { Link, useResponse, useRouter } from "@curi/react-dom";
|
2021-05-12 21:18:17 +08:00
|
|
|
import { faPlayCircle, faVial } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
|
|
import {
|
2021-06-20 15:42:03 +08:00
|
|
|
Backdrop,
|
2021-05-12 21:18:17 +08:00
|
|
|
CircularProgress,
|
|
|
|
Collapse,
|
|
|
|
IconButton,
|
|
|
|
LinearProgress,
|
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListItemIcon,
|
|
|
|
ListItemSecondaryAction,
|
|
|
|
ListItemText,
|
|
|
|
makeStyles,
|
|
|
|
useTheme,
|
2021-06-20 15:42:03 +08:00
|
|
|
withStyles,
|
2021-05-12 21:18:17 +08:00
|
|
|
} from "@material-ui/core";
|
|
|
|
import {
|
|
|
|
Cancel,
|
|
|
|
CheckCircle,
|
|
|
|
CloudDownload,
|
|
|
|
ShoppingCart,
|
2021-06-20 15:42:03 +08:00
|
|
|
Stop,
|
2021-05-12 21:18:17 +08:00
|
|
|
Timer,
|
|
|
|
} from "@material-ui/icons";
|
|
|
|
import { format } from "date-fns";
|
2021-06-05 19:17:45 +08:00
|
|
|
import { useSnackbar } from "notistack";
|
|
|
|
import { complement, equals, find, propEq, takeWhile } from "ramda";
|
|
|
|
import {
|
|
|
|
FC,
|
|
|
|
Fragment,
|
2021-06-20 15:42:03 +08:00
|
|
|
MouseEventHandler,
|
2021-06-05 19:17:45 +08:00
|
|
|
ReactNode,
|
|
|
|
useCallback,
|
|
|
|
useMemo,
|
|
|
|
useState,
|
|
|
|
} from "react";
|
2021-05-12 21:18:17 +08:00
|
|
|
import {
|
|
|
|
Commit,
|
|
|
|
CreatePipelineTaskInput,
|
|
|
|
Pipeline,
|
|
|
|
PipelineTask,
|
|
|
|
TaskStatuses,
|
|
|
|
PipelineUnits,
|
|
|
|
} from "../generated/graphql";
|
2021-06-20 15:42:03 +08:00
|
|
|
import { CREATE_PIPELINE_TASK, STOP_PIPELINE_TASK } from "./mutations";
|
2021-05-12 21:18:17 +08:00
|
|
|
import { COMMITS } from "./queries";
|
2021-06-05 19:17:45 +08:00
|
|
|
import { SYNC_COMMITS } from "./subscriptions";
|
2021-05-09 15:29:11 +08:00
|
|
|
|
|
|
|
interface Props {
|
|
|
|
pipeline: Pipeline;
|
|
|
|
}
|
|
|
|
|
|
|
|
const useStyles = makeStyles((theme) => ({
|
|
|
|
root: {
|
|
|
|
flex: "1 1 100%",
|
|
|
|
},
|
|
|
|
nested: {
|
|
|
|
paddingLeft: theme.spacing(4),
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
2021-05-12 21:18:17 +08:00
|
|
|
export const CommitList: FC<Props> = ({ pipeline }) => {
|
2021-06-05 19:17:45 +08:00
|
|
|
const { enqueueSnackbar } = useSnackbar();
|
|
|
|
|
|
|
|
const { data, loading, refetch } = useQuery<{ commits?: Commit[] }>(COMMITS, {
|
2021-05-09 15:29:11 +08:00
|
|
|
variables: {
|
|
|
|
pipelineId: pipeline.id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-06-05 19:17:45 +08:00
|
|
|
const { loading: syncing } = useSubscription<{ syncCommits: boolean }>(
|
|
|
|
SYNC_COMMITS,
|
|
|
|
{
|
|
|
|
variables: {
|
|
|
|
pipelineId: pipeline.id,
|
|
|
|
},
|
|
|
|
onSubscriptionData({ subscriptionData: { data, error } }) {
|
|
|
|
if (error) {
|
|
|
|
enqueueSnackbar(error.message, {
|
|
|
|
variant: "warning",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (data?.syncCommits) {
|
|
|
|
refetch({
|
|
|
|
appInstance: data.syncCommits,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2021-05-09 15:29:11 +08:00
|
|
|
const classes = useStyles();
|
|
|
|
|
|
|
|
return (
|
|
|
|
<section className={classes.root}>
|
|
|
|
{(() => {
|
|
|
|
if (loading) {
|
|
|
|
return <LinearProgress color="secondary" />;
|
|
|
|
}
|
|
|
|
|
2021-05-09 16:42:19 +08:00
|
|
|
return (
|
2021-06-05 19:17:45 +08:00
|
|
|
<section>
|
|
|
|
{syncing && <LinearProgress color="secondary" />}
|
|
|
|
<List>
|
|
|
|
{data?.commits?.map((commit) => (
|
|
|
|
<Item key={commit.hash} commit={commit} pipeline={pipeline} />
|
|
|
|
))}
|
|
|
|
</List>
|
|
|
|
</section>
|
2021-05-09 16:42:19 +08:00
|
|
|
);
|
2021-05-09 15:29:11 +08:00
|
|
|
})()}
|
|
|
|
</section>
|
2021-05-12 21:18:17 +08:00
|
|
|
);
|
|
|
|
};
|
2021-05-09 15:29:11 +08:00
|
|
|
|
2021-05-12 21:18:17 +08:00
|
|
|
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,
|
|
|
|
}) => {
|
2021-05-09 15:29:11 +08:00
|
|
|
const [isOpen, setOpen] = useState(() => false);
|
2021-05-09 16:42:19 +08:00
|
|
|
|
2021-06-05 19:17:45 +08:00
|
|
|
const [createTask, { loading }] =
|
|
|
|
useMutation<
|
|
|
|
{ createPipelineTask: PipelineTask },
|
|
|
|
{ task: CreatePipelineTaskInput }
|
|
|
|
>(CREATE_PIPELINE_TASK);
|
2021-05-12 21:18:17 +08:00
|
|
|
|
|
|
|
const units = useMemo(
|
|
|
|
() => pipeline.workUnitMetadata.units.map((unit) => unit.type),
|
|
|
|
[pipeline]
|
|
|
|
);
|
|
|
|
|
2021-06-05 19:17:45 +08:00
|
|
|
const { navigate, url } = useRouter();
|
2021-05-12 21:18:17 +08:00
|
|
|
const { response } = useResponse();
|
|
|
|
|
2021-06-05 19:17:45 +08:00
|
|
|
const handleCreateTask = useCallback(
|
|
|
|
(unit: PipelineUnits) => {
|
|
|
|
const _units = [...takeWhile(complement(equals(unit)), units), unit];
|
|
|
|
createTask({
|
|
|
|
variables: {
|
|
|
|
task: {
|
|
|
|
units: _units,
|
|
|
|
pipelineId: pipeline.id,
|
|
|
|
commit: commit.hash,
|
|
|
|
},
|
2021-05-12 21:18:17 +08:00
|
|
|
},
|
2021-06-05 19:17:45 +08:00
|
|
|
}).then(({ data }) => {
|
|
|
|
navigate({
|
|
|
|
url: url({
|
|
|
|
name: "pipeline-task-detail",
|
|
|
|
params: { ...response.params, taskId: data?.createPipelineTask.id },
|
|
|
|
}),
|
|
|
|
});
|
2021-05-12 21:18:17 +08:00
|
|
|
});
|
2021-06-05 19:17:45 +08:00
|
|
|
},
|
|
|
|
[commit, createTask, navigate, pipeline, response, units, url]
|
|
|
|
);
|
2021-05-12 21:18:17 +08:00
|
|
|
|
|
|
|
const actions = useMemo(
|
|
|
|
() =>
|
|
|
|
units.map((unit) => {
|
|
|
|
const pair = find(propEq(0, unit), unitActionPairs);
|
|
|
|
return (
|
|
|
|
pair && (
|
|
|
|
<IconButton
|
2021-06-05 19:17:45 +08:00
|
|
|
key={unit}
|
2021-05-12 21:18:17 +08:00
|
|
|
aria-label={pair[2]}
|
|
|
|
disabled={loading}
|
|
|
|
onClick={() => handleCreateTask(unit)}
|
|
|
|
>
|
|
|
|
{pair[1]}
|
|
|
|
</IconButton>
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}),
|
2021-06-05 19:17:45 +08:00
|
|
|
[units, handleCreateTask, loading]
|
2021-05-12 21:18:17 +08:00
|
|
|
);
|
2021-05-09 15:29:11 +08:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<ListItem button onClick={() => setOpen(!isOpen)}>
|
|
|
|
<ListItemText
|
|
|
|
primary={commit.message}
|
2021-06-05 19:17:45 +08:00
|
|
|
secondary={commit.date && format(commit.date, "yyyy-MM-dd HH:mm:ss")}
|
2021-05-09 15:29:11 +08:00
|
|
|
/>
|
2021-05-12 21:18:17 +08:00
|
|
|
<ListItemSecondaryAction>{actions}</ListItemSecondaryAction>
|
2021-05-09 15:29:11 +08:00
|
|
|
</ListItem>
|
2021-05-12 21:18:17 +08:00
|
|
|
{loading && <LinearProgress color="secondary" />}
|
2021-05-09 15:29:11 +08:00
|
|
|
<Collapse in={isOpen} timeout="auto" unmountOnExit>
|
|
|
|
<List component="div" disablePadding>
|
2021-05-09 16:42:19 +08:00
|
|
|
{commit.tasks.map((task) => (
|
|
|
|
<Link
|
|
|
|
key={task.id}
|
|
|
|
name="pipeline-task-detail"
|
|
|
|
params={{ ...response.params, taskId: task.id }}
|
|
|
|
>
|
|
|
|
<TaskItem task={task} />
|
|
|
|
</Link>
|
|
|
|
))}
|
2021-05-09 15:29:11 +08:00
|
|
|
</List>
|
|
|
|
</Collapse>
|
|
|
|
</Fragment>
|
|
|
|
);
|
2021-05-12 21:18:17 +08:00
|
|
|
};
|
2021-05-09 15:29:11 +08:00
|
|
|
|
2021-05-12 21:18:17 +08:00
|
|
|
const TaskItem: FC<{ task: PipelineTask }> = ({ task }) => {
|
2021-05-09 15:29:11 +08:00
|
|
|
const classes = useStyles();
|
|
|
|
const theme = useTheme();
|
|
|
|
const statusIcon: ReactNode = (() => {
|
|
|
|
switch (task.status) {
|
|
|
|
case TaskStatuses.Pending:
|
|
|
|
return <Timer style={{ color: theme.palette.info.main }} />;
|
|
|
|
case TaskStatuses.Success:
|
|
|
|
return <CheckCircle style={{ color: theme.palette.success.main }} />;
|
|
|
|
case TaskStatuses.Failed:
|
|
|
|
return <Cancel style={{ color: theme.palette.error.main }} />;
|
|
|
|
case TaskStatuses.Working:
|
2021-05-12 21:18:17 +08:00
|
|
|
return (
|
|
|
|
<CircularProgress style={{ color: theme.palette.secondary.main }} />
|
|
|
|
);
|
2021-05-09 15:29:11 +08:00
|
|
|
}
|
2021-05-12 21:18:17 +08:00
|
|
|
})();
|
2021-06-20 15:42:03 +08:00
|
|
|
const [stopTask, { loading: stopTaskWaiting }] = useMutation(
|
|
|
|
STOP_PIPELINE_TASK,
|
|
|
|
{
|
|
|
|
variables: { taskId: task.id },
|
|
|
|
}
|
|
|
|
);
|
|
|
|
const stop: MouseEventHandler = useCallback(
|
|
|
|
(event) => {
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
|
|
|
stopTask();
|
|
|
|
},
|
|
|
|
[stopTask]
|
|
|
|
);
|
2021-05-09 15:29:11 +08:00
|
|
|
return (
|
|
|
|
<ListItem button className={classes.nested}>
|
|
|
|
<ListItemIcon>{statusIcon}</ListItemIcon>
|
2021-06-05 19:17:45 +08:00
|
|
|
<ListItemText
|
|
|
|
primary={
|
|
|
|
task.startedAt && format(task.startedAt, "yyyy-MM-dd HH:mm:ss")
|
|
|
|
}
|
|
|
|
/>
|
2021-06-20 15:42:03 +08:00
|
|
|
<ListItemSecondaryAction>
|
|
|
|
{task.status === TaskStatuses.Working && (
|
|
|
|
<IconButton edge="end" aria-label="stop" onClick={stop}>
|
|
|
|
<Stop />
|
|
|
|
</IconButton>
|
|
|
|
)}
|
|
|
|
</ListItemSecondaryAction>
|
|
|
|
<LimitedBackdrop open={stopTaskWaiting} />
|
2021-05-09 15:29:11 +08:00
|
|
|
</ListItem>
|
|
|
|
);
|
2021-05-12 21:18:17 +08:00
|
|
|
};
|
2021-06-20 15:42:03 +08:00
|
|
|
const LimitedBackdrop = withStyles({
|
|
|
|
root: {
|
|
|
|
position: "absolute",
|
|
|
|
zIndex: 1,
|
|
|
|
},
|
|
|
|
})(Backdrop);
|