diff options
-rw-r--r-- | app/assets/javascripts/jobs/components/jobs_container.vue | 60 | ||||
-rw-r--r-- | app/assets/javascripts/jobs/components/stages_dropdown.vue | 97 | ||||
-rw-r--r-- | changelogs/unreleased/50101-builds-dropdown.yml | 6 | ||||
-rw-r--r-- | locale/gitlab.pot | 3 | ||||
-rw-r--r-- | spec/javascripts/jobs/jobs_container_spec.js | 126 | ||||
-rw-r--r-- | spec/javascripts/jobs/stages_dropdown_spec.js | 63 |
6 files changed, 355 insertions, 0 deletions
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue new file mode 100644 index 00000000000..b81109bdd06 --- /dev/null +++ b/app/assets/javascripts/jobs/components/jobs_container.vue @@ -0,0 +1,60 @@ +<script> + import CiIcon from '~/vue_shared/components/ci_icon.vue'; + import Icon from '~/vue_shared/components/icon.vue'; + import tooltip from '~/vue_shared/directives/tooltip'; + + export default { + components: { + CiIcon, + Icon, + }, + directives: { + tooltip, + }, + props: { + jobs: { + type: Array, + required: true, + }, + }, + }; +</script> +<template> + <div class="builds-container"> + <div + class="build-job" + > + <a + v-tooltip + v-for="job in jobs" + :key="job.id" + :href="job.path" + :title="job.tooltip" + :class="{ active: job.active, retried: job.retried }" + > + <icon + v-if="job.active" + name="arrow-right" + class="js-arrow-right" + /> + + <ci-icon :status="job.status" /> + + <span> + <template v-if="job.name"> + {{ job.name }} + </template> + <template v-else> + {{ job.id }} + </template> + </span> + + <icon + v-if="job.retried" + name="retry" + class="js-retry-icon" + /> + </a> + </div> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue new file mode 100644 index 00000000000..d6d64fa32f7 --- /dev/null +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -0,0 +1,97 @@ +<script> + import CiIcon from '~/vue_shared/components/ci_icon.vue'; + import Icon from '~/vue_shared/components/icon.vue'; + + import { sprintf, __ } from '~/locale'; + + export default { + components: { + CiIcon, + Icon, + }, + props: { + pipelineId: { + type: Number, + required: true, + }, + pipelinePath: { + type: String, + required: true, + }, + pipelineRef: { + type: String, + required: true, + }, + pipelineRefPath: { + type: String, + required: true, + }, + stages: { + type: Array, + required: true, + }, + pipelineStatus: { + type: Object, + required: true, + }, + }, + data() { + return { + selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'), + }; + }, + computed: { + pipelineLink() { + return sprintf(__('Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}'), { + pipelineLinkStart: `<a href=${this.pipelinePath} class="js-pipeline-path link-commit">`, + pipelineId: this.pipelineId, + pipelineLinkEnd: '</a>', + pipelineLinkRefStart: `<a href=${this.pipelineRefPath} class="link-commit ref-name">`, + pipelineRef: this.pipelineRef, + pipelineLinkRefEnd: '</a>', + }, false); + }, + }, + methods: { + onStageClick(stage) { + // todo: consider moving into store + this.selectedStage = stage.name; + + // update dropdown with jobs + // jobs container is a new component. + this.$emit('requestSidebarStageDropdown', stage); + }, + }, + }; +</script> +<template> + <div class="block-last"> + <ci-icon :status="pipelineStatus" /> + + <p v-html="pipelineLink"></p> + + <div class="dropdown"> + <button + type="button" + data-toggle="dropdown" + > + {{ selectedStage }} + <icon name="chevron-down" /> + </button> + <ul class="dropdown-menu"> + <li + v-for="(stage, index) in stages" + :key="index" + > + <button + type="button" + class="stage-item" + @click="onStageClick(stage)" + > + {{ stage.name }} + </button> + </li> + </ul> + </div> + </div> +</template> diff --git a/changelogs/unreleased/50101-builds-dropdown.yml b/changelogs/unreleased/50101-builds-dropdown.yml new file mode 100644 index 00000000000..9194b0e0d31 --- /dev/null +++ b/changelogs/unreleased/50101-builds-dropdown.yml @@ -0,0 +1,6 @@ +--- +title: Creates vue components for stage dropdowns and job list container for job log + view +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1f61ac872f8..73bff79aabe 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4110,6 +4110,9 @@ msgstr "" msgid "Pipeline" msgstr "" +msgid "Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}" +msgstr "" + msgid "Pipeline Health" msgstr "" diff --git a/spec/javascripts/jobs/jobs_container_spec.js b/spec/javascripts/jobs/jobs_container_spec.js new file mode 100644 index 00000000000..bf52e65cbc8 --- /dev/null +++ b/spec/javascripts/jobs/jobs_container_spec.js @@ -0,0 +1,126 @@ +import Vue from 'vue'; +import component from '~/jobs/components/jobs_container.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Artifacts block', () => { + const Component = Vue.extend(component); + let vm; + + const retried = { + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + path: 'job/233432756', + id: '233432756', + tooltip: 'build - passed', + retried: true, + }; + + const active = { + name: 'test', + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + path: 'job/2322756', + id: '2322756', + tooltip: 'build - passed', + active: true, + }; + + const job = { + name: 'build', + status: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + path: 'job/232153', + id: '232153', + tooltip: 'build - passed', + }; + + afterEach(() => { + vm.$destroy(); + }); + + it('renders list of jobs', () => { + vm = mountComponent(Component, { + jobs: [job, retried, active], + }); + + expect(vm.$el.querySelectorAll('a').length).toEqual(3); + }); + + it('renders arrow right when job is active', () => { + vm = mountComponent(Component, { + jobs: [active], + }); + + expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull(); + }); + + it('does not render arrow right when job is not active', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull(); + }); + + it('renders job name when present', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name); + expect(vm.$el.querySelector('a').textContent.trim()).not.toContain(job.id); + }); + + it('renders job id when job name is not available', () => { + vm = mountComponent(Component, { + jobs: [retried], + }); + + expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id); + }); + + it('links to the job page', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.path); + }); + + it('renders retry icon when job was retried', () => { + vm = mountComponent(Component, { + jobs: [retried], + }); + + expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull(); + }); + + it('does not render retry icon when job was not retried', () => { + vm = mountComponent(Component, { + jobs: [job], + }); + + expect(vm.$el.querySelector('.js-retry-icon')).toBeNull(); + }); +}); diff --git a/spec/javascripts/jobs/stages_dropdown_spec.js b/spec/javascripts/jobs/stages_dropdown_spec.js new file mode 100644 index 00000000000..d3a5d48f56c --- /dev/null +++ b/spec/javascripts/jobs/stages_dropdown_spec.js @@ -0,0 +1,63 @@ +import Vue from 'vue'; +import component from '~/jobs/components/stages_dropdown.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Artifacts block', () => { + const Component = Vue.extend(component); + let vm; + + beforeEach(() => { + vm = mountComponent(Component, { + pipelineId: 28029444, + pipelinePath: 'pipeline/28029444', + pipelineRef: '50101-truncated-job-information', + pipelineRefPath: 'commits/50101-truncated-job-information', + stages: [ + { + name: 'build', + }, + { + name: 'test', + }, + ], + pipelineStatus: { + details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', + group: 'success', + has_details: true, + icon: 'status_success', + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders pipeline status', () => { + expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull(); + }); + + it('renders pipeline link', () => { + expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual( + 'pipeline/28029444', + ); + }); + + it('renders dropdown with stages', () => { + expect(vm.$el.querySelector('.dropdown button').textContent).toContain('build'); + }); + + it('updates selected stage on click', done => { + vm.$el.querySelectorAll('.stage-item')[1].click(); + vm + .$nextTick() + .then(() => { + expect(vm.$el.querySelector('.dropdown button').textContent).toContain('test'); + }) + .then(done) + .catch(done.fail); + }); +}); |