feat(pipeline-task): 实时展示执行日志

This commit is contained in:
Ivan Li 2021-06-06 22:41:56 +08:00
parent 939777910c
commit 6bc9f787f3
5 changed files with 253 additions and 22 deletions

View File

@ -17,7 +17,7 @@ import { getMainDefinition } from "@apollo/client/utilities";
import { useSnackbar } from "notistack"; import { useSnackbar } from "notistack";
const schema = buildClientSchema( const schema = buildClientSchema(
(introspectionResult as unknown) as IntrospectionQuery introspectionResult as unknown as IntrospectionQuery
); );
const typesMap = { const typesMap = {
@ -70,13 +70,28 @@ export const FennecApolloClientProvider: FC = ({ children }) => {
); );
const link = ApolloLink.from([ const link = ApolloLink.from([
errorLink, errorLink,
(withScalars({ schema, typesMap }) as unknown) as ApolloLink, withScalars({ schema, typesMap }) as unknown as ApolloLink,
splitLink, splitLink,
]); ]);
const client = new ApolloClient({ const client = new ApolloClient({
ssrMode: typeof window === "undefined", ssrMode: typeof window === "undefined",
link, link,
cache: new InMemoryCache(), cache: new InMemoryCache({
typePolicies: {
// PipelineTaskLogs: {
// keyFields: ["unit"],
// },
// PipelineTask: {
// fields: {
// logs: {
// merge(existing = [], incoming: any[]) {
// return [...existing, ...incoming];
// },
// },
// },
// },
},
}),
}); });
return <ApolloProvider client={client}>{children}</ApolloProvider>; return <ApolloProvider client={client}>{children}</ApolloProvider>;

View File

@ -1156,6 +1156,54 @@
"name": "PipelineTaskEvent", "name": "PipelineTaskEvent",
"description": null, "description": null,
"fields": [ "fields": [
{
"name": "taskId",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pipelineId",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "projectId",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "unit", "name": "unit",
"description": null, "description": null,
@ -1168,6 +1216,54 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "emittedAt",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "message",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "messageType",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "status", "name": "status",
"description": null, "description": null,

View File

@ -142,7 +142,13 @@ export type PipelineTask = {
export type PipelineTaskEvent = { export type PipelineTaskEvent = {
__typename?: 'PipelineTaskEvent'; __typename?: 'PipelineTaskEvent';
taskId: Scalars['String'];
pipelineId: Scalars['String'];
projectId: Scalars['String'];
unit?: Maybe<PipelineUnits>; unit?: Maybe<PipelineUnits>;
emittedAt: Scalars['DateTime'];
message: Scalars['String'];
messageType: Scalars['String'];
status: TaskStatuses; status: TaskStatuses;
}; };

View File

@ -1,9 +1,17 @@
import { gql, useQuery } from "@apollo/client"; import { gql, useQuery, useSubscription } from "@apollo/client";
import { LinearProgress, makeStyles, Typography } from "@material-ui/core"; import { LinearProgress, makeStyles, Typography } from "@material-ui/core";
import { format } from 'date-fns'; import { format } from "date-fns";
import React, { FC } from "react"; import React, { FC, useState } from "react";
import { ErrorPage } from "../commons/fallbacks/error-page"; import { ErrorPage } from "../commons/fallbacks/error-page";
import { PipelineTask, PipelineTaskLogs } from "../generated/graphql"; import {
PipelineTask,
PipelineTaskEvent,
PipelineTaskLogs,
TaskStatuses,
} from "../generated/graphql";
import { PIPELINE_TASK_EVENT } from "./subscriptions";
import { clone, find, propEq } from "ramda";
import { PipelineUnits } from '../generated/graphql';
interface Props { interface Props {
taskId: string; taskId: string;
@ -30,30 +38,111 @@ const PIPELINE_TASK = gql`
`; `;
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
root: { root: {},
},
groupTitle: { groupTitle: {
backgroundColor: 'white', backgroundColor: "white",
fontWeight: 500, fontWeight: 500,
borderTop: '1px solid #eee', borderTop: "1px solid #eee",
fontSize: '16px', fontSize: "16px",
padding: '12px', padding: "12px",
marginLeft: '1px', marginLeft: "1px",
}, },
logText: { logText: {
fontFamily: 'Menlo, Monaco, "Courier New", monospace', fontFamily: 'Menlo, Monaco, "Courier New", monospace',
whiteSpace: 'pre-wrap', whiteSpace: "pre-wrap",
border: 'none', border: "none",
margin: '6px 12px', margin: "6px 12px",
display: "block" display: "block",
}, },
})); }));
export const PipelineTaskDetail: FC<Props> = ({ taskId }) => { export const PipelineTaskDetail: FC<Props> = ({ taskId }) => {
const { data, loading, error } = useQuery<{ pipelineTask: PipelineTask }>( const [taskEvents, setTaskEvents] = useState(
PIPELINE_TASK, () => new Array<PipelineTaskEvent>()
);
const { data, loading, error, client } = useQuery<{
pipelineTask: PipelineTask;
}>(PIPELINE_TASK, {
variables: { taskId },
});
const task = data?.pipelineTask;
useSubscription<{ pipelineTaskEvent: PipelineTaskEvent }>(
PIPELINE_TASK_EVENT,
{ {
variables: { taskId }, variables: { taskId },
onSubscriptionData({ subscriptionData: { data } }) {
const event = data?.pipelineTaskEvent;
console.log(event);
if (event && task) {
setTaskEvents((prev) => [...prev, event]);
if (event.unit) {
// event of running scripts
client.cache.modify({
id: client.cache.identify(task!),
fields: {
logs(before: PipelineTaskLogs[]) {
before = clone(before);
let l = find(propEq("unit", event.unit), before);
if (l) {
l.logs += event.message;
} else {
l = {
unit: event.unit!,
logs: event.message,
status: event.status,
startedAt: event.emittedAt,
endedAt: null,
__typename: "PipelineTaskLogs",
};
before.push(l);
}
if (event.status === TaskStatuses.Working) {
l.startedAt || (l.startedAt = event.emittedAt);
}
if (
[TaskStatuses.Failed, TaskStatuses.Success].includes(
event.status
)
) {
l.startedAt && (l.startedAt = event.emittedAt);
l.endedAt = event.emittedAt;
}
l.status = event.status;
return before;
},
},
});
} else {
// event of task status change
client.cache.modify({
id: client.cache.identify(task!),
fields: {
status() {
return event.status;
},
startedAt(before) {
if (event.status === TaskStatuses.Working) {
return event.emittedAt;
} else {
return before;
}
},
endedAt(before) {
if (
[TaskStatuses.Success, TaskStatuses.Failed].includes(
event.status
)
) {
return event.emittedAt;
} else {
return before;
}
},
},
});
}
}
},
} }
); );
const classes = useStyles(); const classes = useStyles();
@ -71,8 +160,15 @@ export const PipelineTaskDetail: FC<Props> = ({ taskId }) => {
<Typography variant="h4" component="h2"> <Typography variant="h4" component="h2">
{taskId} detail {taskId} detail
</Typography> </Typography>
<Typography variant="h5" component="h3">
{task?.status}
</Typography>
<Typography variant="h5" component="h3">
{task?.startedAt && format(task?.startedAt, "yyyy-MM-dd HH:mm:ss.SSS")}-
{task?.endedAt && format(task?.endedAt, "yyyy-MM-dd HH:mm:ss.SSS")}
</Typography>
{data?.pipelineTask.logs.map((logs) => ( {task?.logs.map((logs) => (
<LogGroup key={logs.unit} logs={logs} /> <LogGroup key={logs.unit} logs={logs} />
))} ))}
</div> </div>
@ -84,7 +180,9 @@ const LogGroup: FC<{ logs: PipelineTaskLogs }> = ({ logs }) => {
return ( return (
<div> <div>
<div className={classes.groupTitle}> <div className={classes.groupTitle}>
{logs.unit} {format(logs.startedAt, 'yyyy-MM-dd HH:mm:ss')} {logs.status} {logs.unit}{" "}
{logs.startedAt && format(logs.startedAt, "yyyy-MM-dd HH:mm:ss")}{" "}
{logs.endedAt && format(logs.endedAt, "yyyy-MM-dd HH:mm:ss")} {logs.status}
</div> </div>
<code className={classes.logText}>{logs.logs}</code> <code className={classes.logText}>{logs.logs}</code>
</div> </div>

View File

@ -0,0 +1,16 @@
import { gql } from "@apollo/client";
export const PIPELINE_TASK_EVENT = gql`
subscription PipelineTaskEvent($taskId: String!) {
pipelineTaskEvent(taskId: $taskId) {
taskId
pipelineId
projectId
unit
emittedAt
message
messageType
status
}
}
`;