From eb92900c3440f96e88501b01d2d14cf7f1ae8f3f Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Thu, 13 Dec 2018 11:50:40 -0600 Subject: [PATCH 1/4] Fix typo and i18n in mr approval settings form --- .../projects/_merge_request_approvals_settings_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml b/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml index 83bb1e4dbe247b..dbc2c5c29427d7 100644 --- a/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml +++ b/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml @@ -7,7 +7,7 @@ = hidden_field_tag "project[approver_group_ids]" .input-group.input-btn-group = hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project', :style => "max-width: unset;" } - %button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' } + %button.btn.btn-success.js-add-approvers{ type: 'button', title: _('Add approver(s)') } = _("Add") .form-text.text-muted = _("Add users or groups who are allowed to approve every merge request") -- GitLab From 8c315434bf0e892f77feb83031787cb23d185e50 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Mon, 17 Dec 2018 16:07:56 -0600 Subject: [PATCH 2/4] Refactor "approvals" into directory **Why?** This directory will host the Vue components for multi rule approvals. https://gitlab.com/gitlab-org/gitlab-ee/issues/1979 --- .../setup_single_rule_approvals.js} | 0 .../pages/projects/merge_requests/shared/init_form.js | 2 +- ...pprovals_spec.js => setup_single_rule_approvals_spec.js} | 6 +++--- .../components}/approvals/approvals_body_spec.js | 0 .../components}/approvals/approvals_footer_spec.js | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename ee/app/assets/javascripts/{approvals.js => approvals/setup_single_rule_approvals.js} (100%) rename ee/spec/javascripts/approvals/{approvals_spec.js => setup_single_rule_approvals_spec.js} (95%) rename ee/spec/javascripts/{ => vue_mr_widget/components}/approvals/approvals_body_spec.js (100%) rename ee/spec/javascripts/{ => vue_mr_widget/components}/approvals/approvals_footer_spec.js (100%) diff --git a/ee/app/assets/javascripts/approvals.js b/ee/app/assets/javascripts/approvals/setup_single_rule_approvals.js similarity index 100% rename from ee/app/assets/javascripts/approvals.js rename to ee/app/assets/javascripts/approvals/setup_single_rule_approvals.js diff --git a/ee/app/assets/javascripts/pages/projects/merge_requests/shared/init_form.js b/ee/app/assets/javascripts/pages/projects/merge_requests/shared/init_form.js index b3d9865a2d4f7b..234a74a5a8e827 100644 --- a/ee/app/assets/javascripts/pages/projects/merge_requests/shared/init_form.js +++ b/ee/app/assets/javascripts/pages/projects/merge_requests/shared/init_form.js @@ -1,3 +1,3 @@ -import initApprovals from '../../../../approvals'; +import initApprovals from 'ee/approvals/setup_single_rule_approvals'; export default () => initApprovals(); diff --git a/ee/spec/javascripts/approvals/approvals_spec.js b/ee/spec/javascripts/approvals/setup_single_rule_approvals_spec.js similarity index 95% rename from ee/spec/javascripts/approvals/approvals_spec.js rename to ee/spec/javascripts/approvals/setup_single_rule_approvals_spec.js index 137635cc872cb2..28a18bb9e77c46 100644 --- a/ee/spec/javascripts/approvals/approvals_spec.js +++ b/ee/spec/javascripts/approvals/setup_single_rule_approvals_spec.js @@ -1,7 +1,7 @@ import $ from 'jquery'; -import approvals from 'ee/approvals'; +import setup from 'ee/approvals/setup_single_rule_approvals'; -describe('Approvals', () => { +describe('EE setup_single_rule_approvals', () => { preloadFixtures('merge_requests_ee/merge_request_edit.html.raw'); let $approversEl; @@ -11,7 +11,7 @@ describe('Approvals', () => { loadFixtures('merge_requests_ee/merge_request_edit.html.raw'); $approversEl = $('ul.approver-list'); $suggestionEl = $('.suggested-approvers'); - approvals(); + setup(); }); describe('add suggested approver', () => { diff --git a/ee/spec/javascripts/approvals/approvals_body_spec.js b/ee/spec/javascripts/vue_mr_widget/components/approvals/approvals_body_spec.js similarity index 100% rename from ee/spec/javascripts/approvals/approvals_body_spec.js rename to ee/spec/javascripts/vue_mr_widget/components/approvals/approvals_body_spec.js diff --git a/ee/spec/javascripts/approvals/approvals_footer_spec.js b/ee/spec/javascripts/vue_mr_widget/components/approvals/approvals_footer_spec.js similarity index 100% rename from ee/spec/javascripts/approvals/approvals_footer_spec.js rename to ee/spec/javascripts/vue_mr_widget/components/approvals/approvals_footer_spec.js -- GitLab From cd73950fac3fa6c7f850d6b45165f795c6828767 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Mon, 17 Dec 2018 16:39:22 -0600 Subject: [PATCH 3/4] Integrate multiple approval rules settings - This is behind the feature flag `:apporval_rule` --- .../javascripts/pages/projects/edit/index.js | 2 + ..._request_approvals_settings_form.html.haml | 59 ++----------------- .../_multiple_rules_form.html.haml | 6 ++ .../_single_rule_form.html.haml | 55 +++++++++++++++++ .../settings/merge_requests_settings_spec.rb | 2 + 5 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 ee/app/views/shared/merge_request_approvals_settings/_multiple_rules_form.html.haml create mode 100644 ee/app/views/shared/merge_request_approvals_settings/_single_rule_form.html.haml diff --git a/ee/app/assets/javascripts/pages/projects/edit/index.js b/ee/app/assets/javascripts/pages/projects/edit/index.js index c62a23c057693b..5aeb5cb0640925 100644 --- a/ee/app/assets/javascripts/pages/projects/edit/index.js +++ b/ee/app/assets/javascripts/pages/projects/edit/index.js @@ -5,6 +5,7 @@ import UsersSelect from '~/users_select'; import UserCallout from '~/user_callout'; import groupsSelect from '~/groups_select'; import ApproversSelect from 'ee/approvers_select'; +import mountApprovalsSettings from 'ee/approvals'; import initServiceDesk from 'ee/projects/settings_service_desk'; document.addEventListener('DOMContentLoaded', () => { @@ -15,4 +16,5 @@ document.addEventListener('DOMContentLoaded', () => { new UserCallout({ className: 'js-mr-approval-callout' }); new ApproversSelect(); initServiceDesk(); + mountApprovalsSettings(document.getElementById('js-mr-approvals-settings')); }); diff --git a/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml b/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml index dbc2c5c29427d7..9218c2ffb99f15 100644 --- a/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml +++ b/ee/app/views/projects/_merge_request_approvals_settings_form.html.haml @@ -1,60 +1,9 @@ - can_override_approvers = project.can_override_approvers? -.form-group - = form.label :approver_ids, class: 'label-bold' do - = _("Approvers") - = hidden_field_tag "project[approver_ids]" - = hidden_field_tag "project[approver_group_ids]" - .input-group.input-btn-group - = hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project', :style => "max-width: unset;" } - %button.btn.btn-success.js-add-approvers{ type: 'button', title: _('Add approver(s)') } - = _("Add") - .form-text.text-muted - = _("Add users or groups who are allowed to approve every merge request") - - .card.prepend-top-10.js-current-approvers - .load-wrapper.hidden - = icon('spinner spin', class: 'approver-list-loader') - .card-header - = _("Approvers") - %span.badge.badge-pill - - ids = [] - - project.approvers.each do |user| - - ids << user.user_id - - project.approver_groups.each do |group| - - group.users.each do |user| - - unless ids.include?(user.id) - - ids << user.id - = ids.count - %ul.content-list.approver-list - - project.approvers.each do |approver| - %li.approver.settings-flex-row.js-approver{ data: { id: approver.user_id } } - = link_to approver.user.name, approver.user - .float-right - - confirm_message = _("Are you sure you want to remove approver %{name}?") % { name: approver.user.name } - %button{ href: project_approver_path(project, approver), data: { confirm: confirm_message }, class: "btn btn-remove js-approver-remove", title: _("Remove approver") } - = icon("trash") - - project.approver_groups.each do |approver_group| - %li.approver-group.settings-flex-row.js-approver-group{ data: { id: approver_group.group.id } } - %span - %span.light - = _("Group:") - = link_to approver_group.group.name, approver_group.group - %span.badge.badge-pill - = approver_group.group.members.count - .float-right - - confirm_message = _("Are you sure you want to remove group %{name}?") % { name: approver_group.group.name } - %button{ href: project_approver_group_path(project, approver_group), data: { confirm: confirm_message }, class: "btn btn-remove js-approver-remove", title: _("Remove group") } - = icon("trash") - - if project.approvers.empty? && project.approver_groups.empty? - %li= _("There are no approvers") - -.form-group - = form.label :approvals_before_merge, class: 'label-bold' do - = _("Approvals required") - = form.number_field :approvals_before_merge, class: "form-control", min: 0 - .form-text.text-muted - = _("Set number of approvers required before open merge requests can be merged") +- if Feature.enabled?(:approval_rule, project) + = render 'shared/merge_request_approvals_settings/multiple_rules_form', form: form, project: project +- else + = render 'shared/merge_request_approvals_settings/single_rule_form', form: form, project: project .form-group .form-check diff --git a/ee/app/views/shared/merge_request_approvals_settings/_multiple_rules_form.html.haml b/ee/app/views/shared/merge_request_approvals_settings/_multiple_rules_form.html.haml new file mode 100644 index 00000000000000..1de91e37206b6c --- /dev/null +++ b/ee/app/views/shared/merge_request_approvals_settings/_multiple_rules_form.html.haml @@ -0,0 +1,6 @@ +.form-group + = form.label :approver_ids, class: 'label-bold' do + = _("Approvers") + #js-mr-approvals-settings{ data: { 'project_id': @project.id } } + .text-center.prepend-top-default + = sprite_icon('spinner', size: 24, css_class: 'gl-spinner') diff --git a/ee/app/views/shared/merge_request_approvals_settings/_single_rule_form.html.haml b/ee/app/views/shared/merge_request_approvals_settings/_single_rule_form.html.haml new file mode 100644 index 00000000000000..20981d58abc128 --- /dev/null +++ b/ee/app/views/shared/merge_request_approvals_settings/_single_rule_form.html.haml @@ -0,0 +1,55 @@ +.form-group + = form.label :approver_ids, class: 'label-bold' do + = _("Approvers") + = hidden_field_tag "project[approver_ids]" + = hidden_field_tag "project[approver_group_ids]" + .input-group.input-btn-group + = hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project', :style => "max-width: unset;" } + %button.btn.btn-success.js-add-approvers{ type: 'button', title: _('Add approver(s)') } + = _("Add") + .form-text.text-muted + = _("Add users or groups who are allowed to approve every merge request") + + .card.prepend-top-10.js-current-approvers + .load-wrapper.hidden + = icon('spinner spin', class: 'approver-list-loader') + .card-header + = _("Approvers") + %span.badge.badge-pill + - ids = [] + - project.approvers.each do |user| + - ids << user.user_id + - project.approver_groups.each do |group| + - group.users.each do |user| + - unless ids.include?(user.id) + - ids << user.id + = ids.count + %ul.content-list.approver-list + - project.approvers.each do |approver| + %li.approver.settings-flex-row.js-approver{ data: { id: approver.user_id } } + = link_to approver.user.name, approver.user + .float-right + - confirm_message = _("Are you sure you want to remove approver %{name}?") % { name: approver.user.name } + %button{ href: project_approver_path(project, approver), data: { confirm: confirm_message }, class: "btn btn-remove js-approver-remove", title: _("Remove approver") } + = icon("trash") + - project.approver_groups.each do |approver_group| + %li.approver-group.settings-flex-row.js-approver-group{ data: { id: approver_group.group.id } } + %span + %span.light + = _("Group:") + = link_to approver_group.group.name, approver_group.group + %span.badge.badge-pill + = approver_group.group.members.count + .float-right + - confirm_message = _("Are you sure you want to remove group %{name}?") % { name: approver_group.group.name } + %button{ href: project_approver_group_path(project, approver_group), data: { confirm: confirm_message }, class: "btn btn-remove js-approver-remove", title: _("Remove group") } + = icon("trash") + - if project.approvers.empty? && project.approver_groups.empty? + %li= _("There are no approvers") + +.form-group + = form.label :approvals_before_merge, class: 'label-bold' do + = _("Approvals required") + = form.number_field :approvals_before_merge, class: "form-control", min: 0 + .form-text.text-muted + = _("Set number of approvers required before open merge requests can be merged") diff --git a/ee/spec/features/projects/settings/merge_requests_settings_spec.rb b/ee/spec/features/projects/settings/merge_requests_settings_spec.rb index dd5d1f3a9cb772..c3d416321d2e05 100644 --- a/ee/spec/features/projects/settings/merge_requests_settings_spec.rb +++ b/ee/spec/features/projects/settings/merge_requests_settings_spec.rb @@ -10,6 +10,8 @@ let(:non_member) { create(:user) } before do + stub_feature_flags(approval_rule: false) + sign_in(user) project.add_maintainer(user) group.add_developer(user) -- GitLab From 0e8502f7bb39192f60950790e949e6ffa0ba3457 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Tue, 18 Dec 2018 14:50:18 -0600 Subject: [PATCH 4/4] Create empty state approvals settings form **Note:** - This includes a stubbed service in the FE - This is an iteration towards https://gitlab.com/gitlab-org/gitlab-ee/issues/1979 --- .../components/approval_rules_empty.vue | 28 +++++ .../approvals/components/settings.vue | 29 +++++ ee/app/assets/javascripts/approvals/index.js | 23 ++++ .../services/approvals_service_stub.js | 20 ++++ .../javascripts/approvals/stores/actions.js | 34 ++++++ .../javascripts/approvals/stores/index.js | 11 ++ .../approvals/stores/mutation_types.js | 3 + .../javascripts/approvals/stores/mutations.js | 13 +++ .../javascripts/approvals/stores/state.js | 5 + .../components/approval_rules_empty_spec.js | 42 ++++++++ .../approvals/components/settings_spec.js | 71 ++++++++++++ .../approvals/stores/actions_spec.js | 102 ++++++++++++++++++ .../approvals/stores/mutations_spec.js | 43 ++++++++ locale/gitlab.pot | 12 +++ 14 files changed, 436 insertions(+) create mode 100644 ee/app/assets/javascripts/approvals/components/approval_rules_empty.vue create mode 100644 ee/app/assets/javascripts/approvals/components/settings.vue create mode 100644 ee/app/assets/javascripts/approvals/index.js create mode 100644 ee/app/assets/javascripts/approvals/services/approvals_service_stub.js create mode 100644 ee/app/assets/javascripts/approvals/stores/actions.js create mode 100644 ee/app/assets/javascripts/approvals/stores/index.js create mode 100644 ee/app/assets/javascripts/approvals/stores/mutation_types.js create mode 100644 ee/app/assets/javascripts/approvals/stores/mutations.js create mode 100644 ee/app/assets/javascripts/approvals/stores/state.js create mode 100644 ee/spec/javascripts/approvals/components/approval_rules_empty_spec.js create mode 100644 ee/spec/javascripts/approvals/components/settings_spec.js create mode 100644 ee/spec/javascripts/approvals/stores/actions_spec.js create mode 100644 ee/spec/javascripts/approvals/stores/mutations_spec.js diff --git a/ee/app/assets/javascripts/approvals/components/approval_rules_empty.vue b/ee/app/assets/javascripts/approvals/components/approval_rules_empty.vue new file mode 100644 index 00000000000000..251f8b8e346832 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/components/approval_rules_empty.vue @@ -0,0 +1,28 @@ + + + diff --git a/ee/app/assets/javascripts/approvals/components/settings.vue b/ee/app/assets/javascripts/approvals/components/settings.vue new file mode 100644 index 00000000000000..e0d9f85f159d56 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/components/settings.vue @@ -0,0 +1,29 @@ + + + diff --git a/ee/app/assets/javascripts/approvals/index.js b/ee/app/assets/javascripts/approvals/index.js new file mode 100644 index 00000000000000..71dde3690c0e01 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/index.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import createStore from './stores'; +import Settings from './components/settings.vue'; + +Vue.use(Vuex); + +export default function mountApprovalsSettings(el) { + if (!el) { + return null; + } + + const store = createStore(); + store.dispatch('setSettings', el.dataset); + + return new Vue({ + el, + store, + render(h) { + return h(Settings); + }, + }); +} diff --git a/ee/app/assets/javascripts/approvals/services/approvals_service_stub.js b/ee/app/assets/javascripts/approvals/services/approvals_service_stub.js new file mode 100644 index 00000000000000..4288f874520664 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/services/approvals_service_stub.js @@ -0,0 +1,20 @@ +/** + * This provides a stubbed API for approval rule requests. + * + * **PLEASE NOTE:** + * - This class will be removed when the BE is merged for https://gitlab.com/gitlab-org/gitlab-ee/issues/1979 + */ + +export function createApprovalsServiceStub() { + const projectApprovalRules = []; + + return { + getProjectApprovalRules() { + return Promise.resolve({ + data: { rules: projectApprovalRules }, + }); + }, + }; +} + +export default createApprovalsServiceStub(); diff --git a/ee/app/assets/javascripts/approvals/stores/actions.js b/ee/app/assets/javascripts/approvals/stores/actions.js new file mode 100644 index 00000000000000..1b121b508783b0 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/stores/actions.js @@ -0,0 +1,34 @@ +import createFlash from '~/flash'; +import { __ } from '~/locale'; +import * as types from './mutation_types'; +import service from '../services/approvals_service_stub'; + +export const setSettings = ({ commit }, settings) => { + commit(types.SET_SETTINGS, settings); +}; + +export const requestRules = ({ commit }) => { + commit(types.SET_LOADING, true); +}; + +export const receiveRulesSuccess = ({ commit }, { rules }) => { + commit(types.SET_RULES, rules); + commit(types.SET_LOADING, false); +}; + +export const receiveRulesError = () => { + createFlash(__('An error occurred fetching the approval rules.')); +}; + +export const fetchRules = ({ state, dispatch }) => { + if (state.isLoading) { + return; + } + + dispatch('requestRules'); + + service + .getProjectApprovalRules() + .then(response => dispatch('receiveRulesSuccess', response.data)) + .catch(() => dispatch('receiveRulesError')); +}; diff --git a/ee/app/assets/javascripts/approvals/stores/index.js b/ee/app/assets/javascripts/approvals/stores/index.js new file mode 100644 index 00000000000000..64c78d2cb4ab7d --- /dev/null +++ b/ee/app/assets/javascripts/approvals/stores/index.js @@ -0,0 +1,11 @@ +import Vuex from 'vuex'; +import state from './state'; +import mutations from './mutations'; +import * as actions from './actions'; + +export default () => + new Vuex.Store({ + state: state(), + mutations, + actions, + }); diff --git a/ee/app/assets/javascripts/approvals/stores/mutation_types.js b/ee/app/assets/javascripts/approvals/stores/mutation_types.js new file mode 100644 index 00000000000000..ed91b8e4bea363 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/stores/mutation_types.js @@ -0,0 +1,3 @@ +export const SET_SETTINGS = 'SET_SETTINGS'; +export const SET_LOADING = 'SET_LOADING'; +export const SET_RULES = 'SET_RULES'; diff --git a/ee/app/assets/javascripts/approvals/stores/mutations.js b/ee/app/assets/javascripts/approvals/stores/mutations.js new file mode 100644 index 00000000000000..228a4765ab4232 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/stores/mutations.js @@ -0,0 +1,13 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_SETTINGS](state, settings) { + state.settings = { ...settings }; + }, + [types.SET_LOADING](state, isLoading) { + state.isLoading = isLoading; + }, + [types.SET_RULES](state, rules) { + state.rules = rules; + }, +}; diff --git a/ee/app/assets/javascripts/approvals/stores/state.js b/ee/app/assets/javascripts/approvals/stores/state.js new file mode 100644 index 00000000000000..34bde76e505335 --- /dev/null +++ b/ee/app/assets/javascripts/approvals/stores/state.js @@ -0,0 +1,5 @@ +export default () => ({ + settings: {}, + isLoading: false, + rules: [], +}); diff --git a/ee/spec/javascripts/approvals/components/approval_rules_empty_spec.js b/ee/spec/javascripts/approvals/components/approval_rules_empty_spec.js new file mode 100644 index 00000000000000..47c65198080935 --- /dev/null +++ b/ee/spec/javascripts/approvals/components/approval_rules_empty_spec.js @@ -0,0 +1,42 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; +import ApprovalRulesEmpty from 'ee/approvals/components/approval_rules_empty.vue'; + +const localVue = createLocalVue(); + +describe('EE ApprovalsSettingsEmpty', () => { + let wrapper; + + const factory = options => { + wrapper = shallowMount(localVue.extend(ApprovalRulesEmpty), { + localVue, + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('shows message', () => { + factory(); + + expect(wrapper.text()).toContain(ApprovalRulesEmpty.message); + }); + + it('shows button', () => { + factory(); + + expect(wrapper.find(GlButton).exists()).toBe(true); + }); + + it('emits "click" on button press', () => { + factory(); + + expect(wrapper.emittedByOrder().length).toEqual(0); + + wrapper.find(GlButton).vm.$emit('click'); + + expect(wrapper.emittedByOrder().map(x => x.name)).toEqual(['click']); + }); +}); diff --git a/ee/spec/javascripts/approvals/components/settings_spec.js b/ee/spec/javascripts/approvals/components/settings_spec.js new file mode 100644 index 00000000000000..a01ae979bb1f0f --- /dev/null +++ b/ee/spec/javascripts/approvals/components/settings_spec.js @@ -0,0 +1,71 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; +import ApprovalRulesEmpty from 'ee/approvals/components/approval_rules_empty.vue'; +import Settings from 'ee/approvals/components/settings.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('EE ApprovalsSettingsForm', () => { + let state; + let actions; + let wrapper; + + const factory = () => { + const store = new Vuex.Store({ + state, + actions, + }); + + wrapper = shallowMount(localVue.extend(Settings), { + localVue, + store, + sync: false, + }); + }; + + beforeEach(() => { + state = {}; + + actions = { + fetchRules: jasmine.createSpy('fetchRules'), + }; + }); + + it('dispatches fetchRules action on created', () => { + expect(actions.fetchRules).not.toHaveBeenCalled(); + + factory(); + + expect(actions.fetchRules).toHaveBeenCalledTimes(1); + }); + + it('shows loading icon if loading', () => { + state.isLoading = true; + factory(); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('does not show loading icon if not loading', () => { + state.isLoading = false; + factory(); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + }); + + it('shows ApprovalsSettingsEmpty if empty', () => { + state.rules = []; + factory(); + + expect(wrapper.find(ApprovalRulesEmpty).exists()).toBe(true); + }); + + it('does not show ApprovalsSettingsEmpty is not empty', () => { + state.rules = [{ id: 1 }]; + factory(); + + expect(wrapper.find(ApprovalRulesEmpty).exists()).toBe(false); + }); +}); diff --git a/ee/spec/javascripts/approvals/stores/actions_spec.js b/ee/spec/javascripts/approvals/stores/actions_spec.js new file mode 100644 index 00000000000000..05ee18d8d75794 --- /dev/null +++ b/ee/spec/javascripts/approvals/stores/actions_spec.js @@ -0,0 +1,102 @@ +import testAction from 'spec/helpers/vuex_action_helper'; +import * as types from 'ee/approvals/stores/mutation_types'; +import actionsModule, * as actions from 'ee/approvals/stores/actions'; +import service from 'ee/approvals/services/approvals_service_stub'; + +describe('EE approvals store actions', () => { + let flashSpy; + + beforeEach(() => { + flashSpy = spyOnDependency(actionsModule, 'createFlash'); + spyOn(service, 'getProjectApprovalRules'); + }); + + describe('setSettings', () => { + it('sets the settings', done => { + const settings = { projectId: 7 }; + + testAction( + actions.setSettings, + settings, + {}, + [{ type: types.SET_SETTINGS, payload: settings }], + [], + done, + ); + }); + }); + + describe('requestRules', () => { + it('sets loading', done => { + testAction( + actions.requestRules, + null, + {}, + [{ type: types.SET_LOADING, payload: true }], + [], + done, + ); + }); + }); + + describe('receiveRulesSuccess', () => { + it('sets rules', done => { + const rules = [{ id: 1 }]; + + testAction( + actions.receiveRulesSuccess, + { rules }, + {}, + [{ type: types.SET_RULES, payload: rules }, { type: types.SET_LOADING, payload: false }], + [], + done, + ); + }); + }); + + describe('receiveRulesError', () => { + it('creates a flash', () => { + expect(flashSpy).not.toHaveBeenCalled(); + + actions.receiveRulesError(); + + expect(flashSpy).toHaveBeenCalledTimes(1); + expect(flashSpy).toHaveBeenCalledWith(jasmine.stringMatching('error occurred')); + }); + }); + + describe('fetchRules', () => { + it('does nothing if loading', done => { + testAction(actions.fetchRules, null, { isLoading: true }, [], [], done); + }); + + it('dispatches request/receive', done => { + const response = { + data: { rules: [] }, + }; + service.getProjectApprovalRules.and.returnValue(Promise.resolve(response)); + + testAction( + actions.fetchRules, + null, + {}, + [], + [{ type: 'requestRules' }, { type: 'receiveRulesSuccess', payload: response.data }], + done, + ); + }); + + it('dispatches request/receive on error', done => { + service.getProjectApprovalRules.and.returnValue(Promise.reject()); + + testAction( + actions.fetchRules, + null, + {}, + [], + [{ type: 'requestRules' }, { type: 'receiveRulesError' }], + done, + ); + }); + }); +}); diff --git a/ee/spec/javascripts/approvals/stores/mutations_spec.js b/ee/spec/javascripts/approvals/stores/mutations_spec.js new file mode 100644 index 00000000000000..9c5a4f95a86ea8 --- /dev/null +++ b/ee/spec/javascripts/approvals/stores/mutations_spec.js @@ -0,0 +1,43 @@ +import createState from 'ee/approvals/stores/state'; +import * as types from 'ee/approvals/stores/mutation_types'; +import mutations from 'ee/approvals/stores/mutations'; + +describe('EE approvals store mutations', () => { + let state; + + beforeEach(() => { + state = createState(); + }); + + describe(types.SET_SETTINGS, () => { + it('sets the settings', () => { + const newSettings = { projectId: 7 }; + + mutations[types.SET_SETTINGS](state, newSettings); + + expect(state.settings).toEqual(newSettings); + }); + }); + + describe(types.SET_LOADING, () => { + it('sets isLoading', () => { + state.isLoading = false; + + mutations[types.SET_LOADING](state, true); + + expect(state.isLoading).toBe(true); + }); + }); + + describe(types.SET_RULES, () => { + it('sets rules', () => { + const newRules = [{ id: 1 }, { id: 2 }]; + + state.rules = []; + + mutations[types.SET_RULES](state, newRules); + + expect(state.rules).toEqual(newRules); + }); + }); +}); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e2a2eead377454..0106ce0243d0bb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -453,6 +453,12 @@ msgstr "" msgid "Add additional text to appear in all email communications. %{character_limit} character limit" msgstr "" +msgid "Add approver(s)" +msgstr "" + +msgid "Add approvers" +msgstr "" + msgid "Add comment now" msgstr "" @@ -662,6 +668,9 @@ msgstr "" msgid "An error occurred creating the new branch." msgstr "" +msgid "An error occurred fetching the approval rules." +msgstr "" + msgid "An error occurred previewing the blob" msgstr "" @@ -8724,6 +8733,9 @@ msgstr "" msgid "There are no approvers" msgstr "" +msgid "There are no approvers explicitly added for this project. Members who are of Developer role or higher and code owners (if any) are eligible to approve." +msgstr "" + msgid "There are no archived projects yet" msgstr "" -- GitLab