Commit 43690d15 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '33537-refactor-env-pod-logs-to-vue-store' into 'master'

Logs Page: Add vuex store

See merge request gitlab-org/gitlab!19013
parents 34034ea0 98f1e332
import { backOff } from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { s__ } from '~/locale';
import * as types from './mutation_types';
const requestUntilData = (url, params) =>
backOff((next, stop) => {
axios
.get(url, {
params,
})
.then(res => {
if (!res.data) {
next();
return;
}
stop(res);
})
.catch(err => {
stop(err);
});
});
export const setLogsEndpoint = ({ commit }, logsEndpoint) => {
commit(types.SET_LOGS_ENDPOINT, logsEndpoint);
};
export const fetchEnvironments = ({ commit }, environmentsPath) => {
commit(types.REQUEST_ENVIRONMENTS_DATA);
axios
.get(environmentsPath)
.then(({ data }) => {
commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data.environments);
})
.catch(() => {
commit(types.RECEIVE_ENVIRONMENTS_DATA_ERROR);
flash(s__('Metrics|There was an error fetching the environments data, please try again'));
});
};
export const fetchLogs = ({ commit, state }, podName) => {
if (podName) {
commit(types.SET_CURRENT_POD_NAME, podName);
}
commit(types.REQUEST_PODS_DATA);
commit(types.REQUEST_LOGS_DATA);
return requestUntilData(state.logs.endpoint, { pod_name: podName })
.then(({ data }) => {
const { pods, logs } = data;
// Set first pod as default, if none is set
if (!podName && pods[0]) {
commit(types.SET_CURRENT_POD_NAME, pods[0]);
}
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
commit(types.RECEIVE_LOGS_DATA_SUCCESS, logs);
})
.catch(() => {
commit(types.RECEIVE_PODS_DATA_ERROR);
commit(types.RECEIVE_LOGS_DATA_ERROR);
flash(s__('Metrics|There was an error fetching the logs, please try again'));
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const trace = state => state.logs.lines.join('\n');
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
environmentLogs: {
namespaced: true,
actions,
mutations,
state: state(),
getters,
},
},
});
export default createStore;
export const SET_LOGS_ENDPOINT = 'SET_LOGS_ENDPOINT';
export const SET_CURRENT_POD_NAME = 'SET_CURRENT_POD_NAME';
export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR';
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
export const REQUEST_PODS_DATA = 'REQUEST_PODS_DATA';
export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS';
export const RECEIVE_PODS_DATA_ERROR = 'RECEIVE_PODS_DATA_ERROR';
import * as types from './mutation_types';
export default {
[types.SET_LOGS_ENDPOINT](state, endpoint) {
state.logs.endpoint = endpoint;
},
[types.SET_CURRENT_POD_NAME](state, podName) {
state.pods.current = podName;
},
[types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = [];
state.environments.isLoading = true;
},
[types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, environmentOptions) {
state.environments.options = environmentOptions;
state.environments.isLoading = false;
},
[types.RECEIVE_ENVIRONMENTS_DATA_ERROR](state) {
state.environments.options = [];
state.environments.isLoading = false;
},
[types.REQUEST_LOGS_DATA](state) {
state.logs.lines = [];
state.logs.isLoading = true;
state.logs.isComplete = false;
},
[types.RECEIVE_LOGS_DATA_SUCCESS](state, lines) {
state.logs.lines = lines;
state.logs.isLoading = false;
state.logs.isComplete = true;
},
[types.RECEIVE_LOGS_DATA_ERROR](state) {
state.logs.lines = [];
state.logs.isLoading = false;
state.logs.isComplete = true;
},
[types.REQUEST_PODS_DATA](state) {
state.pods.options = [];
},
[types.RECEIVE_PODS_DATA_SUCCESS](state, podOptions) {
state.pods.options = podOptions;
},
[types.RECEIVE_PODS_DATA_ERROR](state) {
state.pods.options = [];
},
};
export default () => ({
/**
* Environments list information
*/
environments: {
options: [],
isLoading: false,
},
/**
* Logs including trace
*/
logs: {
endpoint: null,
lines: [],
isLoading: false,
isComplete: true,
},
/**
* Pods list information
*/
pods: {
options: [],
current: null,
},
});
const makeMockLogsPath = id => `/root/autodevops-deploy/environments/${id}/logs`;
const makeMockEnvironment = (id, name) => ({
id,
logs_path: makeMockLogsPath(id),
name,
});
export const mockEnvironment = makeMockEnvironment(99, 'production');
export const mockEnvironmentsEndpoint = '/root/autodevops-deploy/environments.json';
export const mockEnvironments = [
mockEnvironment,
makeMockEnvironment(101, 'staging'),
makeMockEnvironment(102, 'review/a-feature'),
];
export const mockPodName = 'production-764c58d697-aaaaa';
export const mockPods = [
mockPodName,
'production-764c58d697-bbbbb',
'production-764c58d697-ccccc',
'production-764c58d697-ddddd',
];
export const mockLogsEndpoint = `/root/autodevops-deploy/environments/${mockEnvironment.id}/logs.json`;
export const mockLines = [
'10.36.0.1 - - [16/Oct/2019:06:29:48 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
'10.36.0.1 - - [16/Oct/2019:06:29:57 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
'10.36.0.1 - - [16/Oct/2019:06:29:58 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
'10.36.0.1 - - [16/Oct/2019:06:30:07 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
'10.36.0.1 - - [16/Oct/2019:06:30:08 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
'10.36.0.1 - - [16/Oct/2019:06:30:17 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
'10.36.0.1 - - [16/Oct/2019:06:30:18 UTC] "GET / HTTP/1.1" 200 13',
'- -> /',
];
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import * as types from 'ee/logs/stores/mutation_types';
import logsPageState from 'ee/logs/stores/state';
import { setLogsEndpoint, fetchEnvironments, fetchLogs } from 'ee/logs/stores/actions';
import flash from '~/flash';
import {
mockLogsEndpoint,
mockEnvironments,
mockPods,
mockPodName,
mockLines,
mockEnvironmentsEndpoint,
} from '../mock_data';
jest.mock('~/flash');
describe('Logs Store actions', () => {
let state;
let mock;
beforeEach(() => {
state = logsPageState();
});
afterEach(() => {
flash.mockClear();
});
describe('setLogsEndpoint', () => {
it('should commit SET_LOGS_ENDPOINT mutation', done => {
testAction(
setLogsEndpoint,
mockLogsEndpoint,
state,
[{ type: types.SET_LOGS_ENDPOINT, payload: mockLogsEndpoint }],
[],
done,
);
});
});
describe('fetchEnvironments', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
it('should commit RECEIVE_ENVIRONMENTS_DATA_SUCCESS mutation on correct data', done => {
mock.onGet(mockEnvironmentsEndpoint).replyOnce(200, { environments: mockEnvironments });
testAction(
fetchEnvironments,
mockEnvironmentsEndpoint,
state,
[
{ type: types.REQUEST_ENVIRONMENTS_DATA },
{ type: types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, payload: mockEnvironments },
],
[],
done,
);
});
it('should commit RECEIVE_ENVIRONMENTS_DATA_ERROR on wrong data', done => {
mock.onGet(mockEnvironmentsEndpoint).replyOnce(500);
testAction(
fetchEnvironments,
mockEnvironmentsEndpoint,
state,
[
{ type: types.REQUEST_ENVIRONMENTS_DATA },
{ type: types.RECEIVE_ENVIRONMENTS_DATA_ERROR },
],
[],
() => {
expect(flash).toHaveBeenCalledTimes(1);
done();
},
);
});
it('should commit RECEIVE_ENVIRONMENTS_DATA_ERROR on error', done => {
mock.onGet('/root/autodevops-deploy/environments.json').replyOnce(500);
testAction(
fetchEnvironments,
'/root/autodevops-deploy/environments.json',
state,
[
{ type: types.REQUEST_ENVIRONMENTS_DATA },
{ type: types.RECEIVE_ENVIRONMENTS_DATA_ERROR },
],
[],
() => {
expect(flash).toHaveBeenCalledTimes(1);
done();
},
);
});
});
describe('fetchLogs', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
});
it('should commit logs and pod data when there is pod name defined', done => {
state.logs.endpoint = mockLogsEndpoint;
mock.onGet(mockLogsEndpoint).replyOnce(200, {
pods: mockPods,
logs: mockLines,
});
testAction(
fetchLogs,
mockPodName,
state,
[
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLines },
],
[],
done,
);
});
it('should commit logs and pod data when no pod name defined', done => {
state.logs.endpoint = mockLogsEndpoint;
mock.onGet(mockLogsEndpoint).replyOnce(200, {
pods: mockPods,
logs: mockLines,
});
testAction(
fetchLogs,
null,
state,
[
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPods[0] },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLines },
],
[],
done,
);
});
it('should commit logs and pod errors', done => {
state.logs.endpoint = mockLogsEndpoint;
mock.onGet(mockLogsEndpoint).replyOnce(500);
testAction(
fetchLogs,
mockPodName,
state,
[
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA },
{ type: types.RECEIVE_PODS_DATA_ERROR },
{ type: types.RECEIVE_LOGS_DATA_ERROR },
],
[],
() => {
expect(flash).toHaveBeenCalledTimes(1);
done();
},
);
});
});
});
import * as getters from 'ee/logs/stores/getters';
import logsPageState from 'ee/logs/stores/state';
import { mockLines } from '../mock_data';
describe('Logs Store getters', () => {
let state;
beforeEach(() => {
state = logsPageState();
});
describe('trace', () => {
describe('when state is initialized', () => {
it('returns an empty string', () => {
expect(getters.trace(state)).toEqual('');
});
});
describe('when state logs are empty', () => {
beforeEach(() => {
state.logs.lines = [];
});
it('returns an empty string', () => {
expect(getters.trace(state)).toEqual('');
});
});
describe('when state logs are set', () => {
beforeEach(() => {
state.logs.lines = mockLines;
});
it('returns an empty string', () => {
expect(getters.trace(state)).toEqual(mockLines.join('\n'));
});
});
});
});
import mutations from 'ee/logs/stores/mutations';
import * as types from 'ee/logs/stores/mutation_types';
import logsPageState from 'ee/logs/stores/state';
import { mockLogsEndpoint, mockEnvironments, mockPods, mockPodName, mockLines } from '../mock_data';
describe('Logs Store Mutations', () => {
let state;
beforeEach(() => {
state = logsPageState();
});
it('ensures mutation types are correctly named', () => {
Object.keys(types).forEach(k => {
expect(k).toEqual(types[k]);
});
});
describe('SET_LOGS_ENDPOINT', () => {
it('sets the logs json endpoint', () => {
mutations[types.SET_LOGS_ENDPOINT](state, mockLogsEndpoint);
expect(state.logs.endpoint).toEqual(mockLogsEndpoint);
});
});
describe('SET_CURRENT_POD_NAME', () => {
it('sets current pod name', () => {
mutations[types.SET_CURRENT_POD_NAME](state, mockPodName);
expect(state.pods.current).toEqual(mockPodName);
});
});
describe('REQUEST_ENVIRONMENTS_DATA', () => {
it('inits data', () => {
mutations[types.REQUEST_ENVIRONMENTS_DATA](state);
expect(state.environments.options).toEqual([]);
expect(state.environments.isLoading).toEqual(true);
});
});
describe('RECEIVE_ENVIRONMENTS_DATA_SUCCESS', () => {
it('receives environments data and stores it as options', () => {
expect(state.environments.options).toEqual([]);
mutations[types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS](state, mockEnvironments);
expect(state.environments.options).toEqual(mockEnvironments);
expect(state.environments.isLoading).toEqual(false);
});
});
describe('RECEIVE_ENVIRONMENTS_DATA_ERROR', () => {
it('captures an error loading environments', () => {
mutations[types.RECEIVE_ENVIRONMENTS_DATA_ERROR](state);
expect(state.environments).toEqual({
options: [],
isLoading: false,
});
});
});
describe('REQUEST_LOGS_DATA', () => {
it('starts loading for logs', () => {
mutations[types.REQUEST_LOGS_DATA](state);
expect(state.logs).toEqual(
expect.objectContaining({
lines: [],
isLoading: true,
isComplete: false,
}),
);
});
});
describe('RECEIVE_LOGS_DATA_SUCCESS', () => {
it('receives logs lines', () => {
mutations[types.RECEIVE_LOGS_DATA_SUCCESS](state, mockLines);
expect(state.logs).toEqual(
expect.objectContaining({
lines: mockLines,
isLoading: false,
isComplete: true,
}),
);
});
});
describe('RECEIVE_LOGS_DATA_ERROR', () => {
it('receives log data error and stops loading', () => {
mutations[types.RECEIVE_LOGS_DATA_ERROR](state);
expect(state.logs).toEqual(
expect.objectContaining({
lines: [],
isLoading: false,
isComplete: true,
}),
);
});
});
describe('REQUEST_PODS_DATA', () => {
it('receives log data error and stops loading', () => {
mutations[types.REQUEST_PODS_DATA](state);
expect(state.pods).toEqual(
expect.objectContaining({
options: [],
}),
);
});
});
describe('RECEIVE_PODS_DATA_SUCCESS', () => {
it('receives pods data success', () => {
mutations[types.RECEIVE_PODS_DATA_SUCCESS](state, mockPods);
expect(state.pods).toEqual(
expect.objectContaining({
options: mockPods,
}),
);
});
});
describe('RECEIVE_PODS_DATA_ERROR', () => {
it('receives pods data error', () => {
mutations[types.RECEIVE_PODS_DATA_ERROR](state);
expect(state.pods).toEqual(
expect.objectContaining({
options: [],
}),
);
});
});
});
...@@ -10505,6 +10505,9 @@ msgstr "" ...@@ -10505,6 +10505,9 @@ msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again" msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "" msgstr ""
msgid "Metrics|There was an error fetching the logs, please try again"
msgstr ""
msgid "Metrics|There was an error getting deployment information." msgid "Metrics|There was an error getting deployment information."
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