Commit 90216b20 authored by Phil Hughes's avatar Phil Hughes

Show job logs in web IDE

[ci skip]

Closes #46245
parent af07c490
<script>
import { mapState } from 'vuex';
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import Job from '../../../job';
export default {
directives: {
tooltip,
},
components: {
Icon,
},
computed: {
...mapState('pipelines', ['detailJob']),
rawUrl() {
return `${this.detailJob.path}/raw`;
},
},
mounted() {
this.job = new Job({
buildStage: 'a',
buildState: this.detailJob.status.text,
pagePath: this.detailJob.path,
redirectToJob: false,
});
},
beforeDestroy() {
this.job.destroy();
},
};
</script>
<template>
<div class="ide-pipeline build-page">
<header
class="ide-tree-header ide-pipeline-header"
>
<button
class="btn btn-default btn-sm"
@click="() => { $store.state.pipelines.detailJob = null; $store.dispatch('setRightPane', 'pipelines-list') }"
>
<icon
name="chevron-left"
/>
{{ __('View jobs') }}
</button>
</header>
<div class="build-trace-container prepend-top-default">
<div
v-once
class="top-bar js-top-bar"
>
<div class="controllers float-right">
<a
v-tooltip
:title="__('Show complete raw')"
data-placement="top"
data-container="body"
class="js-raw-link-controller controllers-buttons"
:href="rawUrl"
>
<i
aria-hidden="true"
class="fa fa-file-text-o"
></i>
</a>
<div
v-tooltip
class="controllers-buttons"
data-container="body"
data-placement="top"
:title="__('Scroll to top')"
>
<button
class="js-scroll-up btn-scroll btn-transparent btn-blank"
disabled
type="button"
>
<icon
name="scroll_up"
/>
</button>
</div>
<div
v-tooltip
class="controllers-buttons"
data-container="body"
data-placement="top"
:title="__('Scroll to top')"
>
<button
class="js-scroll-up btn-scroll btn-transparent btn-blank"
disabled
type="button"
>
<icon
name="scroll_down"
/>
</button>
</div>
</div>
</div>
<pre
class="build-trace"
id="build-trace"
>
<code class="bash js-build-output">
</code>
</pre>
</div>
</div>
</template>
<style scoped>
.build-trace-container {
flex: 1;
display: flex;
flex-direction: column;
}
.ide-tree-header .btn {
display: flex;
}
</style>
\ No newline at end of file
......@@ -42,5 +42,17 @@ export default {
/>
</a>
</span>
<button
class="btn btn-default btn-sm"
@click="() => { $store.state.pipelines.detailJob = job; $store.dispatch('setRightPane', 'jobs-detail') }"
>
{{ __('View log') }}
</button>
</div>
</template>
<style scoped>
.btn {
margin-left: auto;
}
</style>
......@@ -4,6 +4,7 @@ import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
import { rightSidebarViews } from '../../constants';
import PipelinesList from '../pipelines/list.vue';
import JobsDetail from '../jobs/detail.vue';
export default {
directives: {
......@@ -12,6 +13,7 @@ export default {
components: {
Icon,
PipelinesList,
JobsDetail,
},
computed: {
...mapState(['rightPane']),
......
......@@ -23,4 +23,5 @@ export const viewerTypes = {
export const rightSidebarViews = {
pipelines: 'pipelines-list',
jobsDetail: 'jobs-detail',
};
......@@ -77,4 +77,6 @@ export const fetchJobs = ({ dispatch }, stage) => {
export const toggleStageCollapsed = ({ commit }, stageId) =>
commit(types.TOGGLE_STAGE_COLLAPSE, stageId);
export const setDetailJob = ({ commit }, job) => commit(types.SET_DETAIL_JOB, job);
export default () => {};
......@@ -7,3 +7,5 @@ export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR';
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
export const TOGGLE_STAGE_COLLAPSE = 'TOGGLE_STAGE_COLLAPSE';
export const SET_DETAIL_JOB = 'SET_DETAIL_JOB';
......@@ -63,4 +63,7 @@ export default {
isCollapsed: stage.id === id ? !stage.isCollapsed : stage.isCollapsed,
}));
},
[types.SET_DETAIL_JOB](state, job) {
state.detailJob = job;
},
};
......@@ -3,4 +3,5 @@ export default () => ({
isLoadingJobs: false,
latestPipeline: null,
stages: [],
detailJob: null,
});
......@@ -4,4 +4,5 @@ export const normalizeJob = job => ({
name: job.name,
status: job.status,
path: job.build_path,
started: job.started,
});
......@@ -22,6 +22,8 @@ export default class Job {
this.$window = $(window);
this.logBytes = 0;
this.updateDropdown = this.updateDropdown.bind(this);
this.redirectToJob =
this.options.redirectToJob !== undefined ? this.options.redirectToJob : true;
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
......@@ -44,31 +46,23 @@ export default class Job {
.off('click', '.js-sidebar-build-toggle')
.on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
this.$document
.off('click', '.stage-item')
.on('click', '.stage-item', this.updateDropdown);
this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
// add event listeners to the scroll buttons
this.$scrollTopBtn
.off('click')
.on('click', this.scrollToTop.bind(this));
this.$scrollTopBtn.off('click').on('click', this.scrollToTop.bind(this));
this.$scrollBottomBtn
.off('click')
.on('click', this.scrollToBottom.bind(this));
this.$scrollBottomBtn.off('click').on('click', this.scrollToBottom.bind(this));
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$window
.off('scroll')
.on('scroll', () => {
if (!this.isScrolledToBottom()) {
this.toggleScrollAnimation(false);
} else if (this.isScrolledToBottom() && !this.isLogComplete) {
this.toggleScrollAnimation(true);
}
this.scrollThrottled();
});
this.$window.off('scroll').on('scroll', () => {
if (!this.isScrolledToBottom()) {
this.toggleScrollAnimation(false);
} else if (this.isScrolledToBottom() && !this.isLogComplete) {
this.toggleScrollAnimation(true);
}
this.scrollThrottled();
});
this.$window
.off('resize.build')
......@@ -79,6 +73,10 @@ export default class Job {
this.getBuildTrace();
}
destroy() {
clearTimeout(this.timeout);
}
initAffixTopArea() {
/**
If the browser does not support position sticky, it returns the position as static.
......@@ -102,9 +100,8 @@ export default class Job {
const windowHeight = $(window).height();
if (this.canScroll()) {
if (currentPosition > 0 &&
(scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log
if (currentPosition > 0 && scrollHeight - currentPosition !== windowHeight) {
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false);
......@@ -169,10 +166,11 @@ export default class Job {
}
getBuildTrace() {
return axios.get(`${this.pagePath}/trace.json`, {
params: { state: this.state },
})
.then((res) => {
return axios
.get(`${this.pagePath}/trace.json`, {
params: { state: this.state },
})
.then(res => {
const log = res.data;
if (!this.fetchingStatusFavicon) {
......@@ -222,7 +220,7 @@ export default class Job {
this.toggleScrollAnimation(false);
}
if (log.status !== this.buildStatus) {
if (log.status !== this.buildStatus && this.redirectToJob) {
visitUrl(this.pagePath);
}
})
......
......@@ -1202,7 +1202,7 @@
}
.ide-pipeline-header {
min-height: 50px;
min-height: 55px;
padding-left: $gl-padding;
padding-right: $gl-padding;
......@@ -1222,8 +1222,6 @@
.ci-status-icon {
display: flex;
justify-content: center;
height: 20px;
margin-top: -2px;
overflow: hidden;
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment