Commit 25e57691 authored by pburdette's avatar pburdette

Add Vuex store for ci variables

Introduce vuex store to manage state
for ci project/group variables refactor
from Haml to Vue.
parent 291dc6e8
......@@ -47,6 +47,7 @@ const Api = {
adminStatisticsPath: '/api/:version/application/statistics',
pipelineSinglePath: '/api/:version/projects/:id/pipelines/:pipeline_id',
lsifPath: '/api/:version/projects/:id/commits/:commit_id/lsif/info',
environmentsPath: '/api/:version/projects/:id/environments',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -483,6 +484,11 @@ const Api = {
return axios.get(url, { params: { path } });
},
environments(id) {
const url = Api.buildUrl(this.environmentsPath).replace(':id', encodeURIComponent(id));
return axios.get(url);
},
buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
},
......
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import Api from '~/api';
import createFlash from '~/flash';
import { prepareDataForApi, prepareDataForDisplay, prepareEnvironments } from './utils';
export const toggleValues = ({ commit }, valueState) => {
commit(types.TOGGLE_VALUES, valueState);
};
export const clearModal = ({ commit }) => {
commit(types.CLEAR_MODAL);
};
export const resetEditing = ({ commit, dispatch }) => {
// fetch variables again if modal is being edited and then hidden
// without saving changes, to cover use case of reactivity in the table
dispatch('fetchVariables');
commit(types.RESET_EDITING);
};
export const requestAddVariable = ({ commit }) => {
commit(types.REQUEST_ADD_VARIABLE);
};
export const receiveAddVariableSuccess = ({ commit }) => {
commit(types.RECEIVE_ADD_VARIABLE_SUCCESS);
};
export const receiveAddVariableError = ({ commit }, error) => {
commit(types.RECEIVE_ADD_VARIABLE_ERROR, error);
};
export const addVariable = ({ state, dispatch }) => {
dispatch('requestAddVariable');
return axios
.patch(state.endpoint, {
variables_attributes: [prepareDataForApi(state.variable)],
})
.then(() => {
dispatch('receiveAddVariableSuccess');
dispatch('fetchVariables');
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveAddVariableError', error);
});
};
export const requestUpdateVariable = ({ commit }) => {
commit(types.REQUEST_UPDATE_VARIABLE);
};
export const receiveUpdateVariableSuccess = ({ commit }) => {
commit(types.RECEIVE_UPDATE_VARIABLE_SUCCESS);
};
export const receiveUpdateVariableError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_VARIABLE_ERROR, error);
};
export const updateVariable = ({ state, dispatch }, variable) => {
dispatch('requestUpdateVariable');
const updatedVariable = prepareDataForApi(variable);
updatedVariable.secrect_value = updateVariable.value;
return axios
.patch(state.endpoint, { variables_attributes: [updatedVariable] })
.then(() => {
dispatch('receiveUpdateVariableSuccess');
dispatch('fetchVariables');
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveUpdateVariableError', error);
});
};
export const editVariable = ({ commit }, variable) => {
const variableToEdit = variable;
variableToEdit.secret_value = variableToEdit.value;
commit(types.VARIABLE_BEING_EDITED, variableToEdit);
};
export const requestVariables = ({ commit }) => {
commit(types.REQUEST_VARIABLES);
};
export const receiveVariablesSuccess = ({ commit }, variables) => {
commit(types.RECEIVE_VARIABLES_SUCCESS, variables);
};
export const receiveVariablesError = ({ commit }, error) => {
commit(types.RECEIVE_VARIABLES_ERROR, error);
};
export const fetchVariables = ({ dispatch, state }) => {
dispatch('requestVariables');
return axios
.get(state.endpoint)
.then(({ data }) => {
dispatch('receiveVariablesSuccess', prepareDataForDisplay(data.variables));
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveVariablesError', error);
});
};
export const requestDeleteVariable = ({ commit }) => {
commit(types.REQUEST_DELETE_VARIABLE);
};
export const receiveDeleteVariableSuccess = ({ commit }) => {
commit(types.RECEIVE_DELETE_VARIABLE_SUCCESS);
};
export const receiveDeleteVariableError = ({ commit }, error) => {
commit(types.RECEIVE_DELETE_VARIABLE_ERROR, error);
};
export const deleteVariable = ({ dispatch, state }, variable) => {
dispatch('requestDeleteVariable');
const destroy = true;
return axios
.patch(state.endpoint, { variables_attributes: [prepareDataForApi(variable, destroy)] })
.then(() => {
dispatch('receiveDeleteVariableSuccess');
dispatch('fetchVariables');
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveDeleteVariableError', error);
});
};
export const requestEnvironments = ({ commit }) => {
commit(types.REQUEST_ENVIRONMENTS);
};
export const receiveEnvironmentsSuccess = ({ commit }, environments) => {
commit(types.RECEIVE_ENVIRONMENTS_SUCCESS, environments);
};
export const receiveEnvironmentsError = ({ commit }, error) => {
commit(types.RECEIVE_ENVIRONMENTS_ERROR, error);
};
export const fetchEnvironments = ({ dispatch, state }) => {
dispatch('requestEnvironments');
return Api.environments(state.projectId)
.then(res => {
dispatch('receiveEnvironmentsSuccess', prepareEnvironments(res.data));
})
.catch(error => {
createFlash(error.response.data[0]);
dispatch('receiveEnvironmentsError', error);
});
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export default (initialState = {}) =>
new Vuex.Store({
actions,
mutations,
state: {
...state(),
...initialState,
},
});
export const TOGGLE_VALUES = 'TOGGLE_VALUES';
export const VARIABLE_BEING_EDITED = 'VARIABLE_BEING_EDITED';
export const RESET_EDITING = 'RESET_EDITING';
export const CLEAR_MODAL = 'CLEAR_MODAL';
export const REQUEST_VARIABLES = 'REQUEST_VARIABLES';
export const RECEIVE_VARIABLES_SUCCESS = 'RECEIVE_VARIABLES_SUCCESS';
export const RECEIVE_VARIABLES_ERROR = 'RECEIVE_VARIABLES_ERROR';
export const REQUEST_DELETE_VARIABLE = 'REQUEST_DELETE_VARIABLE';
export const RECEIVE_DELETE_VARIABLE_SUCCESS = 'RECEIVE_DELETE_VARIABLE_SUCCESS';
export const RECEIVE_DELETE_VARIABLE_ERROR = 'RECEIVE_DELETE_VARIABLE_ERROR';
export const REQUEST_ADD_VARIABLE = 'REQUEST_ADD_VARIABLE';
export const RECEIVE_ADD_VARIABLE_SUCCESS = 'RECEIVE_ADD_VARIABLE_SUCCESS';
export const RECEIVE_ADD_VARIABLE_ERROR = 'RECEIVE_ADD_VARIABLE_ERROR';
export const REQUEST_UPDATE_VARIABLE = 'REQUEST_UPDATE_VARIABLE';
export const RECEIVE_UPDATE_VARIABLE_SUCCESS = 'RECEIVE_UPDATE_VARIABLE_SUCCESS';
export const RECEIVE_UPDATE_VARIABLE_ERROR = 'RECEIVE_UPDATE_VARIABLE_ERROR';
export const REQUEST_ENVIRONMENTS = 'REQUEST_ENVIRONMENTS';
export const RECEIVE_ENVIRONMENTS_SUCCESS = 'RECEIVE_ENVIRONMENTS_SUCCESS';
export const RECEIVE_ENVIRONMENTS_ERROR = 'RECEIVE_ENVIRONMENTS_ERROR';
import * as types from './mutation_types';
import { __ } from '~/locale';
export default {
[types.REQUEST_VARIABLES](state) {
state.isLoading = true;
},
[types.RECEIVE_VARIABLES_SUCCESS](state, variables) {
state.isLoading = false;
state.variables = variables;
},
[types.RECEIVE_VARIABLES_ERROR](state) {
state.isLoading = false;
},
[types.REQUEST_DELETE_VARIABLE](state) {
state.isDeleting = true;
},
[types.RECEIVE_DELETE_VARIABLE_SUCCESS](state) {
state.isDeleting = false;
},
[types.RECEIVE_DELETE_VARIABLE_ERROR](state, error) {
state.isDeleting = false;
state.error = error;
},
[types.REQUEST_ADD_VARIABLE](state) {
state.isLoading = true;
},
[types.RECEIVE_ADD_VARIABLE_SUCCESS](state) {
state.isLoading = false;
},
[types.RECEIVE_ADD_VARIABLE_ERROR](state, error) {
state.isLoading = false;
state.error = error;
},
[types.REQUEST_UPDATE_VARIABLE](state) {
state.isLoading = true;
},
[types.RECEIVE_UPDATE_VARIABLE_SUCCESS](state) {
state.isLoading = false;
},
[types.RECEIVE_UPDATE_VARIABLE_ERROR](state, error) {
state.isLoading = false;
state.error = error;
},
[types.TOGGLE_VALUES](state, valueState) {
state.valuesHidden = valueState;
},
[types.REQUEST_ENVIRONMENTS](state) {
state.isLoading = true;
},
[types.RECEIVE_ENVIRONMENTS_SUCCESS](state, environments) {
state.isLoading = false;
state.environments = environments;
state.environments.unshift(__('All environments'));
},
[types.RECEIVE_ENVIRONMENTS_ERROR](state, error) {
state.isLoading = false;
state.error = error;
},
[types.VARIABLE_BEING_EDITED](state, variable) {
state.variableBeingEdited = variable;
},
[types.CLEAR_MODAL](state) {
state.variable = {
variable_type: __('Variable'),
key: '',
secret_value: '',
protected: false,
masked: false,
environment_scope: __('All environments'),
};
},
[types.RESET_EDITING](state) {
state.variableBeingEdited = null;
state.showInputValue = false;
},
};
import { __ } from '~/locale';
export default () => ({
endpoint: null,
projectId: null,
isGroup: null,
maskableRegex: null,
isLoading: false,
isDeleting: false,
variable: {
variable_type: __('Variable'),
key: '',
secret_value: '',
protected: false,
masked: false,
environment_scope: __('All environments'),
},
variables: null,
valuesHidden: true,
error: null,
environments: [],
typeOptions: [__('Variable'), __('File')],
variableBeingEdited: null,
});
import { __ } from '~/locale';
const variableTypeHandler = type => (type === 'Variable' ? 'env_var' : 'file');
export const prepareDataForDisplay = variables => {
const variablesToDisplay = [];
variables.forEach(variable => {
const variableCopy = variable;
if (variableCopy.variable_type === 'env_var') {
variableCopy.variable_type = __('Variable');
} else {
variableCopy.variable_type = __('File');
}
if (variableCopy.environment_scope === '*') {
variableCopy.environment_scope = __('All environments');
}
variablesToDisplay.push(variableCopy);
});
return variablesToDisplay;
};
export const prepareDataForApi = (variable, destroy = false) => {
const variableCopy = variable;
variableCopy.protected.toString();
variableCopy.masked.toString();
variableCopy.variable_type = variableTypeHandler(variableCopy.variable_type);
if (variableCopy.environment_scope === __('All environments')) {
variableCopy.environment_scope = __('*');
}
if (destroy) {
// eslint-disable-next-line
variableCopy._destroy = destroy;
}
return variableCopy;
};
export const prepareEnvironments = environments => {
const environmentNames = [];
environments.forEach(environment => {
environmentNames.push(environment.name);
});
return environmentNames;
};
......@@ -525,6 +525,9 @@ msgstr ""
msgid "(removed)"
msgstr ""
msgid "*"
msgstr ""
msgid "+ %{amount} more"
msgstr ""
......@@ -1529,6 +1532,9 @@ msgstr ""
msgid "All email addresses will be used to identify your commits."
msgstr ""
msgid "All environments"
msgstr ""
msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings."
msgstr ""
......@@ -8346,6 +8352,9 @@ msgstr ""
msgid "Fetching licenses failed. You are not permitted to perform this action."
msgstr ""
msgid "File"
msgstr ""
msgid "File Hooks"
msgstr ""
......@@ -21292,6 +21301,9 @@ msgstr ""
msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
msgid "Variable"
msgstr ""
msgid "Variables"
msgstr ""
......
export default {
mockVariables: [
{
environment_scope: 'All environments',
id: 113,
key: 'test_var',
masked: false,
protected: false,
value: 'test_val',
variable_type: 'Variable',
},
{
environment_scope: 'All environments',
id: 114,
key: 'test_var_2',
masked: false,
protected: false,
value: 'test_val_2',
variable_type: 'Variable',
},
{
environment_scope: 'All environments',
id: 115,
key: 'test_var_3',
masked: false,
protected: false,
value: 'test_val_3',
variable_type: 'Variable',
},
],
mockEnvironments: [
{
id: 28,
name: 'staging',
slug: 'staging',
external_url: 'https://staging.example.com',
state: 'available',
},
{
id: 29,
name: 'production',
slug: 'production',
external_url: 'https://production.example.com',
state: 'available',
},
],
};
import Api from '~/api';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import getInitialState from '~/ci_variable_list/store/state';
import * as actions from '~/ci_variable_list/store/actions';
import * as types from '~/ci_variable_list/store/mutation_types';
import mockData from '../services/mock_data';
import { prepareDataForDisplay, prepareEnvironments } from '~/ci_variable_list/store/utils';
jest.mock('~/api.js');
describe('CI variable list store actions', () => {
let mock;
let state;
const mockVariable = {
environment_scope: '*',
id: 63,
key: 'test_var',
masked: false,
protected: false,
value: 'test_val',
variable_type: 'env_var',
_destory: true,
};
beforeEach(() => {
mock = new MockAdapter(axios);
state = getInitialState();
state.endpoint = '/variables';
});
afterEach(() => {
mock.restore();
});
describe('toggleValues', () => {
const valuesHidden = false;
it('commits TOGGLE_VALUES mutation', () => {
testAction(actions.toggleValues, valuesHidden, {}, [
{
type: types.TOGGLE_VALUES,
payload: valuesHidden,
},
]);
});
});
describe('clearModal', () => {
it('commits CLEAR_MODAL mutation', () => {
testAction(actions.clearModal, {}, {}, [
{
type: types.CLEAR_MODAL,
},
]);
});
});
describe('resetEditing', () => {
it('commits RESET_EDITING mutation', () => {
testAction(
actions.resetEditing,
{},
{},
[
{
type: types.RESET_EDITING,
},
],
[{ type: 'fetchVariables' }],
);
});
});
describe('deleteVariable', () => {
it('dispatch correct actions on successful deleted variable', done => {
mock.onPatch(state.endpoint).reply(200);
testAction(
actions.deleteVariable,
mockVariable,
state,
[],
[
{ type: 'requestDeleteVariable' },
{ type: 'receiveDeleteVariableSuccess' },
{ type: 'fetchVariables' },
],
() => {
done();
},
);
});
});
describe('updateVariable', () => {
it('dispatch correct actions on successful updated variable', done => {
mock.onPatch(state.endpoint).reply(200);
testAction(
actions.updateVariable,
mockVariable,
state,
[],
[
{ type: 'requestUpdateVariable' },
{ type: 'receiveUpdateVariableSuccess' },
{ type: 'fetchVariables' },
],
() => {
done();
},
);
});
});
describe('addVariable', () => {
it('dispatch correct actions on successful added variable', done => {
mock.onPatch(state.endpoint).reply(200);
testAction(
actions.addVariable,
{},
state,
[],
[
{ type: 'requestAddVariable' },
{ type: 'receiveAddVariableSuccess' },
{ type: 'fetchVariables' },
],
() => {
done();
},
);
});
});
describe('fetchVariables', () => {
it('dispatch correct actions on fetchVariables', done => {
mock.onGet(state.endpoint).reply(200, { variables: mockData.mockVariables });
testAction(
actions.fetchVariables,
{},
state,
[],
[
{ type: 'requestVariables' },
{
type: 'receiveVariablesSuccess',
payload: prepareDataForDisplay(mockData.mockVariables),
},
],
() => {
done();
},
);
});
});
describe('fetchEnvironments', () => {
it('dispatch correct actions on fetchEnvironments', done => {
Api.environments = jest.fn().mockResolvedValue({ data: mockData.mockEnvironments });
testAction(
actions.fetchEnvironments,
{},
state,
[],
[
{ type: 'requestEnvironments' },
{
type: 'receiveEnvironmentsSuccess',
payload: prepareEnvironments(mockData.mockEnvironments),
},
],
() => {
done();
},
);
});
});
});
import state from '~/ci_variable_list/store/state';
import mutations from '~/ci_variable_list/store/mutations';
import * as types from '~/ci_variable_list/store/mutation_types';
describe('CI variable list mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe('TOGGLE_VALUES', () => {
it('should toggle state', () => {
const valuesHidden = false;
mutations[types.TOGGLE_VALUES](stateCopy, valuesHidden);
expect(stateCopy.valuesHidden).toEqual(valuesHidden);
});
});
describe('VARIABLE_BEING_EDITED', () => {
it('should set variable that is being edited', () => {
const variableBeingEdited = {
environment_scope: '*',
id: 63,
key: 'test_var',
masked: false,
protected: false,
value: 'test_val',
variable_type: 'env_var',
};
mutations[types.VARIABLE_BEING_EDITED](stateCopy, variableBeingEdited);
expect(stateCopy.variableBeingEdited).toEqual(variableBeingEdited);
});
});
describe('RESET_EDITING', () => {
it('should reset variableBeingEdited to null', () => {
mutations[types.RESET_EDITING](stateCopy);
expect(stateCopy.variableBeingEdited).toEqual(null);
});
});
describe('CLEAR_MODAL', () => {
it('should clear modal state ', () => {
const modalState = {
variable_type: 'Variable',
key: '',
secret_value: '',
protected: false,
masked: false,
environment_scope: 'All environments',
};
mutations[types.CLEAR_MODAL](stateCopy);
expect(stateCopy.variable).toEqual(modalState);
});
});
});
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