feat(commits): commit and task list.

This commit is contained in:
Ivan Li
2021-05-09 15:29:11 +08:00
parent 5d3f97667a
commit dbb81fa952
16 changed files with 784 additions and 32 deletions

109
src/commits/commit-list.tsx Normal file
View File

@@ -0,0 +1,109 @@
import { useQuery } from '@apollo/client';
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';
interface Props {
pipeline: Pipeline;
}
const useStyles = makeStyles((theme) => ({
root: {
flex: "1 1 100%",
},
nested: {
paddingLeft: theme.spacing(4),
},
}));
export const CommitList: FC<Props> = ({pipeline}) => {
const { data, loading } = useQuery<{ commits: Commit[] }>(COMMITS, {
variables: {
pipelineId: pipeline.id,
},
});
const classes = useStyles();
return (
<section className={classes.root}>
{(() => {
if (loading) {
return <LinearProgress color="secondary" />;
}
return <List>
{data?.commits.map(commit => (
<Item key={commit.hash} commit={commit} />
))}
</List>;
})()}
</section>
)
}
const Item: FC<{commit: Commit}> = ({commit}) => {
const [isOpen, setOpen] = useState(() => false);
const classes = useStyles();
return (
<Fragment>
<ListItem button onClick={() => setOpen(!isOpen)}>
<ListItemText
primary={commit.message}
secondary={format(commit.date, "yyyy-MM-dd HH:mm:ss")}
/>
<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>
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{
commit.tasks.map(task => (<TaskItem key={task.id} task={task} />))
}
</List>
</Collapse>
</Fragment>
);
}
const TaskItem: FC<{task: PipelineTask}> = ({task}) => {
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:
return <CircularProgress style={{ color: theme.palette.secondary.main }} />;
}
}
)()
return (
<ListItem button className={classes.nested}>
<ListItemIcon>{statusIcon}</ListItemIcon>
<ListItemText primary={format(task.startedAt, "yyyy-MM-dd HH:mm:ss")} />
</ListItem>
);
};

20
src/commits/queries.ts Normal file
View File

@@ -0,0 +1,20 @@
import { gql } from '@apollo/client';
export const COMMITS = gql`
query Commits($pipelineId: String!) {
commits(pipelineId: $pipelineId) {
message
hash
date
body
author_name
tasks {
id
units
status
startedAt
endedAt
}
}
}
`;

View File

@@ -1,6 +1,29 @@
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from "@apollo/client";
import { withScalars } from 'apollo-link-scalars';
import { buildClientSchema, IntrospectionQuery } from 'graphql';
import { DateTimeResolver } from 'graphql-scalars';
import introspectionResult from "../../generated/graphql.schema.json";
export const client = new ApolloClient({
const schema = buildClientSchema(
(introspectionResult as unknown) as IntrospectionQuery
);
const httpLink = new HttpLink({
uri: "/api/graphql",
cache: new InMemoryCache(),
});
const typesMap = {
DateTime: DateTimeResolver,
};
const link = ApolloLink.from([
(withScalars({ schema, typesMap }) as unknown) as ApolloLink,
httpLink,
]);
export function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link,
cache: new InMemoryCache(),
});
}

View File

