From 2ade712988934c1dc96191e213896e5211053531 Mon Sep 17 00:00:00 2001
From: Tom Quirk <tquirk@gitlab.com>
Date: Wed, 4 Aug 2021 15:53:58 +0000
Subject: [PATCH] UI polish for Jira Connect Create branch

- Replace success alert with empty state
- Dynamic page title
---
 .../branches/components/new_branch_form.vue   | 105 +++++++-----------
 .../jira_connect/branches/constants.js        |  15 ++-
 .../jira_connect/branches/index.js            |  14 +--
 .../jira_connect/branches/pages/index.vue     |  60 ++++++++++
 .../jira_connect/branches_controller.rb       |  21 +++-
 app/views/jira_connect/branches/new.html.haml |   2 +-
 locale/gitlab.pot                             |   3 +
 .../jira_connect/branches_controller_spec.rb  |   9 +-
 .../components/new_branch_form_spec.js        |  26 +++--
 .../jira_connect/branches/pages/index_spec.js |  65 +++++++++++
 10 files changed, 223 insertions(+), 97 deletions(-)
 create mode 100644 app/assets/javascripts/jira_connect/branches/pages/index.vue
 create mode 100644 spec/frontend/jira_connect/branches/pages/index_spec.js

diff --git a/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue b/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue
index b2cc3a315cca9..66fcb8e10eb01 100644
--- a/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue
+++ b/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue
@@ -3,8 +3,6 @@ import { GlFormGroup, GlButton, GlFormInput, GlForm, GlAlert } from '@gitlab/ui'
 import {
   CREATE_BRANCH_ERROR_GENERIC,
   CREATE_BRANCH_ERROR_WITH_CONTEXT,
-  CREATE_BRANCH_SUCCESS_ALERT,
-  I18N_NEW_BRANCH_PAGE_TITLE,
   I18N_NEW_BRANCH_LABEL_DROPDOWN,
   I18N_NEW_BRANCH_LABEL_BRANCH,
   I18N_NEW_BRANCH_LABEL_SOURCE,
@@ -19,8 +17,6 @@ const DEFAULT_ALERT_PARAMS = {
   title: '',
   message: '',
   variant: DEFAULT_ALERT_VARIANT,
-  primaryButtonLink: '',
-  primaryButtonText: '',
 };
 
 export default {
@@ -34,13 +30,7 @@ export default {
     ProjectDropdown,
     SourceBranchDropdown,
   },
-  props: {
-    initialBranchName: {
-      type: String,
-      required: false,
-      default: '',
-    },
-  },
+  inject: ['initialBranchName'],
   data() {
     return {
       selectedProject: null,
@@ -111,10 +101,7 @@ export default {
             message: errors[0],
           });
         } else {
-          this.displayAlert({
-            ...CREATE_BRANCH_SUCCESS_ALERT,
-            variant: 'success',
-          });
+          this.$emit('success');
         }
       } catch (e) {
         this.onError({
@@ -126,7 +113,6 @@ export default {
     },
   },
   i18n: {
-    I18N_NEW_BRANCH_PAGE_TITLE,
     I18N_NEW_BRANCH_LABEL_DROPDOWN,
     I18N_NEW_BRANCH_LABEL_BRANCH,
     I18N_NEW_BRANCH_LABEL_SOURCE,
@@ -134,15 +120,8 @@ export default {
   },
 };
 </script>
-
 <template>
-  <div>
-    <div class="gl-border-1 gl-border-b-solid gl-border-gray-100 gl-mb-5 gl-mt-7">
-      <h1 class="page-title">
-        {{ $options.i18n.I18N_NEW_BRANCH_PAGE_TITLE }}
-      </h1>
-    </div>
-
+  <gl-form @submit.prevent="onSubmit">
     <gl-alert
       v-if="showAlert"
       class="gl-mb-5"
@@ -152,50 +131,44 @@ export default {
     >
       {{ alertParams.message }}
     </gl-alert>
+    <gl-form-group :label="$options.i18n.I18N_NEW_BRANCH_LABEL_DROPDOWN" label-for="project-select">
+      <project-dropdown
+        id="project-select"
+        :selected-project="selectedProject"
+        @change="onProjectSelect"
+        @error="onError"
+      />
+    </gl-form-group>
 
-    <gl-form @submit.prevent="onSubmit">
-      <gl-form-group
-        :label="$options.i18n.I18N_NEW_BRANCH_LABEL_DROPDOWN"
-        label-for="project-select"
-      >
-        <project-dropdown
-          id="project-select"
-          :selected-project="selectedProject"
-          @change="onProjectSelect"
-          @error="onError"
-        />
-      </gl-form-group>
+    <gl-form-group
+      :label="$options.i18n.I18N_NEW_BRANCH_LABEL_BRANCH"
+      label-for="branch-name-input"
+    >
+      <gl-form-input id="branch-name-input" v-model="branchName" type="text" required />
+    </gl-form-group>
 
-      <gl-form-group
-        :label="$options.i18n.I18N_NEW_BRANCH_LABEL_BRANCH"
-        label-for="branch-name-input"
-      >
-        <gl-form-input id="branch-name-input" v-model="branchName" type="text" required />
-      </gl-form-group>
+    <gl-form-group
+      :label="$options.i18n.I18N_NEW_BRANCH_LABEL_SOURCE"
+      label-for="source-branch-select"
+    >
+      <source-branch-dropdown
+        id="source-branch-select"
+        :selected-project="selectedProject"
+        :selected-branch-name="selectedSourceBranchName"
+        @change="onSourceBranchSelect"
+        @error="onError"
+      />
+    </gl-form-group>
 
-      <gl-form-group
-        :label="$options.i18n.I18N_NEW_BRANCH_LABEL_SOURCE"
-        label-for="source-branch-select"
+    <div class="form-actions">
+      <gl-button
+        :loading="createBranchLoading"
+        type="submit"
+        variant="confirm"
+        :disabled="disableSubmitButton"
       >
-        <source-branch-dropdown
-          id="source-branch-select"
-          :selected-project="selectedProject"
-          :selected-branch-name="selectedSourceBranchName"
-          @change="onSourceBranchSelect"
-          @error="onError"
-        />
-      </gl-form-group>
-
-      <div class="form-actions">
-        <gl-button
-          :loading="createBranchLoading"
-          type="submit"
-          variant="confirm"
-          :disabled="disableSubmitButton"
-        >
-          {{ $options.i18n.I18N_NEW_BRANCH_SUBMIT_BUTTON_TEXT }}
-        </gl-button>
-      </div>
-    </gl-form>
-  </div>
+        {{ $options.i18n.I18N_NEW_BRANCH_SUBMIT_BUTTON_TEXT }}
+      </gl-button>
+    </div>
+  </gl-form>
 </template>
diff --git a/app/assets/javascripts/jira_connect/branches/constants.js b/app/assets/javascripts/jira_connect/branches/constants.js
index 7095f123a9e22..ab9d3b2c110f0 100644
--- a/app/assets/javascripts/jira_connect/branches/constants.js
+++ b/app/assets/javascripts/jira_connect/branches/constants.js
@@ -3,7 +3,6 @@ import { __, s__ } from '~/locale';
 export const BRANCHES_PER_PAGE = 20;
 export const PROJECTS_PER_PAGE = 20;
 
-export const I18N_NEW_BRANCH_PAGE_TITLE = __('New branch');
 export const I18N_NEW_BRANCH_LABEL_DROPDOWN = __('Project');
 export const I18N_NEW_BRANCH_LABEL_BRANCH = __('Branch name');
 export const I18N_NEW_BRANCH_LABEL_SOURCE = __('Source branch');
@@ -14,7 +13,13 @@ export const CREATE_BRANCH_ERROR_GENERIC = s__(
 );
 export const CREATE_BRANCH_ERROR_WITH_CONTEXT = s__('JiraConnect|Failed to create branch.');
 
-export const CREATE_BRANCH_SUCCESS_ALERT = {
-  title: s__('JiraConnect|New branch was successfully created.'),
-  message: s__('JiraConnect|You can now close this window and return to Jira.'),
-};
+export const I18N_PAGE_TITLE_WITH_BRANCH_NAME = s__(
+  'JiraConnect|Create branch for Jira issue %{jiraIssue}',
+);
+export const I18N_PAGE_TITLE_DEFAULT = __('New branch');
+export const I18N_NEW_BRANCH_SUCCESS_TITLE = s__(
+  'JiraConnect|New branch was successfully created.',
+);
+export const I18N_NEW_BRANCH_SUCCESS_MESSAGE = s__(
+  'JiraConnect|You can now close this window and return to Jira.',
+);
diff --git a/app/assets/javascripts/jira_connect/branches/index.js b/app/assets/javascripts/jira_connect/branches/index.js
index b8fe255e31061..95bd4f5c6752f 100644
--- a/app/assets/javascripts/jira_connect/branches/index.js
+++ b/app/assets/javascripts/jira_connect/branches/index.js
@@ -1,6 +1,6 @@
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
-import JiraConnectNewBranchForm from '~/jira_connect/branches/components/new_branch_form.vue';
+import JiraConnectNewBranchPage from '~/jira_connect/branches/pages/index.vue';
 import createDefaultClient from '~/lib/graphql';
 
 Vue.use(VueApollo);
@@ -11,7 +11,7 @@ export default async function initJiraConnectBranches() {
     return null;
   }
 
-  const { initialBranchName } = el.dataset;
+  const { initialBranchName, successStateSvgPath } = el.dataset;
 
   const apolloProvider = new VueApollo({
     defaultClient: createDefaultClient(
@@ -25,12 +25,12 @@ export default async function initJiraConnectBranches() {
   return new Vue({
     el,
     apolloProvider,
+    provide: {
+      initialBranchName,
+      successStateSvgPath,
+    },
     render(createElement) {
-      return createElement(JiraConnectNewBranchForm, {
-        props: {
-          initialBranchName,
-        },
-      });
+      return createElement(JiraConnectNewBranchPage);
     },
   });
 }
diff --git a/app/assets/javascripts/jira_connect/branches/pages/index.vue b/app/assets/javascripts/jira_connect/branches/pages/index.vue
new file mode 100644
index 0000000000000..d72dec6cdee9f
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/branches/pages/index.vue
@@ -0,0 +1,60 @@
+<script>
+import { GlEmptyState } from '@gitlab/ui';
+import { sprintf } from '~/locale';
+import NewBranchForm from '../components/new_branch_form.vue';
+import {
+  I18N_PAGE_TITLE_WITH_BRANCH_NAME,
+  I18N_PAGE_TITLE_DEFAULT,
+  I18N_NEW_BRANCH_SUCCESS_TITLE,
+  I18N_NEW_BRANCH_SUCCESS_MESSAGE,
+} from '../constants';
+
+export default {
+  components: {
+    GlEmptyState,
+    NewBranchForm,
+  },
+  inject: ['initialBranchName', 'successStateSvgPath'],
+  data() {
+    return {
+      showForm: true,
+    };
+  },
+  computed: {
+    pageTitle() {
+      return this.initialBranchName
+        ? sprintf(this.$options.i18n.I18N_PAGE_TITLE_WITH_BRANCH_NAME, {
+            jiraIssue: this.initialBranchName,
+          })
+        : this.$options.i18n.I18N_PAGE_TITLE_DEFAULT;
+    },
+  },
+  methods: {
+    onNewBranchFormSuccess() {
+      // light-weight toggle to hide the form and show the success state
+      this.showForm = false;
+    },
+  },
+  i18n: {
+    I18N_PAGE_TITLE_WITH_BRANCH_NAME,
+    I18N_PAGE_TITLE_DEFAULT,
+    I18N_NEW_BRANCH_SUCCESS_TITLE,
+    I18N_NEW_BRANCH_SUCCESS_MESSAGE,
+  },
+};
+</script>
+<template>
+  <div>
+    <div class="gl-border-1 gl-border-b-solid gl-border-gray-100 gl-mb-5 gl-mt-7">
+      <h1 data-testid="page-title" class="page-title">{{ pageTitle }}</h1>
+    </div>
+
+    <new-branch-form v-if="showForm" @success="onNewBranchFormSuccess" />
+    <gl-empty-state
+      v-else
+      :title="$options.i18n.I18N_NEW_BRANCH_SUCCESS_TITLE"
+      :description="$options.i18n.I18N_NEW_BRANCH_SUCCESS_MESSAGE"
+      :svg-path="successStateSvgPath"
+    />
+  </div>
+</template>
diff --git a/app/controllers/jira_connect/branches_controller.rb b/app/controllers/jira_connect/branches_controller.rb
index 7d7faae62a5f7..97d0b75639e93 100644
--- a/app/controllers/jira_connect/branches_controller.rb
+++ b/app/controllers/jira_connect/branches_controller.rb
@@ -8,20 +8,31 @@ class JiraConnect::BranchesController < ApplicationController
   feature_category :integrations
 
   def new
+    @new_branch_data = new_branch_data
+  end
+
+  def self.feature_enabled?(user)
+    Feature.enabled?(:jira_connect_create_branch, user, default_enabled: :yaml)
+  end
+
+  private
+
+  def initial_branch_name
     return unless params[:issue_key].present?
 
-    @branch_name = Issue.to_branch_name(
+    Issue.to_branch_name(
       params[:issue_key],
       params[:issue_summary]
     )
   end
 
-  def self.feature_enabled?(user)
-    Feature.enabled?(:jira_connect_create_branch, user, default_enabled: :yaml)
+  def new_branch_data
+    {
+      initial_branch_name: initial_branch_name,
+      success_state_svg_path: ActionController::Base.helpers.image_path('illustrations/merge_requests.svg')
+    }
   end
 
-  private
-
   def feature_enabled!
     render_404 unless self.class.feature_enabled?(current_user)
   end
diff --git a/app/views/jira_connect/branches/new.html.haml b/app/views/jira_connect/branches/new.html.haml
index ec2b7be47ca6f..f0e34c3001869 100644
--- a/app/views/jira_connect/branches/new.html.haml
+++ b/app/views/jira_connect/branches/new.html.haml
@@ -2,4 +2,4 @@
 - @hide_top_links = true
 - page_title _('New branch')
 
-.js-jira-connect-create-branch{ data: { initial_branch_name: @branch_name } }
+.js-jira-connect-create-branch{ data: @new_branch_data }
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 22e6e93d5535c..cd5002e351f08 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18692,6 +18692,9 @@ msgstr ""
 msgid "Jira-GitLab user mapping template"
 msgstr ""
 
+msgid "JiraConnect|Create branch for Jira issue %{jiraIssue}"
+msgstr ""
+
 msgid "JiraConnect|Failed to create branch."
 msgstr ""
 
diff --git a/spec/controllers/jira_connect/branches_controller_spec.rb b/spec/controllers/jira_connect/branches_controller_spec.rb
index 4b198eb882047..31f6860891829 100644
--- a/spec/controllers/jira_connect/branches_controller_spec.rb
+++ b/spec/controllers/jira_connect/branches_controller_spec.rb
@@ -15,21 +15,24 @@
         get :new, params: { issue_key: 'ACME-123', issue_summary: 'My Issue !@#$%' }
 
         expect(response).to be_successful
-        expect(assigns(:branch_name)).to eq('ACME-123-my-issue')
+        expect(assigns(:new_branch_data)).to include(
+          initial_branch_name: 'ACME-123-my-issue',
+          success_state_svg_path: start_with('/assets/illustrations/merge_requests-')
+        )
       end
 
       it 'ignores missing summary' do
         get :new, params: { issue_key: 'ACME-123' }
 
         expect(response).to be_successful
-        expect(assigns(:branch_name)).to eq('ACME-123')
+        expect(assigns(:new_branch_data)).to include(initial_branch_name: 'ACME-123')
       end
 
       it 'does not set a branch name if key is not passed' do
         get :new, params: { issue_summary: 'My issue' }
 
         expect(response).to be_successful
-        expect(assigns(:branch_name)).to be_nil
+        expect(assigns(:new_branch_data)).to include('initial_branch_name': nil)
       end
 
       context 'when feature flag is disabled' do
diff --git a/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js b/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
index 7ea47c2737ab3..7326b84ad54ec 100644
--- a/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
+++ b/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
@@ -9,7 +9,6 @@ import SourceBranchDropdown from '~/jira_connect/branches/components/source_bran
 import {
   CREATE_BRANCH_ERROR_GENERIC,
   CREATE_BRANCH_ERROR_WITH_CONTEXT,
-  CREATE_BRANCH_SUCCESS_ALERT,
 } from '~/jira_connect/branches/constants';
 import createBranchMutation from '~/jira_connect/branches/graphql/mutations/create_branch.mutation.graphql';
 
@@ -74,10 +73,14 @@ describe('NewBranchForm', () => {
     return mockApollo;
   }
 
-  function createComponent({ mockApollo } = {}) {
+  function createComponent({ mockApollo, provide } = {}) {
     wrapper = shallowMount(NewBranchForm, {
       localVue,
       apolloProvider: mockApollo || createMockApolloProvider(),
+      provide: {
+        initialBranchName: '',
+        ...provide,
+      },
     });
   }
 
@@ -139,14 +142,8 @@ describe('NewBranchForm', () => {
         await waitForPromises();
       });
 
-      it('displays a success message', () => {
-        const alert = findAlert();
-        expect(alert.exists()).toBe(true);
-        expect(alert.text()).toBe(CREATE_BRANCH_SUCCESS_ALERT.message);
-        expect(alert.props()).toMatchObject({
-          title: CREATE_BRANCH_SUCCESS_ALERT.title,
-          variant: 'success',
-        });
+      it('emits `success` event', () => {
+        expect(wrapper.emitted('success')).toBeTruthy();
       });
 
       it('called `createBranch` mutation correctly', () => {
@@ -195,6 +192,15 @@ describe('NewBranchForm', () => {
     });
   });
 
+  describe('when `initialBranchName` is specified', () => {
+    it('sets value of branch name input to `initialBranchName` by default', () => {
+      const mockInitialBranchName = 'ap1-test-branch-name';
+
+      createComponent({ provide: { initialBranchName: mockInitialBranchName } });
+      expect(findInput().attributes('value')).toBe(mockInitialBranchName);
+    });
+  });
+
   describe('error handling', () => {
     describe.each`
       component               | componentName
diff --git a/spec/frontend/jira_connect/branches/pages/index_spec.js b/spec/frontend/jira_connect/branches/pages/index_spec.js
new file mode 100644
index 0000000000000..92976dd28dafa
--- /dev/null
+++ b/spec/frontend/jira_connect/branches/pages/index_spec.js
@@ -0,0 +1,65 @@
+import { GlEmptyState } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import NewBranchForm from '~/jira_connect/branches/components/new_branch_form.vue';
+import {
+  I18N_PAGE_TITLE_WITH_BRANCH_NAME,
+  I18N_PAGE_TITLE_DEFAULT,
+} from '~/jira_connect/branches/constants';
+import JiraConnectNewBranchPage from '~/jira_connect/branches/pages/index.vue';
+import { sprintf } from '~/locale';
+
+describe('NewBranchForm', () => {
+  let wrapper;
+
+  const findPageTitle = () => wrapper.find('h1');
+  const findNewBranchForm = () => wrapper.findComponent(NewBranchForm);
+  const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+  function createComponent({ provide } = {}) {
+    wrapper = shallowMount(JiraConnectNewBranchPage, {
+      provide: {
+        initialBranchName: '',
+        successStateSvgPath: '',
+        ...provide,
+      },
+    });
+  }
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
+
+  describe('page title', () => {
+    it.each`
+      initialBranchName    | pageTitle
+      ${undefined}         | ${I18N_PAGE_TITLE_DEFAULT}
+      ${'ap1-test-button'} | ${sprintf(I18N_PAGE_TITLE_WITH_BRANCH_NAME, { jiraIssue: 'ap1-test-button' })}
+    `(
+      'sets page title to "$pageTitle" when initial branch name is "$initialBranchName"',
+      ({ initialBranchName, pageTitle }) => {
+        createComponent({ provide: { initialBranchName } });
+
+        expect(findPageTitle().text()).toBe(pageTitle);
+      },
+    );
+  });
+
+  it('renders NewBranchForm by default', () => {
+    createComponent();
+
+    expect(findNewBranchForm().exists()).toBe(true);
+    expect(findEmptyState().exists()).toBe(false);
+  });
+
+  describe('when `sucesss` event emitted from NewBranchForm', () => {
+    it('renders the success state', async () => {
+      createComponent();
+
+      const newBranchForm = findNewBranchForm();
+      await newBranchForm.vm.$emit('success');
+
+      expect(findNewBranchForm().exists()).toBe(false);
+      expect(findEmptyState().exists()).toBe(true);
+    });
+  });
+});
-- 
GitLab