diff options
-rw-r--r-- | app/assets/javascripts/jobs/components/job_log_controllers.vue | 139 | ||||
-rw-r--r-- | changelogs/unreleased/50101-truncated-job-information.yml | 5 | ||||
-rw-r--r-- | locale/gitlab.pot | 18 | ||||
-rw-r--r-- | spec/javascripts/jobs/components/job_log_controllers_spec.js | 217 |
4 files changed, 379 insertions, 0 deletions
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue new file mode 100644 index 00000000000..513851e376f --- /dev/null +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -0,0 +1,139 @@ +<script> + import Icon from '~/vue_shared/components/icon.vue'; + import tooltip from '~/vue_shared/directives/tooltip'; + import { numberToHumanSize } from '~/lib/utils/number_utils'; + import { s__, sprintf } from '~/locale'; + + export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + canEraseJob: { + type: Boolean, + required: true, + }, + size: { + type: Number, + required: true, + }, + rawTracePath: { + type: String, + required: false, + default: null, + }, + canScrollToTop: { + type: Boolean, + required: true, + }, + canScrollToBottom: { + type: Boolean, + required: true, + }, + }, + computed: { + jobLogSize() { + return sprintf('Showing last %{startSpanTag} %{size} %{endSpanTag} of log -', { + startSpanTag: '<span class="s-truncated-info-size truncated-info-size">', + endSpanTag: '</span>', + size: numberToHumanSize(this.size), + }); + }, + }, + methods: { + handleEraseJobClick() { + // eslint-disable-next-line no-alert + if (window.confirm(s__('Job|Are you sure you want to erase this job?'))) { + this.$emit('eraseJob'); + } + }, + handleScrollToTop() { + this.$emit('scrollJobLogTop'); + }, + handleScrollToBottom() { + this.$emit('scrollJobLogBottom'); + }, + }, + }; +</script> +<template> + <div class="top-bar"> + <!-- truncate information --> + <div class="js-truncated-info truncated-info d-none d-sm-block float-left"> + <p v-html="jobLogSize"></p> + + <a + v-if="rawTracePath" + :href="rawTracePath" + class="js-raw-link raw-link" + > + {{ s__("Job|Complete Raw") }} + </a> + </div> + <!-- eo truncate information --> + + <div class="controllers float-right"> + <!-- links --> + <a + v-tooltip + v-if="rawTracePath" + :title="s__('Job|Show complete raw')" + :href="rawTracePath" + class="js-raw-link-controller controllers-buttons" + data-container="body" + > + <icon name="doc-text" /> + </a> + + <button + v-tooltip + v-if="canEraseJob" + :title="s__('Job|Erase job log')" + type="button" + class="js-erase-link controllers-buttons" + data-container="body" + @click="handleEraseJobClick" + > + <icon name="remove" /> + </button> + <!-- eo links --> + + <!-- scroll buttons --> + <div + v-tooltip + :title="s__('Job|Scroll to top')" + class="controllers-buttons" + data-container="body" + > + <button + :disabled="!canScrollToTop" + type="button" + class="js-scroll-top btn-scroll btn-transparent btn-blank" + @click="handleScrollToTop" + > + <icon name="scroll_up"/> + </button> + </div> + + <div + v-tooltip + :title="s__('Job|Scroll to bottom')" + class="controllers-buttons" + data-container="body" + > + <button + :disabled="!canScrollToBottom" + type="button" + class="js-scroll-bottom btn-scroll btn-transparent btn-blank" + @click="handleScrollToBottom" + > + <icon name="scroll_down"/> + </button> + </div> + <!-- eo scroll buttons --> + </div> + </div> +</template> diff --git a/changelogs/unreleased/50101-truncated-job-information.yml b/changelogs/unreleased/50101-truncated-job-information.yml new file mode 100644 index 00000000000..b873b8b7bf6 --- /dev/null +++ b/changelogs/unreleased/50101-truncated-job-information.yml @@ -0,0 +1,5 @@ +--- +title: Creates vue component for job log top bar with controllers +merge_request: +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index b01a0068694..b370cc13f11 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3139,12 +3139,21 @@ msgstr "" msgid "Jobs" msgstr "" +msgid "Job|Are you sure you want to erase this job?" +msgstr "" + msgid "Job|Browse" msgstr "" +msgid "Job|Complete Raw" +msgstr "" + msgid "Job|Download" msgstr "" +msgid "Job|Erase job log" +msgstr "" + msgid "Job|Job artifacts" msgstr "" @@ -3157,6 +3166,15 @@ msgstr "" msgid "Job|Keep" msgstr "" +msgid "Job|Scroll to bottom" +msgstr "" + +msgid "Job|Scroll to top" +msgstr "" + +msgid "Job|Show complete raw" +msgstr "" + msgid "Job|The artifacts were removed" msgstr "" diff --git a/spec/javascripts/jobs/components/job_log_controllers_spec.js b/spec/javascripts/jobs/components/job_log_controllers_spec.js new file mode 100644 index 00000000000..416dfab8a48 --- /dev/null +++ b/spec/javascripts/jobs/components/job_log_controllers_spec.js @@ -0,0 +1,217 @@ +import Vue from 'vue'; +import component from '~/jobs/components/job_log_controllers.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Job log controllers', () => { + const Component = Vue.extend(component); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe('Truncate information', () => { + + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders size information', () => { + expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB'); + }); + + it('renders link to raw trace', () => { + expect(vm.$el.querySelector('.js-raw-link').getAttribute('href')).toEqual('/raw'); + }); + + }); + + describe('links section', () => { + describe('with raw trace path', () => { + it('renders raw trace link', () => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + + expect(vm.$el.querySelector('.js-raw-link-controller').getAttribute('href')).toEqual('/raw'); + }); + }); + + describe('without raw trace path', () => { + it('does not render raw trace link', () => { + vm = mountComponent(Component, { + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + + expect(vm.$el.querySelector('.js-raw-link-controller')).toBeNull(); + }); + }); + + describe('when is erasable', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders erase job button', () => { + expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); + }); + + describe('on click', () => { + describe('when user confirms action', () => { + it('emits eraseJob event', () => { + spyOn(window, 'confirm').and.returnValue(true); + spyOn(vm, '$emit'); + + vm.$el.querySelector('.js-erase-link').click(); + + expect(vm.$emit).toHaveBeenCalledWith('eraseJob'); + }); + }); + + describe('when user does not confirm action', () => { + it('does not emit eraseJob event', () => { + spyOn(window, 'confirm').and.returnValue(false); + spyOn(vm, '$emit'); + + vm.$el.querySelector('.js-erase-link').click(); + + expect(vm.$emit).not.toHaveBeenCalledWith('eraseJob'); + }); + }); + }); + }); + + describe('when it is not erasable', () => { + it('does not render erase button', () => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: false, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + + expect(vm.$el.querySelector('.js-erase-link')).toBeNull(); + }); + }); + }); + + describe('scroll buttons', () => { + describe('scroll top button', () => { + describe('when user can scroll top', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders enabled scroll top button', () => { + expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toBeNull(); + }); + + it('emits scrollJobLogTop event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-top').click(); + + expect(vm.$emit).toHaveBeenCalledWith('scrollJobLogTop'); + }); + }); + + describe('when user can not scroll top', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: false, + canScrollToBottom: true, + }); + }); + + it('renders disabled scroll top button', () => { + expect(vm.$el.querySelector('.js-scroll-top').getAttribute('disabled')).toEqual('disabled'); + }); + + it('does not emit scrollJobLogTop event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-top').click(); + + expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogTop'); + }); + }); + }); + + describe('scroll bottom button', () => { + describe('when user can scroll bottom', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: true, + }); + }); + + it('renders enabled scroll bottom button', () => { + expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toBeNull(); + }); + + it('emits scrollJobLogBottom event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-bottom').click(); + + expect(vm.$emit).toHaveBeenCalledWith('scrollJobLogBottom'); + }); + }); + + describe('when user can not scroll bottom', () => { + beforeEach(() => { + vm = mountComponent(Component, { + rawTracePath: '/raw', + canEraseJob: true, + size: 511952, + canScrollToTop: true, + canScrollToBottom: false, + }); + }); + + it('renders disabled scroll bottom button', () => { + expect(vm.$el.querySelector('.js-scroll-bottom').getAttribute('disabled')).toEqual('disabled'); + + }); + + it('does not emit scrollJobLogBottom event on click', () => { + spyOn(vm, '$emit'); + vm.$el.querySelector('.js-scroll-bottom').click(); + + expect(vm.$emit).not.toHaveBeenCalledWith('scrollJobLogBottom'); + }); + }); + }); + }); +}); + |