// Copyright 2018 Red Hat, Inc // // 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 * as React from 'react' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import PropTypes from 'prop-types' import { parse } from 'query-string' import { EmptyState, EmptyStateVariant, EmptyStateIcon, PageSection, PageSectionVariants, Tab, Tabs, TabTitleIcon, TabTitleText, Title, } from '@patternfly/react-core' import { BuildIcon, FileArchiveIcon, FileCodeIcon, TerminalIcon, PollIcon, } from '@patternfly/react-icons' import { fetchBuildAllInfo } from '../actions/build' import { fetchLogfile } from '../actions/logfile' import { EmptyPage } from '../containers/Errors' import { Fetchable, Fetching } from '../containers/Fetching' import ArtifactList from '../containers/build/Artifact' import Build from '../containers/build/Build' import BuildOutput from '../containers/build/BuildOutput' import Console from '../containers/build/Console' import Manifest from '../containers/build/Manifest' import LogFile from '../containers/logfile/LogFile' class BuildPage extends React.Component { static propTypes = { match: PropTypes.object.isRequired, build: PropTypes.object, logfile: PropTypes.object, isFetching: PropTypes.bool.isRequired, isFetchingManifest: PropTypes.bool.isRequired, isFetchingOutput: PropTypes.bool.isRequired, isFetchingLogfile: PropTypes.bool.isRequired, tenant: PropTypes.object.isRequired, fetchBuildAllInfo: PropTypes.func.isRequired, activeTab: PropTypes.string.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired, } updateData = () => { // The related fetchBuild...() methods won't do anything if the data is // already available in the local state, so just call them. this.props.fetchBuildAllInfo( this.props.tenant, this.props.match.params.buildId, this.props.match.params.file ) } componentDidMount() { document.title = 'Zuul Build' if (this.props.tenant.name) { this.updateData() } } componentDidUpdate(prevProps) { if (this.props.tenant.name !== prevProps.tenant.name) { this.updateData() } } handleTabClick = (tabIndex, build) => { // Usually tabs should only be used to display content in-page and not link // to other pages: // "Tabs are used to present a set on tabs for organizing content on a // .page. It must always be used together with a tab content component." // https://www.patternfly.org/v4/documentation/react/components/tabs // But as want to be able to reach every tab's content via a dedicated URL // while having the look and feel of tabs, we could hijack this onClick // handler to do the link/routing stuff. const { history, tenant } = this.props switch (tabIndex) { case 'artifacts': history.push(`${tenant.linkPrefix}/build/${build.uuid}/artifacts`) break case 'logs': history.push(`${tenant.linkPrefix}/build/${build.uuid}/logs`) break case 'console': history.push(`${tenant.linkPrefix}/build/${build.uuid}/console`) break default: // task summary history.push(`${tenant.linkPrefix}/build/${build.uuid}`) } } handleBreadcrumbItemClick = () => { // Simply link back to the logs tab without an active logfile this.handleTabClick('logs', this.props.build) } render() { const { build, logfile, isFetching, isFetchingManifest, isFetchingOutput, isFetchingLogfile, activeTab, history, location, tenant, } = this.props const hash = location.hash.substring(1).split('/') const severity = parseInt(parse(location.search).severity) // Get the logfile from react-routers URL parameters const logfileName = this.props.match.params.file if (!build && isFetching) { return } if (!build) { return ( ) } const fetchable = ( ) const resultsTabContent = !build.hosts && isFetchingOutput ? ( ) : build.hosts ? ( ) : ( This build does not provide any results ) const artifactsTabContent = build.artifacts.length ? ( ) : ( This build does not provide any artifacts ) let logsTabContent = null if (!build.manifest && isFetchingManifest) { logsTabContent = } else if (logfileName) { logsTabContent = ( ) } else if (build.manifest) { logsTabContent = } else { logsTabContent = ( This build does not provide any logs ) } const consoleTabContent = !build.output && isFetchingOutput ? ( ) : build.output ? ( 0 ? hash : undefined} /> ) : ( This build does not provide any console information ) return ( <> this.handleTabClick(tabIndex, build)} > Task Summary } > {resultsTabContent} Artifacts } > {artifactsTabContent} Logs } > {logsTabContent} Console } > {consoleTabContent} ) } } function mapStateToProps(state, ownProps) { const buildId = ownProps.match.params.buildId const build = buildId && Object.keys(state.build.builds).length > 0 ? state.build.builds[buildId] : null const logfileName = ownProps.match.params.file const logfile = logfileName && Object.keys(state.logfile.files).length > 0 ? state.logfile.files[buildId][logfileName] : null return { build, logfile, tenant: state.tenant, isFetching: state.build.isFetching, isFetchingManifest: state.build.isFetchingManifest, isFetchingOutput: state.build.isFetchingOutput, isFetchingLogfile: state.logfile.isFetching, } } const mapDispatchToProps = { fetchBuildAllInfo, fetchLogfile } export default connect( mapStateToProps, mapDispatchToProps )(withRouter(BuildPage))