Update to include JobLog component.

This commit is contained in:
NetPenguins
2020-08-30 20:45:29 -04:00
parent 8cce7f2f32
commit a31afed4c5
10 changed files with 168 additions and 23454 deletions
+1
View File
@@ -35,6 +35,7 @@
"moment": "^2.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-lazylog": "^4.5.3",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
},
@@ -18,7 +18,7 @@ import { createApiRef } from '@backstage/core';
import {
ActionsListWorkflowRunsForRepoResponseData,
ActionsGetWorkflowResponseData,
ActionsGetWorkflowRunResponseData
ActionsGetWorkflowRunResponseData,
} from '@octokit/types';
export const githubActionsApiRef = createApiRef<GithubActionsApi>({
@@ -75,4 +75,15 @@ export type GithubActionsApi = {
repo: string;
runId: number;
}) => Promise<any>;
downloadJobLogsForWorkflowRun: ({
token,
owner,
repo,
runId,
}: {
token: string;
owner: string;
repo: string;
runId: number;
}) => Promise<any>;
};
@@ -20,7 +20,6 @@ import {
ActionsListWorkflowRunsForRepoResponseData,
ActionsGetWorkflowResponseData,
ActionsGetWorkflowRunResponseData,
ActionsListJobsForWorkflowRunResponseData
} from '@octokit/types';
export class GithubActionsClient implements GithubActionsApi {
@@ -103,22 +102,22 @@ export class GithubActionsClient implements GithubActionsApi {
});
return run.data;
}
async listJobsForWorkflowRun({
async downloadJobLogsForWorkflowRun({
token,
owner,
repo,
id,
runId,
}: {
token: string;
owner: string;
repo: string;
id: number;
}): Promise<ActionsListJobsForWorkflowRunResponseData> {
const workflow = await new Octokit({ auth: token }).actions.listJobsForWorkflowRun({
runId: number;
}): Promise<any> {
const workflow = await new Octokit({ auth: token }).actions.downloadJobLogsForWorkflowRun({
owner,
repo,
run_id: id,
job_id: runId,
});
return workflow.data;
}
}
};
+1
View File
@@ -29,6 +29,7 @@ export type Job = {
conclusion: string;
started_at: string;
completed_at: string;
id: string;
name: string;
steps: Step[];
};
@@ -1,181 +0,0 @@
/*
* 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){
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>
);
};
@@ -18,6 +18,8 @@ import { useEntityCompoundName } from '@backstage/plugin-catalog';
import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
import { useWorkflowRunJobs } from './useWorkflowRunJobs';
import { useProjectName } from '../useProjectName';
import { WorkflowRunLogs } from '../WorkflowRunLogs';
import {
makeStyles,
Box,
@@ -136,6 +138,9 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => {
</Table>
</TableContainer>
</ExpansionPanelDetails>
<WorkflowRunLogs
runId={job.id}
/>
</ExpansionPanel>
);
};
@@ -0,0 +1,106 @@
/*
* 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 {
LinearProgress,
ExpansionPanel,
Typography,
makeStyles,
Theme,
ExpansionPanelSummary,
} from '@material-ui/core';
import React, {Suspense} from 'react';
import { useEntityCompoundName } from '@backstage/plugin-catalog';
import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs';
import LinePart from 'react-lazylog/build/LinePart';
import { useProjectName } from '../useProjectName';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
const useStyles = makeStyles<Theme>(() => ({
button: {
order: -1,
marginRight: 0,
marginLeft: '-20px',
},
}));
const DisplayLog = (jobLogs: any) =>{
return(
<Suspense fallback={<LinearProgress />}>
<div style={{ height: '50vh', width: '100%' }}>
<LazyLog
text={jobLogs.jobLogs ?? "No Values Found"}
extraLines={1}
caseInsensitive
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>
)
}
/**
* A component for Run Logs visualization.
*/
export const WorkflowRunLogs = ({runId}:{runId: string}) => {
const classes = useStyles();
let entityCompoundName = useEntityCompoundName();
if (!entityCompoundName.name) {
// TODO(shmidt-i): remove when is fully integrated
// into the entity view
entityCompoundName = {
kind: 'Component',
name: 'backstage',
namespace: 'default',
};
}
const projectName = useProjectName(entityCompoundName);
const [owner, repo] = projectName.value ? projectName.value.split('/') : [];
const jobLogs = useDownloadWorkflowRunLogs(repo, owner, runId);
return (
<ExpansionPanel
TransitionProps={{ unmountOnExit: true }}
>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
IconButtonProps={{
className: classes.button,
}}
>
<Typography variant="button">
{jobLogs.value === null ? "No Logs to Display" : "Job Log"}
</Typography>
</ExpansionPanelSummary>
{jobLogs.value && <DisplayLog jobLogs={jobLogs.value || undefined}/>}
</ExpansionPanel>
);
};
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { BuildSteps } from './BuildSteps';
export { WorkflowRunLogs } from './WorkflowRunLogs';
@@ -0,0 +1,35 @@
/*
* 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 { useApi, githubAuthApiRef } from '@backstage/core';
import { useAsync } from 'react-use';
import { githubActionsApiRef } from '../../api';
export const useDownloadWorkflowRunLogs = (repo: string, owner: string, id: string) => {
const api = useApi(githubActionsApiRef);
const auth = useApi(githubAuthApiRef);
const details = useAsync(async () => {
const token = await auth.getAccessToken(['repo']);
return repo && owner
? api.downloadJobLogsForWorkflowRun({
token,
owner,
repo,
runId: parseInt(id, 10),
})
: Promise.reject('No repo/owner provided');
}, [repo, owner, id]);
return details;
};
-23263
View File
File diff suppressed because it is too large Load Diff