diff --git a/ee/app/assets/javascripts/work_items/components/work_item_weight_with_edit.vue b/ee/app/assets/javascripts/work_items/components/work_item_weight_with_edit.vue index 9f532e1b92f85fbecf33cad5d8a88c2f81e362b0..6ccdb4c7a997239d71594c53152fae0504397952 100644 --- a/ee/app/assets/javascripts/work_items/components/work_item_weight_with_edit.vue +++ b/ee/app/assets/javascripts/work_items/components/work_item_weight_with_edit.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlForm, GlFormInput, GlTooltipDirective } from '@gitlab/ui'; +import { GlButton, GlForm, GlFormInput, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import * as Sentry from '~/sentry/sentry_browser_wrapper'; import Tracking from '~/tracking'; import { @@ -18,6 +18,7 @@ export default { GlButton, GlForm, GlFormInput, + GlLoadingIcon, }, mixins: [Tracking.mixin()], inject: ['hasIssueWeightsFeature'], @@ -50,12 +51,16 @@ export default { isEditing: false, clickingClearButton: false, workItem: {}, + isUpdating: false, }; }, computed: { hasWeight() { return this.weight !== null; }, + showRemoveWeight() { + return this.hasWeight && !this.isUpdating; + }, tracking() { return { category: TRACKING_CATEGORY_SHOW, @@ -83,9 +88,13 @@ export default { updateWeight(weight) { if (this.clickingClearButton) return; if (!this.canUpdate) return; - this.isEditing = false; - if (this.weight === weight) return; + if (this.weight === weight) { + this.isEditing = false; + return; + } + + this.isUpdating = true; this.track('updated_weight'); this.$apollo @@ -109,6 +118,10 @@ export default { const msg = sprintfWorkItem(I18N_WORK_ITEM_ERROR_UPDATING, this.workItemType); this.$emit('error', msg); Sentry.captureException(error); + }) + .finally(() => { + this.isUpdating = false; + this.isEditing = false; }); }, }, @@ -132,12 +145,15 @@ export default { > </div> <gl-form v-if="isEditing" @submit.prevent="blurInput"> - <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center"> + <div class="gl-display-flex gl-align-items-center"> <label :for="$options.inputId" class="gl-mb-0">{{ __('Weight') }}</label> + <gl-loading-icon v-if="isUpdating" size="sm" inline class="gl-ml-3" /> <gl-button data-testid="apply-weight" category="tertiary" size="small" + class="gl-ml-auto" + :disabled="isUpdating" @click="isEditing = false" >{{ __('Apply') }}</gl-button > @@ -150,6 +166,7 @@ export default { min="0" class="hide-unfocused-input-decoration gl-display-block" type="number" + :disabled="isUpdating" :placeholder="__('Enter a number')" :value="weight" autofocus @@ -158,7 +175,7 @@ export default { @keydown.exact.esc.stop="blurInput" /> <gl-button - v-if="hasWeight" + v-if="showRemoveWeight" v-gl-tooltip data-testid="remove-weight" variant="default" diff --git a/ee/spec/frontend/work_items/components/work_item_weight_with_edit_spec.js b/ee/spec/frontend/work_items/components/work_item_weight_with_edit_spec.js index ca506ae6d78b640dc23a2c2ceb713d21c4d778b4..d8f61957bbd7234f244f4c94a2882549f4be70e4 100644 --- a/ee/spec/frontend/work_items/components/work_item_weight_with_edit_spec.js +++ b/ee/spec/frontend/work_items/components/work_item_weight_with_edit_spec.js @@ -1,4 +1,4 @@ -import { GlForm, GlFormInput } from '@gitlab/ui'; +import { GlForm, GlFormInput, GlLoadingIcon } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import WorkItemWeight from 'ee/work_items/components/work_item_weight_with_edit.vue'; @@ -25,6 +25,7 @@ describe('WorkItemWeight component', () => { const findLabel = () => wrapper.find('label'); const findForm = () => wrapper.findComponent(GlForm); const findInput = () => wrapper.findComponent(GlFormInput); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findClearButton = () => wrapper.find('[data-testid="remove-weight"]'); const createComponent = ({ @@ -80,6 +81,27 @@ describe('WorkItemWeight component', () => { expect(findLabel().exists()).toBe(true); expect(findHeader().classes('gl-sr-only')).toBe(true); }); + + it('shows loading spinner while updating', async () => { + createComponent({ + isEditing: true, + weight: 0, + canUpdate: true, + }); + + await nextTick(); + + findInput().setValue('1'); + findInput().trigger('blur'); + + await nextTick(); + + expect(findLoadingIcon().exists()).toBe(true); + + await waitForPromises(); + + expect(findLoadingIcon().exists()).toBe(false); + }); }); describe('edit button', () => { @@ -212,6 +234,27 @@ describe('WorkItemWeight component', () => { }); }); + it('is disabled while updating, and removed after', async () => { + createComponent({ + isEditing: true, + weight: 0, + canUpdate: true, + }); + + await nextTick(); + + findInput().setValue('1'); + findInput().trigger('blur'); + + await nextTick(); + + expect(findInput().attributes('disabled')).toBe('disabled'); + + await waitForPromises(); + + expect(findInput().exists()).toBe(false); + }); + it('does not call a mutation to update the weight when the input value is the same', async () => { const mutationSpy = jest.fn().mockResolvedValue(updateWorkItemMutationResponse); createComponent({ isEditing: true, mutationHandler: mutationSpy, canUpdate: true });