diff --git a/src/commits/commit-list.tsx b/src/commits/commit-list.tsx index 23aac32..38dce3f 100644 --- a/src/commits/commit-list.tsx +++ b/src/commits/commit-list.tsx @@ -1,13 +1,41 @@ -import { useQuery } from '@apollo/client'; -import { Link, useResponse } from '@curi/react-dom'; -import { faPlayCircle, faVial } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CircularProgress, Collapse, IconButton, LinearProgress, 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 React, { FC, Fragment, ReactNode, useState } from 'react'; -import { Commit, Pipeline, PipelineTask, TaskStatuses } from '../generated/graphql'; -import { COMMITS } from './queries'; +import { useMutation, useQuery } from "@apollo/client"; +import { Link, useResponse, useRouter } from '@curi/react-dom'; +import { faPlayCircle, faVial } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + CircularProgress, + Collapse, + IconButton, + LinearProgress, + 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 { pipeline: Pipeline; @@ -22,7 +50,7 @@ const useStyles = makeStyles((theme) => ({ }, })); -export const CommitList: FC = ({pipeline}) => { +export const CommitList: FC = ({ pipeline }) => { const { data, loading } = useQuery<{ commits: Commit[] }>(COMMITS, { variables: { pipelineId: pipeline.id, @@ -41,20 +69,84 @@ export const CommitList: FC = ({pipeline}) => { return ( {data?.commits.map((commit) => ( - + ))} ); })()} - ) -} + ); +}; -const Item: FC<{commit: Commit}> = ({commit}) => { +const unitActionPairs: Array<[PipelineUnits, ReactNode, string]> = [ + [PipelineUnits.Checkout, , "checkout"], + [ + PipelineUnits.InstallDependencies, + , + "install dependencies", + ], + [PipelineUnits.Test, , "test"], + [PipelineUnits.Deploy, , "deploy"], +]; + +const Item: FC<{ commit: Commit; pipeline: Pipeline }> = ({ + commit, + pipeline, +}) => { const [isOpen, setOpen] = useState(() => false); 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 && ( + handleCreateTask(unit)} + > + {pair[1]} + + ) + ); + }), + [units] + ); return ( setOpen(!isOpen)}> @@ -62,21 +154,9 @@ const Item: FC<{commit: Commit}> = ({commit}) => { primary={commit.message} secondary={format(commit.date, "yyyy-MM-dd HH:mm:ss")} /> - - - - - - - - - - - - - - + {actions} + {loading && } {commit.tasks.map((task) => ( @@ -92,9 +172,9 @@ const Item: FC<{commit: Commit}> = ({commit}) => { ); -} +}; -const TaskItem: FC<{task: PipelineTask}> = ({task}) => { +const TaskItem: FC<{ task: PipelineTask }> = ({ task }) => { const classes = useStyles(); const theme = useTheme(); const statusIcon: ReactNode = (() => { @@ -106,15 +186,15 @@ const TaskItem: FC<{task: PipelineTask}> = ({task}) => { case TaskStatuses.Failed: return ; case TaskStatuses.Working: - return ; - + return ( + + ); } - } - )() + })(); return ( {statusIcon} ); -}; \ No newline at end of file +}; diff --git a/src/commits/muations.ts b/src/commits/muations.ts new file mode 100644 index 0000000..6a7518e --- /dev/null +++ b/src/commits/muations.ts @@ -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 + } + } +` diff --git a/src/commons/fallbacks/error-page.tsx b/src/commons/fallbacks/error-page.tsx new file mode 100644 index 0000000..4615c9f --- /dev/null +++ b/src/commons/fallbacks/error-page.tsx @@ -0,0 +1,11 @@ +import { Typography } from '@material-ui/core'; +import React, { FC } from 'react'; + +export const ErrorPage: FC = ({children}) => { + return ( +
+ Something is wrong :( + {children} +
+ ); +} \ No newline at end of file diff --git a/src/generated/graphql.schema.json b/src/generated/graphql.schema.json index 922a33d..294a73f 100644 --- a/src/generated/graphql.schema.json +++ b/src/generated/graphql.schema.json @@ -1710,7 +1710,7 @@ "deprecationReason": null }, { - "name": "findPipelineTask", + "name": "pipelineTask", "description": null, "args": [ { diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index b82b249..a812624 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -186,7 +186,7 @@ export type Query = { pipeline: Pipeline; commits: Array; listPipelineTaskByPipelineId: Array; - findPipelineTask: PipelineTask; + pipelineTask: PipelineTask; }; @@ -215,7 +215,7 @@ export type QueryListPipelineTaskByPipelineIdArgs = { }; -export type QueryFindPipelineTaskArgs = { +export type QueryPipelineTaskArgs = { id: Scalars['String']; }; diff --git a/src/pipeline-tasks/pipeline-task-detail.tsx b/src/pipeline-tasks/pipeline-task-detail.tsx index 94d7556..ac0fc32 100644 --- a/src/pipeline-tasks/pipeline-task-detail.tsx +++ b/src/pipeline-tasks/pipeline-task-detail.tsx @@ -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 { taskId: string; } -export const PipelineTaskDetail: FC = ({taskId}) => { - return
{taskId} detail
-} \ No newline at end of file +const PIPELINE_TASK = gql` + 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 = ({ taskId }) => { + const { data, loading, error } = useQuery<{ pipelineTask: PipelineTask }>( + PIPELINE_TASK, + { + variables: { taskId }, + } + ); + const classes = useStyles(); + + if (error) { + return {error.message}; + } + + if (loading) { + return ; + } + + return ( +
+ + {taskId} detail + + + {data?.pipelineTask.logs.map((logs) => ( + + ))} +
+ ); +}; + +const LogGroup: FC<{ logs: PipelineTaskLogs }> = ({ logs }) => { + const classes = useStyles(); + return ( +
+
+ {logs.unit} {format(logs.startedAt, 'yyyy-MM-dd HH:mm:ss')} {logs.status} +
+ {logs.logs} +
+ ); +}; diff --git a/src/projects/project-detail.tsx b/src/projects/project-detail.tsx index 9813981..785dd07 100644 --- a/src/projects/project-detail.tsx +++ b/src/projects/project-detail.tsx @@ -60,12 +60,12 @@ export const ProjectDetail: FC = ({ project, children }) => { alignItems="stretch" className={classes.root} > - + - + {children}