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 { ...@@ -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>
...@@ -4,6 +4,7 @@ import tooltip from '../../../vue_shared/directives/tooltip'; ...@@ -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 { ...@@ -12,6 +13,7 @@ export default {
components: { components: {
Icon, Icon,
PipelinesList, PipelinesList,
JobsDetail,
}, },
computed: { computed: {
...mapState(['rightPane']), ...mapState(['rightPane']),
......
...@@ -23,4 +23,5 @@ export const viewerTypes = { ...@@ -23,4 +23,5 @@ export const viewerTypes = {
export const rightSidebarViews = { export const rightSidebarViews = {
pipelines: 'pipelines-list', pipelines: 'pipelines-list',
jobsDetail: 'jobs-detail',
}; };
...@@ -77,4 +77,6 @@ export const fetchJobs = ({ dispatch }, stage) => { ...@@ -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 () => {};
...@@ -7,3 +7,5 @@ export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR'; ...@@ -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';
...@@ -63,4 +63,7 @@ export default { ...@@ -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;
},
}; };
...@@ -3,4 +3,5 @@ export default () => ({ ...@@ -3,4 +3,5 @@ export default () => ({
isLoadingJobs: false, isLoadingJobs: false,
latestPipeline: null, latestPipeline: null,
stages: [], stages: [],
detailJob: null,
}); });
...@@ -4,4 +4,5 @@ export const normalizeJob = job => ({ ...@@ -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,
}); });
...@@ -22,6 +22,8 @@ export default class Job { ...@@ -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,24 +46,16 @@ export default class Job { ...@@ -44,24 +46,16 @@ 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')
.on('scroll', () => {
if (!this.isScrolledToBottom()) { if (!this.isScrolledToBottom()) {
this.toggleScrollAnimation(false); this.toggleScrollAnimation(false);
} else if (this.isScrolledToBottom() && !this.isLogComplete) { } else if (this.isScrolledToBottom() && !this.isLogComplete) {
...@@ -79,6 +73,10 @@ export default class Job { ...@@ -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,8 +100,7 @@ export default class Job { ...@@ -102,8 +100,7 @@ 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);
...@@ -169,10 +166,11 @@ export default class Job { ...@@ -169,10 +166,11 @@ export default class Job {
} }
getBuildTrace() { getBuildTrace() {
return axios.get(`${this.pagePath}/trace.json`, { return axios
.get(`${this.pagePath}/trace.json`, {
params: { state: this.state }, 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 { ...@@ -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);
} }
}) })
......
...@@ -1202,7 +1202,7 @@ ...@@ -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 @@ ...@@ -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;
} }
} }
......
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