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
This commit is contained in:
Filipa Lacerda 2018-08-22 10:26:15 +00:00
commit 726f5cdc2b
11 changed files with 591 additions and 0 deletions

View File

@ -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) {

View File

@ -0,0 +1,82 @@
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 () => {};

View File

@ -0,0 +1,23 @@
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 () => {};

View File

@ -0,0 +1,12 @@
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,
};

View File

@ -0,0 +1,7 @@
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';

View File

@ -0,0 +1,21 @@
/* 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;
},
};

View File

@ -0,0 +1,6 @@
export default () => ({
isLoading: false,
templates: [],
selectedTemplateType: {},
updateSuccess: false,
});

View File

@ -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 ""

View File

@ -0,0 +1,336 @@
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);
});
});
});

View File

@ -0,0 +1,30 @@
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);
});
});
});

View File

@ -0,0 +1,61 @@
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);
});
});
});