summaryrefslogtreecommitdiff
path: root/web/src/actions
diff options
context:
space:
mode:
authorJames E. Blair <jeblair@redhat.com>2019-07-20 11:08:05 -0700
committerJames E. Blair <jeblair@redhat.com>2019-07-24 09:25:13 -0700
commit8fdc387c83140305d2374b954916868f8fbf0e40 (patch)
tree3aeaad1019f779a8b6587e3a504c84417ce51cf8 /web/src/actions
parentd48c2b82fc7e69dc9f2f493859236af342ec888e (diff)
downloadzuul-8fdc387c83140305d2374b954916868f8fbf0e40.tar.gz
Add log browsing to build page
This looks for a zuul_manifest artifact, and if it is present, it fetches it and shows a tree view of logs. Text logs are displayed in-app with some basic line anchoring. Part of the implementation of https://zuul-ci.org/docs/zuul/developer/specs/logs.html es6 added to .eslint to allow use of Promise. Change-Id: Ib04d013b4118005ba66a91d2bec0b0c429d12863
Diffstat (limited to 'web/src/actions')
-rw-r--r--web/src/actions/build.js136
-rw-r--r--web/src/actions/logfile.js64
2 files changed, 165 insertions, 35 deletions
diff --git a/web/src/actions/build.js b/web/src/actions/build.js
index b291c88b8..a14945b22 100644
--- a/web/src/actions/build.js
+++ b/web/src/actions/build.js
@@ -19,7 +19,14 @@ import * as API from '../api'
export const BUILD_FETCH_REQUEST = 'BUILD_FETCH_REQUEST'
export const BUILD_FETCH_SUCCESS = 'BUILD_FETCH_SUCCESS'
export const BUILD_FETCH_FAIL = 'BUILD_FETCH_FAIL'
-export const BUILD_OUTPUT_FETCH_SUCCESS = 'BUILD_OUTPUT_FETCH_SUCCESS'
+
+export const BUILD_OUTPUT_REQUEST = 'BUILD_OUTPUT_REQUEST'
+export const BUILD_OUTPUT_SUCCESS = 'BUILD_OUTPUT_SUCCESS'
+export const BUILD_OUTPUT_FAIL = 'BUILD_OUTPUT_FAIL'
+
+export const BUILD_MANIFEST_REQUEST = 'BUILD_MANIFEST_REQUEST'
+export const BUILD_MANIFEST_SUCCESS = 'BUILD_MANIFEST_SUCCESS'
+export const BUILD_MANIFEST_FAIL = 'BUILD_MANIFEST_FAIL'
export const requestBuild = () => ({
type: BUILD_FETCH_REQUEST
@@ -32,6 +39,15 @@ export const receiveBuild = (buildId, build) => ({
receivedAt: Date.now()
})
+const failedBuild = error => ({
+ type: BUILD_FETCH_FAIL,
+ error
+})
+
+export const requestBuildOutput = () => ({
+ type: BUILD_OUTPUT_REQUEST
+})
+
const receiveBuildOutput = (buildId, output) => {
const hosts = {}
// Compute stats
@@ -70,58 +86,108 @@ const receiveBuildOutput = (buildId, output) => {
})
})
return {
- type: BUILD_OUTPUT_FETCH_SUCCESS,
+ type: BUILD_OUTPUT_SUCCESS,
buildId: buildId,
output: hosts,
receivedAt: Date.now()
}
}
-const failedBuild = error => ({
- type: BUILD_FETCH_FAIL,
+const failedBuildOutput = error => ({
+ type: BUILD_OUTPUT_FAIL,
error
})
-const fetchBuild = (tenant, build) => dispatch => {
+export const requestBuildManifest = () => ({
+ type: BUILD_MANIFEST_REQUEST
+})
+
+const receiveBuildManifest = (buildId, manifest) => {
+ const index = {}
+
+ const renderNode = (root, object) => {
+ const path = root + '/' + object.name
+
+ if ('children' in object && object.children) {
+ object.children.map(n => renderNode(path, n))
+ } else {
+ index[path] = object
+ }
+ }
+
+ manifest.tree.map(n => renderNode('', n))
+ return {
+ type: BUILD_MANIFEST_SUCCESS,
+ buildId: buildId,
+ manifest: {tree: manifest.tree, index: index},
+ receivedAt: Date.now()
+ }
+}
+
+const failedBuildManifest = error => ({
+ type: BUILD_MANIFEST_FAIL,
+ error
+})
+
+export const fetchBuild = (tenant, buildId, state, force) => dispatch => {
+ const build = state.build.builds[buildId]
+ if (!force && build) {
+ return Promise.resolve()
+ }
dispatch(requestBuild())
- return API.fetchBuild(tenant.apiPrefix, build)
+ return API.fetchBuild(tenant.apiPrefix, buildId)
.then(response => {
- dispatch(receiveBuild(build, response.data))
- if (response.data.log_url) {
- const url = response.data.log_url.substr(
- 0, response.data.log_url.lastIndexOf('/') + 1)
- Axios.get(url + 'job-output.json.gz')
- .then(response => dispatch(receiveBuildOutput(build, response.data)))
- .catch(error => {
- if (!error.request) {
- throw error
- }
- // Try without compression
- Axios.get(url + 'job-output.json')
- .then(response => dispatch(receiveBuildOutput(
- build, response.data)))
- })
- .catch(error => console.error(
- 'Couldn\'t decode job-output...', error))
- }
+ dispatch(receiveBuild(buildId, response.data))
})
.catch(error => dispatch(failedBuild(error)))
}
-const shouldFetchBuild = (buildId, state) => {
+const fetchBuildOutput = (buildId, state, force) => dispatch => {
const build = state.build.builds[buildId]
- if (!build) {
- return true
- }
- if (build.isFetching) {
- return false
+ const url = build.log_url.substr(0, build.log_url.lastIndexOf('/') + 1)
+ if (!force && build.output) {
+ return Promise.resolve()
}
- return false
+ dispatch(requestBuildOutput())
+ return Axios.get(url + 'job-output.json.gz')
+ .then(response => dispatch(receiveBuildOutput(buildId, response.data)))
+ .catch(error => {
+ if (!error.request) {
+ throw error
+ }
+ // Try without compression
+ Axios.get(url + 'job-output.json')
+ .then(response => dispatch(receiveBuildOutput(
+ buildId, response.data)))
+ })
+ .catch(error => dispatch(failedBuildOutput(error)))
}
-export const fetchBuildIfNeeded = (tenant, buildId, force) => (
- dispatch, getState) => {
- if (force || shouldFetchBuild(buildId, getState())) {
- return dispatch(fetchBuild(tenant, buildId))
+export const fetchBuildManifest = (buildId, state, force) => dispatch => {
+ const build = state.build.builds[buildId]
+ if (!force && build.manifest) {
+ return Promise.resolve()
+ }
+
+ dispatch(requestBuildManifest())
+ for (let artifact of build.artifacts) {
+ if ('metadata' in artifact &&
+ 'type' in artifact.metadata &&
+ artifact.metadata.type === 'zuul_manifest') {
+ return Axios.get(artifact.url)
+ .then(manifest => {
+ dispatch(receiveBuildManifest(buildId, manifest.data))
+ })
+ .catch(error => dispatch(failedBuildManifest(error)))
}
+ }
+ dispatch(failedBuildManifest('no manifest found'))
+}
+
+export const fetchBuildIfNeeded = (tenant, buildId, force) => (dispatch, getState) => {
+ dispatch(fetchBuild(tenant, buildId, getState(), force))
+ .then(() => {
+ dispatch(fetchBuildOutput(buildId, getState(), force))
+ dispatch(fetchBuildManifest(buildId, getState(), force))
+ })
}
diff --git a/web/src/actions/logfile.js b/web/src/actions/logfile.js
new file mode 100644
index 000000000..8e5d99fe3
--- /dev/null
+++ b/web/src/actions/logfile.js
@@ -0,0 +1,64 @@
+// 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 Axios from 'axios'
+
+import {fetchBuild, fetchBuildManifest} from './build'
+
+export const LOGFILE_FETCH_REQUEST = 'LOGFILE_FETCH_REQUEST'
+export const LOGFILE_FETCH_SUCCESS = 'LOGFILE_FETCH_SUCCESS'
+export const LOGFILE_FETCH_FAIL = 'LOGFILE_FETCH_FAIL'
+
+export const requestLogfile = (url) => ({
+ type: LOGFILE_FETCH_REQUEST,
+ url: url,
+})
+
+const receiveLogfile = (data) => ({
+ type: LOGFILE_FETCH_SUCCESS,
+ data: data,
+ receivedAt: Date.now()
+})
+
+const failedLogfile = error => ({
+ type: LOGFILE_FETCH_FAIL,
+ error
+})
+
+const fetchLogfile = (buildId, file, state, force) => dispatch => {
+ const build = state.build.builds[buildId]
+ const item = build.manifest.index['/' + file]
+ const url = build.log_url + item.name
+
+ if (!force && state.logfile.url === url) {
+ return Promise.resolve()
+ }
+ dispatch(requestLogfile())
+ if (item.mimetype === 'text/plain') {
+ return Axios.get(url)
+ .then(response => dispatch(receiveLogfile(response.data)))
+ .catch(error => dispatch(failedLogfile(error)))
+ }
+ dispatch(failedLogfile(null))
+}
+
+export const fetchLogfileIfNeeded = (tenant, buildId, file, force) => (dispatch, getState) => {
+ dispatch(fetchBuild(tenant, buildId, getState(), force))
+ .then(() => {
+ dispatch(fetchBuildManifest(buildId, getState(), force))
+ .then(() => {
+ dispatch(fetchLogfile(buildId, file, getState(), force))
+ })
+ })
+}