diff --git a/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx index 2a059f7e02..65e4537ad7 100644 --- a/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx +++ b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx @@ -30,7 +30,7 @@ import React, { FC, Suspense, useEffect, useState } from 'react'; const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); moment.relativeTimeThreshold('ss', 0); const useStyles = makeStyles({ - expansionPanelDetails: { + accordionDetails: { padding: 0, }, button: { @@ -80,7 +80,7 @@ export const ActionOutput: FC<{ {name} ({timeElapsed}) - + {messages.length === 0 ? ( 'Nothing here...' ) : ( diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 25508a91ff..49862dea80 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -34,6 +34,7 @@ "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-lazylog": "^4.5.3", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3" diff --git a/plugins/github-actions/src/api/GithubActionsApi.ts b/plugins/github-actions/src/api/GithubActionsApi.ts index 9a95f21860..bf79e74351 100644 --- a/plugins/github-actions/src/api/GithubActionsApi.ts +++ b/plugins/github-actions/src/api/GithubActionsApi.ts @@ -19,6 +19,7 @@ import { ActionsListWorkflowRunsForRepoResponseData, ActionsGetWorkflowResponseData, ActionsGetWorkflowRunResponseData, + EndpointInterface, } from '@octokit/types'; export const githubActionsApiRef = createApiRef({ @@ -75,4 +76,15 @@ export type GithubActionsApi = { repo: string; runId: number; }) => Promise; + downloadJobLogsForWorkflowRun: ({ + token, + owner, + repo, + runId, + }: { + token: string; + owner: string; + repo: string; + runId: number; + }) => Promise; }; diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index 72f65075e6..94fb774537 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -20,6 +20,7 @@ import { ActionsListWorkflowRunsForRepoResponseData, ActionsGetWorkflowResponseData, ActionsGetWorkflowRunResponseData, + EndpointInterface, } from '@octokit/types'; export class GithubActionsClient implements GithubActionsApi { @@ -102,4 +103,24 @@ export class GithubActionsClient implements GithubActionsApi { }); return run.data; } + async downloadJobLogsForWorkflowRun({ + token, + owner, + repo, + runId, + }: { + token: string; + owner: string; + repo: string; + runId: number; + }): Promise { + const workflow = await new Octokit({ + auth: token, + }).actions.downloadJobLogsForWorkflowRun({ + owner, + repo, + job_id: runId, + }); + return workflow.data; + } } diff --git a/plugins/github-actions/src/api/types.ts b/plugins/github-actions/src/api/types.ts index cd74a20cda..ccc9198e58 100644 --- a/plugins/github-actions/src/api/types.ts +++ b/plugins/github-actions/src/api/types.ts @@ -29,6 +29,7 @@ export type Job = { conclusion: string; started_at: string; completed_at: string; + id: string; name: string; steps: Step[]; }; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index a8ceb717da..fd2cc69376 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { Entity } from '@backstage/catalog-model'; import { Link } from '@backstage/core'; import { @@ -45,6 +44,7 @@ import { useProjectName } from '../useProjectName'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { useWorkflowRunJobs } from './useWorkflowRunJobs'; import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; +import { WorkflowRunLogs } from '../WorkflowRunLogs'; const useStyles = makeStyles(theme => ({ root: { @@ -57,7 +57,7 @@ const useStyles = makeStyles(theme => ({ table: { padding: theme.spacing(1), }, - expansionPanelDetails: { + accordionDetails: { padding: 0, }, button: { @@ -71,7 +71,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const JobsList = ({ jobs }: { jobs?: Jobs }) => { +const JobsList = ({ jobs, entity }: { jobs?: Jobs; entity: Entity }) => { const classes = useStyles(); return ( @@ -83,6 +83,7 @@ const JobsList = ({ jobs }: { jobs?: Jobs }) => { className={ job.status !== 'success' ? classes.failed : classes.success } + entity={entity} /> ))} @@ -111,7 +112,15 @@ const StepView = ({ step }: { step: Step }) => { ); }; -const JobListItem = ({ job, className }: { job: Job; className: string }) => { +const JobListItem = ({ + job, + className, + entity, +}: { + job: Job; + className: string; + entity: Entity; +}) => { const classes = useStyles(); return ( @@ -127,7 +136,7 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => { {job.name} ({getElapsedTime(job.started_at, job.completed_at)}) - + {job.steps.map((step: Step) => ( @@ -136,6 +145,11 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => {
+ {job.status === 'queued' || job.status === 'in_progress' ? ( + + ) : ( + + )}
); }; @@ -218,7 +232,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { {jobs.loading ? ( ) : ( - + )} diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx new file mode 100644 index 0000000000..390f35d516 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx @@ -0,0 +1,169 @@ +/* + * 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 { + Accordion, + AccordionSummary, + CircularProgress, + Fade, + LinearProgress, + makeStyles, + Modal, + Theme, + Tooltip, + Typography, + Zoom, +} from '@material-ui/core'; + +import React, { Suspense } from 'react'; +import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs'; +import LinePart from 'react-lazylog/build/LinePart'; +import { useProjectName } from '../useProjectName'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import DescriptionIcon from '@material-ui/icons/Description'; +import { Entity } from '@backstage/catalog-model'; + +const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); + +const useStyles = makeStyles(() => ({ + button: { + order: -1, + marginRight: 0, + marginLeft: '-20px', + }, + modal: { + display: 'flex', + alignItems: 'center', + width: '85%', + height: '85%', + justifyContent: 'center', + margin: 'auto', + }, + normalLog: { + height: '75vh', + width: '100%', + }, + modalLog: { + height: '100%', + width: '100%', + }, +})); + +const DisplayLog = ({ + jobLogs, + className, +}: { + jobLogs: any; + className: string; +}) => { + return ( + }> +
+ { + if ( + line.toLocaleLowerCase().includes('error') || + line.toLocaleLowerCase().includes('failed') || + line.toLocaleLowerCase().includes('failure') + ) { + return ( + + ); + } + return line; + }} + /> +
+
+ ); +}; + +/** + * A component for Run Logs visualization. + */ +export const WorkflowRunLogs = ({ + entity, + runId, + inProgress, +}: { + entity: Entity; + runId: string; + inProgress: boolean; +}) => { + const classes = useStyles(); + const projectName = useProjectName(entity); + + const [owner, repo] = projectName.value ? projectName.value.split('/') : []; + const jobLogs = useDownloadWorkflowRunLogs(repo, owner, runId); + const [open, setOpen] = React.useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + return ( + + } + aria-controls={`panel-${name}-content`} + id={`panel-${name}-header`} + IconButtonProps={{ + className: classes.button, + }} + > + + {jobLogs.loading ? : 'Job Log'} + + + { + event.stopPropagation(); + handleOpen(); + }} + style={{ marginLeft: 'auto' }} + /> + + event.stopPropagation()} + open={open} + onClose={handleClose} + > + + + + + + {jobLogs.value && ( + + )} + + ); +}; diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/index.ts b/plugins/github-actions/src/components/WorkflowRunLogs/index.ts new file mode 100644 index 0000000000..0fcffd4dec --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunLogs/index.ts @@ -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 { WorkflowRunLogs } from './WorkflowRunLogs'; diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/useDownloadWorkflowRunLogs.ts b/plugins/github-actions/src/components/WorkflowRunLogs/useDownloadWorkflowRunLogs.ts new file mode 100644 index 0000000000..734f617b1f --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunLogs/useDownloadWorkflowRunLogs.ts @@ -0,0 +1,40 @@ +/* + * 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; +}; diff --git a/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx index a9b73f6f0b..0dcaeb8948 100644 --- a/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx +++ b/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx @@ -25,7 +25,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import React, { FC, useEffect } from 'react'; const useStyles = makeStyles({ - expansionPanelDetails: { + accordionDetails: { padding: 0, }, button: { @@ -57,7 +57,7 @@ export const ActionOutput: FC<{ > {name} - + Nothing here... diff --git a/plugins/scaffolder/src/components/JobStage/JobStage.tsx b/plugins/scaffolder/src/components/JobStage/JobStage.tsx index c7de6dff61..0baba0c9b9 100644 --- a/plugins/scaffolder/src/components/JobStage/JobStage.tsx +++ b/plugins/scaffolder/src/components/JobStage/JobStage.tsx @@ -35,7 +35,7 @@ const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); moment.relativeTimeThreshold('ss', 0); const useStyles = makeStyles(theme => ({ - expansionPanelDetails: { + accordionDetails: { padding: 0, }, button: { @@ -45,7 +45,7 @@ const useStyles = makeStyles(theme => ({ cardContent: { backgroundColor: theme.palette.background.default, }, - expansionPanel: { + accordion: { position: 'relative', '&:after': { pointerEvents: 'none', @@ -103,7 +103,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { ] ?? classes.neutral, )} @@ -123,7 +123,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { {startedAt && !endedAt && } - + {log.length === 0 ? ( No logs available for this step ) : (