Skip to content
Snippets Groups Projects
Verified Commit 6837ad94 authored by Paul Slaughter's avatar Paul Slaughter :two:
Browse files

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
parent f8bdb988
No related branches found
No related tags found
No related merge requests found
Showing
with 436 additions and 0 deletions
<script>
import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import Callout from '~/vue_shared/components/callout.vue';
const message = __(
'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.',
);
export default {
components: {
Callout,
GlButton,
},
message,
};
</script>
<template>
<callout category="info">
<div>{{ $options.message }}</div>
<div class="prepend-top-default">
<gl-button class="btn-info btn-inverted" @click="$emit('click');">{{
__('Add approvers')
}}</gl-button>
</div>
</callout>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import ApprovalRulesEmpty from './approval_rules_empty.vue';
export default {
components: {
GlLoadingIcon,
ApprovalRulesEmpty,
},
computed: {
...mapState(['isLoading', 'rules']),
isEmpty() {
return !this.rules || !this.rules.length;
},
},
created() {
this.fetchRules();
},
methods: {
...mapActions(['fetchRules']),
},
};
</script>
<template>
<gl-loading-icon v-if="isLoading" :size="2" />
<approval-rules-empty v-else-if="isEmpty" />
</template>
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);
},
});
}
/**
* 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();
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'));
};
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,
});
export const SET_SETTINGS = 'SET_SETTINGS';
export const SET_LOADING = 'SET_LOADING';
export const SET_RULES = 'SET_RULES';
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;
},
};
export default () => ({
settings: {},
isLoading: false,
rules: [],
});
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']);
});
});
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);
});
});
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,
);
});
});
});
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);
});
});
});
......@@ -464,6 +464,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 ""
......@@ -730,6 +736,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 ""
......@@ -9391,6 +9400,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 ""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment