diff options
author | Phil Hughes <me@iamphill.com> | 2018-05-31 15:50:24 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2018-06-06 08:33:02 +0100 |
commit | 90216b206659b78c802ef287726552275d87daf1 (patch) | |
tree | 65ae10be2bc4f5b986445cf7a50da095a8e47b72 | |
parent | af07c490b2a32ed4c88e387d1133e7882f79abc5 (diff) | |
download | gitlab-ce-90216b206659b78c802ef287726552275d87daf1.tar.gz |
Show job logs in web IDE
[ci skip]
Closes #46245
11 files changed, 175 insertions, 30 deletions
diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue new file mode 100644 index 00000000000..28d858cf793 --- /dev/null +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -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>
\ No newline at end of file diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue index c33936021d4..0baeb4bb04f 100644 --- a/app/assets/javascripts/ide/components/jobs/item.vue +++ b/app/assets/javascripts/ide/components/jobs/item.vue @@ -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> diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index 703c4a70cfa..7f4650cce1a 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -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']), diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 33cd20caf52..65886c02b92 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -23,4 +23,5 @@ export const viewerTypes = { export const rightSidebarViews = { pipelines: 'pipelines-list', + jobsDetail: 'jobs-detail', }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 1ebe487263b..b112bc0e1ac 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -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 () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js index 3ddc8409c5b..d95ce7c7440 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js @@ -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'; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js index 745797e1ee5..bb49af1f45b 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -63,4 +63,7 @@ export default { isCollapsed: stage.id === id ? !stage.isCollapsed : stage.isCollapsed, })); }, + [types.SET_DETAIL_JOB](state, job) { + state.detailJob = job; + }, }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/state.js b/app/assets/javascripts/ide/stores/modules/pipelines/state.js index 0f83b315fff..8651e267b53 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/state.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/state.js @@ -3,4 +3,5 @@ export default () => ({ isLoadingJobs: false, latestPipeline: null, stages: [], + detailJob: null, }); diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js index 9f4b0d7d726..4f40e8766e0 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js @@ -4,4 +4,5 @@ export const normalizeJob = job => ({ name: job.name, status: job.status, path: job.build_path, + started: job.started, }); diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 611e8200b4d..095d2ff9942 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -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); } }) diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 2b3cc33c8ae..2419fa08b73 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -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; } } |