feat(pipeline-task): 实时展示执行日志
This commit is contained in:
parent
939777910c
commit
6bc9f787f3
@ -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>;
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
16
src/pipeline-tasks/subscriptions.ts
Normal file
16
src/pipeline-tasks/subscriptions.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
Loading…
x
Reference in New Issue
Block a user