@@ -0,0 +1,26 @@
import { gql } from '@apollo/client';
export const COMMIT_LIST_QUERY = gql`
query CommitListQuery($projectId: String!, $pipelineId: String!) {
project(id: $projectId) {
id
name
comment
webUrl
sshUrl
webHookSecret
}
pipeline(id: $pipelineId) {
id
name
branch
workUnitMetadata {
version
units {
type
scripts
}
}
}
}
`;

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,18 @@ export type Scalars = {
DateTime: any;
};
export type Commit = {
__typename?: 'Commit';
hash: Scalars['String'];
date: Scalars['DateTime'];
message: Scalars['String'];
refs: Scalars['String'];
body: Scalars['String'];
author_name: Scalars['String'];
author_email: Scalars['String'];
tasks: Array<PipelineTask>;
};
export type CreatePipelineInput = {
projectId: Scalars['String'];
branch: Scalars['String'];
@@ -172,6 +184,7 @@ export type Query = {
project: Project;
pipelines: Array<Pipeline>;
pipeline: Pipeline;
commits: Array<Commit>;
listPipelineTaskByPipelineId: Array<PipelineTask>;
findPipelineTask: PipelineTask;
};
@@ -192,6 +205,11 @@ export type QueryPipelineArgs = {
};
export type QueryCommitsArgs = {
pipelineId: Scalars['String'];
};
export type QueryListPipelineTaskByPipelineIdArgs = {
pipelineId: Scalars['String'];
};

View File

@@ -4,7 +4,7 @@ import "./index.css";
import "fontsource-roboto";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { client } from "./commons/graphql/client";
import { createApolloClient } from "./commons/graphql/client";
import { ApolloProvider } from "@apollo/client";
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
import DateFnsUtils from "@date-io/date-fns";
@@ -16,6 +16,8 @@ import routes from "./routes";
import { ConfirmProvider } from 'material-ui-confirm';
import { SnackbarProvider } from 'notistack';
const client = createApolloClient();
const router = createRouter(browser, routes, {
sideEffects: [
announce(({ response }) => {

3
src/pipelines/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './pipeline-detail';
export * from './pipeline-list';
export * from './queries';

View File

@@ -0,0 +1,11 @@
import { FC } from 'react';
import { Pipeline } from '../generated/graphql';
interface Props {
pipeline: Pipeline;
}
export const PipelineDetail: FC<Props> = ({pipeline}) => {
return <div>PipelineDetail</div>
}

View File

@@ -27,8 +27,8 @@ export const PipelineList: FC<Props> = ({ projectId }) => {
<List>
{data?.pipelines.map((pipeline) => (
<Link
name="pipeline-details"
params={{ pipelineId: pipeline.id }}
name="pipeline-commits"
params={{ pipelineId: pipeline.id, projectId: projectId }}
key={pipeline.id}
>
<Item pipeline={pipeline} />

18
src/pipelines/queries.ts Normal file
View File

@@ -0,0 +1,18 @@
import { gql } from '@apollo/client';
export const PIPELINE = gql`
query Pipeline($id: String!) {
pipeline(id: $id) {
id
name
branch
workUnitMetadata {
version
units {
type
scripts
}
}
}
}
`

View File

@@ -23,7 +23,7 @@ const useStyles = makeStyles(() => ({
},
}));
export const ProjectDetail: FC<Props> = ({ project }) => {
export const ProjectDetail: FC<Props> = ({ project, children }) => {
const headerContainer = useHeaderContainer();
const classes = useStyles();
@@ -55,7 +55,7 @@ export const ProjectDetail: FC<Props> = ({ project }) => {
</Portal>
<Grid
container
spacing={1}
spacing={0}
direction="row"
alignItems="stretch"
className={classes.root}
@@ -65,6 +65,9 @@ export const ProjectDetail: FC<Props> = ({ project }) => {
<PipelineList projectId={project.id} />
</Paper>
</Grid>
<Grid item xs={9} style={{ height: "100%", display: "flex", overflowY: 'auto' }}>
{children}
</Grid>
</Grid>
</Fragment>
);

View File

@@ -2,8 +2,12 @@ import { ApolloClient, InMemoryCache } from "@apollo/client";
import { prepareRoutes } from "@curi/router";
import { omit } from 'ramda';
import React from 'react';
import { CreateProjectInput, Project } from './generated/graphql';
import { CreateProjectInput, Pipeline, Project } from './generated/graphql';
import { PipelineDetail } from './pipelines';
import { ProjectDetail, ProjectEditor, PROJECT } from "./projects";
import { COMMIT_LIST_QUERY } from './commons/graphql/queries';
import { CommitList } from './commits/commit-list';
import { COMMITS } from './commits/queries';
export default prepareRoutes([
{
@@ -12,7 +16,7 @@ export default prepareRoutes([
respond() {
return { body: () => <div>DashBoard</div> };
},
},
}, // dashboard
{
name: "create-project",
path: "projects/create",
@@ -26,7 +30,7 @@ export default prepareRoutes([
};
return { body: () => <ProjectEditor project={input} /> };
},
},
}, // create-project
{
name: "edit-project",
path: "projects/:projectId/edit",
@@ -47,7 +51,7 @@ export default prepareRoutes([
respond({ resolved }) {
return resolved;
},
},
}, // edit-project
{
name: "project-detail",
path: "projects/:projectId",
@@ -68,5 +72,38 @@ export default prepareRoutes([
respond({ resolved }) {
return resolved;
},
},
children: [
{
name: "pipeline-commits",
path: "pipelines/:pipelineId/commits",
async resolve(
matched,
{ client }: { client: ApolloClient<InMemoryCache> }
) {
const { data } = await client.query<{
pipeline: Pipeline;
project: Project;
}>({
query: COMMIT_LIST_QUERY,
variables: {
projectId: matched?.params.projectId,
pipelineId: matched?.params.pipelineId,
},
});
return {
body: () => (
<ProjectDetail project={omit(["__typename"], data.project)}>
<CommitList
pipeline={omit(["__typename"], data.pipeline)}
/>
</ProjectDetail>
),
};
},
respond({ resolved, error }) {
return resolved || <div>Failed</div>;
},
},
],
}, // project-detail
]);