diff options
11 files changed, 163 insertions, 57 deletions
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 @@ +<script> +import Stage from './stage.vue'; + +export default { + components: { + Stage, + }, + props: { + stages: { + type: Array, + required: true, + }, + }, +}; +</script> + +<template> + <div style="overflow: auto;"> + <stage + v-for="stage in stages" + :key="stage.id" + :stage="stage" + /> + </div> +</template> 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 @@ +<script> +import { mapActions } from 'vuex'; +import Icon from '../../../vue_shared/components/icon.vue'; +import CiIcon from '../../../vue_shared/components/ci_icon.vue'; +import LoadingIcon from '../../../vue_shared/components/loading_icon.vue'; + +export default { + components: { + Icon, + CiIcon, + LoadingIcon, + }, + props: { + stage: { + type: Object, + required: true, + }, + }, + computed: { + collapseIcon() { + return this.stage.isCollapsed ? 'angle-left' : 'angle-down'; + }, + }, + created() { + this.fetchJobs(this.stage); + }, + methods: { + ...mapActions('pipelines', ['fetchJobs']), + }, +}; +</script> + +<template> + <div + class="panel panel-default prepend-top-default" + > + <div + class="panel-heading" + @click="() => stage.isCollapsed = !stage.isCollapsed" + > + <ci-icon + :status="stage.status" + /> + <span class="prepend-left-8"> + {{ stage.title }} + </span> + <div> + <span class="badge"> + {{ stage.jobs.length }} + </span> + </div> + <icon + :name="collapseIcon" + css-classes="pull-right" + /> + </div> + <div + class="panel-body" + v-show="!stage.isCollapsed" + > + <loading-icon + v-if="stage.isLoading && !stage.jobs.length" + /> + <template v-else> + <div + v-for="job in stage.jobs" + :key="job.id" + > + <ci-icon :status="job.status" /> + {{ job.name }} + <a + :href="job.build_path" + target="_blank" + >#{{ job.id }}</a> + </div> + </template> + </div> + </div> +</template> + +<style scoped> +.panel-heading { + display: flex; + cursor: pointer; +} +.panel-heading .ci-status-icon { + display: flex; + align-items: center; +} + +.panel-heading .pull-right { + margin: auto 0 auto auto; +} +</style> 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" > - <keep-alive> - <component :is="rightPane" /> - </keep-alive> + <component :is="rightPane" /> </div> <nav class="ide-activity-bar"> <ul class="list-unstyled"> - <li v-once> + <li> <button v-tooltip data-container="body" data-placement="left" :title="__('Pipelines')" - class="ide-sidebar-link" + class="ide-sidebar-link is-right" + :class="{ + active: rightPane === $options.rightSidebarViews.pipelines + }" type="button" @click="setRightPane($options.rightSidebarViews.pipelines)" > diff --git a/app/assets/javascripts/ide/components/pipelines/jobs.vue b/app/assets/javascripts/ide/components/pipelines/jobs.vue index a3a99ad457c..ffe8a9e1696 100644 --- a/app/assets/javascripts/ide/components/pipelines/jobs.vue +++ b/app/assets/javascripts/ide/components/pipelines/jobs.vue @@ -1,22 +1,20 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; -import Icon from '../../../vue_shared/components/icon.vue'; -import CiIcon from '../../../vue_shared/components/ci_icon.vue'; import Tabs from '../../../vue_shared/components/tabs/tabs'; import Tab from '../../../vue_shared/components/tabs/tab.vue'; +import JobsList from '../jobs/list.vue'; export default { components: { Tabs, Tab, - Icon, - CiIcon, + JobsList, }, computed: { - ...mapGetters('pipelines', ['jobsCount', 'failedJobsCount']), + ...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages']), ...mapState('pipelines', ['stages']), }, - mounted() { + created() { this.fetchStages(); }, methods: { @@ -32,46 +30,17 @@ export default { <template slot="title"> Jobs <span class="badge">{{ jobsCount }}</span> </template> - <div style="overflow: auto;"> - <div - v-for="stage in stages" - :key="stage.id" - class="panel panel-default" - > - <div - class="panel-heading" - @click="() => stage.isCollapsed = !stage.isCollapsed" - > - <ci-icon :status="stage.status" /> - {{ stage.title }} - <span class="badge"> - {{ stage.jobs.length }} - </span> - <icon - :name="stage.isCollapsed ? 'angle-left' : 'angle-down'" - css-classes="pull-right" - /> - </div> - <div - class="panel-body" - v-show="!stage.isCollapsed" - > - <div - v-for="job in stage.jobs" - :key="job.id" - > - <ci-icon :status="job.status" /> - {{ job.name }} #{{ job.id }} - </div> - </div> - </div> - </div> + <jobs-list + :stages="stages" + /> </tab> <tab> <template slot="title"> Failed Jobs <span class="badge">{{ failedJobsCount }}</span> </template> - List all failed jobs here + <jobs-list + :stages="failedStages" + /> </tab> </tabs> </div> diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index e76ea0b50af..74ff51c7fad 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -20,7 +20,7 @@ export default { }; }, }, - mounted() { + created() { this.fetchLatestPipeline(); }, methods: { @@ -32,7 +32,7 @@ export default { <template> <div> <loading-icon - v-if="isLoadingPipeline" + v-if="isLoadingPipeline && !latestPipeline" class="prepend-top-default" size="2" /> diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 994edd74aef..b5d78b381ea 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -35,7 +35,6 @@ export const fetchStages = ({ dispatch, state, rootState }) => { Api.pipelineJobs(rootState.currentProjectId, state.latestPipeline.id) .then(({ data }) => dispatch('receiveStagesSuccess', data)) - .then(() => state.stages.forEach(stage => dispatch('fetchJobs', stage))) .catch(() => dispatch('receiveStagesError')); }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js index 99b4554a96e..e4570717791 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js @@ -1,5 +1,7 @@ export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline; +export const failedStages = state => state.stages.filter(stage => stage.status.label === 'failed'); + export const failedJobsCount = state => state.stages.reduce( (acc, stage) => acc + stage.jobs.filter(j => j.status.label === 'failed').length, diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js index 7115f5e5386..0713aa4a3f5 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -27,13 +27,16 @@ export default { [types.RECEIVE_STAGES_SUCCESS](state, stages) { state.isLoadingJobs = false; - state.stages = stages.map((stage, i) => ({ - ...stage, - id: i, - isCollapsed: false, - isLoading: false, - jobs: [], - })); + state.stages = stages.map((stage, i) => { + const foundStage = state.stages.find(s => s.id === i); + return { + ...stage, + id: i, + isCollapsed: foundStage ? foundStage.isCollapsed : false, + isLoading: foundStage ? foundStage.isLoading : false, + jobs: foundStage ? foundStage.jobs : [], + }; + }); }, [types.REQUEST_JOBS](state, id) { state.stages = state.stages.reduce( diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js index 3dff37b1c84..ac804c0ab3d 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tabs.js +++ b/app/assets/javascripts/vue_shared/components/tabs/tabs.js @@ -32,7 +32,9 @@ export default { h( 'a', { - href: '#', + attrs: { + href: '#', + }, on: { click: () => this.setTab(i), }, diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 0bbd6eb27c1..8df2906f906 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -189,6 +189,10 @@ &.active { color: $color-700; box-shadow: inset 3px 0 $color-700; + + &.is-right { + box-shadow: inset -3px 0 $color-700; + } } } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 175d2779bb7..a6bba7eb9e5 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -903,6 +903,13 @@ width: 1px; background: $white-light; } + + &.is-right { + &::after { + right: auto; + left: -1px; + } + } } } |