Added LazyLog to Github-Actions for displaying workflow run logs.

This commit is contained in:
NetPenguins
2020-08-29 09:59:38 -04:00
parent 3158c2390b
commit 21bd252051
5 changed files with 285 additions and 5 deletions
@@ -19,6 +19,7 @@ import {
ActionsListWorkflowRunsForRepoResponseData,
ActionsGetWorkflowResponseData,
ActionsGetWorkflowRunResponseData,
ActionsListJobsForWorkflowRunResponseData
} from '@octokit/types';
export const githubActionsApiRef = createApiRef<GithubActionsApi>({
@@ -73,4 +74,15 @@ export type GithubActionsApi = {
repo: string;
runId: number;
}) => void;
listJobsForWorkflowRun: ({
token,
owner,
repo,
id,
}: {
token: string;
owner: string;
repo: string;
id: number;
}) => Promise<ActionsListJobsForWorkflowRunResponseData>;
};
@@ -20,6 +20,7 @@ import {
ActionsListWorkflowRunsForRepoResponseData,
ActionsGetWorkflowResponseData,
ActionsGetWorkflowRunResponseData,
ActionsListJobsForWorkflowRunResponseData
} from '@octokit/types';
export class GithubActionsClient implements GithubActionsApi {
@@ -99,4 +100,22 @@ export class GithubActionsClient implements GithubActionsApi {
});
return run.data;
}
async listJobsForWorkflowRun({
token,
owner,
repo,
id,
}: {
token: string;
owner: string;
repo: string;
id: number;
}): Promise<ActionsListJobsForWorkflowRunResponseData> {
const workflow = await new Octokit({ auth: token }).actions.listJobsForWorkflowRun({
owner,
repo,
run_id: id,
});
return workflow.data;
}
}
@@ -16,6 +16,7 @@
import {
Button,
Box,
LinearProgress,
makeStyles,
Paper,
@@ -27,12 +28,13 @@ import {
Theme,
Typography,
} from '@material-ui/core';
import React from 'react';
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { useAsync } from 'react-use';
import { Link, useApi, githubAuthApiRef } from '@backstage/core';
import { githubActionsApiRef } from '../../api';
import { BuildSteps } from'../BuildSteps/BuildSteps';
import ArrowBackRoundedIcon from '@material-ui/icons/ArrowBackRounded';
const useStyles = makeStyles<Theme>(theme => ({
root: {
maxWidth: 720,
@@ -46,14 +48,46 @@ const useStyles = makeStyles<Theme>(theme => ({
},
}));
export const BuildDetailsPage = () => {
const repo = 'try-ssr';
const owner = 'CircleCITest3';
const api = useApi(githubActionsApiRef);
const auth = useApi(githubAuthApiRef);
const classes = useStyles();
const { id } = useParams();
let jobs: {
jobName: string,
jobStart: string,
jobEnd: string,
jobStatus: string,
steps: any[]
}[] = [];
const [jobsArray] = useState(jobs);
const jobsStatus = useAsync(async () => {
const token = await auth.getAccessToken(['repo', 'user']);
return api
.listJobsForWorkflowRun({
token,
owner,
repo,
id: parseInt(id, 10),
})
.then(data => {
data.jobs.map(job => {
jobs.push({
jobName: job.name,
jobStart: job.started_at,
jobEnd: job.completed_at,
jobStatus: job.conclusion,
steps: job.steps
})
})
return data;
});
});
const status = useAsync(async () => {
const token = await auth.getAccessToken(['repo', 'user']);
return api
@@ -84,8 +118,8 @@ export const BuildDetailsPage = () => {
<div className={classes.root}>
<Typography className={classes.title} variant="h3">
<Link to="/github-actions">
<Typography component="span" variant="h3" color="primary">
&lt;
<Typography component="span" variant="h1" color="primary">
<ArrowBackRoundedIcon />
</Typography>
</Link>
Build Details
@@ -138,6 +172,23 @@ export const BuildDetailsPage = () => {
</TableBody>
</Table>
</TableContainer>
<Typography className={classes.title} variant="h3">
Build Log
</Typography>
<Box>
{jobsStatus.loading
? <LinearProgress />
: jobsStatus.error
? <Typography variant="h6" color="error">
Failed to load build, {jobsStatus.error.message}
</Typography>
: jobsArray.map(
(job) => (
<BuildSteps runData={job} key={`${job.jobName}-${job.jobStart}`}/>
),
)
}
</Box>
</div>
);
};
@@ -0,0 +1,182 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState, FC, Suspense, useEffect } from 'react';
import {
ExpansionPanel,
ExpansionPanelSummary,
Typography,
ExpansionPanelDetails,
LinearProgress,
} from '@material-ui/core';
import moment from 'moment';
import CheckCircleRoundedIcon from '@material-ui/icons/CheckCircleRounded';
import ErrorRoundedIcon from '@material-ui/icons/ErrorRounded';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { makeStyles } from '@material-ui/core/styles';
import LinePart from 'react-lazylog/build/LinePart';
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
moment.relativeTimeThreshold('ss', 0);
const useStyles = makeStyles(theme => ({
expansionPanelDetails: {
padding: 0,
},
button: {
order: -1,
marginRight: 0,
marginLeft: '-20px',
},
neutral: {},
failed: {
position: 'relative',
'&:after': {
pointerEvents: 'none',
content: '""',
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
boxShadow: `inset 4px 0px 0px ${theme.palette.error.main}`,
},
},
running: {
position: 'relative',
'&:after': {
pointerEvents: 'none',
content: '""',
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
boxShadow: `inset 4px 0px 0px ${theme.palette.info.main}`,
},
},
cardContent: {
backgroundColor: theme.palette.background.default,
},
success: {
position: 'relative',
'&:after': {
pointerEvents: 'none',
content: '""',
position: 'absolute',
top: 0,
right: 0,
left: 0,
bottom: 0,
color: 'green',
boxShadow: `inset 4px 0px 0px ${theme.palette.success.main}`,
},
},
}));
const pickClassName = (
classes: ReturnType<typeof useStyles>,
build: any,
) => {
if (build === 'failed') return classes.failed;
if (['running', 'queued'].includes(build!)) return classes.running;
if (build === 'success') return classes.success;
return classes.neutral;
};
const timeElapsed = (
start: string,
end: string,
humanize: boolean = false
) => {
if(humanize){
return moment.duration(
moment(start|| moment()).diff(moment(end)),
).humanize();
}else{
return moment.duration(
moment(start|| moment()).diff(moment(end)),
).asSeconds();
}
}
export const BuildSteps: FC<{
runData: {
jobName: string,
jobStart: string,
jobEnd: string,
jobStatus: string,
steps: Array<any>
};
}> = ({ runData }) => {
const classes = useStyles();
const [messages, setMessages] = useState([] as any);
useEffect(() => {
if(runData.steps !== undefined){
console.log(runData.steps)
setMessages(runData.steps.map(
({ name, completed_at, started_at }: {
name: string; completed_at: string; started_at: string;
}) => `${name} ${timeElapsed(started_at, completed_at)}s`
));
}
}, [runData.steps])
return (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
className={pickClassName(classes, runData.jobStatus)}
>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${runData.jobName}-content`}
id={`panel-${runData.jobName}-header`}
>
{
runData.jobStatus == 'success' ?
<CheckCircleRoundedIcon style={{ color: 'lime' }}/>
: <ErrorRoundedIcon style={{ color: 'red' }}/>
}
<Typography variant="button">
{runData.jobName || "No Run Logs"} {`(${timeElapsed(runData.jobStart, runData.jobEnd, true)})` || ""}
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.expansionPanelDetails}>
{messages.length === 0 ? (
'Nothing here...'
) : (
<Suspense fallback={<LinearProgress />}>
<div style={{ height: '25vh', width: '100%' }}>
<LazyLog
text={messages.join('\n')}
extraLines={1}
caseInsensitive={true}
enableSearch
formatPart={(line) => {
if(line.toLocaleLowerCase().includes("error")
|| line.toLocaleLowerCase().includes("failed")
|| line.toLocaleLowerCase().includes("failure"))
{
return <LinePart style={{color: 'red'}} part={{text: line}}/>
}
return line;
}}
/>
</div>
</Suspense>
)}
</ExpansionPanelDetails>
</ExpansionPanel>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { BuildSteps } from './BuildSteps';