Browse Source

Merge "web: add errors from the job-output to the build page"

tags/3.4.0
Zuul 5 months ago
parent
commit
0372d3a872

+ 68
- 1
web/src/actions/build.js View File

@@ -12,11 +12,14 @@
12 12
 // License for the specific language governing permissions and limitations
13 13
 // under the License.
14 14
 
15
+import Axios from 'axios'
16
+
15 17
 import * as API from '../api'
16 18
 
17 19
 export const BUILD_FETCH_REQUEST = 'BUILD_FETCH_REQUEST'
18 20
 export const BUILD_FETCH_SUCCESS = 'BUILD_FETCH_SUCCESS'
19 21
 export const BUILD_FETCH_FAIL = 'BUILD_FETCH_FAIL'
22
+export const BUILD_OUTPUT_FETCH_SUCCESS = 'BUILD_OUTPUT_FETCH_SUCCESS'
20 23
 
21 24
 export const requestBuild = () => ({
22 25
   type: BUILD_FETCH_REQUEST
@@ -29,6 +32,51 @@ export const receiveBuild = (buildId, build) => ({
29 32
   receivedAt: Date.now()
30 33
 })
31 34
 
35
+const receiveBuildOutput = (buildId, output) => {
36
+  const hosts = {}
37
+  // Compute stats
38
+  output.forEach(phase => {
39
+    Object.entries(phase.stats).forEach(([host, stats]) => {
40
+      if (!hosts[host]) {
41
+        hosts[host] = stats
42
+        hosts[host].failed = []
43
+      } else {
44
+        hosts[host].changed += stats.changed
45
+        hosts[host].failures += stats.failures
46
+        hosts[host].ok += stats.ok
47
+      }
48
+      if (stats.failures > 0) {
49
+        // Look for failed tasks
50
+        phase.plays.forEach(play => {
51
+          play.tasks.forEach(task => {
52
+            if (task.hosts[host]) {
53
+              if (task.hosts[host].results &&
54
+                  task.hosts[host].results.length > 0) {
55
+                task.hosts[host].results.forEach(result => {
56
+                  if (result.failed) {
57
+                    result.name = task.task.name
58
+                    hosts[host].failed.push(result)
59
+                  }
60
+                })
61
+              } else if (task.hosts[host].rc || task.hosts[host].failed) {
62
+                let result = task.hosts[host]
63
+                result.name = task.task.name
64
+                hosts[host].failed.push(result)
65
+              }
66
+            }
67
+          })
68
+        })
69
+      }
70
+    })
71
+  })
72
+  return {
73
+    type: BUILD_OUTPUT_FETCH_SUCCESS,
74
+    buildId: buildId,
75
+    output: hosts,
76
+    receivedAt: Date.now()
77
+  }
78
+}
79
+
32 80
 const failedBuild = error => ({
33 81
   type: BUILD_FETCH_FAIL,
34 82
   error
@@ -37,7 +85,26 @@ const failedBuild = error => ({
37 85
 const fetchBuild = (tenant, build) => dispatch => {
38 86
   dispatch(requestBuild())
39 87
   return API.fetchBuild(tenant.apiPrefix, build)
40
-    .then(response => dispatch(receiveBuild(build, response.data)))
88
+    .then(response => {
89
+      dispatch(receiveBuild(build, response.data))
90
+      if (response.data.log_url) {
91
+        const url = response.data.log_url.substr(
92
+          0, response.data.log_url.lastIndexOf('/') + 1)
93
+        Axios.get(url + 'job-output.json.gz')
94
+          .then(response => dispatch(receiveBuildOutput(build, response.data)))
95
+          .catch(error => {
96
+            if (!error.request) {
97
+              throw error
98
+            }
99
+            // Try without compression
100
+            Axios.get(url + 'job-output.json')
101
+              .then(response => dispatch(receiveBuildOutput(
102
+                build, response.data)))
103
+          })
104
+          .catch(error => console.error(
105
+            'Couldn\'t decode job-output...', error))
106
+      }
107
+    })
41 108
     .catch(error => dispatch(failedBuild(error)))
42 109
 }
43 110
 

+ 3
- 0
web/src/containers/build/Build.jsx View File

@@ -18,6 +18,8 @@ import { connect } from 'react-redux'
18 18
 import { Link } from 'react-router-dom'
19 19
 import { Panel } from 'react-bootstrap'
20 20
 
21
+import BuildOutput from './BuildOutput'
22
+
21 23
 
22 24
 class Build extends React.Component {
23 25
   static propTypes = {
@@ -79,6 +81,7 @@ class Build extends React.Component {
79 81
               ))}
80 82
             </tbody>
81 83
           </table>
84
+          {build.output && <BuildOutput output={build.output}/>}
82 85
         </Panel.Body>
83 86
       </Panel>
84 87
     )

+ 111
- 0
web/src/containers/build/BuildOutput.jsx View File

@@ -0,0 +1,111 @@
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
+
15
+import * as React from 'react'
16
+import PropTypes from 'prop-types'
17
+import { Panel } from 'react-bootstrap'
18
+import {
19
+  Icon,
20
+  ListView,
21
+} from 'patternfly-react'
22
+
23
+
24
+class BuildOutput extends React.Component {
25
+  static propTypes = {
26
+    output: PropTypes.object,
27
+  }
28
+
29
+  renderHosts (hosts) {
30
+    return (
31
+      <ListView>
32
+        {Object.entries(hosts).map(([host, values]) => (
33
+          <ListView.Item
34
+            key={host}
35
+            heading={host}
36
+            additionalInfo={[
37
+              <ListView.InfoItem key="ok" title="Task OK">
38
+                <Icon type='pf' name='info' />
39
+                <strong>{values.ok}</strong>
40
+              </ListView.InfoItem>,
41
+              <ListView.InfoItem key="changed" title="Task changed">
42
+                <Icon type='pf' name='ok' />
43
+                <strong>{values.changed}</strong>
44
+              </ListView.InfoItem>,
45
+              <ListView.InfoItem key="fail" title="Task failure">
46
+                <Icon type='pf' name='error-circle-o' />
47
+                <strong>{values.failures}</strong>
48
+              </ListView.InfoItem>
49
+            ]}
50
+          />
51
+        ))}
52
+      </ListView>
53
+    )
54
+  }
55
+
56
+  renderFailedTask (host, task) {
57
+    return (
58
+      <Panel key={host + task.zuul_log_id}>
59
+        <Panel.Heading>{host}: {task.name}</Panel.Heading>
60
+        <Panel.Body>
61
+          {task.invocation && task.invocation.module_args &&
62
+           task.invocation.module_args._raw_params && (
63
+             <strong key="cmd">
64
+               {task.invocation.module_args._raw_params} <br />
65
+             </strong>
66
+           )}
67
+          {task.msg && (
68
+            <pre key="msg">{task.msg}</pre>
69
+          )}
70
+          {task.exception && (
71
+            <pre key="exc">{task.exception}</pre>
72
+          )}
73
+          {task.stdout_lines && task.stdout_lines.length > 0 && (
74
+            <span key="stdout" style={{whiteSpace: 'pre'}} title="stdout">
75
+              {task.stdout_lines.slice(-42).map((line, idx) => (
76
+                <span key={idx}>{line}<br/></span>))}
77
+              <br />
78
+            </span>
79
+          )}
80
+          {task.stderr_lines && task.stderr_lines.length > 0 && (
81
+            <span key="stderr" style={{whiteSpace: 'pre'}} title="stderr">
82
+              {task.stderr_lines.slice(-42).map((line, idx) => (
83
+                <span key={idx}>{line}<br/></span>))}
84
+              <br />
85
+            </span>
86
+          )}
87
+        </Panel.Body>
88
+      </Panel>
89
+    )
90
+  }
91
+
92
+  render () {
93
+    const { output } = this.props
94
+    return (
95
+      <React.Fragment>
96
+        <div key="tasks">
97
+          {Object.entries(output)
98
+           .filter(([, values]) => values.failed.length > 0)
99
+           .map(([host, values]) => (values.failed.map(failed => (
100
+             this.renderFailedTask(host, failed)))))}
101
+        </div>
102
+        <div key="hosts">
103
+          {this.renderHosts(output)}
104
+        </div>
105
+      </React.Fragment>
106
+    )
107
+  }
108
+}
109
+
110
+
111
+export default BuildOutput

+ 7
- 2
web/src/reducers/build.js View File

@@ -12,13 +12,15 @@
12 12
 // License for the specific language governing permissions and limitations
13 13
 // under the License.
14 14
 
15
+import update from 'immutability-helper'
16
+
15 17
 import {
16 18
   BUILD_FETCH_FAIL,
17 19
   BUILD_FETCH_REQUEST,
18
-  BUILD_FETCH_SUCCESS
20
+  BUILD_FETCH_SUCCESS,
21
+  BUILD_OUTPUT_FETCH_SUCCESS
19 22
 } from '../actions/build'
20 23
 
21
-import update from 'immutability-helper'
22 24
 
23 25
 export default (state = {
24 26
   isFetching: false,
@@ -33,6 +35,9 @@ export default (state = {
33 35
       return update(state, {$merge: {isFetching: false}})
34 36
     case BUILD_FETCH_FAIL:
35 37
       return update(state, {$merge: {isFetching: false}})
38
+    case BUILD_OUTPUT_FETCH_SUCCESS:
39
+      return update(
40
+        state, {builds: {[action.buildId]: {$merge: {output: action.output}}}})
36 41
     default:
37 42
       return state
38 43
   }

Loading…
Cancel
Save