The Gatekeeper, or a project gating system
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

272 lines
7.7 KiB

  1. // Copyright 2018 Red Hat, Inc
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. import * as React from 'react'
  15. import { connect } from 'react-redux'
  16. import { withRouter } from 'react-router-dom'
  17. import PropTypes from 'prop-types'
  18. import {
  19. EmptyState,
  20. EmptyStateVariant,
  21. EmptyStateIcon,
  22. PageSection,
  23. PageSectionVariants,
  24. Tab,
  25. Tabs,
  26. TabTitleIcon,
  27. TabTitleText,
  28. Title,
  29. } from '@patternfly/react-core'
  30. import {
  31. BuildIcon,
  32. FileArchiveIcon,
  33. FileCodeIcon,
  34. TerminalIcon,
  35. PollIcon,
  36. } from '@patternfly/react-icons'
  37. import { fetchBuildIfNeeded } from '../actions/build'
  38. import { EmptyPage } from '../containers/Errors'
  39. import { Fetchable, Fetching } from '../containers/Fetching'
  40. import ArtifactList from '../containers/build/Artifact'
  41. import Build from '../containers/build/Build'
  42. import BuildOutput from '../containers/build/BuildOutput'
  43. import Console from '../containers/build/Console'
  44. import Manifest from '../containers/build/Manifest'
  45. class BuildPage extends React.Component {
  46. static propTypes = {
  47. match: PropTypes.object.isRequired,
  48. remoteData: PropTypes.object,
  49. tenant: PropTypes.object,
  50. dispatch: PropTypes.func,
  51. activeTab: PropTypes.string.isRequired,
  52. location: PropTypes.object,
  53. history: PropTypes.object,
  54. }
  55. updateData = (force) => {
  56. this.props.dispatch(
  57. fetchBuildIfNeeded(
  58. this.props.tenant,
  59. this.props.match.params.buildId,
  60. force
  61. )
  62. )
  63. }
  64. componentDidMount() {
  65. document.title = 'Zuul Build'
  66. if (this.props.tenant.name) {
  67. this.updateData()
  68. }
  69. }
  70. componentDidUpdate(prevProps) {
  71. if (this.props.tenant.name !== prevProps.tenant.name) {
  72. this.updateData()
  73. }
  74. }
  75. handleTabClick = (tabIndex, build) => {
  76. // Usually tabs should only be used to display content in-page and not link
  77. // to other pages:
  78. // "Tabs are used to present a set on tabs for organizing content on a
  79. // .page. It must always be used together with a tab content component."
  80. // https://www.patternfly.org/v4/documentation/react/components/tabs
  81. // But as want to be able to reach every tab's content via a dedicated URL
  82. // while having the look and feel of tabs, we could hijack this onClick
  83. // handler to do the link/routing stuff.
  84. const { history, tenant } = this.props
  85. switch (tabIndex) {
  86. case 'artifacts':
  87. history.push(`${tenant.linkPrefix}/build/${build.uuid}/artifacts`)
  88. break
  89. case 'logs':
  90. history.push(`${tenant.linkPrefix}/build/${build.uuid}/logs`)
  91. break
  92. case 'console':
  93. history.push(`${tenant.linkPrefix}/build/${build.uuid}/console`)
  94. break
  95. default:
  96. // results
  97. history.push(`${tenant.linkPrefix}/build/${build.uuid}`)
  98. }
  99. }
  100. render() {
  101. const { remoteData, activeTab, location, tenant } = this.props
  102. const build = remoteData.builds[this.props.match.params.buildId]
  103. const hash = location.hash.substring(1).split('/')
  104. if (!build && remoteData.isFetching) {
  105. return <Fetching />
  106. }
  107. if (!build) {
  108. return (
  109. <EmptyPage
  110. title="This build does not exist"
  111. icon={BuildIcon}
  112. linkTarget={`${tenant.linkPrefix}/builds`}
  113. linkText="Show all builds"
  114. />
  115. )
  116. }
  117. const fetchable = (
  118. <Fetchable
  119. isFetching={remoteData.isFetching}
  120. fetchCallback={this.updateData}
  121. />
  122. )
  123. const resultsTabContent =
  124. !build.hosts && remoteData.isFetchingOutput ? (
  125. <Fetching />
  126. ) : build.hosts ? (
  127. <BuildOutput output={build.hosts} />
  128. ) : (
  129. <EmptyState variant={EmptyStateVariant.small}>
  130. <EmptyStateIcon icon={PollIcon} />
  131. <Title headingLevel="h4" size="lg">
  132. This build does not provide any results
  133. </Title>
  134. </EmptyState>
  135. )
  136. const artifactsTabContent = build.artifacts.length ? (
  137. <ArtifactList artifacts={build.artifacts} />
  138. ) : (
  139. <EmptyState variant={EmptyStateVariant.small}>
  140. <EmptyStateIcon icon={FileArchiveIcon} />
  141. <Title headingLevel="h4" size="lg">
  142. This build does not provide any artifacts
  143. </Title>
  144. </EmptyState>
  145. )
  146. const logsTabContent =
  147. !build.manifest && remoteData.isFetchingManifest ? (
  148. <Fetching />
  149. ) : build.manifest ? (
  150. <Manifest tenant={this.props.tenant} build={build} />
  151. ) : (
  152. <EmptyState variant={EmptyStateVariant.small}>
  153. <EmptyStateIcon icon={FileCodeIcon} />
  154. <Title headingLevel="h4" size="lg">
  155. This build does not provide any logs
  156. </Title>
  157. </EmptyState>
  158. )
  159. const consoleTabContent =
  160. !build.output && remoteData.isFetchingOutput ? (
  161. <Fetching />
  162. ) : build.output ? (
  163. <Console
  164. output={build.output}
  165. errorIds={build.errorIds}
  166. displayPath={hash.length > 0 ? hash : undefined}
  167. />
  168. ) : (
  169. <EmptyState variant={EmptyStateVariant.small}>
  170. <EmptyStateIcon icon={TerminalIcon} />
  171. <Title headingLevel="h4" size="lg">
  172. This build does not provide any console information
  173. </Title>
  174. </EmptyState>
  175. )
  176. return (
  177. <>
  178. <PageSection variant={PageSectionVariants.light}>
  179. <Build
  180. build={build}
  181. active={activeTab}
  182. hash={hash}
  183. fetchable={fetchable}
  184. />
  185. </PageSection>
  186. <PageSection variant={PageSectionVariants.light}>
  187. <Tabs
  188. isFilled
  189. activeKey={activeTab}
  190. onSelect={(event, tabIndex) => this.handleTabClick(tabIndex, build)}
  191. >
  192. <Tab
  193. eventKey="results"
  194. title={
  195. <>
  196. <TabTitleIcon>
  197. <PollIcon />
  198. </TabTitleIcon>
  199. <TabTitleText>Results</TabTitleText>
  200. </>
  201. }
  202. >
  203. {resultsTabContent}
  204. </Tab>
  205. <Tab
  206. eventKey="artifacts"
  207. title={
  208. <>
  209. <TabTitleIcon>
  210. <FileArchiveIcon />
  211. </TabTitleIcon>
  212. <TabTitleText>Artifacts</TabTitleText>
  213. </>
  214. }
  215. >
  216. {artifactsTabContent}
  217. </Tab>
  218. <Tab
  219. eventKey="logs"
  220. title={
  221. <>
  222. <TabTitleIcon>
  223. <FileCodeIcon />
  224. </TabTitleIcon>
  225. <TabTitleText>Logs</TabTitleText>
  226. </>
  227. }
  228. >
  229. {logsTabContent}
  230. </Tab>
  231. <Tab
  232. eventKey="console"
  233. title={
  234. <>
  235. <TabTitleIcon>
  236. <TerminalIcon />
  237. </TabTitleIcon>
  238. <TabTitleText>Console</TabTitleText>
  239. </>
  240. }
  241. >
  242. {consoleTabContent}
  243. </Tab>
  244. </Tabs>
  245. </PageSection>
  246. </>
  247. )
  248. }
  249. }
  250. export default connect((state) => ({
  251. tenant: state.tenant,
  252. remoteData: state.build,
  253. }))(withRouter(BuildPage))