From 90216b206659b78c802ef287726552275d87daf1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 31 May 2018 15:50:24 +0100 Subject: [PATCH 01/11] Show job logs in web IDE [ci skip] Closes #46245 --- .../ide/components/jobs/detail.vue | 125 ++++++++++++++++++ .../javascripts/ide/components/jobs/item.vue | 12 ++ .../ide/components/panes/right.vue | 2 + app/assets/javascripts/ide/constants.js | 1 + .../ide/stores/modules/pipelines/actions.js | 2 + .../modules/pipelines/mutation_types.js | 2 + .../ide/stores/modules/pipelines/mutations.js | 3 + .../ide/stores/modules/pipelines/state.js | 1 + .../ide/stores/modules/pipelines/utils.js | 1 + app/assets/javascripts/job.js | 52 ++++---- app/assets/stylesheets/pages/repo.scss | 4 +- 11 files changed, 175 insertions(+), 30 deletions(-) create mode 100644 app/assets/javascripts/ide/components/jobs/detail.vue 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 @@ + + + + + \ 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 { /> + + + 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; } } From e59d9a0c09d4ab3543015a9ca8a8012dc7faad03 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 31 May 2018 17:28:12 +0100 Subject: [PATCH 02/11] removed need for job.js it was a super hacky implementation that just wasn't working as intended current sitatuon is still super hacky, but getting better! --- .../ide/components/jobs/detail.vue | 232 +++++++++++++----- .../ide/stores/modules/pipelines/utils.js | 1 + app/assets/javascripts/job.js | 52 ++-- 3 files changed, 193 insertions(+), 92 deletions(-) diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue index 28d858cf793..330d2a08eec 100644 --- a/app/assets/javascripts/ide/components/jobs/detail.vue +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -1,33 +1,91 @@ @@ -46,69 +104,97 @@ export default { {{ __('View jobs') }} -
-
-
- + +
+ + + +
+ +
+
+ -
-
- -
+ +
-
-        
-        
-      
+
+      
+      
+
+
@@ -122,4 +208,16 @@ export default { .ide-tree-header .btn { display: flex; } - \ No newline at end of file + +.ide-job-details { + display: flex; +} + +.ide-job-details .ci-status-icon { + height: 0; +} + +.build-trace { + margin-bottom: 0; +} + diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js index 4f40e8766e0..4ee030731e3 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js @@ -5,4 +5,5 @@ export const normalizeJob = job => ({ status: job.status, path: job.build_path, started: job.started, + output: '', }); diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 095d2ff9942..611e8200b4d 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -22,8 +22,6 @@ 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'); @@ -46,23 +44,31 @@ 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') @@ -73,10 +79,6 @@ 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. @@ -100,8 +102,9 @@ 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); @@ -166,11 +169,10 @@ 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) { @@ -220,7 +222,7 @@ export default class Job { this.toggleScrollAnimation(false); } - if (log.status !== this.buildStatus && this.redirectToJob) { + if (log.status !== this.buildStatus) { visitUrl(this.pagePath); } }) From 9827f3db10fc46d902f6670c183c09661ea5da07 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jun 2018 12:59:56 +0100 Subject: [PATCH 03/11] moved fetching trace to store improved components by making them unaware of the store removed need for extra CSS - all achieved with BS classes --- .../ide/components/jobs/detail.vue | 122 +++++------------- .../javascripts/ide/components/jobs/item.vue | 15 +-- .../javascripts/ide/components/jobs/list.vue | 3 +- .../javascripts/ide/components/jobs/stage.vue | 4 + .../ide/stores/modules/pipelines/actions.js | 22 +++- .../modules/pipelines/mutation_types.js | 4 + .../ide/stores/modules/pipelines/mutations.js | 12 +- .../ide/stores/modules/pipelines/utils.js | 2 + app/assets/stylesheets/pages/builds.scss | 1 + 9 files changed, 87 insertions(+), 98 deletions(-) diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue index 330d2a08eec..bacf08c5b5c 100644 --- a/app/assets/javascripts/ide/components/jobs/detail.vue +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -1,14 +1,13 @@ - - diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue index 0baeb4bb04f..597b65fa392 100644 --- a/app/assets/javascripts/ide/components/jobs/item.vue +++ b/app/assets/javascripts/ide/components/jobs/item.vue @@ -18,6 +18,11 @@ export default { return `#${this.job.id}`; }, }, + methods: { + clickViewLog() { + this.$emit('clickViewLog', this.job); + }, + }, }; @@ -43,16 +48,10 @@ export default { - - diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue index bdd0364c9b9..3b16b860ecd 100644 --- a/app/assets/javascripts/ide/components/jobs/list.vue +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -19,7 +19,7 @@ export default { }, }, methods: { - ...mapActions('pipelines', ['fetchJobs', 'toggleStageCollapsed']), + ...mapActions('pipelines', ['fetchJobs', 'toggleStageCollapsed', 'setDetailJob']), }, }; @@ -38,6 +38,7 @@ export default { :stage="stage" @fetch="fetchJobs" @toggleCollapsed="toggleStageCollapsed" + @clickViewLog="setDetailJob" /> diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index 5b24bb1f5a7..b1428f885fb 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -48,6 +48,9 @@ export default { toggleCollapsed() { this.$emit('toggleCollapsed', this.stage.id); }, + clickViewLog(job) { + this.$emit('clickViewLog', job); + }, }, }; @@ -101,6 +104,7 @@ export default { v-for="job in stage.jobs" :key="job.id" :job="job" + @clickViewLog="clickViewLog" /> diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index b112bc0e1ac..deb84212fc3 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -4,6 +4,7 @@ import { __ } from '../../../../locale'; import flash from '../../../../flash'; import Poll from '../../../../lib/utils/poll'; import service from '../../../services'; +import { rightSidebarViews } from '../../../constants'; import * as types from './mutation_types'; let eTagPoll; @@ -77,6 +78,25 @@ 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 const setDetailJob = ({ commit, dispatch }, job) => { + commit(types.SET_DETAIL_JOB, job); + dispatch('setRightPane', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, { + root: true, + }); +}; + +export const requestJobTrace = ({ commit }) => commit(types.REQUEST_JOB_TRACE); +export const receiveJobTraceError = ({ commit }) => commit(types.RECEIVE_JOB_TRACE_ERROR); +export const receiveJobTraceSuccess = ({ commit }, data) => + commit(types.RECEIVE_JOB_TRACE_SUCCESS, data); + +export const fetchJobTrace = ({ dispatch, state }) => { + dispatch('requestJobTrace'); + + return axios + .get(`${state.detailJob.path}/trace`, { params: { format: 'json' } }) + .then(({ data }) => dispatch('receiveJobTraceSuccess', data)) + .catch(() => dispatch('requestJobTraceError')); +}; 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 d95ce7c7440..f4c36b9d96f 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js @@ -9,3 +9,7 @@ export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; export const TOGGLE_STAGE_COLLAPSE = 'TOGGLE_STAGE_COLLAPSE'; export const SET_DETAIL_JOB = 'SET_DETAIL_JOB'; + +export const REQUEST_JOB_TRACE = 'REQUEST_JOB_TRACE'; +export const RECEIVE_JOB_TRACE_ERROR = 'RECEIVE_JOB_TRACE_ERROR'; +export const RECEIVE_JOB_TRACE_SUCCESS = 'RECEIVE_JOB_TRACE_SUCCESS'; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js index bb49af1f45b..5a2213bbe89 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -64,6 +64,16 @@ export default { })); }, [types.SET_DETAIL_JOB](state, job) { - state.detailJob = job; + state.detailJob = { ...job }; + }, + [types.REQUEST_JOB_TRACE](state) { + state.detailJob.isLoading = true; + }, + [types.RECEIVE_JOB_TRACE_ERROR](state) { + state.detailJob.isLoading = false; + }, + [types.RECEIVE_JOB_TRACE_SUCCESS](state, data) { + state.detailJob.isLoading = false; + state.detailJob.output = data.html; }, }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js index 4ee030731e3..a6caca2d2dc 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/utils.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/utils.js @@ -4,6 +4,8 @@ export const normalizeJob = job => ({ name: job.name, status: job.status, path: job.build_path, + rawPath: `${job.build_path}/raw`, started: job.started, output: '', + isLoading: false, }); diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 9ee02ca1d83..9213ccd4cdf 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -75,6 +75,7 @@ .top-bar { height: 35px; + min-height: 35px; background: $gray-light; border: 1px solid $border-color; color: $gl-text-color; From 1d43689e5aa6bcd4f14bdff9e03540d3b3d8ca37 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jun 2018 14:14:33 +0100 Subject: [PATCH 04/11] reduced duplication of job details --- .../ide/components/jobs/detail.vue | 75 +++++-------------- .../components/jobs/detail/description.vue | 47 ++++++++++++ .../components/jobs/detail/scroll_button.vue | 61 +++++++++++++++ .../javascripts/ide/components/jobs/item.vue | 26 +------ .../ide/stores/modules/pipelines/actions.js | 5 +- 5 files changed, 134 insertions(+), 80 deletions(-) create mode 100644 app/assets/javascripts/ide/components/jobs/detail/description.vue create mode 100644 app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue index bacf08c5b5c..72daba5486b 100644 --- a/app/assets/javascripts/ide/components/jobs/detail.vue +++ b/app/assets/javascripts/ide/components/jobs/detail.vue @@ -1,9 +1,10 @@ + + diff --git a/app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue b/app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue new file mode 100644 index 00000000000..3ecb99c581b --- /dev/null +++ b/app/assets/javascripts/ide/components/jobs/detail/scroll_button.vue @@ -0,0 +1,61 @@ + + + diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue index 597b65fa392..224e4d5158b 100644 --- a/app/assets/javascripts/ide/components/jobs/item.vue +++ b/app/assets/javascripts/ide/components/jobs/item.vue @@ -1,11 +1,9 @@