Commit 20005cdf authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '216134_03-add-geo-settings-api' into 'master'

Geo Settings Form - Fetch Data from API

See merge request gitlab-org/gitlab!35181
parents 4d3ef0f8 d239af20
...@@ -39,6 +39,7 @@ export default { ...@@ -39,6 +39,7 @@ export default {
vulnerabilityActionPath: '/api/:version/vulnerabilities/:id/:action', vulnerabilityActionPath: '/api/:version/vulnerabilities/:id/:action',
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists', featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid', featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
applicationSettingsPath: '/api/:version/application/settings',
userSubscription(namespaceId) { userSubscription(namespaceId) {
const url = Api.buildUrl(this.subscriptionPath).replace(':id', encodeURIComponent(namespaceId)); const url = Api.buildUrl(this.subscriptionPath).replace(':id', encodeURIComponent(namespaceId));
...@@ -349,4 +350,9 @@ export default { ...@@ -349,4 +350,9 @@ export default {
return axios.delete(url); return axios.delete(url);
}, },
getApplicationSettings() {
const url = Api.buildUrl(this.applicationSettingsPath);
return axios.get(url);
},
}; };
<script> <script>
import { mapActions, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import GeoSettingsForm from './geo_settings_form.vue'; import GeoSettingsForm from './geo_settings_form.vue';
export default { export default {
name: 'GeoSettingsApp', name: 'GeoSettingsApp',
components: { components: {
GlLoadingIcon,
GeoSettingsForm, GeoSettingsForm,
}, },
computed: {
...mapState(['isLoading']),
},
created() {
this.fetchGeoSettings();
},
methods: {
...mapActions(['fetchGeoSettings']),
},
}; };
</script> </script>
...@@ -19,6 +31,7 @@ export default { ...@@ -19,6 +31,7 @@ export default {
) )
}} }}
</p> </p>
<geo-settings-form /> <gl-loading-icon v-if="isLoading" size="xl" />
<geo-settings-form v-else />
</article> </article>
</template> </template>
<script> <script>
import { mapState } from 'vuex';
import { GlFormGroup, GlFormInput, GlButton } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlButton } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { DEFAULT_TIMEOUT, DEFAULT_ALLOWED_IP } from '../constants';
export default { export default {
name: 'GeoSettingsForm', name: 'GeoSettingsForm',
...@@ -10,11 +10,10 @@ export default { ...@@ -10,11 +10,10 @@ export default {
GlFormInput, GlFormInput,
GlButton, GlButton,
}, },
data() { computed: {
return { // The real connection between vuex and the component will be implemented in
timeout: DEFAULT_TIMEOUT, // a later MR, this feature is anyhow behind feature flag
allowedIp: DEFAULT_ALLOWED_IP, ...mapState(['timeout', 'allowedIp']),
};
}, },
methods: { methods: {
redirect() { redirect() {
......
import Vue from 'vue'; import Vue from 'vue';
import Translate from '~/vue_shared/translate'; import Translate from '~/vue_shared/translate';
import createStore from './store';
import GeoSettingsApp from './components/app.vue'; import GeoSettingsApp from './components/app.vue';
Vue.use(Translate); Vue.use(Translate);
...@@ -9,6 +10,7 @@ export default () => { ...@@ -9,6 +10,7 @@ export default () => {
return new Vue({ return new Vue({
el, el,
store: createStore(),
components: { components: {
GeoSettingsApp, GeoSettingsApp,
}, },
......
import Api from 'ee/api';
import createFlash from '~/flash';
import { __ } from '~/locale';
import * as types from './mutation_types';
// eslint-disable-next-line import/prefer-default-export
export const fetchGeoSettings = ({ commit }) => {
commit(types.REQUEST_GEO_SETTINGS);
Api.getApplicationSettings()
.then(({ data }) => {
commit(types.RECEIVE_GEO_SETTINGS_SUCCESS, {
timeout: data.geo_status_timeout,
allowedIp: data.geo_node_allowed_ips,
});
})
.catch(() => {
createFlash(__('There was an error fetching the Geo Settings'));
commit(types.RECEIVE_GEO_SETTINGS_ERROR);
});
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import createState from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
mutations,
state: createState(),
});
export const REQUEST_GEO_SETTINGS = 'REQUEST_GEO_SETTINGS';
export const RECEIVE_GEO_SETTINGS_SUCCESS = 'RECEIVE_GEO_SETTINGS_SUCCESS';
export const RECEIVE_GEO_SETTINGS_ERROR = 'RECEIVE_GEO_SETTINGS_ERROR';
import * as types from './mutation_types';
import { DEFAULT_TIMEOUT, DEFAULT_ALLOWED_IP } from '../constants';
export default {
[types.REQUEST_GEO_SETTINGS](state) {
state.isLoading = true;
},
[types.RECEIVE_GEO_SETTINGS_SUCCESS](state, { timeout, allowedIp }) {
state.isLoading = false;
state.timeout = timeout;
state.allowedIp = allowedIp;
},
[types.RECEIVE_GEO_SETTINGS_ERROR](state) {
state.isLoading = false;
state.timeout = DEFAULT_TIMEOUT;
state.allowedIp = DEFAULT_ALLOWED_IP;
},
};
import { DEFAULT_TIMEOUT, DEFAULT_ALLOWED_IP } from '../constants';
export default () => ({
isLoading: false,
timeout: DEFAULT_TIMEOUT,
allowedIp: DEFAULT_ALLOWED_IP,
});
...@@ -841,4 +841,20 @@ describe('Api', () => { ...@@ -841,4 +841,20 @@ describe('Api', () => {
}); });
}); });
}); });
describe('getApplicationSettings', () => {
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/application/settings`;
const apiResponse = { mock_setting: 1, mock_setting2: 2 };
it('fetches applications settings', () => {
jest.spyOn(Api, 'buildUrl').mockReturnValue(expectedUrl);
jest.spyOn(axios, 'get');
mock.onGet(expectedUrl).replyOnce(200, apiResponse);
return Api.getApplicationSettings().then(({ data }) => {
expect(data).toEqual(apiResponse);
expect(axios.get).toHaveBeenCalledWith(expectedUrl);
});
});
});
}); });
import { shallowMount } from '@vue/test-utils'; import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import initStore from 'ee/geo_settings/store';
import * as types from 'ee/geo_settings/store/mutation_types';
import GeoSettingsApp from 'ee/geo_settings/components/app.vue'; import GeoSettingsApp from 'ee/geo_settings/components/app.vue';
import GeoSettingsForm from 'ee/geo_settings/components/geo_settings_form.vue'; import GeoSettingsForm from 'ee/geo_settings/components/geo_settings_form.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('GeoSettingsApp', () => { describe('GeoSettingsApp', () => {
let wrapper; let wrapper;
let store;
const createStore = () => {
store = initStore();
jest.spyOn(store, 'dispatch').mockImplementation();
};
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(GeoSettingsApp); wrapper = shallowMount(GeoSettingsApp, {
store,
});
}; };
afterEach(() => { afterEach(() => {
...@@ -14,10 +31,12 @@ describe('GeoSettingsApp', () => { ...@@ -14,10 +31,12 @@ describe('GeoSettingsApp', () => {
}); });
const findGeoSettingsContainer = () => wrapper.find('[data-testid="geoSettingsContainer"]'); const findGeoSettingsContainer = () => wrapper.find('[data-testid="geoSettingsContainer"]');
const findGeoSettingsForm = () => wrapper.find(GeoSettingsForm); const containsGeoSettingsForm = () => wrapper.contains(GeoSettingsForm);
const containsGlLoadingIcon = () => wrapper.contains(GlLoadingIcon);
describe('renders', () => { describe('renders', () => {
beforeEach(() => { beforeEach(() => {
createStore();
createComponent(); createComponent();
}); });
...@@ -29,8 +48,39 @@ describe('GeoSettingsApp', () => { ...@@ -29,8 +48,39 @@ describe('GeoSettingsApp', () => {
expect(findGeoSettingsContainer().text()).toContain('Geo Settings'); expect(findGeoSettingsContainer().text()).toContain('Geo Settings');
}); });
describe('when not loading', () => {
it('Geo Settings Form', () => { it('Geo Settings Form', () => {
expect(findGeoSettingsForm().exists()).toBe(true); expect(containsGeoSettingsForm()).toBe(true);
});
it('not GlLoadingIcon', () => {
expect(containsGlLoadingIcon()).toBe(false);
});
});
describe('when loading', () => {
beforeEach(() => {
store.commit(types.REQUEST_GEO_SETTINGS);
});
it('not Geo Settings Form', () => {
expect(containsGeoSettingsForm()).toBe(false);
});
it('GlLoadingIcon', () => {
expect(containsGlLoadingIcon()).toBe(true);
});
});
});
describe('onCreate', () => {
beforeEach(() => {
createStore();
createComponent();
});
it('calls fetchGeoSettings', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchGeoSettings');
}); });
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import store from 'ee/geo_settings/store';
import GeoSettingsForm from 'ee/geo_settings/components/geo_settings_form.vue'; import GeoSettingsForm from 'ee/geo_settings/components/geo_settings_form.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrlMock'), visitUrl: jest.fn().mockName('visitUrlMock'),
})); }));
...@@ -10,7 +15,9 @@ describe('GeoSettingsForm', () => { ...@@ -10,7 +15,9 @@ describe('GeoSettingsForm', () => {
let wrapper; let wrapper;
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(GeoSettingsForm); wrapper = shallowMount(GeoSettingsForm, {
store,
});
}; };
afterEach(() => { afterEach(() => {
......
export const MOCK_APPLICATION_SETTINGS_FETCH_RESPONSE = {
geo_status_timeout: 10,
geo_node_allowed_ips: '0.0.0.0/0, ::/0',
};
export const MOCK_BASIC_SETTINGS_DATA = {
timeout: MOCK_APPLICATION_SETTINGS_FETCH_RESPONSE.geo_status_timeout,
allowedIp: MOCK_APPLICATION_SETTINGS_FETCH_RESPONSE.geo_node_allowed_ips,
};
import testAction from 'helpers/vuex_action_helper';
import flash from '~/flash';
import Api from 'ee/api';
import * as actions from 'ee/geo_settings/store/actions';
import * as types from 'ee/geo_settings/store/mutation_types';
import state from 'ee/geo_settings/store/state';
import { MOCK_BASIC_SETTINGS_DATA, MOCK_APPLICATION_SETTINGS_FETCH_RESPONSE } from '../mock_data';
jest.mock('~/flash');
describe('GeoSettings Store Actions', () => {
describe('fetchGeoSettings', () => {
describe('on success', () => {
beforeEach(() => {
jest
.spyOn(Api, 'getApplicationSettings')
.mockResolvedValue({ data: MOCK_APPLICATION_SETTINGS_FETCH_RESPONSE });
});
it('should commit the request and success actions', done => {
testAction(
actions.fetchGeoSettings,
{},
state,
[
{ type: types.REQUEST_GEO_SETTINGS },
{ type: types.RECEIVE_GEO_SETTINGS_SUCCESS, payload: MOCK_BASIC_SETTINGS_DATA },
],
[],
done,
);
});
});
describe('on error', () => {
beforeEach(() => {
jest.spyOn(Api, 'getApplicationSettings').mockRejectedValue(new Error(500));
});
it('should commit the request and error actions', () => {
testAction(
actions.fetchGeoSettings,
{},
state,
[{ type: types.REQUEST_GEO_SETTINGS }, { type: types.RECEIVE_GEO_SETTINGS_ERROR }],
[],
() => {
expect(flash).toHaveBeenCalledTimes(1);
},
);
});
});
});
});
import mutations from 'ee/geo_settings/store/mutations';
import createState from 'ee/geo_settings/store/state';
import * as types from 'ee/geo_settings/store/mutation_types';
import { DEFAULT_TIMEOUT, DEFAULT_ALLOWED_IP } from 'ee/geo_settings/constants';
import { MOCK_BASIC_SETTINGS_DATA } from '../mock_data';
describe('GeoSettings Store Mutations', () => {
let state;
beforeEach(() => {
state = createState();
});
describe('REQUEST_GEO_SETTINGS', () => {
it('sets isLoading to true', () => {
mutations[types.REQUEST_GEO_SETTINGS](state);
expect(state.isLoading).toEqual(true);
});
});
describe('RECEIVE_GEO_SETTINGS_SUCCESS', () => {
const mockData = MOCK_BASIC_SETTINGS_DATA;
beforeEach(() => {
state.isLoading = true;
});
it('sets isLoading to false', () => {
mutations[types.RECEIVE_GEO_SETTINGS_SUCCESS](state, mockData);
expect(state.isLoading).toEqual(false);
});
it('sets timeout and allowedIp array with data', () => {
mutations[types.RECEIVE_GEO_SETTINGS_SUCCESS](state, mockData);
expect(state.timeout).toBe(mockData.timeout);
expect(state.allowedIp).toBe(mockData.allowedIp);
});
});
describe('RECEIVE_GEO_SETTINGS_ERROR', () => {
const mockData = MOCK_BASIC_SETTINGS_DATA;
beforeEach(() => {
state.isLoading = true;
state.timeout = mockData.timeout;
state.allowedIp = mockData.allowedIp;
});
it('sets isLoading to false', () => {
mutations[types.RECEIVE_GEO_SETTINGS_ERROR](state);
expect(state.isLoading).toEqual(false);
});
it('resets timeout and allowedIp array', () => {
mutations[types.RECEIVE_GEO_SETTINGS_ERROR](state);
expect(state.timeout).toBe(DEFAULT_TIMEOUT);
expect(state.allowedIp).toBe(DEFAULT_ALLOWED_IP);
});
});
});
...@@ -22954,6 +22954,9 @@ msgstr "" ...@@ -22954,6 +22954,9 @@ msgstr ""
msgid "There was an error fetching the %{replicableType}" msgid "There was an error fetching the %{replicableType}"
msgstr "" msgstr ""
msgid "There was an error fetching the Geo Settings"
msgstr ""
msgid "There was an error fetching the Node's Groups" msgid "There was an error fetching the Node's Groups"
msgstr "" msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment