diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue index ba81cab225626e967f00bcd8ff3f073a4d646ddb..d608eebd1ae6b029ec413a9fd1cfcda74d7149c8 100644 --- a/app/assets/javascripts/environments/components/environment_stop.vue +++ b/app/assets/javascripts/environments/components/environment_stop.vue @@ -39,7 +39,8 @@ export default { }, }, i18n: { - title: s__('Environments|Stop environment'), + stopTitle: s__('Environments|Stop environment'), + stoppingTitle: s__('Environments|Stopping environment'), }, data() { return { @@ -47,6 +48,14 @@ export default { isEnvironmentStopping: false, }; }, + computed: { + isLoadingState() { + return this.environment.state === 'stopping' || this.isEnvironmentStopping || this.isLoading; + }, + title() { + return this.isLoadingState ? this.$options.i18n.stoppingTitle : this.$options.i18n.stopTitle; + }, + }, mounted() { eventHub.$on('stopEnvironment', this.onStopEnvironment); }, @@ -75,16 +84,23 @@ export default { }; </script> <template> - <gl-button + <div v-gl-tooltip="{ id: $options.stopEnvironmentTooltipId }" - v-gl-modal-directive="'stop-environment-modal'" - :loading="isLoading || isEnvironmentStopping" - :title="$options.i18n.title" - :aria-label="$options.i18n.title" - size="small" - icon="stop" - category="secondary" - variant="danger" - @click="onClick" - /> + :title="title" + :tabindex="isLoadingState ? 0 : null" + class="gl-relative -gl-ml-[1px]" + > + <gl-button + v-gl-modal-directive="'stop-environment-modal'" + :loading="isLoadingState" + :aria-label="title" + :class="{ 'gl-pointer-events-none': isLoadingState }" + class="!gl-rounded-none" + size="small" + icon="stop" + category="secondary" + variant="danger" + @click="onClick" + /> + </div> </template> diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index 12b2aec47b51565a4ab8ceefdc02f7c11dcaa935..72946320ada912097aef59c2ad08ecf446ff6737 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -110,6 +110,9 @@ export default { ...action, })); }, + isEnvironmentStopping() { + return this.environment?.state === 'stopping'; + }, canStop() { return this.environment?.canStop; }, @@ -233,7 +236,7 @@ export default { /> <stop-component - v-if="canStop" + v-if="canStop || isEnvironmentStopping" :environment="environment" data-track-action="click_button" data-track-label="environment_stop" diff --git a/ee/spec/features/projects/environments/environments_spec.rb b/ee/spec/features/projects/environments/environments_spec.rb index 03485a4ff1b3ecdc67f45ad570fbf4f76366ea9f..b76bfc3868d3cdf00229a0916f3c27ad34e796bd 100644 --- a/ee/spec/features/projects/environments/environments_spec.rb +++ b/ee/spec/features/projects/environments/environments_spec.rb @@ -69,7 +69,7 @@ def actions_button_selector end it 'shows a stop button' do - stop_button_selector = %q(button[title="Stop environment"]) + stop_button_selector = %q(button[aria-label="Stop environment"]) expect(page).to have_selector(stop_button_selector) end @@ -166,7 +166,7 @@ def actions_button_selector end it 'does not show a stop button' do - stop_button_selector = %q(button[title="Stop environment"]) + stop_button_selector = %q(button[aria-label="Stop environment"]) expect(page).not_to have_selector(stop_button_selector) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3d52109a16d3f3a307f373dd7c6df0cf51bdaf65..c3b6e65cc358a296498aac32b383d058a4f1a54d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22805,6 +22805,9 @@ msgstr "" msgid "Environments|Stop unused environments" msgstr "" +msgid "Environments|Stopping environment" +msgstr "" + msgid "Environments|Synced" msgstr "" diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 0e44ab71ce15cdac554c7f07f081943271060a06..c928a570d1dd37ecca1dd4246ccea161338844d6 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -23,7 +23,7 @@ def action_link_selector end def stop_button_selector - 'button[title="Stop environment"]' + 'button[aria-label="Stop environment"]' end def upcoming_deployment_content_selector diff --git a/spec/frontend/environments/environment_stop_spec.js b/spec/frontend/environments/environment_stop_spec.js index 3e27b8822e197306aac8a32d6749e3154f69e655..186126332a815e3ae5e3555000ece93b5f2ea26c 100644 --- a/spec/frontend/environments/environment_stop_spec.js +++ b/spec/frontend/environments/environment_stop_spec.js @@ -7,6 +7,7 @@ import isEnvironmentStoppingQuery from '~/environments/graphql/queries/is_enviro import StopComponent from '~/environments/components/environment_stop.vue'; import eventHub from '~/environments/event_hub'; import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { resolvedEnvironment } from './graphql/mock_data'; describe('Stop Component', () => { @@ -24,7 +25,7 @@ describe('Stop Component', () => { const findButton = () => wrapper.findComponent(GlButton); - describe('eventHub', () => { + describe('default', () => { beforeEach(() => { createWrapper(); }); @@ -33,6 +34,12 @@ describe('Stop Component', () => { expect(findButton().exists()).toBe(true); expect(wrapper.attributes('title')).toEqual('Stop environment'); }); + }); + + describe('eventHub', () => { + beforeEach(() => { + createWrapper(); + }); it('emits requestStopEnvironment in the event hub when button is clicked', () => { jest.spyOn(eventHub, '$emit'); @@ -44,37 +51,72 @@ describe('Stop Component', () => { describe('graphql', () => { Vue.use(VueApollo); let mockApollo; + const resolvers = { + Query: { + isEnvironmentStopping: () => true, + }, + }; - beforeEach(() => { - mockApollo = createMockApollo(); - mockApollo.clients.defaultClient.writeQuery({ - query: isEnvironmentStoppingQuery, - variables: { environment: resolvedEnvironment }, - data: { isEnvironmentStopping: true }, - }); - + const createWrapperWithApollo = () => { createWrapper( { graphql: true, environment: resolvedEnvironment }, { apolloProvider: mockApollo }, ); - }); + }; - it('should render a button to stop the environment', () => { - expect(findButton().exists()).toBe(true); - expect(wrapper.attributes('title')).toEqual('Stop environment'); + it('queries for environment stopping state', () => { + mockApollo = createMockApollo([], resolvers); + jest.spyOn(mockApollo.defaultClient, 'watchQuery'); + + createWrapperWithApollo(); + + expect(mockApollo.defaultClient.watchQuery).toHaveBeenCalledWith({ + query: isEnvironmentStoppingQuery, + variables: { environment: resolvedEnvironment }, + }); }); it('sets the environment to stop on click', () => { + mockApollo = createMockApollo(); jest.spyOn(mockApollo.defaultClient, 'mutate'); + + createWrapperWithApollo(); + findButton().vm.$emit('click'); + expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({ mutation: setEnvironmentToStopMutation, variables: { environment: resolvedEnvironment }, }); }); - it('should show a loading icon if the environment is currently stopping', () => { - expect(findButton().props('loading')).toBe(true); + describe('when the environment is currently stopping', () => { + beforeEach(async () => { + mockApollo = createMockApollo([], resolvers); + + createWrapperWithApollo(); + await waitForPromises(); + }); + + it('should render a button with a loading icon and a correct title', () => { + const button = findButton(); + + expect(button.props('loading')).toBe(true); + expect(wrapper.attributes('title')).toBe('Stopping environment'); + }); + }); + }); + + describe('when the environment is in stopping state', () => { + beforeEach(() => { + createWrapper({ environment: { ...resolvedEnvironment, state: 'stopping' } }); + }); + + it('should render a button with a loading icon and a correct title', () => { + const button = findButton(); + + expect(button.props('loading')).toBe(true); + expect(wrapper.attributes('title')).toBe('Stopping environment'); }); }); }); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index 8ad30e657c226e29aff9cee8b024ae1b14e2b489..fbc9d1aac65842f72f305bb966acb4cbdeb053cc 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -163,6 +163,15 @@ describe('~/environments/components/new_environment_item.vue', () => { expect(findStopComponent().exists()).toBe(false); }); + + it('shows a button to stop the environment if the environment is in stopping state', () => { + wrapper = createWrapper({ + propsData: { environment: { ...resolvedEnvironment, state: 'stopping' } }, + apolloProvider: createApolloProvider(), + }); + + expect(findStopComponent().exists()).toBe(true); + }); }); describe('rollback', () => {