Show job logs in web IDE

[ci skip]

Closes #46245
This commit is contained in:
Phil Hughes 2018-05-31 15:50:24 +01:00
parent af07c490b2
commit 90216b2066
No known key found for this signature in database
GPG key ID: 32245528C52E0F9F
11 changed files with 175 additions and 30 deletions

View file

@ -0,0 +1,125 @@
<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>

View file

@ -42,5 +42,17 @@ export default {
/> />
</a> </a>
</span> </span>
<button
class="btn btn-default btn-sm"
@click="() => { $store.state.pipelines.detailJob = job; $store.dispatch('setRightPane', 'jobs-detail') }"
>
{{ __('View log') }}
</button>
</div> </div>
</template> </template>
<style scoped>
.btn {
margin-left: auto;
}
</style>

View file

@ -4,6 +4,7 @@ import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue'; import Icon from '../../../vue_shared/components/icon.vue';
import { rightSidebarViews } from '../../constants'; import { rightSidebarViews } from '../../constants';
import PipelinesList from '../pipelines/list.vue'; import PipelinesList from '../pipelines/list.vue';
import JobsDetail from '../jobs/detail.vue';
export default { export default {
directives: { directives: {
@ -12,6 +13,7 @@ export default {
components: { components: {
Icon, Icon,
PipelinesList, PipelinesList,
JobsDetail,
}, },
computed: { computed: {
...mapState(['rightPane']), ...mapState(['rightPane']),

View file

@ -23,4 +23,5 @@ export const viewerTypes = {
export const rightSidebarViews = { export const rightSidebarViews = {
pipelines: 'pipelines-list', pipelines: 'pipelines-list',
jobsDetail: 'jobs-detail',
}; };

View file

@ -77,4 +77,6 @@ export const fetchJobs = ({ dispatch }, stage) => {
export const toggleStageCollapsed = ({ commit }, stageId) => export const toggleStageCollapsed = ({ commit }, stageId) =>
commit(types.TOGGLE_STAGE_COLLAPSE, stageId); commit(types.TOGGLE_STAGE_COLLAPSE, stageId);
export const setDetailJob = ({ commit }, job) => commit(types.SET_DETAIL_JOB, job);
export default () => {}; export default () => {};

View file

@ -7,3 +7,5 @@ export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR';
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
export const TOGGLE_STAGE_COLLAPSE = 'TOGGLE_STAGE_COLLAPSE'; export const TOGGLE_STAGE_COLLAPSE = 'TOGGLE_STAGE_COLLAPSE';
export const SET_DETAIL_JOB = 'SET_DETAIL_JOB';

View file

@ -63,4 +63,7 @@ export default {
isCollapsed: stage.id === id ? !stage.isCollapsed : stage.isCollapsed, isCollapsed: stage.id === id ? !stage.isCollapsed : stage.isCollapsed,
})); }));
}, },
[types.SET_DETAIL_JOB](state, job) {
state.detailJob = job;
},
}; };

View file

@ -3,4 +3,5 @@ export default () => ({
isLoadingJobs: false, isLoadingJobs: false,
latestPipeline: null, latestPipeline: null,
stages: [], stages: [],
detailJob: null,
}); });

View file

@ -4,4 +4,5 @@ export const normalizeJob = job => ({
name: job.name, name: job.name,
status: job.status, status: job.status,
path: job.build_path, path: job.build_path,
started: job.started,
}); });

View file

@ -22,6 +22,8 @@ export default class Job {
this.$window = $(window); this.$window = $(window);
this.logBytes = 0; this.logBytes = 0;
this.updateDropdown = this.updateDropdown.bind(this); this.updateDropdown = this.updateDropdown.bind(this);
this.redirectToJob =
this.options.redirectToJob !== undefined ? this.options.redirectToJob : true;
this.$buildTrace = $('#build-trace'); this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh'); this.$buildRefreshAnimation = $('.js-build-refresh');
@ -44,31 +46,23 @@ export default class Job {
.off('click', '.js-sidebar-build-toggle') .off('click', '.js-sidebar-build-toggle')
.on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
this.$document this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
.off('click', '.stage-item')
.on('click', '.stage-item', this.updateDropdown);
// add event listeners to the scroll buttons // add event listeners to the scroll buttons
this.$scrollTopBtn this.$scrollTopBtn.off('click').on('click', this.scrollToTop.bind(this));
.off('click')
.on('click', this.scrollToTop.bind(this));
this.$scrollBottomBtn this.$scrollBottomBtn.off('click').on('click', this.scrollToBottom.bind(this));
.off('click')
.on('click', this.scrollToBottom.bind(this));
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$window this.$window.off('scroll').on('scroll', () => {
.off('scroll') if (!this.isScrolledToBottom()) {
.on('scroll', () => { this.toggleScrollAnimation(false);
if (!this.isScrolledToBottom()) { } else if (this.isScrolledToBottom() && !this.isLogComplete) {
this.toggleScrollAnimation(false); this.toggleScrollAnimation(true);
} else if (this.isScrolledToBottom() && !this.isLogComplete) { }
this.toggleScrollAnimation(true); this.scrollThrottled();
} });
this.scrollThrottled();
});
this.$window this.$window
.off('resize.build') .off('resize.build')
@ -79,6 +73,10 @@ export default class Job {
this.getBuildTrace(); this.getBuildTrace();
} }
destroy() {
clearTimeout(this.timeout);
}
initAffixTopArea() { initAffixTopArea() {
/** /**
If the browser does not support position sticky, it returns the position as static. 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(); const windowHeight = $(window).height();
if (this.canScroll()) { if (this.canScroll()) {
if (currentPosition > 0 && if (currentPosition > 0 && scrollHeight - currentPosition !== windowHeight) {
(scrollHeight - currentPosition !== windowHeight)) { // User is in the middle of the log
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false); this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, false); this.toggleDisableButton(this.$scrollBottomBtn, false);
@ -169,10 +166,11 @@ export default class Job {
} }
getBuildTrace() { getBuildTrace() {
return axios.get(`${this.pagePath}/trace.json`, { return axios
params: { state: this.state }, .get(`${this.pagePath}/trace.json`, {
}) params: { state: this.state },
.then((res) => { })
.then(res => {
const log = res.data; const log = res.data;
if (!this.fetchingStatusFavicon) { if (!this.fetchingStatusFavicon) {
@ -222,7 +220,7 @@ export default class Job {
this.toggleScrollAnimation(false); this.toggleScrollAnimation(false);
} }
if (log.status !== this.buildStatus) { if (log.status !== this.buildStatus && this.redirectToJob) {
visitUrl(this.pagePath); visitUrl(this.pagePath);
} }
}) })

View file

@ -1202,7 +1202,7 @@
} }
.ide-pipeline-header { .ide-pipeline-header {
min-height: 50px; min-height: 55px;
padding-left: $gl-padding; padding-left: $gl-padding;
padding-right: $gl-padding; padding-right: $gl-padding;
@ -1222,8 +1222,6 @@
.ci-status-icon { .ci-status-icon {
display: flex; display: flex;
justify-content: center; justify-content: center;
height: 20px;
margin-top: -2px;
overflow: hidden; overflow: hidden;
} }
} }