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