From 14855c11e959d9b73439609ebab78962610515bb Mon Sep 17 00:00:00 2001 From: Justin Ho Tuan Duong <hduong@gitlab.com> Date: Fri, 19 Apr 2024 14:33:40 +0000 Subject: [PATCH] Add "Imported" badge to MR header This is not shown at the moment as the backend is not providing any data. Changelog: changed --- .../components/merge_request_header.vue | 12 +++ .../components/sticky_header.vue | 20 +++-- .../merge_requests/init_merge_request_show.js | 3 +- .../pages/projects/merge_requests/page.js | 3 + .../components/merge_request_header_spec.js | 19 ++++- .../components/sticky_header_spec.js | 83 ++++++++++++------- 6 files changed, 102 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/merge_requests/components/merge_request_header.vue b/app/assets/javascripts/merge_requests/components/merge_request_header.vue index b2e7245bd8883..667670b46eb67 100644 --- a/app/assets/javascripts/merge_requests/components/merge_request_header.vue +++ b/app/assets/javascripts/merge_requests/components/merge_request_header.vue @@ -8,6 +8,7 @@ import StatusBadge from '~/issuable/components/status_badge.vue'; import { TYPE_ISSUE, TYPE_MERGE_REQUEST, WORKSPACE_PROJECT } from '~/issues/constants'; import { fetchPolicies } from '~/lib/graphql'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; +import ImportedBadge from '~/vue_shared/components/imported_badge.vue'; export const badgeState = Vue.observable({ state: '', @@ -22,6 +23,7 @@ export default { ConfidentialityBadge, LockedBadge, HiddenBadge, + ImportedBadge, StatusBadge, }, inject: { @@ -36,6 +38,11 @@ export default { required: false, default: null, }, + isImported: { + type: Boolean, + required: false, + default: false, + }, }, data() { if (!this.iid) { @@ -109,5 +116,10 @@ export default { class="gl-align-self-center gl-mr-2" :issuable-type="$options.TYPE_MERGE_REQUEST" /> + <imported-badge + v-if="isImported" + class="gl-align-self-center gl-mr-2" + :importable-type="$options.TYPE_MERGE_REQUEST" + /> </span> </template> diff --git a/app/assets/javascripts/merge_requests/components/sticky_header.vue b/app/assets/javascripts/merge_requests/components/sticky_header.vue index 9c4a3ad2efa6e..94e7acd9e773c 100644 --- a/app/assets/javascripts/merge_requests/components/sticky_header.vue +++ b/app/assets/javascripts/merge_requests/components/sticky_header.vue @@ -18,6 +18,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { isLoggedIn } from '~/lib/utils/common_utils'; import StatusBadge from '~/issuable/components/status_badge.vue'; +import ImportedBadge from '~/vue_shared/components/imported_badge.vue'; import { TYPE_MERGE_REQUEST } from '~/issues/constants'; import DiscussionCounter from '~/notes/components/discussion_counter.vue'; import TodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue'; @@ -58,6 +59,7 @@ export default { GlIcon, DiscussionCounter, StatusBadge, + ImportedBadge, TodoWidget, SubscriptionsWidget, ClipboardButton, @@ -75,6 +77,11 @@ export default { blocksMerge: { default: false }, }, props: { + isImported: { + type: Boolean, + required: false, + default: false, + }, tabs: { type: Array, required: true, @@ -160,18 +167,15 @@ export default { class="issue-sticky-header-text gl-display-flex gl-flex-direction-column gl-align-items-center gl-mx-auto gl-w-full" :class="{ 'container-limited': !isFluidLayout }" > - <div class="gl-w-full gl-display-flex gl-align-items-baseline"> - <status-badge - class="gl-align-self-center gl-mr-3" - :issuable-type="$options.TYPE_MERGE_REQUEST" - :state="badgeState.state" - /> + <div class="gl-w-full gl-display-flex gl-align-items-center gl-gap-2"> + <status-badge :issuable-type="$options.TYPE_MERGE_REQUEST" :state="badgeState.state" /> + <imported-badge v-if="isImported" :importable-type="$options.TYPE_MERGE_REQUEST" /> <a v-safe-html:[$options.safeHtmlConfig]="titleHtml" href="#top" - class="gl-display-none gl-lg-display-block gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-mr-4 gl-text-black-normal" + class="gl-display-none gl-lg-display-block gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-ml-1 gl-mr-2 gl-text-black-normal" ></a> - <div class="gl-display-flex gl-align-items-baseline"> + <div class="gl-display-flex gl-align-items-center"> <gl-sprintf :message="__('%{source} %{copyButton} into %{target}')"> <template #copyButton> <clipboard-button diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js index 955c26139a6d0..6b50cea7e90ec 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js @@ -27,7 +27,7 @@ export default function initMergeRequestShow(store) { initCheckoutModal(); const el = document.querySelector('.js-mr-header'); - const { hidden, iid, projectPath, state } = el.dataset; + const { hidden, imported, iid, projectPath, state } = el.dataset; // eslint-disable-next-line no-new new Vue({ @@ -47,6 +47,7 @@ export default function initMergeRequestShow(store) { return createElement(MergeRequestHeader, { props: { initialState: state, + isImported: parseBoolean(imported), }, }); }, diff --git a/app/assets/javascripts/pages/projects/merge_requests/page.js b/app/assets/javascripts/pages/projects/merge_requests/page.js index 6114b6fe9d4d6..6fb8f3608b870 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/page.js +++ b/app/assets/javascripts/pages/projects/merge_requests/page.js @@ -51,6 +51,7 @@ requestIdleCallback(() => { isFluidLayout, sourceProjectPath, blocksMerge, + imported, } = JSON.parse(data); tabData.tabs = tabs; @@ -58,6 +59,7 @@ requestIdleCallback(() => { // eslint-disable-next-line no-new new Vue({ el, + name: 'MergeRequestStickyHeaderRoot', store, apolloProvider, provide: { @@ -73,6 +75,7 @@ requestIdleCallback(() => { return h(StickyHeader, { props: { tabs: tabData.tabs, + isImported: parseBoolean(imported), }, }); }, diff --git a/spec/frontend/merge_requests/components/merge_request_header_spec.js b/spec/frontend/merge_requests/components/merge_request_header_spec.js index 3f774098379af..c53a340281fa3 100644 --- a/spec/frontend/merge_requests/components/merge_request_header_spec.js +++ b/spec/frontend/merge_requests/components/merge_request_header_spec.js @@ -5,6 +5,7 @@ import StatusBadge from '~/issuable/components/status_badge.vue'; import MergeRequestHeader from '~/merge_requests/components/merge_request_header.vue'; import mrStore from '~/mr_notes/stores'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; +import ImportedBadge from '~/vue_shared/components/imported_badge.vue'; jest.mock('~/mr_notes/stores', () => jest.requireActual('helpers/mocks/mr_notes/stores')); @@ -14,11 +15,12 @@ describe('MergeRequestHeader component', () => { const findConfidentialBadge = () => wrapper.findComponent(ConfidentialityBadge); const findLockedBadge = () => wrapper.findComponent(LockedBadge); const findHiddenBadge = () => wrapper.findComponent(HiddenBadge); + const findImportedBadge = () => wrapper.findComponent(ImportedBadge); const findStatusBadge = () => wrapper.findComponent(StatusBadge); const renderTestMessage = (renders) => (renders ? 'renders' : 'does not render'); - const createComponent = ({ confidential, hidden, locked }) => { + const createComponent = ({ confidential, hidden, locked, isImported = false }) => { const store = mrStore; store.getters.getNoteableData = {}; store.getters.getNoteableData.confidential = confidential; @@ -34,6 +36,7 @@ describe('MergeRequestHeader component', () => { }, propsData: { initialState: 'opened', + isImported, }, }); }; @@ -85,4 +88,18 @@ describe('MergeRequestHeader component', () => { }); }, ); + + describe('imported badge', () => { + it('renders when merge request is imported', () => { + createComponent({ isImported: true }); + + expect(findImportedBadge().props('importableType')).toBe('merge_request'); + }); + + it('does not render when merge request is not imported', () => { + createComponent({ isImported: false }); + + expect(findImportedBadge().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/merge_requests/components/sticky_header_spec.js b/spec/frontend/merge_requests/components/sticky_header_spec.js index 9fc265cd9ad07..70ec76cd0afd1 100644 --- a/spec/frontend/merge_requests/components/sticky_header_spec.js +++ b/spec/frontend/merge_requests/components/sticky_header_spec.js @@ -4,45 +4,72 @@ import Vuex from 'vuex'; import { GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import StickyHeader from '~/merge_requests/components/sticky_header.vue'; +import ImportedBadge from '~/vue_shared/components/imported_badge.vue'; Vue.use(Vuex); -let wrapper; - -function createComponent(provide = {}) { - const store = new Vuex.Store({ - state: { - page: { activeTab: 'overview' }, - notes: { notes: { doneFetchingBatchDiscussions: true } }, - }, - getters: { - getNoteableData: () => ({ - id: 1, - source_branch: 'source-branch', - target_branch: 'main', - }), - discussionTabCounter: () => 1, - }, - }); +describe('Merge requests sticky header component', () => { + let wrapper; - wrapper = shallowMountExtended(StickyHeader, { - store, - provide, - stubs: { - GlSprintf, - }, - }); -} + const createComponent = ({ provide = {}, props = {} } = {}) => { + const store = new Vuex.Store({ + state: { + page: { activeTab: 'overview' }, + notes: { notes: { doneFetchingBatchDiscussions: true } }, + }, + getters: { + getNoteableData: () => ({ + id: 1, + source_branch: 'source-branch', + target_branch: 'main', + }), + discussionTabCounter: () => 1, + }, + }); + + wrapper = shallowMountExtended(StickyHeader, { + store, + provide, + propsData: { + tabs: [], + ...props, + }, + stubs: { + GlSprintf, + }, + }); + }; + + const findImportedBadge = () => wrapper.findComponent(ImportedBadge); -describe('Merge requests sticky header component', () => { describe('forked project', () => { it('renders source branch with source project path', () => { createComponent({ - projectPath: 'gitlab-org/gitlab', - sourceProjectPath: 'root/gitlab', + provide: { + projectPath: 'gitlab-org/gitlab', + sourceProjectPath: 'root/gitlab', + }, }); expect(wrapper.findByTestId('source-branch').text()).toBe('root/gitlab:source-branch'); }); }); + + describe('imported badge', () => { + it('renders when merge request is imported', () => { + createComponent({ + props: { isImported: true }, + }); + + expect(findImportedBadge().props('importableType')).toBe('merge_request'); + }); + + it('does not render when merge request is not imported', () => { + createComponent({ + props: { isImported: false }, + }); + + expect(findImportedBadge().exists()).toBe(false); + }); + }); }); -- GitLab