Commit 726f5cdc authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ide-file-templates-store' into 'master'

Added store for file templates in the Web IDE

See merge request gitlab-org/gitlab-ce!21272
parents 1c95c5ec bdf70248
Pipeline #28426790 passed with stages
in 37 minutes and 1 second
......@@ -15,6 +15,7 @@ const Api = {
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
groupLabelsPath: '/groups/:namespace_path/-/labels',
templatesPath: '/api/:version/templates/:key',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
......@@ -265,6 +266,12 @@ const Api = {
});
},
templates(key, params = {}) {
const url = Api.buildUrl(this.templatesPath).replace(':key', key);
return axios.get(url, { params });
},
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {
......
import Api from '~/api';
import { __ } from '~/locale';
import * as types from './mutation_types';
export const requestTemplateTypes = ({ commit }) => commit(types.REQUEST_TEMPLATE_TYPES);
export const receiveTemplateTypesError = ({ commit, dispatch }) => {
commit(types.RECEIVE_TEMPLATE_TYPES_ERROR);
dispatch(
'setErrorMessage',
{
text: __('Error loading template types.'),
action: () =>
dispatch('fetchTemplateTypes').then(() =>
dispatch('setErrorMessage', null, { root: true }),
),
actionText: __('Please try again'),
},
{ root: true },
);
};
export const receiveTemplateTypesSuccess = ({ commit }, templates) =>
commit(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, templates);
export const fetchTemplateTypes = ({ dispatch, state }) => {
if (!Object.keys(state.selectedTemplateType).length) return Promise.reject();
dispatch('requestTemplateTypes');
return Api.templates(state.selectedTemplateType.key)
.then(({ data }) => dispatch('receiveTemplateTypesSuccess', data))
.catch(() => dispatch('receiveTemplateTypesError'));
};
export const setSelectedTemplateType = ({ commit }, type) =>
commit(types.SET_SELECTED_TEMPLATE_TYPE, type);
export const receiveTemplateError = ({ dispatch }, template) => {
dispatch(
'setErrorMessage',
{
text: __('Error loading template.'),
action: payload =>
dispatch('fetchTemplateTypes', payload).then(() =>
dispatch('setErrorMessage', null, { root: true }),
),
actionText: __('Please try again'),
actionPayload: template,
},
{ root: true },
);
};
export const fetchTemplate = ({ dispatch, state }, template) => {
if (template.content) {
return dispatch('setFileTemplate', template);
}
return Api.templates(`${state.selectedTemplateType.key}/${template.key || template.name}`)
.then(({ data }) => {
dispatch('setFileTemplate', data);
})
.catch(() => dispatch('receiveTemplateError', template));
};
export const setFileTemplate = ({ dispatch, commit, rootGetters }, template) => {
dispatch(
'changeFileContent',
{ path: rootGetters.activeFile.path, content: template.content },
{ root: true },
);
commit(types.SET_UPDATE_SUCCESS, true);
};
export const undoFileTemplate = ({ dispatch, commit, rootGetters }) => {
const file = rootGetters.activeFile;
dispatch('changeFileContent', { path: file.path, content: file.raw }, { root: true });
commit(types.SET_UPDATE_SUCCESS, false);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const templateTypes = () => [
{
name: '.gitlab-ci.yml',
key: 'gitlab_ci_ymls',
},
{
name: '.gitignore',
key: 'gitignores',
},
{
name: 'LICENSE',
key: 'licenses',
},
{
name: 'Dockerfile',
key: 'dockerfiles',
},
];
export const showFileTemplatesBar = (_, getters) => name =>
getters.templateTypes.find(t => t.name === name);
export default () => {};
import createState from './state';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
export default {
namespaced: true,
actions,
state: createState(),
getters,
mutations,
};
export const REQUEST_TEMPLATE_TYPES = 'REQUEST_TEMPLATE_TYPES';
export const RECEIVE_TEMPLATE_TYPES_ERROR = 'RECEIVE_TEMPLATE_TYPES_ERROR';
export const RECEIVE_TEMPLATE_TYPES_SUCCESS = 'RECEIVE_TEMPLATE_TYPES_SUCCESS';
export const SET_SELECTED_TEMPLATE_TYPE = 'SET_SELECTED_TEMPLATE_TYPE';
export const SET_UPDATE_SUCCESS = 'SET_UPDATE_SUCCESS';
/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
export default {
[types.REQUEST_TEMPLATE_TYPES](state) {
state.isLoading = true;
},
[types.RECEIVE_TEMPLATE_TYPES_ERROR](state) {
state.isLoading = false;
},
[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, templates) {
state.isLoading = false;
state.templates = templates;
},
[types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
state.selectedTemplateType = type;
},
[types.SET_UPDATE_SUCCESS](state, success) {
state.updateSuccess = success;
},
};
export default () => ({
isLoading: false,
templates: [],
selectedTemplateType: {},
updateSuccess: false,
});
......@@ -2508,6 +2508,12 @@ msgstr ""
msgid "Error loading project data. Please try again."
msgstr ""
msgid "Error loading template types."
msgstr ""
msgid "Error loading template."
msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import createState from '~/ide/stores/modules/file_templates/state';
import * as actions from '~/ide/stores/modules/file_templates/actions';
import * as types from '~/ide/stores/modules/file_templates/mutation_types';
import testAction from 'spec/helpers/vuex_action_helper';
describe('IDE file templates actions', () => {
let state;
let mock;
beforeEach(() => {
state = createState();
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('requestTemplateTypes', () => {
it('commits REQUEST_TEMPLATE_TYPES', done => {
testAction(
actions.requestTemplateTypes,
null,
state,
[{ type: types.REQUEST_TEMPLATE_TYPES }],
[],
done,
);
});
});
describe('receiveTemplateTypesError', () => {
it('commits RECEIVE_TEMPLATE_TYPES_ERROR and dispatches setErrorMessage', done => {
testAction(
actions.receiveTemplateTypesError,
null,
state,
[{ type: types.RECEIVE_TEMPLATE_TYPES_ERROR }],
[
{
type: 'setErrorMessage',
payload: {
action: jasmine.any(Function),
actionText: 'Please try again',
text: 'Error loading template types.',
},
},
],
done,
);
});
});
describe('receiveTemplateTypesSuccess', () => {
it('commits RECEIVE_TEMPLATE_TYPES_SUCCESS', done => {
testAction(
actions.receiveTemplateTypesSuccess,
'test',
state,
[{ type: types.RECEIVE_TEMPLATE_TYPES_SUCCESS, payload: 'test' }],
[],
done,
);
});
});
describe('fetchTemplateTypes', () => {
describe('success', () => {
beforeEach(() => {
mock.onGet(/api\/(.*)\/templates\/licenses/).replyOnce(200, [
{
name: 'MIT',
},
]);
});
it('rejects if selectedTemplateType is empty', done => {
const dispatch = jasmine.createSpy('dispatch');
actions
.fetchTemplateTypes({ dispatch, state })
.then(done.fail)
.catch(() => {
expect(dispatch).not.toHaveBeenCalled();
done();
});
});
it('dispatches actions', done => {
state.selectedTemplateType = {
key: 'licenses',
};
testAction(
actions.fetchTemplateTypes,
null,
state,
[],
[
{
type: 'requestTemplateTypes',
},
{
type: 'receiveTemplateTypesSuccess',
payload: [
{
name: 'MIT',
},
],
},
],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(/api\/(.*)\/templates\/licenses/).replyOnce(500);
});
it('dispatches actions', done => {
state.selectedTemplateType = {
key: 'licenses',
};
testAction(
actions.fetchTemplateTypes,
null,
state,
[],
[
{
type: 'requestTemplateTypes',
},
{
type: 'receiveTemplateTypesError',
},
],
done,
);
});
});
});
describe('setSelectedTemplateType', () => {
it('commits SET_SELECTED_TEMPLATE_TYPE', done => {
testAction(
actions.setSelectedTemplateType,
'test',
state,
[{ type: types.SET_SELECTED_TEMPLATE_TYPE, payload: 'test' }],
[],
done,
);
});
});
describe('receiveTemplateError', () => {
it('dispatches setErrorMessage', done => {
testAction(
actions.receiveTemplateError,
'test',
state,
[],
[
{
type: 'setErrorMessage',
payload: {
action: jasmine.any(Function),
actionText: 'Please try again',
text: 'Error loading template.',
actionPayload: 'test',
},
},
],
done,
);
});
});
describe('fetchTemplate', () => {
describe('success', () => {
beforeEach(() => {
mock.onGet(/api\/(.*)\/templates\/licenses\/mit/).replyOnce(200, {
content: 'MIT content',
});
mock.onGet(/api\/(.*)\/templates\/licenses\/testing/).replyOnce(200, {
content: 'testing content',
});
});
it('dispatches setFileTemplate if template already has content', done => {
const template = {
content: 'already has content',
};
testAction(
actions.fetchTemplate,
template,
state,
[],
[{ type: 'setFileTemplate', payload: template }],
done,
);
});
it('dispatches success', done => {
const template = {
key: 'mit',
};
state.selectedTemplateType = {
key: 'licenses',
};
testAction(
actions.fetchTemplate,
template,
state,
[],
[{ type: 'setFileTemplate', payload: { content: 'MIT content' } }],
done,
);
});
it('dispatches success and uses name key for API call', done => {
const template = {
name: 'testing',
};
state.selectedTemplateType = {
key: 'licenses',
};
testAction(
actions.fetchTemplate,
template,
state,
[],
[{ type: 'setFileTemplate', payload: { content: 'testing content' } }],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(/api\/(.*)\/templates\/licenses\/mit/).replyOnce(500);
});
it('dispatches error', done => {
const template = {
name: 'testing',
};
state.selectedTemplateType = {
key: 'licenses',
};
testAction(
actions.fetchTemplate,
template,
state,
[],
[{ type: 'receiveTemplateError', payload: template }],
done,
);
});
});
});
describe('setFileTemplate', () => {
it('dispatches changeFileContent', () => {
const dispatch = jasmine.createSpy('dispatch');
const commit = jasmine.createSpy('commit');
const rootGetters = {
activeFile: { path: 'test' },
};
actions.setFileTemplate({ dispatch, commit, rootGetters }, { content: 'content' });
expect(dispatch).toHaveBeenCalledWith(
'changeFileContent',
{ path: 'test', content: 'content' },
{ root: true },
);
});
it('commits SET_UPDATE_SUCCESS', () => {
const dispatch = jasmine.createSpy('dispatch');
const commit = jasmine.createSpy('commit');
const rootGetters = {
activeFile: { path: 'test' },
};
actions.setFileTemplate({ dispatch, commit, rootGetters }, { content: 'content' });
expect(commit).toHaveBeenCalledWith('SET_UPDATE_SUCCESS', true);
});
});
describe('undoFileTemplate', () => {
it('dispatches changeFileContent', () => {
const dispatch = jasmine.createSpy('dispatch');
const commit = jasmine.createSpy('commit');
const rootGetters = {
activeFile: { path: 'test', raw: 'raw content' },
};
actions.undoFileTemplate({ dispatch, commit, rootGetters });
expect(dispatch).toHaveBeenCalledWith(
'changeFileContent',
{ path: 'test', content: 'raw content' },
{ root: true },
);
});
it('commits SET_UPDATE_SUCCESS', () => {
const dispatch = jasmine.createSpy('dispatch');
const commit = jasmine.createSpy('commit');
const rootGetters = {
activeFile: { path: 'test', raw: 'raw content' },
};
actions.undoFileTemplate({ dispatch, commit, rootGetters });
expect(commit).toHaveBeenCalledWith('SET_UPDATE_SUCCESS', false);
});
});
});
import * as getters from '~/ide/stores/modules/file_templates/getters';
describe('IDE file templates getters', () => {
describe('templateTypes', () => {
it('returns list of template types', () => {
expect(getters.templateTypes().length).toBe(4);
});
});
describe('showFileTemplatesBar', () => {
it('finds template type by name', () => {
expect(
getters.showFileTemplatesBar(null, {
templateTypes: getters.templateTypes(),
})('LICENSE'),
).toEqual({
name: 'LICENSE',
key: 'licenses',
});
});
it('returns undefined if not found', () => {
expect(
getters.showFileTemplatesBar(null, {
templateTypes: getters.templateTypes(),
})('test'),
).toBe(undefined);
});
});
});
import createState from '~/ide/stores/modules/file_templates/state';
import * as types from '~/ide/stores/modules/file_templates/mutation_types';
import mutations from '~/ide/stores/modules/file_templates/mutations';
describe('IDE file templates mutations', () => {
let state;
beforeEach(() => {
state = createState();
});
describe(types.REQUEST_TEMPLATE_TYPES, () => {
it('sets isLoading', () => {
mutations[types.REQUEST_TEMPLATE_TYPES](state);
expect(state.isLoading).toBe(true);
});
});
describe(types.RECEIVE_TEMPLATE_TYPES_ERROR, () => {
it('sets isLoading', () => {
state.isLoading = true;
mutations[types.RECEIVE_TEMPLATE_TYPES_ERROR](state);
expect(state.isLoading).toBe(false);
});
});
describe(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, () => {
it('sets isLoading to false', () => {
state.isLoading = true;
mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, []);
expect(state.isLoading).toBe(false);
});
it('sets templates', () => {
mutations[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, ['test']);
expect(state.templates).toEqual(['test']);
});
});
describe(types.SET_SELECTED_TEMPLATE_TYPE, () => {
it('sets selectedTemplateType', () => {
mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type');
expect(state.selectedTemplateType).toBe('type');
});
});
describe(types.SET_UPDATE_SUCCESS, () => {
it('sets updateSuccess', () => {
mutations[types.SET_UPDATE_SUCCESS](state, true);
expect(state.updateSuccess).toBe(true);
});
});
});
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