Commit 18df46fd authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '5696-refactor-separate-component--log-buttons' into 'master'

Add new component for log control buttons

Closes #5696

See merge request gitlab-org/gitlab!20698
parents 2ba9ed88 d64ea312
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { GlDropdown, GlDropdownItem, GlFormGroup, GlButton, GlTooltipDirective } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
import { import { scrollDown } from '~/lib/utils/scroll_utils';
canScroll, import LogControlButtons from './log_control_buttons.vue';
isScrolledToTop,
isScrolledToBottom,
scrollDown,
scrollUp,
} from '~/lib/utils/scroll_utils';
import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
components: { components: {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlFormGroup, GlFormGroup,
GlButton, LogControlButtons,
Icon,
},
directives: {
GlTooltip: GlTooltipDirective,
}, },
props: { props: {
environmentId: { environmentId: {
...@@ -46,12 +36,6 @@ export default { ...@@ -46,12 +36,6 @@ export default {
default: '', default: '',
}, },
}, },
data() {
return {
scrollToTopEnabled: false,
scrollToBottomEnabled: false,
};
},
computed: { computed: {
...mapState('environmentLogs', ['environments', 'logs', 'pods']), ...mapState('environmentLogs', ['environments', 'logs', 'pods']),
...mapGetters('environmentLogs', ['trace']), ...mapGetters('environmentLogs', ['trace']),
...@@ -63,16 +47,12 @@ export default { ...@@ -63,16 +47,12 @@ export default {
trace(val) { trace(val) {
this.$nextTick(() => { this.$nextTick(() => {
if (val) { if (val) {
this.scrollDown(); scrollDown();
} else {
this.updateScrollState();
} }
this.$refs.scrollButtons.update();
}); });
}, },
}, },
created() {
window.addEventListener('scroll', this.updateScrollState);
},
mounted() { mounted() {
this.setInitData({ this.setInitData({
projectPath: this.projectFullPath, projectPath: this.projectFullPath,
...@@ -82,17 +62,8 @@ export default { ...@@ -82,17 +62,8 @@ export default {
this.fetchEnvironments(this.environmentsPath); this.fetchEnvironments(this.environmentsPath);
}, },
destroyed() {
window.removeEventListener('scroll', this.updateScrollState);
},
methods: { methods: {
...mapActions('environmentLogs', ['setInitData', 'showPodLogs', 'fetchEnvironments']), ...mapActions('environmentLogs', ['setInitData', 'showPodLogs', 'fetchEnvironments']),
updateScrollState() {
this.scrollToTopEnabled = canScroll() && !isScrolledToTop();
this.scrollToBottomEnabled = canScroll() && !isScrolledToBottom();
},
scrollUp,
scrollDown,
}, },
}; };
</script> </script>
...@@ -147,48 +118,12 @@ export default { ...@@ -147,48 +118,12 @@ export default {
</gl-dropdown> </gl-dropdown>
</gl-form-group> </gl-form-group>
</div> </div>
<div class="controllers align-self-end">
<div <log-control-buttons
v-gl-tooltip ref="scrollButtons"
class="controllers-buttons" class="controllers align-self-end"
:title="__('Scroll to top')" @refresh="showPodLogs(pods.current)"
aria-labelledby="scroll-to-top" />
>
<gl-button
id="scroll-to-top"
class="btn-blank js-scroll-to-top"
:aria-label="__('Scroll to top')"
:disabled="!scrollToTopEnabled"
@click="scrollUp()"
><icon name="scroll_up"
/></gl-button>
</div>
<div
v-gl-tooltip
class="controllers-buttons"
:title="__('Scroll to bottom')"
aria-labelledby="scroll-to-bottom"
>
<gl-button
id="scroll-to-bottom"
class="btn-blank js-scroll-to-bottom"
:aria-label="__('Scroll to bottom')"
:disabled="!scrollToBottomEnabled"
@click="scrollDown()"
><icon name="scroll_down"
/></gl-button>
</div>
<gl-button
id="refresh-log"
v-gl-tooltip
class="ml-1 px-2 js-refresh-log"
:title="__('Refresh')"
:aria-label="__('Refresh')"
@click="showPodLogs(pods.current)"
>
<icon name="retry" />
</gl-button>
</div>
</div> </div>
<pre class="build-trace js-log-trace"><code class="bash">{{trace}} <pre class="build-trace js-log-trace"><code class="bash">{{trace}}
<div v-if="showLoader" class="build-loader-animation js-build-loader-animation"> <div v-if="showLoader" class="build-loader-animation js-build-loader-animation">
......
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import {
canScroll,
isScrolledToTop,
isScrolledToBottom,
scrollDown,
scrollUp,
} from '~/lib/utils/scroll_utils';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
data() {
return {
scrollToTopEnabled: false,
scrollToBottomEnabled: false,
};
},
created() {
window.addEventListener('scroll', this.update);
},
destroyed() {
window.removeEventListener('scroll', this.update);
},
methods: {
/**
* Checks if page can be scrolled and updates
* enabled/disabled state of buttons accordingly
*/
update() {
this.scrollToTopEnabled = canScroll() && !isScrolledToTop();
this.scrollToBottomEnabled = canScroll() && !isScrolledToBottom();
},
handleRefreshClick() {
this.$emit('refresh');
},
scrollUp,
scrollDown,
},
};
</script>
<template>
<div>
<div
v-gl-tooltip
class="controllers-buttons"
:title="__('Scroll to top')"
aria-labelledby="scroll-to-top"
>
<gl-button
id="scroll-to-top"
class="btn-blank js-scroll-to-top"
:aria-label="__('Scroll to top')"
:disabled="!scrollToTopEnabled"
@click="scrollUp()"
><icon name="scroll_up"
/></gl-button>
</div>
<div
v-gl-tooltip
class="controllers-buttons"
:title="__('Scroll to bottom')"
aria-labelledby="scroll-to-bottom"
>
<gl-button
id="scroll-to-bottom"
class="btn-blank js-scroll-to-bottom"
:aria-label="__('Scroll to bottom')"
:disabled="!scrollToBottomEnabled"
@click="scrollDown()"
><icon name="scroll_down"
/></gl-button>
</div>
<gl-button
id="refresh-log"
v-gl-tooltip
class="ml-1 px-2 js-refresh-log"
:title="__('Refresh')"
:aria-label="__('Refresh')"
@click="handleRefreshClick"
>
<icon name="retry" />
</gl-button>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import { GlDropdown, GlButton, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { import { scrollDown } from '~/lib/utils/scroll_utils';
canScroll,
isScrolledToTop,
isScrolledToBottom,
scrollDown,
scrollUp,
} from '~/lib/utils/scroll_utils';
import EnvironmentLogs from 'ee/logs/components/environment_logs.vue'; import EnvironmentLogs from 'ee/logs/components/environment_logs.vue';
import { createStore } from 'ee/logs/stores'; import { createStore } from 'ee/logs/stores';
import { import {
mockProjectPath, mockProjectPath,
...@@ -30,8 +25,8 @@ describe('EnvironmentLogs', () => { ...@@ -30,8 +25,8 @@ describe('EnvironmentLogs', () => {
let state; let state;
const propsData = { const propsData = {
environmentId: mockEnvId,
projectFullPath: mockProjectPath, projectFullPath: mockProjectPath,
environmentId: mockEnvId,
currentEnvironmentName: mockEnvName, currentEnvironmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint, environmentsPath: mockEnvironmentsEndpoint,
}; };
...@@ -42,11 +37,11 @@ describe('EnvironmentLogs', () => { ...@@ -42,11 +37,11 @@ describe('EnvironmentLogs', () => {
fetchEnvironments: jest.fn(), fetchEnvironments: jest.fn(),
}; };
const updateControlBtnsMock = jest.fn();
const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown'); const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown');
const findPodsDropdown = () => wrapper.find('.js-pods-dropdown'); const findPodsDropdown = () => wrapper.find('.js-pods-dropdown');
const findScrollToTop = () => wrapper.find('.js-scroll-to-top'); const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' });
const findScrollToBottom = () => wrapper.find('.js-scroll-to-bottom');
const findRefreshLog = () => wrapper.find('.js-refresh-log');
const findLogTrace = () => wrapper.find('.js-log-trace'); const findLogTrace = () => wrapper.find('.js-log-trace');
const initWrapper = () => { const initWrapper = () => {
...@@ -55,6 +50,15 @@ describe('EnvironmentLogs', () => { ...@@ -55,6 +50,15 @@ describe('EnvironmentLogs', () => {
sync: false, sync: false,
propsData, propsData,
store, store,
stubs: {
LogControlButtons: {
name: 'log-control-buttons-stub',
template: '<div/>',
methods: {
update: updateControlBtnsMock,
},
},
},
methods: { methods: {
...actionMocks, ...actionMocks,
}, },
...@@ -78,15 +82,12 @@ describe('EnvironmentLogs', () => { ...@@ -78,15 +82,12 @@ describe('EnvironmentLogs', () => {
expect(wrapper.isVueInstance()).toBe(true); expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false); expect(wrapper.isEmpty()).toBe(false);
expect(findLogTrace().isEmpty()).toBe(false);
expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true); expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true);
expect(findPodsDropdown().is(GlDropdown)).toBe(true); expect(findPodsDropdown().is(GlDropdown)).toBe(true);
expect(findScrollToTop().is(GlButton)).toBe(true); expect(findLogControlButtons().exists()).toBe(true);
expect(findScrollToBottom().is(GlButton)).toBe(true);
expect(findRefreshLog().is(GlButton)).toBe(true);
expect(findLogTrace().isEmpty()).toBe(false);
}); });
it('mounted inits data', () => { it('mounted inits data', () => {
...@@ -129,6 +130,10 @@ describe('EnvironmentLogs', () => { ...@@ -129,6 +130,10 @@ describe('EnvironmentLogs', () => {
expect(findPodsDropdown().findAll(GlDropdownItem).length).toBe(0); expect(findPodsDropdown().findAll(GlDropdownItem).length).toBe(0);
}); });
it('does not update buttons state', () => {
expect(updateControlBtnsMock).not.toHaveBeenCalled();
});
it('shows a logs trace', () => { it('shows a logs trace', () => {
expect(findLogTrace().text()).toBe(''); expect(findLogTrace().text()).toBe('');
expect( expect(
...@@ -164,7 +169,7 @@ describe('EnvironmentLogs', () => { ...@@ -164,7 +169,7 @@ describe('EnvironmentLogs', () => {
afterEach(() => { afterEach(() => {
scrollDown.mockReset(); scrollDown.mockReset();
scrollUp.mockReset(); updateControlBtnsMock.mockReset();
actionMocks.setInitData.mockReset(); actionMocks.setInitData.mockReset();
actionMocks.showPodLogs.mockReset(); actionMocks.showPodLogs.mockReset();
...@@ -201,6 +206,10 @@ describe('EnvironmentLogs', () => { ...@@ -201,6 +206,10 @@ describe('EnvironmentLogs', () => {
expect(trace.text().split('\n')).toEqual(mockLines); expect(trace.text().split('\n')).toEqual(mockLines);
}); });
it('update control buttons state', () => {
expect(updateControlBtnsMock).toHaveBeenCalledTimes(1);
});
it('scrolls to bottom when loaded', () => { it('scrolls to bottom when loaded', () => {
expect(scrollDown).toHaveBeenCalledTimes(1); expect(scrollDown).toHaveBeenCalledTimes(1);
}); });
...@@ -221,62 +230,11 @@ describe('EnvironmentLogs', () => { ...@@ -221,62 +230,11 @@ describe('EnvironmentLogs', () => {
it('refresh button, trace is refreshed', () => { it('refresh button, trace is refreshed', () => {
expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0); expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
findRefreshLog().vm.$emit('click'); findLogControlButtons().vm.$emit('refresh');
expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1); expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPodName); expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPodName);
}); });
describe('when scrolling actions are enabled', () => {
beforeEach(done => {
// mock scrolled to the middle of a long page
canScroll.mockReturnValue(true);
isScrolledToBottom.mockReturnValue(false);
isScrolledToTop.mockReturnValue(false);
initWrapper();
wrapper.vm.updateScrollState();
wrapper.vm.$nextTick(done);
});
afterEach(() => {
canScroll.mockReset();
isScrolledToTop.mockReset();
isScrolledToBottom.mockReset();
});
it('click on "scroll to top" scrolls up', () => {
expect(findScrollToTop().is('[disabled]')).toBe(false);
findScrollToTop().vm.$emit('click');
expect(scrollUp).toHaveBeenCalledTimes(1);
});
it('click on "scroll to bottom" scrolls down', () => {
expect(findScrollToBottom().is('[disabled]')).toBe(false);
findScrollToBottom().vm.$emit('click');
expect(scrollDown).toHaveBeenCalledTimes(2); // plus one time when trace was loaded
});
});
describe('when scrolling actions are disabled', () => {
beforeEach(() => {
// mock a short page without a scrollbar
canScroll.mockReturnValue(false);
isScrolledToBottom.mockReturnValue(true);
isScrolledToTop.mockReturnValue(true);
initWrapper();
});
it('buttons are disabled', done => {
wrapper.vm.updateScrollState();
wrapper.vm.$nextTick(() => {
expect(findScrollToTop().is('[disabled]')).toBe(true);
expect(findScrollToBottom().is('[disabled]')).toBe(true);
done();
});
});
});
}); });
}); });
}); });
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import {
canScroll,
isScrolledToTop,
isScrolledToBottom,
scrollDown,
scrollUp,
} from '~/lib/utils/scroll_utils';
import LogControlButtons from 'ee/logs/components/log_control_buttons.vue';
jest.mock('~/lib/utils/scroll_utils');
describe('LogControlButtons', () => {
let wrapper;
const findScrollToTop = () => wrapper.find('.js-scroll-to-top');
const findScrollToBottom = () => wrapper.find('.js-scroll-to-bottom');
const findRefreshBtn = () => wrapper.find('.js-refresh-log');
const initWrapper = () => {
wrapper = shallowMount(LogControlButtons, {
attachToDocument: true,
sync: false,
});
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
it('displays UI elements', () => {
initWrapper();
expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false);
expect(findScrollToTop().is(GlButton)).toBe(true);
expect(findScrollToBottom().is(GlButton)).toBe(true);
expect(findRefreshBtn().is(GlButton)).toBe(true);
});
it('emits a `refresh` event on click on `refersh` button', () => {
initWrapper();
expect(wrapper.emitted('refresh')).toHaveLength(0);
findRefreshBtn().vm.$emit('click');
expect(wrapper.emitted('refresh')).toHaveLength(1);
});
describe('when scrolling actions are enabled', () => {
beforeEach(() => {
// mock scrolled to the middle of a long page
canScroll.mockReturnValue(true);
isScrolledToBottom.mockReturnValue(false);
isScrolledToTop.mockReturnValue(false);
initWrapper();
wrapper.vm.update();
return wrapper.vm.$nextTick();
});
afterEach(() => {
canScroll.mockReset();
isScrolledToTop.mockReset();
isScrolledToBottom.mockReset();
});
it('click on "scroll to top" scrolls up', () => {
expect(findScrollToTop().is('[disabled]')).toBe(false);
findScrollToTop().vm.$emit('click');
expect(scrollUp).toHaveBeenCalledTimes(1);
});
it('click on "scroll to bottom" scrolls down', () => {
expect(findScrollToBottom().is('[disabled]')).toBe(false);
findScrollToBottom().vm.$emit('click');
expect(scrollDown).toHaveBeenCalledTimes(1); // plus one time when trace was loaded
});
});
describe('when scrolling actions are disabled', () => {
beforeEach(() => {
// mock a short page without a scrollbar
canScroll.mockReturnValue(false);
isScrolledToBottom.mockReturnValue(true);
isScrolledToTop.mockReturnValue(true);
initWrapper();
});
it('buttons are disabled', () => {
wrapper.vm.update();
return wrapper.vm.$nextTick(() => {
expect(findScrollToTop().is('[disabled]')).toBe(true);
expect(findScrollToBottom().is('[disabled]')).toBe(true);
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment