Commit d80149e6 authored by Phil Hughes's avatar Phil Hughes

Added store for file templates in the Web IDE

#47947
parent bba75044
...@@ -15,6 +15,7 @@ const Api = { ...@@ -15,6 +15,7 @@ const Api = {
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions', mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
groupLabelsPath: '/groups/:namespace_path/-/labels', groupLabelsPath: '/groups/:namespace_path/-/labels',
templatesPath: '/api/:version/templates/:key',
licensePath: '/api/:version/templates/licenses/:key', licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key', gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key', gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
...@@ -265,6 +266,12 @@ const Api = { ...@@ -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) { buildUrl(url) {
let urlRoot = ''; let urlRoot = '';
if (gon.relative_url_root != null) { 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);
};
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,
});
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