diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue b/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue index 2a0af1381633f826e47dc486a4e569837cfb4c90..bfd7c525ea9f4539bcdbf795715bd86c9ef41037 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab/ui'; import StatusIcon from '../widget/status_icon.vue'; import { FAILURE_REASONS } from './constants'; @@ -12,6 +13,7 @@ const ICON_NAMES = { export default { name: 'MergeChecksMessage', components: { + GlLoadingIcon, StatusIcon, }, props: { @@ -39,7 +41,14 @@ export default { <template> <div class="gl-py-3 gl-pl-7 gl-pr-4"> <div class="gl-flex"> - <status-icon :icon-name="iconName" :level="2" /> + <gl-loading-icon + v-if="check.status === 'CHECKING'" + size="sm" + inline + class="mr-widget-check-checking gl-mr-3 gl-self-center" + data-testid="checking-icon" + /> + <status-icon v-else :icon-name="iconName" :level="2" /> <div class="gl-w-full gl-min-w-0"> <div class="gl-flex">{{ failureReason }}</div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.stories.js b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.stories.js index a1171fe5d256e368c4a790749a12164a82c23b61..8fa8e342c450c0e899466595554358e461865362 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.stories.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.stories.js @@ -20,7 +20,7 @@ const defaultRender = (apolloProvider) => ({ template: '<merge-checks :mr="mr" :service="service" />', }); -const Template = ({ canMerge, failed, pushToSourceBranch }) => { +const Template = ({ canMerge, failed, checking, pushToSourceBranch }) => { const requestHandlers = [ [ mergeChecksQuery, @@ -42,8 +42,8 @@ const Template = ({ canMerge, failed, pushToSourceBranch }) => { status: failed ? 'FAILED' : 'SUCCESS', }, { - identifier: 'DRAFT_STATUS', - status: failed ? 'FAILED' : 'SUCCESS', + identifier: 'NOT_APPROVED', + status: checking ? 'CHECKING' : 'SUCCESS', }, ], }, @@ -82,7 +82,7 @@ const LoadingTemplate = () => { }; export const Default = Template.bind({}); -Default.args = { canMerge: true, failed: true, pushToSourceBranch: true }; +Default.args = { canMerge: true, failed: true, checking: false, pushToSourceBranch: true }; export const Loading = LoadingTemplate.bind({}); Loading.args = {}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue index 9d6f0fc55f91b1727262ebf904d505cd48104f00..f8fb6732adf93162fd7d79573449fb906040a409 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue @@ -82,6 +82,10 @@ export default { return 'warning'; } + if (this.checkingMergeChecks.length) { + return 'loading'; + } + return this.failedChecks.length ? 'failed' : 'success'; }, summaryText() { @@ -91,6 +95,10 @@ export default { : __('%{boldStart}Ready to be merged with caution%{boldEnd}: Override added'); } + if (this.checkingMergeChecks.length) { + return __('Checking if merge request can be merged...'); + } + if (!this.failedChecks.length) { return this.state?.userPermissions?.canMerge ? __('%{boldStart}Ready to merge!%{boldEnd}') @@ -112,16 +120,19 @@ export default { return this.state?.mergeabilityChecks || []; }, sortedChecks() { - const order = ['FAILED', 'WARNING', 'SUCCESS']; + const order = ['CHECKING', 'FAILED', 'WARNING', 'SUCCESS']; return [...this.checks] .filter((s) => { if (this.isStatusInactive(s) || !this.hasMessage(s)) return false; - return this.collapsed ? this.isStatusFailed(s) : true; + return this.collapsed ? this.isStatusFailed(s) || this.isStatusChecking(s) : true; }) .sort((a, b) => order.indexOf(a.status) - order.indexOf(b.status)); }, + checkingMergeChecks() { + return this.checks.filter((c) => this.isStatusChecking(c)); + }, failedChecks() { return this.checks.filter((c) => this.isStatusFailed(c)); }, @@ -129,7 +140,7 @@ export default { return this.checks.filter((c) => this.isStatusWarning(c)); }, showChecks() { - return this.failedChecks.length > 0 || !this.collapsed; + return this.failedChecks.length > 0 || this.checkingMergeChecks.length || !this.collapsed; }, }, methods: { @@ -151,6 +162,9 @@ export default { isStatusWarning(check) { return check.status === 'WARNING'; }, + isStatusChecking(check) { + return check.status === 'CHECKING'; + }, }, }; </script> diff --git a/app/assets/stylesheets/page_bundles/merge_request.scss b/app/assets/stylesheets/page_bundles/merge_request.scss index b85410bbd12691f8bee4831f9f3c1401a1a12bef..40ada141bcf37ffdfae2781247b2a6763d68f579 100644 --- a/app/assets/stylesheets/page_bundles/merge_request.scss +++ b/app/assets/stylesheets/page_bundles/merge_request.scss @@ -292,3 +292,9 @@ $comparison-empty-state-height: 62px; width: 100%; } } + +// stylelint-disable-next-line gitlab/no-gl-class --- We need a slightly smaller spinner to fit the design +.mr-widget-check-checking .gl-spinner { + width: 12px; + height: 12px; +} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 79a7be3ac16acfc6a77f8a84cdb34c3e272803a4..374fc27c51015f94cd42d084fd8e1877225a921d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -11234,6 +11234,9 @@ msgstr "" msgid "Checking group path availability..." msgstr "" +msgid "Checking if merge request can be merged..." +msgstr "" + msgid "Checking username availability..." msgstr "" diff --git a/spec/frontend/vue_merge_request_widget/components/checks/message_spec.js b/spec/frontend/vue_merge_request_widget/components/checks/message_spec.js index acf8572f5f3f2dbdfd8e171af68b73a4e9413e32..f82896b4d6f6e94c92a09423baa7cf34e410bdff 100644 --- a/spec/frontend/vue_merge_request_widget/components/checks/message_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/checks/message_spec.js @@ -46,4 +46,10 @@ describe('Merge request merge checks message component', () => { expect(wrapper.findComponent(StatusIcon).props('iconName')).toBe(icon); }); + + it('renders loading icon when status is CHECKING', () => { + factory({ check: { status: 'CHECKING', identifier: 'discussions_not_resolved' } }); + + expect(wrapper.findByTestId('checking-icon').exists()).toBe(true); + }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js index 0c1792ed4e6cbf950ddcb2bd9859cd5ccd22edef..083b23ddef59a73fa2556471fe8d754dd1abf969 100644 --- a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js @@ -250,4 +250,27 @@ describe('Merge request merge checks component', () => { expect(findMergeChecks().length).toBe(1); }); }); + + describe('checking merge checks', () => { + const findMergeChecks = () => wrapper.findAllByTestId('merge-check'); + + beforeEach(() => { + mountComponent({ + mergeabilityChecks: [ + { identifier: 'discussions_not_resolved', status: 'CHECKING' }, + { identifier: 'not_approved', status: 'SUCCESS' }, + ], + }); + + return waitForPromises(); + }); + + it('renders checking text', () => { + expect(wrapper.text()).toBe('Checking if merge request can be merged...'); + }); + + it('renders checks expanded by default', () => { + expect(findMergeChecks()).toHaveLength(1); + }); + }); });