From 32f965b244f38b8f94aff0d0f7bb952de7593127 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 22 May 2018 12:32:37 +0100 Subject: [PATCH 01/29] Added right sidebar components --- app/assets/javascripts/ide/components/ide.vue | 6 ++ .../ide/components/right_sidebar/index.vue | 61 +++++++++++++++++++ .../components/right_sidebar/pipelines.vue | 46 ++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 app/assets/javascripts/ide/components/right_sidebar/index.vue create mode 100644 app/assets/javascripts/ide/components/right_sidebar/pipelines.vue diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 1ec69adce09..d61ed36a757 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -6,6 +6,7 @@ import RepoTabs from './repo_tabs.vue'; import IdeStatusBar from './ide_status_bar.vue'; import RepoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue'; +import RightSidebar from './right_sidebar/index.vue'; const originalStopCallback = Mousetrap.stopCallback; @@ -16,6 +17,7 @@ export default { IdeStatusBar, RepoEditor, FindFile, + RightSidebar, }, computed: { ...mapState([ @@ -25,6 +27,7 @@ export default { 'currentMergeRequestId', 'fileFindVisible', 'emptyStateSvgPath', + 'currentProjectId', ]), ...mapGetters(['activeFile', 'hasChanges']), }, @@ -122,6 +125,9 @@ export default { + +import tooltip from '../../../vue_shared/directives/tooltip'; +import Icon from '../../../vue_shared/components/icon.vue'; +import Pipelines from './pipelines.vue'; + +export default { + directives: { + tooltip, + }, + components: { + Icon, + Pipelines, + }, +}; + + + + + diff --git a/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue b/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue new file mode 100644 index 00000000000..0ff78242e6a --- /dev/null +++ b/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue @@ -0,0 +1,46 @@ + + + + + From cfe4d2f29dcdcfad96ae7ba5a5eb822fbe46a9a7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 23 May 2018 11:44:47 +0100 Subject: [PATCH 02/29] added tab component --- app/assets/javascripts/ide/components/ide.vue | 6 +- .../index.vue => panes/right.vue} | 32 ++++++--- .../ide/components/pipelines/jobs.vue | 40 +++++++++++ .../pipelines.vue => pipelines/list.vue} | 31 +++++++-- app/assets/javascripts/ide/constants.js | 4 ++ app/assets/javascripts/ide/stores/actions.js | 4 ++ .../ide/stores/modules/pipelines/getters.js | 2 + .../ide/stores/modules/pipelines/mutations.js | 1 + .../javascripts/ide/stores/mutation_types.js | 2 + .../javascripts/ide/stores/mutations.js | 3 + app/assets/javascripts/ide/stores/state.js | 1 + .../vue_shared/components/tabs/tab.vue | 42 ++++++++++++ .../vue_shared/components/tabs/tabs.js | 62 +++++++++++++++++ .../vue_shared/components/tabs/tab_spec.js | 32 +++++++++ .../vue_shared/components/tabs/tabs_spec.js | 68 +++++++++++++++++++ 15 files changed, 314 insertions(+), 16 deletions(-) rename app/assets/javascripts/ide/components/{right_sidebar/index.vue => panes/right.vue} (60%) create mode 100644 app/assets/javascripts/ide/components/pipelines/jobs.vue rename app/assets/javascripts/ide/components/{right_sidebar/pipelines.vue => pipelines/list.vue} (54%) create mode 100644 app/assets/javascripts/vue_shared/components/tabs/tab.vue create mode 100644 app/assets/javascripts/vue_shared/components/tabs/tabs.js create mode 100644 spec/javascripts/vue_shared/components/tabs/tab_spec.js create mode 100644 spec/javascripts/vue_shared/components/tabs/tabs_spec.js diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index d61ed36a757..318e7aa5716 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -6,7 +6,7 @@ import RepoTabs from './repo_tabs.vue'; import IdeStatusBar from './ide_status_bar.vue'; import RepoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue'; -import RightSidebar from './right_sidebar/index.vue'; +import RightPane from './panes/right.vue'; const originalStopCallback = Mousetrap.stopCallback; @@ -17,7 +17,7 @@ export default { IdeStatusBar, RepoEditor, FindFile, - RightSidebar, + RightPane, }, computed: { ...mapState([ @@ -125,7 +125,7 @@ export default { - diff --git a/app/assets/javascripts/ide/components/right_sidebar/index.vue b/app/assets/javascripts/ide/components/panes/right.vue similarity index 60% rename from app/assets/javascripts/ide/components/right_sidebar/index.vue rename to app/assets/javascripts/ide/components/panes/right.vue index 2417e3976aa..7ac79347225 100644 --- a/app/assets/javascripts/ide/components/right_sidebar/index.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -1,7 +1,9 @@ @@ -18,25 +27,31 @@ export default {
-
- +
+ + +
@@ -55,6 +70,7 @@ export default { .ide-right-sidebar .multi-file-commit-panel-inner { width: 300px; + padding: 8px 16px; background-color: #fff; border-left: 1px solid #eaeaea; } diff --git a/app/assets/javascripts/ide/components/pipelines/jobs.vue b/app/assets/javascripts/ide/components/pipelines/jobs.vue new file mode 100644 index 00000000000..d69945b617c --- /dev/null +++ b/app/assets/javascripts/ide/components/pipelines/jobs.vue @@ -0,0 +1,40 @@ + + + diff --git a/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue b/app/assets/javascripts/ide/components/pipelines/list.vue similarity index 54% rename from app/assets/javascripts/ide/components/right_sidebar/pipelines.vue rename to app/assets/javascripts/ide/components/pipelines/list.vue index 0ff78242e6a..e76ea0b50af 100644 --- a/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -1,14 +1,17 @@ + + diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js new file mode 100644 index 00000000000..3dff37b1c84 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/tabs/tabs.js @@ -0,0 +1,62 @@ +export default { + data() { + return { + currentIndex: 0, + tabs: [], + }; + }, + mounted() { + this.updateTabs(); + }, + methods: { + updateTabs() { + this.tabs = this.$children.filter(child => child.isTab); + this.currentIndex = this.tabs.findIndex(tab => tab.localActive); + }, + setTab(index) { + this.tabs[this.currentIndex].localActive = false; + this.tabs[index].localActive = true; + + this.currentIndex = index; + }, + }, + render(h) { + const navItems = this.tabs.map((tab, i) => + h( + 'li', + { + key: i, + class: tab.localActive ? 'active' : null, + }, + [ + h( + 'a', + { + href: '#', + on: { + click: () => this.setTab(i), + }, + }, + tab.$slots.title || tab.title, + ), + ], + ), + ); + const nav = h( + 'ul', + { + class: 'nav-links tab-links', + }, + [navItems], + ); + const content = h( + 'div', + { + class: ['tab-content'], + }, + [this.$slots.default], + ); + + return h('div', {}, [[nav], content]); + }, +}; diff --git a/spec/javascripts/vue_shared/components/tabs/tab_spec.js b/spec/javascripts/vue_shared/components/tabs/tab_spec.js new file mode 100644 index 00000000000..8437fe37738 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tabs/tab_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import Tab from '~/vue_shared/components/tabs/tab.vue'; + +describe('Tab component', () => { + const Component = Vue.extend(Tab); + let vm; + + beforeEach(() => { + vm = mountComponent(Component); + }); + + it('sets localActive to equal active', done => { + vm.active = true; + + vm.$nextTick(() => { + expect(vm.localActive).toBe(true); + + done(); + }); + }); + + it('sets active class', done => { + vm.active = true; + + vm.$nextTick(() => { + expect(vm.$el.classList).toContain('active'); + + done(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/tabs/tabs_spec.js b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js new file mode 100644 index 00000000000..07752329965 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js @@ -0,0 +1,68 @@ +import Vue from 'vue'; +import Tabs from '~/vue_shared/components/tabs/tabs'; +import Tab from '~/vue_shared/components/tabs/tab.vue'; + +describe('Tabs component', () => { + let vm; + + beforeEach(done => { + vm = new Vue({ + components: { + Tabs, + Tab, + }, + template: ` +
+ + + First tab + + + + Second tab + + +
+ `, + }).$mount(); + + setTimeout(done); + }); + + describe('tab links', () => { + it('renders links for tabs', () => { + expect(vm.$el.querySelectorAll('a').length).toBe(2); + }); + + it('renders link titles from props', () => { + expect(vm.$el.querySelector('a').textContent).toContain('Testing'); + }); + + it('renders link titles from slot', () => { + expect(vm.$el.querySelectorAll('a')[1].textContent).toContain('Test slot'); + }); + + it('renders active class', () => { + expect(vm.$el.querySelector('li').classList).toContain('active'); + }); + + it('updates active class on click', done => { + vm.$el.querySelectorAll('a')[1].click(); + + setTimeout(() => { + expect(vm.$el.querySelector('li').classList).not.toContain('active'); + expect(vm.$el.querySelectorAll('li')[1].classList).toContain('active'); + + done(); + }); + }); + }); + + describe('content', () => { + it('renders content panes', () => { + expect(vm.$el.querySelectorAll('.tab-pane').length).toBe(2); + expect(vm.$el.querySelectorAll('.tab-pane')[0].textContent).toContain('First tab'); + expect(vm.$el.querySelectorAll('.tab-pane')[1].textContent).toContain('Second tab'); + }); + }); +}); From 5e79276b53f61cbd727411ed33e71d5b6fa5ca54 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 23 May 2018 14:22:38 +0100 Subject: [PATCH 03/29] improve API calls by calling internal API to get data render job items (needs improvements to components) --- app/assets/javascripts/api.js | 6 +- .../ide/components/pipelines/jobs.vue | 50 ++++++++++++-- .../ide/stores/modules/pipelines/actions.js | 46 ++++++++----- .../ide/stores/modules/pipelines/getters.js | 6 +- .../modules/pipelines/mutation_types.js | 4 ++ .../ide/stores/modules/pipelines/mutations.js | 67 ++++++++++++------- .../vue_shared/components/tabs/tab.vue | 3 + .../projects/pipelines_controller.rb | 11 ++- 8 files changed, 137 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index eb919241318..06fdc8fe65b 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -24,7 +24,7 @@ const Api = { branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', createBranchPath: '/api/:version/projects/:id/repository/branches', pipelinesPath: '/api/:version/projects/:id/pipelines', - pipelineJobsPath: '/api/:version/projects/:id/pipelines/:pipeline_id/jobs', + pipelineJobsPath: '/:project_path/pipelines/:id/builds.json', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); @@ -232,8 +232,8 @@ const Api = { pipelineJobs(projectPath, pipelineId, params = {}) { const url = Api.buildUrl(this.pipelineJobsPath) - .replace(':id', encodeURIComponent(projectPath)) - .replace(':pipeline_id', pipelineId); + .replace(':project_path', projectPath) + .replace(':id', pipelineId); return axios.get(url, { params }); }, diff --git a/app/assets/javascripts/ide/components/pipelines/jobs.vue b/app/assets/javascripts/ide/components/pipelines/jobs.vue index d69945b617c..a3a99ad457c 100644 --- a/app/assets/javascripts/ide/components/pipelines/jobs.vue +++ b/app/assets/javascripts/ide/components/pipelines/jobs.vue @@ -1,5 +1,7 @@ @@ -27,11 +32,44 @@ export default { - List all jobs here +
+
+
+ + {{ stage.title }} + + {{ stage.jobs.length }} + + +
+
+
+ + {{ job.name }} #{{ job.id }} +
+
+
+
List all failed jobs here diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 07f7b201f2e..994edd74aef 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -1,3 +1,4 @@ +import axios from 'axios'; import { __ } from '../../../../locale'; import Api from '../../../../api'; import flash from '../../../../flash'; @@ -21,29 +22,40 @@ export const fetchLatestPipeline = ({ dispatch, rootState }, sha) => { .catch(() => dispatch('receiveLatestPipelineError')); }; -export const requestJobs = ({ commit }) => commit(types.REQUEST_JOBS); -export const receiveJobsError = ({ commit }) => { - flash(__('There was an error loading jobs')); - commit(types.RECEIVE_JOBS_ERROR); +export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES); +export const receiveStagesError = ({ commit }) => { + flash(__('There was an error loading job stages')); + commit(types.RECEIVE_STAGES_ERROR); }; -export const receiveJobsSuccess = ({ commit }, data) => commit(types.RECEIVE_JOBS_SUCCESS, data); +export const receiveStagesSuccess = ({ commit }, data) => + commit(types.RECEIVE_STAGES_SUCCESS, data); -export const fetchJobs = ({ dispatch, state, rootState }, page = '1') => { - dispatch('requestJobs'); +export const fetchStages = ({ dispatch, state, rootState }) => { + dispatch('requestStages'); - Api.pipelineJobs(rootState.currentProjectId, state.latestPipeline.id, { - page, - }) - .then(({ data, headers }) => { - const nextPage = headers && headers['x-next-page']; + Api.pipelineJobs(rootState.currentProjectId, state.latestPipeline.id) + .then(({ data }) => dispatch('receiveStagesSuccess', data)) + .then(() => state.stages.forEach(stage => dispatch('fetchJobs', stage))) + .catch(() => dispatch('receiveStagesError')); +}; - dispatch('receiveJobsSuccess', data); +export const requestJobs = ({ commit }, id) => commit(types.REQUEST_JOBS, id); +export const receiveJobsError = ({ commit }, id) => { + flash(__('There was an error loading jobs')); + commit(types.RECEIVE_JOBS_ERROR, id); +}; +export const receiveJobsSuccess = ({ commit }, { id, data }) => + commit(types.RECEIVE_JOBS_SUCCESS, { id, data }); - if (nextPage) { - dispatch('fetchJobs', nextPage); - } +export const fetchJobs = ({ dispatch }, stage) => { + dispatch('requestJobs', stage.id); + + axios + .get(stage.dropdown_path) + .then(({ data }) => { + dispatch('receiveJobsSuccess', { id: stage.id, data }); }) - .catch(() => dispatch('receiveJobsError')); + .catch(() => dispatch('receiveJobsError', stage.id)); }; export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js index e1f55bcd933..99b4554a96e 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js @@ -1,9 +1,9 @@ export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline; -export const failedJobs = state => +export const failedJobsCount = state => state.stages.reduce( - (acc, stage) => acc.concat(stage.jobs.filter(job => job.status === 'failed')), - [], + (acc, stage) => acc + stage.jobs.filter(j => j.status.label === 'failed').length, + 0, ); export const jobsCount = state => state.stages.reduce((acc, stage) => acc + stage.jobs.length, 0); 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 6b5701670a6..0911b8ee6fb 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js @@ -2,6 +2,10 @@ export const REQUEST_LATEST_PIPELINE = 'REQUEST_LATEST_PIPELINE'; export const RECEIVE_LASTEST_PIPELINE_ERROR = 'RECEIVE_LASTEST_PIPELINE_ERROR'; export const RECEIVE_LASTEST_PIPELINE_SUCCESS = 'RECEIVE_LASTEST_PIPELINE_SUCCESS'; +export const REQUEST_STAGES = 'REQUEST_STAGES'; +export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR'; +export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS'; + export const REQUEST_JOBS = 'REQUEST_JOBS'; export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR'; export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js index d28d9ca776d..7115f5e5386 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -18,37 +18,52 @@ export default { }; } }, - [types.REQUEST_JOBS](state) { + [types.REQUEST_STAGES](state) { state.isLoadingJobs = true; }, - [types.RECEIVE_JOBS_ERROR](state) { + [types.RECEIVE_STAGES_ERROR](state) { state.isLoadingJobs = false; }, - [types.RECEIVE_JOBS_SUCCESS](state, jobs) { + [types.RECEIVE_STAGES_SUCCESS](state, stages) { state.isLoadingJobs = false; - state.stages = jobs.reduce((acc, job) => { - let stage = acc.find(s => s.title === job.stage); - - if (!stage) { - stage = { - title: job.stage, - isCollapsed: false, - jobs: [], - }; - - acc.push(stage); - } - - stage.jobs = stage.jobs.concat({ - id: job.id, - name: job.name, - status: job.status, - stage: job.stage, - duration: job.duration, - }); - - return acc; - }, state.stages); + state.stages = stages.map((stage, i) => ({ + ...stage, + id: i, + isCollapsed: false, + isLoading: false, + jobs: [], + })); + }, + [types.REQUEST_JOBS](state, id) { + state.stages = state.stages.reduce( + (acc, stage) => + acc.concat({ + ...stage, + isLoading: id === stage.id ? true : stage.isLoading, + }), + [], + ); + }, + [types.RECEIVE_JOBS_ERROR](state, id) { + state.stages = state.stages.reduce( + (acc, stage) => + acc.concat({ + ...stage, + isLoading: id === stage.id ? false : stage.isLoading, + }), + [], + ); + }, + [types.RECEIVE_JOBS_SUCCESS](state, { id, data }) { + state.stages = state.stages.reduce( + (acc, stage) => + acc.concat({ + ...stage, + isLoading: id === stage.id ? false : stage.isLoading, + jobs: id === stage.id ? data.latest_statuses : stage.jobs, + }), + [], + ); }, }; diff --git a/app/assets/javascripts/vue_shared/components/tabs/tab.vue b/app/assets/javascripts/vue_shared/components/tabs/tab.vue index 2a35d6bc151..0ec7a0199fe 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tab.vue +++ b/app/assets/javascripts/vue_shared/components/tabs/tab.vue @@ -26,6 +26,9 @@ export default { created() { this.isTab = true; }, + updated() { + this.$parent.$forceUpdate(); + }, }; diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 6b40fc2fe68..7b43ef8ab76 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -76,7 +76,16 @@ class Projects::PipelinesController < Projects::ApplicationController end def builds - render_show + respond_to do |format| + format.html do + render_show + end + format.json do + render json: PipelineSerializer + .new(project: @project, current_user: @current_user) + .represent_stages(@pipeline) + end + end end def failures From 76ffde63189efd81249857a6a9bf612f328322c6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 23 May 2018 15:29:34 +0100 Subject: [PATCH 04/29] style improvements fixed multiple requests causing state to be emptied at wrong time --- .../javascripts/ide/components/jobs/list.vue | 25 +++++ .../javascripts/ide/components/jobs/stage.vue | 94 +++++++++++++++++++ .../ide/components/panes/right.vue | 11 ++- .../ide/components/pipelines/jobs.vue | 51 ++-------- .../ide/components/pipelines/list.vue | 4 +- .../ide/stores/modules/pipelines/actions.js | 1 - .../ide/stores/modules/pipelines/getters.js | 2 + .../ide/stores/modules/pipelines/mutations.js | 17 ++-- .../vue_shared/components/tabs/tabs.js | 4 +- .../stylesheets/framework/gitlab_theme.scss | 4 + app/assets/stylesheets/pages/repo.scss | 7 ++ 11 files changed, 163 insertions(+), 57 deletions(-) create mode 100644 app/assets/javascripts/ide/components/jobs/list.vue create mode 100644 app/assets/javascripts/ide/components/jobs/stage.vue diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue new file mode 100644 index 00000000000..7ac1ce3ef54 --- /dev/null +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue new file mode 100644 index 00000000000..62042892e13 --- /dev/null +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index 7ac79347225..7449822516b 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -31,19 +31,20 @@ export default { class="multi-file-commit-panel-inner" v-if="rightPane" > - - - +
From 4b4618936d0af203820be3a9392d7e555464cf3f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 May 2018 11:49:57 +0100 Subject: [PATCH 06/29] improve design of job items allow ci icon to have a different size & be borderless --- .../javascripts/ide/components/jobs/item.vue | 46 ++++++++++++++++--- .../javascripts/ide/components/jobs/list.vue | 23 ++++++++-- .../javascripts/ide/components/jobs/stage.vue | 39 ++++++++++++++-- .../ide/components/panes/right.vue | 2 +- .../ide/components/pipelines/jobs.vue | 18 ++++++-- .../ide/components/pipelines/list.vue | 1 + .../ide/stores/modules/pipelines/getters.js | 8 +++- .../vue_shared/components/ci_icon.vue | 23 +++++++++- 8 files changed, 137 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/ide/components/jobs/item.vue b/app/assets/javascripts/ide/components/jobs/item.vue index 53d9baffd78..a6ca629a358 100644 --- a/app/assets/javascripts/ide/components/jobs/item.vue +++ b/app/assets/javascripts/ide/components/jobs/item.vue @@ -11,16 +11,48 @@ export default { required: true, }, }, + computed: { + jobId() { + return `#${this.job.id}`; + }, + }, }; + + diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue index 7ac1ce3ef54..ef1d4580d6a 100644 --- a/app/assets/javascripts/ide/components/jobs/list.vue +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -1,8 +1,11 @@ diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index a4e3b8e7926..c00ac458745 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -1,11 +1,15 @@ From 1e48b7eec0499e44eb1dcd32393005f709b5c816 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 May 2018 12:11:55 +0100 Subject: [PATCH 07/29] removed need for jobs component --- .../javascripts/ide/components/jobs/list.vue | 10 ++-- .../javascripts/ide/components/jobs/stage.vue | 10 +++- .../ide/components/pipelines/jobs.vue | 59 ------------------- .../ide/components/pipelines/list.vue | 46 +++++++++++++-- 4 files changed, 54 insertions(+), 71 deletions(-) delete mode 100644 app/assets/javascripts/ide/components/pipelines/jobs.vue diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue index ef1d4580d6a..fd6bfdf86d0 100644 --- a/app/assets/javascripts/ide/components/jobs/list.vue +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -1,5 +1,4 @@ @@ -23,7 +23,7 @@ export default { From 8b1c43bde36c902d138b06dfb91ffab4bc1eb8ad Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 24 May 2018 15:07:09 +0100 Subject: [PATCH 08/29] spec fixes --- .../javascripts/ide/stores/mutations.js | 4 +- .../vue_shared/components/tabs/tab.vue | 4 +- spec/javascripts/ide/mock_data.js | 21 ++++ .../stores/modules/pipelines/actions_spec.js | 100 +++++------------- .../stores/modules/pipelines/getters_spec.js | 31 ------ .../modules/pipelines/mutations_spec.js | 61 ++++------- 6 files changed, 74 insertions(+), 147 deletions(-) diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 375b4d8233d..633c93ed0a8 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -149,7 +149,9 @@ export default { }); }, [types.SET_RIGHT_PANE](state, view) { - state.rightPane = state.rightPane === view ? null : view; + Object.assign(state, { + rightPane: state.rightPane === view ? null : view, + }); }, ...projectMutations, ...mergeRequestMutation, diff --git a/app/assets/javascripts/vue_shared/components/tabs/tab.vue b/app/assets/javascripts/vue_shared/components/tabs/tab.vue index 0ec7a0199fe..9b2f46186ac 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tab.vue +++ b/app/assets/javascripts/vue_shared/components/tabs/tab.vue @@ -27,7 +27,9 @@ export default { this.isTab = true; }, updated() { - this.$parent.$forceUpdate(); + if (this.$parent) { + this.$parent.$forceUpdate(); + } }, }; diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index 7e641c7984b..a0cb8bae91c 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -29,6 +29,27 @@ export const pipelines = [ }, ]; +export const stages = [ + { + dropdown_path: 'testing', + name: 'build', + status: { + icon: 'status_failed', + group: 'failed', + text: 'Failed', + }, + }, + { + dropdown_path: 'testing', + name: 'test', + status: { + icon: 'status_failed', + group: 'failed', + text: 'Failed', + }, + }, +]; + export const jobs = [ { id: 1, diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js index 85fbcf8084b..bcf9d9e1513 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -5,15 +5,15 @@ import actions, { receiveLatestPipelineError, receiveLatestPipelineSuccess, fetchLatestPipeline, - requestJobs, - receiveJobsError, - receiveJobsSuccess, - fetchJobs, + requestStages, + receiveStagesError, + receiveStagesSuccess, + fetchStages, } from '~/ide/stores/modules/pipelines/actions'; import state from '~/ide/stores/modules/pipelines/state'; import * as types from '~/ide/stores/modules/pipelines/mutation_types'; import testAction from '../../../../helpers/vuex_action_helper'; -import { pipelines, jobs } from '../../../mock_data'; +import { pipelines, stages } from '../../../mock_data'; describe('IDE pipelines actions', () => { let mockedState; @@ -141,19 +141,19 @@ describe('IDE pipelines actions', () => { }); }); - describe('requestJobs', () => { + describe('requestStages', () => { it('commits request', done => { - testAction(requestJobs, null, mockedState, [{ type: types.REQUEST_JOBS }], [], done); + testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done); }); }); describe('receiveJobsError', () => { it('commits error', done => { testAction( - receiveJobsError, + receiveStagesError, null, mockedState, - [{ type: types.RECEIVE_JOBS_ERROR }], + [{ type: types.RECEIVE_STAGES_ERROR }], [], done, ); @@ -162,80 +162,53 @@ describe('IDE pipelines actions', () => { it('creates flash message', () => { const flashSpy = spyOnDependency(actions, 'flash'); - receiveJobsError({ commit() {} }); + receiveStagesError({ commit() {} }); expect(flashSpy).toHaveBeenCalled(); }); }); - describe('receiveJobsSuccess', () => { + describe('receiveStagesSuccess', () => { it('commits jobs', done => { testAction( - receiveJobsSuccess, - jobs, + receiveStagesSuccess, + stages, mockedState, - [{ type: types.RECEIVE_JOBS_SUCCESS, payload: jobs }], + [{ type: types.RECEIVE_STAGES_SUCCESS, payload: stages }], [], done, ); }); }); - describe('fetchJobs', () => { - let page = ''; - + describe('fetchStages', () => { beforeEach(() => { mockedState.latestPipeline = pipelines[0]; }); describe('success', () => { beforeEach(() => { - mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines\/(.*)\/jobs/).replyOnce(() => [ - 200, - jobs, - { - 'x-next-page': page, - }, - ]); + mock.onGet(/\/(.*)\/pipelines\/(.*)\/builds.json/).replyOnce(200, stages); }); it('dispatches request', done => { testAction( - fetchJobs, + fetchStages, null, mockedState, [], - [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess' }], + [{ type: 'requestStages' }, { type: 'receiveStagesSuccess' }], done, ); }); it('dispatches success with latest pipeline', done => { testAction( - fetchJobs, + fetchStages, null, mockedState, [], - [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess', payload: jobs }], - done, - ); - }); - - it('dispatches twice for both pages', done => { - page = '2'; - - testAction( - fetchJobs, - null, - mockedState, - [], - [ - { type: 'requestJobs' }, - { type: 'receiveJobsSuccess', payload: jobs }, - { type: 'fetchJobs', payload: '2' }, - { type: 'requestJobs' }, - { type: 'receiveJobsSuccess', payload: jobs }, - ], + [{ type: 'requestStages' }, { type: 'receiveStagesSuccess', payload: stages }], done, ); }); @@ -243,44 +216,27 @@ describe('IDE pipelines actions', () => { it('calls axios with correct URL', () => { const apiSpy = spyOn(axios, 'get').and.callThrough(); - fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); + fetchStages({ dispatch() {}, state: mockedState, rootState: mockedState }); - expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { - params: { page: '1' }, - }); - }); - - it('calls axios with page next page', () => { - const apiSpy = spyOn(axios, 'get').and.callThrough(); - - fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); - - expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { - params: { page: '1' }, - }); - - page = '2'; - - fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }, page); - - expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { - params: { page: '2' }, - }); + expect(apiSpy).toHaveBeenCalledWith( + '/test/project/pipelines/1/builds.json', + jasmine.anything(), + ); }); }); describe('error', () => { beforeEach(() => { - mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + mock.onGet(/\/(.*)\/pipelines\/(.*)\/builds.json/).replyOnce(500); }); it('dispatches error', done => { testAction( - fetchJobs, + fetchStages, null, mockedState, [], - [{ type: 'requestJobs' }, { type: 'receiveJobsError' }], + [{ type: 'requestStages' }, { type: 'receiveStagesError' }], done, ); }); diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js index b2a7e8a9025..4514896b5ea 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js @@ -37,35 +37,4 @@ describe('IDE pipeline getters', () => { expect(getters.hasLatestPipeline(mockedState)).toBe(true); }); }); - - describe('failedJobs', () => { - it('returns array of failed jobs', () => { - mockedState.stages = [ - { - title: 'test', - jobs: [{ id: 1, status: 'failed' }, { id: 2, status: 'success' }], - }, - { - title: 'build', - jobs: [{ id: 3, status: 'failed' }, { id: 4, status: 'failed' }], - }, - ]; - - expect(getters.failedJobs(mockedState).length).toBe(3); - expect(getters.failedJobs(mockedState)).toEqual([ - { - id: 1, - status: jasmine.anything(), - }, - { - id: 3, - status: jasmine.anything(), - }, - { - id: 4, - status: jasmine.anything(), - }, - ]); - }); - }); }); diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js index 8262e916243..d47ec33ad4d 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js @@ -1,7 +1,7 @@ import mutations from '~/ide/stores/modules/pipelines/mutations'; import state from '~/ide/stores/modules/pipelines/state'; import * as types from '~/ide/stores/modules/pipelines/mutation_types'; -import { pipelines, jobs } from '../../../mock_data'; +import { pipelines, stages } from '../../../mock_data'; describe('IDE pipelines mutations', () => { let mockedState; @@ -49,70 +49,47 @@ describe('IDE pipelines mutations', () => { }); }); - describe(types.REQUEST_JOBS, () => { - it('sets jobs loading to true', () => { - mutations[types.REQUEST_JOBS](mockedState); + describe(types.REQUEST_STAGES, () => { + it('sets stages loading to true', () => { + mutations[types.REQUEST_STAGES](mockedState); expect(mockedState.isLoadingJobs).toBe(true); }); }); - describe(types.RECEIVE_JOBS_ERROR, () => { + describe(types.RECEIVE_STAGES_ERROR, () => { it('sets jobs loading to false', () => { - mutations[types.RECEIVE_JOBS_ERROR](mockedState); + mutations[types.RECEIVE_STAGES_ERROR](mockedState); expect(mockedState.isLoadingJobs).toBe(false); }); }); - describe(types.RECEIVE_JOBS_SUCCESS, () => { + describe(types.RECEIVE_STAGES_SUCCESS, () => { it('sets jobs loading to false on success', () => { - mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + mutations[types.RECEIVE_STAGES_SUCCESS](mockedState, stages); expect(mockedState.isLoadingJobs).toBe(false); }); it('sets stages', () => { - mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + mutations[types.RECEIVE_STAGES_SUCCESS](mockedState, stages); expect(mockedState.stages.length).toBe(2); expect(mockedState.stages).toEqual([ { - title: 'test', - jobs: jasmine.anything(), + ...stages[0], + id: 0, + isCollapsed: false, + isLoading: false, + jobs: [], }, { - title: 'build', - jobs: jasmine.anything(), - }, - ]); - }); - - it('sets jobs in stages', () => { - mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); - - expect(mockedState.stages[0].jobs.length).toBe(3); - expect(mockedState.stages[1].jobs.length).toBe(1); - expect(mockedState.stages).toEqual([ - { - title: jasmine.anything(), - jobs: jobs.filter(job => job.stage === 'test').map(job => ({ - id: job.id, - name: job.name, - status: job.status, - stage: job.stage, - duration: job.duration, - })), - }, - { - title: jasmine.anything(), - jobs: jobs.filter(job => job.stage === 'build').map(job => ({ - id: job.id, - name: job.name, - status: job.status, - stage: job.stage, - duration: job.duration, - })), + ...stages[1], + id: 1, + isCollapsed: false, + isLoading: false, + jobs: [], }, ]); }); From 9c464e5eae6e4eb268bbdae6ad542d1de698b623 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 25 May 2018 13:26:44 +0100 Subject: [PATCH 09/29] removed hacky $forceUpdate --- app/assets/javascripts/vue_shared/components/tabs/tab.vue | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/assets/javascripts/vue_shared/components/tabs/tab.vue b/app/assets/javascripts/vue_shared/components/tabs/tab.vue index 9b2f46186ac..2a35d6bc151 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tab.vue +++ b/app/assets/javascripts/vue_shared/components/tabs/tab.vue @@ -26,11 +26,6 @@ export default { created() { this.isTab = true; }, - updated() { - if (this.$parent) { - this.$parent.$forceUpdate(); - } - }, }; From a83dd6642104faf4c8764283f5b7252a8ecd9590 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 25 May 2018 14:13:59 +0100 Subject: [PATCH 10/29] refactored to use data we already have this required moving some data store actions & mutations around --- app/assets/javascripts/api.js | 16 ----- .../ide/components/ide_status_bar.vue | 15 +++-- .../javascripts/ide/components/jobs/stage.vue | 5 +- .../ide/components/pipelines/list.vue | 19 ++---- .../javascripts/ide/stores/actions/project.js | 62 ----------------- .../ide/stores/modules/pipelines/actions.js | 66 +++++++++++-------- .../modules/pipelines/mutation_types.js | 4 -- .../ide/stores/modules/pipelines/mutations.js | 35 ++++------ .../javascripts/ide/stores/mutation_types.js | 1 - .../ide/stores/mutations/branch.js | 9 --- .../projects/pipelines_controller.rb | 11 +--- 11 files changed, 69 insertions(+), 174 deletions(-) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index dbdc4de7986..47bf55b1c23 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -24,8 +24,6 @@ const Api = { commitPipelinesPath: '/:project_id/commit/:sha/pipelines', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', createBranchPath: '/api/:version/projects/:id/repository/branches', - pipelinesPath: '/api/:version/projects/:id/pipelines', - pipelineJobsPath: '/:project_path/pipelines/:id/builds.json', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); @@ -238,20 +236,6 @@ const Api = { }); }, - pipelines(projectPath, params = {}) { - const url = Api.buildUrl(this.pipelinesPath).replace(':id', encodeURIComponent(projectPath)); - - return axios.get(url, { params }); - }, - - pipelineJobs(projectPath, pipelineId, params = {}) { - const url = Api.buildUrl(this.pipelineJobsPath) - .replace(':project_path', projectPath) - .replace(':id', pipelineId); - - return axios.get(url, { params }); - }, - buildUrl(url) { let urlRoot = ''; if (gon.relative_url_root != null) { diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 6f60cfbf184..368a2995ed9 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -31,6 +31,7 @@ export default { computed: { ...mapState(['currentBranchId', 'currentProjectId']), ...mapGetters(['currentProject', 'lastCommit']), + ...mapState('pipelines', ['latestPipeline']), }, watch: { lastCommit() { @@ -51,14 +52,14 @@ export default { } }, methods: { - ...mapActions(['pipelinePoll', 'stopPipelinePolling']), + ...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']), startTimer() { this.intervalId = setInterval(() => { this.commitAgeUpdate(); }, 1000); }, initPipelinePolling() { - this.pipelinePoll(); + this.fetchLatestPipeline(); this.isPollingInitialized = true; }, commitAgeUpdate() { @@ -81,18 +82,18 @@ export default { > Pipeline #{{ lastCommit.pipeline.id }} - {{ lastCommit.pipeline.details.status.text }} + :href="latestPipeline.details.status.details_path">#{{ latestPipeline.id }} + {{ latestPipeline.details.status.text }} for diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index 7f1a0ed1218..342d5f2a859 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -73,7 +73,10 @@ export default { > {{ stage.name }} -
+
{{ jobsCount }} diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 8eed902d4e2..124585cd331 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -15,21 +15,14 @@ export default { JobsList, }, computed: { - ...mapGetters(['currentProject']), ...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages']), ...mapState('pipelines', ['isLoadingPipeline', 'latestPipeline', 'stages', 'isLoadingJobs']), - statusIcon() { - return { - group: this.latestPipeline.status, - icon: `status_${this.latestPipeline.status}`, - }; - }, }, created() { - return this.fetchLatestPipeline().then(() => this.fetchStages()); + this.fetchLatestPipeline(); }, methods: { - ...mapActions('pipelines', ['fetchLatestPipeline', 'fetchStages']), + ...mapActions('pipelines', ['fetchLatestPipeline']), }, }; @@ -46,7 +39,7 @@ export default { class="ide-tree-header ide-pipeline-header" > @@ -54,7 +47,7 @@ export default { Pipeline #{{ latestPipeline.id }} @@ -66,7 +59,7 @@ export default { - - diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 138807492eb..9f2fe1cb95c 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -140,31 +140,3 @@ export default {
- - diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 1affe6ff671..2845063e3c5 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -1148,3 +1148,99 @@ } } } + +.ide-right-sidebar { + width: auto; + min-width: 60px; + + .ide-activity-bar { + border-left: 1px solid $white-dark; + } + + .multi-file-commit-panel-inner { + width: 350px; + padding: $grid-size $gl-padding; + background-color: $white-light; + border-left: 1px solid $white-dark; + } +} + +.ide-pipeline { + display: flex; + flex-direction: column; + height: 100%; + + .empty-state { + margin-top: auto; + margin-bottom: auto; + + p { + margin: $grid-size 0; + text-align: center; + line-height: 24px; + } + + .btn, + h4 { + margin: 0; + } + } +} + +.ide-pipeline-list { + flex: 1; + overflow: auto; +} + +.ide-pipeline-header { + min-height: 50px; + padding-left: $gl-padding; + padding-right: $gl-padding; + + .ci-status-icon { + display: flex; + } +} + +.ide-job-item { + display: flex; + padding: 16px; + + &:not(:last-child) { + border-bottom: 1px solid $border-color; + } + + .ci-status-icon { + display: flex; + justify-content: center; + height: 20px; + margin-top: -2px; + overflow: hidden; + } +} + +.ide-stage { + .card-header { + display: flex; + cursor: pointer; + + .ci-status-icon { + display: flex; + align-items: center; + } + } + + .card-body { + padding: 0; + } +} + +.ide-stage-collapse-icon { + margin: auto 0 auto auto; +} + +.ide-stage-title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} From e6e9706e5308b366ce20ff592d0685fa02e4b404 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 30 May 2018 09:40:04 +0100 Subject: [PATCH 25/29] fixed failing specs fixed merge requests not loading --- app/assets/javascripts/ide/ide_router.js | 2 +- spec/javascripts/ide/components/pipelines/list_spec.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index a21cec4e8d8..b52618f4fde 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -63,7 +63,7 @@ router.beforeEach((to, from, next) => { .then(() => { const fullProjectId = `${to.params.namespace}/${to.params.project}`; - const baseSplit = to.params[0].split('/-/'); + const baseSplit = (to.params[0] && to.params[0].split('/-/')) || ['']; const branchId = baseSplit[0].slice(-1) === '/' ? baseSplit[0].slice(0, -1) : baseSplit[0]; if (branchId) { diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js index 81fd00c7153..2bb5aa08c3b 100644 --- a/spec/javascripts/ide/components/pipelines/list_spec.js +++ b/spec/javascripts/ide/components/pipelines/list_spec.js @@ -46,10 +46,11 @@ describe('IDE pipelines list', () => { }); afterEach(() => { + vm.$store.dispatch('pipelines/stopPipelinePolling'); + vm.$store.dispatch('pipelines/clearEtagPoll'); + vm.$destroy(); mock.restore(); - vm.$store.dispatch('stopPipelinePolling'); - vm.$store.dispatch('clearEtagPoll'); }); it('renders pipeline data', () => { From c9d676c1069e6e88b4eeeea2246cc5706b56f2ab Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 30 May 2018 12:56:06 +0100 Subject: [PATCH 26/29] changed mutations, update single object instead of returning new array bunch of i18n stuff :see_no_evil: --- .../ide/components/pipelines/list.vue | 16 +++++--- .../ide/stores/modules/pipelines/constants.js | 4 ++ .../ide/stores/modules/pipelines/getters.js | 12 ++++-- .../ide/stores/modules/pipelines/mutations.js | 38 +++++-------------- .../ide/stores/modules/pipelines/utils.js | 7 ++++ .../ide/components/jobs/stage_spec.js | 22 ++++++----- 6 files changed, 52 insertions(+), 47 deletions(-) create mode 100644 app/assets/javascripts/ide/stores/modules/pipelines/constants.js create mode 100644 app/assets/javascripts/ide/stores/modules/pipelines/utils.js diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 9f2fe1cb95c..06455fac439 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -1,5 +1,6 @@ @@ -32,6 +36,8 @@ export default { v-for="stage in stages" :key="stage.id" :stage="stage" + @fetch="fetchJobs" + @toggleCollapsed="toggleStageCollapsed" />
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index 370bb61bae8..5b24bb1f5a7 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -1,5 +1,4 @@ @@ -61,7 +61,7 @@ export default { :class="{ 'border-bottom-0': stage.isCollapsed }" - @click="toggleStageCollapsed(stage.id)" + @click="toggleCollapsed" > s.id === id); - stage.isLoading = true; + state.stages = state.stages.map(stage => ({ + ...stage, + isLoading: stage.id === id ? true : stage.isLoading, + })); }, [types.RECEIVE_JOBS_ERROR](state, id) { - const stage = state.stages.find(s => s.id === id); - stage.isLoading = false; + state.stages = state.stages.map(stage => ({ + ...stage, + isLoading: stage.id === id ? false : stage.isLoading, + })); }, [types.RECEIVE_JOBS_SUCCESS](state, { id, data }) { - const stage = state.stages.find(s => s.id === id); - stage.isLoading = false; - stage.jobs = data.latest_statuses.map(normalizeJob); + state.stages = state.stages.map(stage => ({ + ...stage, + isLoading: stage.id === id ? false : stage.isLoading, + jobs: data.latest_statuses.map(normalizeJob), + })); }, [types.TOGGLE_STAGE_COLLAPSE](state, id) { - const stage = state.stages.find(s => s.id === id); - stage.isCollapsed = !stage.isCollapsed; + state.stages = state.stages.map(stage => ({ + ...stage, + isCollapsed: stage.id === id ? !stage.isCollapsed : stage.isCollapsed, + })); }, }; diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js index 484cab3c135..b24853c56fa 100644 --- a/spec/javascripts/ide/components/jobs/list_spec.js +++ b/spec/javascripts/ide/components/jobs/list_spec.js @@ -21,7 +21,12 @@ describe('IDE stages list', () => { isCollapsed: false, })), loading: false, - }).$mount(); + }); + + spyOn(vm, 'fetchJobs'); + spyOn(vm, 'toggleStageCollapsed'); + + vm.$mount(); }); afterEach(() => { @@ -42,4 +47,21 @@ describe('IDE stages list', () => { done(); }); }); + + it('calls toggleStageCollapsed when clicking stage header', done => { + vm.$el.querySelector('.card-header').click(); + + vm.$nextTick(() => { + expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0); + + done(); + }); + }); + + it('calls fetchJobs when stage is mounted', () => { + expect(vm.fetchJobs.calls.count()).toBe(stages.length); + + expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]); + expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]); + }); }); diff --git a/spec/javascripts/ide/components/jobs/stage_spec.js b/spec/javascripts/ide/components/jobs/stage_spec.js index 5496c57bbb7..fc3831f2d05 100644 --- a/spec/javascripts/ide/components/jobs/stage_spec.js +++ b/spec/javascripts/ide/components/jobs/stage_spec.js @@ -1,51 +1,37 @@ import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import { createStore } from '~/ide/stores'; import Stage from '~/ide/components/jobs/stage.vue'; -import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; import { stages, jobs } from '../../mock_data'; describe('IDE pipeline stage', () => { const Component = Vue.extend(Stage); let vm; - let mock; let stage; - beforeEach(done => { - const store = createStore(); - mock = new MockAdapter(axios); + beforeEach(() => { + stage = { + ...stages[0], + id: 0, + dropdownPath: stages[0].dropdown_path, + jobs: [...jobs], + isLoading: false, + isCollapsed: false, + }; - Vue.set( - store.state.pipelines, - 'stages', - stages.map((mappedState, i) => ({ - ...mappedState, - id: i, - dropdownPath: mappedState.dropdown_path, - jobs: [], - isLoading: false, - isCollapsed: false, - })), - ); - - stage = store.state.pipelines.stages[0]; - - mock.onGet(stage.dropdownPath).reply(200, { - latest_statuses: jobs, + vm = new Component({ + propsData: { stage }, }); - vm = createComponentWithStore(Component, store, { - stage, - }).$mount(); + spyOn(vm, '$emit'); - setTimeout(done, 500); + vm.$mount(); }); afterEach(() => { vm.$destroy(); + }); - mock.restore(); + it('emits fetch event when mounted', () => { + expect(vm.$emit).toHaveBeenCalledWith('fetch', vm.stage); }); it('renders stages details', () => { @@ -57,9 +43,19 @@ describe('IDE pipeline stage', () => { }); describe('collapsed', () => { - it('toggles collapse status when clicking header', done => { + it('emits event when clicking header', done => { vm.$el.querySelector('.card-header').click(); + vm.$nextTick(() => { + expect(vm.$emit).toHaveBeenCalledWith('toggleCollapsed', vm.stage.id); + + done(); + }); + }); + + it('toggles collapse status when collapsed', done => { + vm.stage.isCollapsed = true; + vm.$nextTick(() => { expect(vm.$el.querySelector('.card-body').style.display).toBe('none'); @@ -68,7 +64,7 @@ describe('IDE pipeline stage', () => { }); it('sets border bottom class when collapsed', done => { - vm.$el.querySelector('.card-header').click(); + vm.stage.isCollapsed = true; vm.$nextTick(() => { expect(vm.$el.querySelector('.card-header').classList).toContain('border-bottom-0'); From 9797905bd422a540954949d0bb2995b60d01a219 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 30 May 2018 14:04:43 +0100 Subject: [PATCH 28/29] fixed mutations spec --- .../ide/stores/modules/pipelines/mutations_spec.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js index 1bd7bde7c4d..6285c01d483 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js @@ -162,15 +162,13 @@ describe('IDE pipelines mutations', () => { }); it('toggles collapsed state', () => { - const stage = mockedState.stages[0]; + mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, mockedState.stages[0].id); - mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, stage.id); + expect(mockedState.stages[0].isCollapsed).toBe(true); - expect(stage.isCollapsed).toBe(true); + mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, mockedState.stages[0].id); - mutations[types.TOGGLE_STAGE_COLLAPSE](mockedState, stage.id); - - expect(stage.isCollapsed).toBe(false); + expect(mockedState.stages[0].isCollapsed).toBe(false); }); }); }); From 17c9e88c30aa3cc8541cedcf3060e4276ca4d427 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 30 May 2018 14:55:51 +0100 Subject: [PATCH 29/29] fixed jobs getting overwritten incorrectly --- .../javascripts/ide/stores/modules/pipelines/mutations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js index 3c50279642b..745797e1ee5 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -54,7 +54,7 @@ export default { state.stages = state.stages.map(stage => ({ ...stage, isLoading: stage.id === id ? false : stage.isLoading, - jobs: data.latest_statuses.map(normalizeJob), + jobs: stage.id === id ? data.latest_statuses.map(normalizeJob) : stage.jobs, })); }, [types.TOGGLE_STAGE_COLLAPSE](state, id) {