Commit d6a9e35c authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '300261-gl-dropdown-in-pipeline_actions' into 'master'

Use gl-dropdown in pipeline manual actions

See merge request gitlab-org/gitlab!53223
parents 577dcd4a 30695b13
<script> <script>
import { GlTooltipDirective, GlButton, GlLoadingIcon, GlIcon } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from '~/flash'; import createFlash from '~/flash';
import { s__, __, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -11,10 +11,10 @@ export default { ...@@ -11,10 +11,10 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
components: { components: {
GlIcon,
GlCountdown, GlCountdown,
GlButton, GlDropdown,
GlLoadingIcon, GlDropdownItem,
GlIcon,
}, },
props: { props: {
actions: { actions: {
...@@ -61,7 +61,7 @@ export default { ...@@ -61,7 +61,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
flash(__('An error occurred while making the request.')); createFlash({ message: __('An error occurred while making the request.') });
}); });
}, },
...@@ -76,39 +76,27 @@ export default { ...@@ -76,39 +76,27 @@ export default {
}; };
</script> </script>
<template> <template>
<div class="btn-group"> <gl-dropdown
<button
v-gl-tooltip v-gl-tooltip
type="button"
:disabled="isLoading"
class="dropdown-new gl-button btn btn-default js-pipeline-dropdown-manual-actions"
:title="__('Run manual or delayed jobs')" :title="__('Run manual or delayed jobs')"
data-toggle="dropdown" :loading="isLoading"
:aria-label="__('Run manual or delayed jobs')" data-testid="pipelines-manual-actions-dropdown"
right
icon="play"
> >
<gl-icon name="play" class="icon-play" /> <gl-dropdown-item
<gl-icon name="chevron-down" /> v-for="action in actions"
<gl-loading-icon v-if="isLoading" /> :key="action.path"
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li v-for="action in actions" :key="action.path">
<gl-button
category="tertiary"
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)" :disabled="isActionDisabled(action)"
class="js-pipeline-action-link"
@click="onClickAction(action)" @click="onClickAction(action)"
> >
<div class="d-flex justify-content-between flex-wrap"> <div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap">
{{ action.name }} {{ action.name }}
<span v-if="action.scheduled_at"> <span v-if="action.scheduled_at">
<gl-icon name="clock" /> <gl-icon name="clock" />
<gl-countdown :end-date-string="action.scheduled_at" /> <gl-countdown :end-date-string="action.scheduled_at" />
</span> </span>
</div> </div>
</gl-button> </gl-dropdown-item>
</li> </gl-dropdown>
</ul>
</div>
</template> </template>
...@@ -288,23 +288,23 @@ RSpec.describe 'Pipelines', :js do ...@@ -288,23 +288,23 @@ RSpec.describe 'Pipelines', :js do
end end
it 'has a dropdown with play button' do it 'has a dropdown with play button' do
expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
end end
it 'has link to the manual action' do it 'has link to the manual action' do
find('.js-pipeline-dropdown-manual-actions').click find('[data-testid="pipelines-manual-actions-dropdown"]').click
expect(page).to have_button('manual build') expect(page).to have_button('manual build')
end end
context 'when manual action was played' do context 'when manual action was played' do
before do before do
find('.js-pipeline-dropdown-manual-actions').click find('[data-testid="pipelines-manual-actions-dropdown"]').click
click_button('manual build') click_button('manual build')
end end
it 'enqueues manual action job' do it 'enqueues manual action job' do
expect(page).to have_selector('.js-pipeline-dropdown-manual-actions:disabled') expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] .gl-dropdown-toggle:disabled')
end end
end end
end end
...@@ -322,11 +322,11 @@ RSpec.describe 'Pipelines', :js do ...@@ -322,11 +322,11 @@ RSpec.describe 'Pipelines', :js do
end end
it 'has a dropdown for actionable jobs' do it 'has a dropdown for actionable jobs' do
expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
end end
it "has link to the delayed job's action" do it "has link to the delayed job's action" do
find('.js-pipeline-dropdown-manual-actions').click find('[data-testid="pipelines-manual-actions-dropdown"]').click
time_diff = [0, delayed_job.scheduled_at - Time.now].max time_diff = [0, delayed_job.scheduled_at - Time.now].max
expect(page).to have_button('delayed job 1') expect(page).to have_button('delayed job 1')
...@@ -342,7 +342,7 @@ RSpec.describe 'Pipelines', :js do ...@@ -342,7 +342,7 @@ RSpec.describe 'Pipelines', :js do
end end
it "shows 00:00:00 as the remaining time" do it "shows 00:00:00 as the remaining time" do
find('.js-pipeline-dropdown-manual-actions').click find('[data-testid="pipelines-manual-actions-dropdown"]').click
expect(page).to have_content("00:00:00") expect(page).to have_content("00:00:00")
end end
...@@ -350,7 +350,7 @@ RSpec.describe 'Pipelines', :js do ...@@ -350,7 +350,7 @@ RSpec.describe 'Pipelines', :js do
context 'when user played a delayed job immediately' do context 'when user played a delayed job immediately' do
before do before do
find('.js-pipeline-dropdown-manual-actions').click find('[data-testid="pipelines-manual-actions-dropdown"]').click
page.accept_confirm { click_button('delayed job 1') } page.accept_confirm { click_button('delayed job 1') }
wait_for_requests wait_for_requests
end end
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { GlButton } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue'; import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
jest.mock('~/flash');
describe('Pipelines Actions dropdown', () => { describe('Pipelines Actions dropdown', () => {
let wrapper; let wrapper;
let mock; let mock;
const createComponent = (actions = []) => { const createComponent = (props, mountFn = shallowMount) => {
wrapper = shallowMount(PipelinesActions, { wrapper = mountFn(PipelinesActions, {
propsData: { propsData: {
actions, ...props,
}, },
}); });
}; };
const findAllDropdownItems = () => wrapper.findAll(GlButton); const findDropdown = () => wrapper.find(GlDropdown);
const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
const findAllCountdowns = () => wrapper.findAll(GlCountdown); const findAllCountdowns = () => wrapper.findAll(GlCountdown);
beforeEach(() => { beforeEach(() => {
...@@ -47,7 +51,7 @@ describe('Pipelines Actions dropdown', () => { ...@@ -47,7 +51,7 @@ describe('Pipelines Actions dropdown', () => {
]; ];
beforeEach(() => { beforeEach(() => {
createComponent(mockActions); createComponent({ actions: mockActions });
}); });
it('renders a dropdown with the provided actions', () => { it('renders a dropdown with the provided actions', () => {
...@@ -59,16 +63,33 @@ describe('Pipelines Actions dropdown', () => { ...@@ -59,16 +63,33 @@ describe('Pipelines Actions dropdown', () => {
}); });
describe('on click', () => { describe('on click', () => {
it('makes a request and toggles the loading state', () => { beforeEach(() => {
createComponent({ actions: mockActions }, mount);
});
it('makes a request and toggles the loading state', async () => {
mock.onPost(mockActions.path).reply(200); mock.onPost(mockActions.path).reply(200);
wrapper.find(GlButton).vm.$emit('click'); findAllDropdownItems().at(0).vm.$emit('click');
expect(wrapper.vm.isLoading).toBe(true); await wrapper.vm.$nextTick();
expect(findDropdown().props('loading')).toBe(true);
return waitForPromises().then(() => { await waitForPromises();
expect(wrapper.vm.isLoading).toBe(false); expect(findDropdown().props('loading')).toBe(false);
}); });
it('makes a failed request and toggles the loading state', async () => {
mock.onPost(mockActions.path).reply(500);
findAllDropdownItems().at(0).vm.$emit('click');
await wrapper.vm.$nextTick();
expect(findDropdown().props('loading')).toBe(true);
await waitForPromises();
expect(findDropdown().props('loading')).toBe(false);
expect(createFlash).toHaveBeenCalledTimes(1);
}); });
}); });
}); });
...@@ -89,10 +110,10 @@ describe('Pipelines Actions dropdown', () => { ...@@ -89,10 +110,10 @@ describe('Pipelines Actions dropdown', () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime()); jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
createComponent([scheduledJobAction, expiredJobAction]); createComponent({ actions: [scheduledJobAction, expiredJobAction] });
}); });
it('makes post request after confirming', () => { it('makes post request after confirming', async () => {
mock.onPost(scheduledJobAction.path).reply(200); mock.onPost(scheduledJobAction.path).reply(200);
jest.spyOn(window, 'confirm').mockReturnValue(true); jest.spyOn(window, 'confirm').mockReturnValue(true);
...@@ -100,19 +121,22 @@ describe('Pipelines Actions dropdown', () => { ...@@ -100,19 +121,22 @@ describe('Pipelines Actions dropdown', () => {
expect(window.confirm).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalled();
return waitForPromises().then(() => { await waitForPromises();
expect(mock.history.post.length).toBe(1);
}); expect(mock.history.post).toHaveLength(1);
}); });
it('does not make post request if confirmation is cancelled', () => { it('does not make post request if confirmation is cancelled', async () => {
mock.onPost(scheduledJobAction.path).reply(200); mock.onPost(scheduledJobAction.path).reply(200);
jest.spyOn(window, 'confirm').mockReturnValue(false); jest.spyOn(window, 'confirm').mockReturnValue(false);
findAllDropdownItems().at(0).vm.$emit('click'); findAllDropdownItems().at(0).vm.$emit('click');
expect(window.confirm).toHaveBeenCalled(); expect(window.confirm).toHaveBeenCalled();
expect(mock.history.post.length).toBe(0);
await waitForPromises();
expect(mock.history.post).toHaveLength(0);
}); });
it('displays the remaining time in the dropdown', () => { it('displays the remaining time in the dropdown', () => {
......
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