Commit 98d75c2e authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '33537-update-backend-endpoint-changes' into 'master'

Fix compatibility issues with new backend logs API

Closes #36101

See merge request gitlab-org/gitlab!19752
parents 56846090 8024da58
...@@ -78,7 +78,17 @@ export default { ...@@ -78,7 +78,17 @@ export default {
return axios.delete(url); return axios.delete(url);
}, },
getPodLogs({ projectFullPath, environmentId, podName, containerName }) { /**
* Returns pods logs for an environment with an optional pod and container
*
* @param {Object} params
* @param {string} param.projectFullPath - Path of the project, in format `/<namespace>/<project-key>`
* @param {number} param.environmentId - Id of the environment
* @param {string=} params.podName - Pod name, if not set the backend assumes a default one
* @param {string=} params.containerName - Container name, if not set the backend assumes a default one
* @returns {Promise} Axios promise for the result of a GET request of logs
*/
getPodLogs({ projectPath, environmentId, podName, containerName }) {
let logPath = this.podLogsPath; let logPath = this.podLogsPath;
if (podName && containerName) { if (podName && containerName) {
logPath = this.podLogsPathWithPodContainer; logPath = this.podLogsPathWithPodContainer;
...@@ -87,7 +97,7 @@ export default { ...@@ -87,7 +97,7 @@ export default {
} }
let url = this.buildUrl(logPath) let url = this.buildUrl(logPath)
.replace(':project_full_path', projectFullPath) .replace(':project_full_path', projectPath)
.replace(':environment_id', environmentId); .replace(':environment_id', environmentId);
if (podName) { if (podName) {
......
...@@ -5,16 +5,16 @@ import { isScrolledToBottom, scrollDown, toggleDisableButton } from '~/lib/utils ...@@ -5,16 +5,16 @@ import { isScrolledToBottom, scrollDown, toggleDisableButton } from '~/lib/utils
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import LogOutputBehaviours from '~/lib/utils/logoutput_behaviours'; import LogOutputBehaviours from '~/lib/utils/logoutput_behaviours';
import flash from '~/flash'; import flash from '~/flash';
import { __, s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import _ from 'underscore'; import _ from 'underscore';
import { backOff } from '~/lib/utils/common_utils'; import { backOff } from '~/lib/utils/common_utils';
import Api from 'ee/api'; import Api from 'ee/api';
const TWO_MINUTES = 120000; const TWO_MINUTES = 120000;
const requestWithBackoff = (projectFullPath, environmentId, podName, containerName) => const requestWithBackoff = (projectPath, environmentId, podName, containerName) =>
backOff((next, stop) => { backOff((next, stop) => {
Api.getPodLogs({ projectFullPath, environmentId, podName, containerName }) Api.getPodLogs({ projectPath, environmentId, podName, containerName })
.then(res => { .then(res => {
if (!res.data) { if (!res.data) {
next(); next();
...@@ -128,7 +128,7 @@ export default class KubernetesPodLogs extends LogOutputBehaviours { ...@@ -128,7 +128,7 @@ export default class KubernetesPodLogs extends LogOutputBehaviours {
); );
} }
} else { } else {
flash(__('Environments|An error occurred while fetching the logs')); flash(s__('Environments|An error occurred while fetching the logs'));
} }
}) })
.finally(() => { .finally(() => {
......
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { GlDropdown, GlDropdownItem, GlFormGroup, GlButton, GlTooltipDirective } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlFormGroup, GlButton, GlTooltipDirective } from '@gitlab/ui';
import flash from '~/flash';
import { import {
canScroll, canScroll,
isScrolledToTop, isScrolledToTop,
...@@ -10,7 +9,6 @@ import { ...@@ -10,7 +9,6 @@ import {
scrollUp, scrollUp,
} from '~/lib/utils/scroll_utils'; } from '~/lib/utils/scroll_utils';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
export default { export default {
components: { components: {
...@@ -24,6 +22,14 @@ export default { ...@@ -24,6 +22,14 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
props: { props: {
environmentId: {
type: String,
required: true,
},
projectFullPath: {
type: String,
required: true,
},
currentEnvironmentName: { currentEnvironmentName: {
type: String, type: String,
required: false, required: false,
...@@ -39,11 +45,6 @@ export default { ...@@ -39,11 +45,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
logsEndpoint: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -73,24 +74,19 @@ export default { ...@@ -73,24 +74,19 @@ export default {
window.addEventListener('scroll', this.updateScrollState); window.addEventListener('scroll', this.updateScrollState);
}, },
mounted() { mounted() {
this.fetchEnvironments(this.environmentsPath); this.setInitData({
projectPath: this.projectFullPath,
environmentId: this.environmentId,
podName: this.currentPodName,
});
this.setLogsEndpoint(this.logsEndpoint) this.fetchEnvironments(this.environmentsPath);
.then(() => {
this.fetchLogs(this.currentPodName);
})
.catch(() => {
flash(__('Something went wrong on our end. Please try again!'));
});
}, },
destroyed() { destroyed() {
window.removeEventListener('scroll', this.updateScrollState); window.removeEventListener('scroll', this.updateScrollState);
}, },
methods: { methods: {
...mapActions('environmentLogs', ['setLogsEndpoint', 'fetchEnvironments', 'fetchLogs']), ...mapActions('environmentLogs', ['setInitData', 'showPodLogs', 'fetchEnvironments']),
showPod(podName) {
this.fetchLogs(podName);
},
updateScrollState() { updateScrollState() {
this.scrollToTopEnabled = canScroll() && !isScrolledToTop(); this.scrollToTopEnabled = canScroll() && !isScrolledToTop();
this.scrollToBottomEnabled = canScroll() && !isScrolledToBottom(); this.scrollToBottomEnabled = canScroll() && !isScrolledToBottom();
...@@ -136,7 +132,7 @@ export default { ...@@ -136,7 +132,7 @@ export default {
> >
<gl-dropdown <gl-dropdown
id="pods-dropdown" id="pods-dropdown"
:text="pods.current" :text="pods.current || s__('Environments|No pods to display')"
:disabled="logs.isLoading" :disabled="logs.isLoading"
class="d-flex js-pods-dropdown" class="d-flex js-pods-dropdown"
toggle-class="dropdown-menu-toggle" toggle-class="dropdown-menu-toggle"
...@@ -144,7 +140,7 @@ export default { ...@@ -144,7 +140,7 @@ export default {
<gl-dropdown-item <gl-dropdown-item
v-for="podName in pods.options" v-for="podName in pods.options"
:key="podName" :key="podName"
@click="showPod(podName)" @click="showPodLogs(podName)"
> >
{{ podName }} {{ podName }}
</gl-dropdown-item> </gl-dropdown-item>
...@@ -188,7 +184,7 @@ export default { ...@@ -188,7 +184,7 @@ export default {
class="ml-1 px-2 js-refresh-log" class="ml-1 px-2 js-refresh-log"
:title="__('Refresh')" :title="__('Refresh')"
:aria-label="__('Refresh')" :aria-label="__('Refresh')"
@click="showPod(pods.current)" @click="showPodLogs(pods.current)"
> >
<icon name="retry" /> <icon name="retry" />
</gl-button> </gl-button>
......
import { backOff } from '~/lib/utils/common_utils'; import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import flash from '~/flash'; import flash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import Api from 'ee/api';
import * as types from './mutation_types'; import * as types from './mutation_types';
const requestUntilData = (url, params) => const requestLogsUntilData = ({ projectPath, environmentId, podName }) =>
backOff((next, stop) => { backOff((next, stop) => {
axios Api.getPodLogs({ projectPath, environmentId, podName })
.get(url, {
params,
})
.then(res => { .then(res => {
if (!res.data) { if (res.status === httpStatusCodes.ACCEPTED) {
next(); next();
return; return;
} }
...@@ -22,8 +21,15 @@ const requestUntilData = (url, params) => ...@@ -22,8 +21,15 @@ const requestUntilData = (url, params) =>
}); });
}); });
export const setLogsEndpoint = ({ commit }, logsEndpoint) => { export const setInitData = ({ dispatch, commit }, { projectPath, environmentId, podName }) => {
commit(types.SET_LOGS_ENDPOINT, logsEndpoint); commit(types.SET_PROJECT_ENVIRONMENT, { projectPath, environmentId });
commit(types.SET_CURRENT_POD_NAME, podName);
dispatch('fetchLogs');
};
export const showPodLogs = ({ dispatch, commit }, podName) => {
commit(types.SET_CURRENT_POD_NAME, podName);
dispatch('fetchLogs');
}; };
export const fetchEnvironments = ({ commit }, environmentsPath) => { export const fetchEnvironments = ({ commit }, environmentsPath) => {
...@@ -40,20 +46,20 @@ export const fetchEnvironments = ({ commit }, environmentsPath) => { ...@@ -40,20 +46,20 @@ export const fetchEnvironments = ({ commit }, environmentsPath) => {
}); });
}; };
export const fetchLogs = ({ commit, state }, podName) => { export const fetchLogs = ({ commit, state }) => {
if (podName) { const params = {
commit(types.SET_CURRENT_POD_NAME, podName); projectPath: state.projectPath,
} environmentId: state.environments.current,
podName: state.pods.current,
};
commit(types.REQUEST_PODS_DATA); commit(types.REQUEST_PODS_DATA);
commit(types.REQUEST_LOGS_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 return requestLogsUntilData(params)
if (!podName && pods[0]) { .then(({ data }) => {
commit(types.SET_CURRENT_POD_NAME, pods[0]); const { pod_name, pods, logs } = data;
} commit(types.SET_CURRENT_POD_NAME, pod_name);
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods); commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
commit(types.RECEIVE_LOGS_DATA_SUCCESS, logs); commit(types.RECEIVE_LOGS_DATA_SUCCESS, logs);
......
export const SET_LOGS_ENDPOINT = 'SET_LOGS_ENDPOINT'; export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT';
export const SET_CURRENT_POD_NAME = 'SET_CURRENT_POD_NAME';
export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA'; export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA';
export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS'; export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS';
...@@ -9,6 +8,7 @@ export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA'; ...@@ -9,6 +8,7 @@ export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS'; export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR'; export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
export const SET_CURRENT_POD_NAME = 'SET_CURRENT_POD_NAME';
export const REQUEST_PODS_DATA = 'REQUEST_PODS_DATA'; export const REQUEST_PODS_DATA = 'REQUEST_PODS_DATA';
export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS'; export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS';
export const RECEIVE_PODS_DATA_ERROR = 'RECEIVE_PODS_DATA_ERROR'; export const RECEIVE_PODS_DATA_ERROR = 'RECEIVE_PODS_DATA_ERROR';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.SET_LOGS_ENDPOINT](state, endpoint) { /** Project data */
state.logs.endpoint = endpoint; [types.SET_PROJECT_ENVIRONMENT](state, { projectPath, environmentId }) {
}, state.projectPath = projectPath;
[types.SET_CURRENT_POD_NAME](state, podName) { state.environments.current = environmentId;
state.pods.current = podName;
}, },
/** Environments data */
[types.REQUEST_ENVIRONMENTS_DATA](state) { [types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = []; state.environments.options = [];
state.environments.isLoading = true; state.environments.isLoading = true;
...@@ -21,6 +21,7 @@ export default { ...@@ -21,6 +21,7 @@ export default {
state.environments.isLoading = false; state.environments.isLoading = false;
}, },
/** Logs data */
[types.REQUEST_LOGS_DATA](state) { [types.REQUEST_LOGS_DATA](state) {
state.logs.lines = []; state.logs.lines = [];
state.logs.isLoading = true; state.logs.isLoading = true;
...@@ -37,6 +38,10 @@ export default { ...@@ -37,6 +38,10 @@ export default {
state.logs.isComplete = true; state.logs.isComplete = true;
}, },
/** Pods data */
[types.SET_CURRENT_POD_NAME](state, podName) {
state.pods.current = podName;
},
[types.REQUEST_PODS_DATA](state) { [types.REQUEST_PODS_DATA](state) {
state.pods.options = []; state.pods.options = [];
}, },
......
export default () => ({ export default () => ({
/**
* Current project path
*/
projectPath: '',
/** /**
* Environments list information * Environments list information
*/ */
environments: { environments: {
options: [], options: [],
isLoading: false, isLoading: false,
current: null,
}, },
/** /**
* Logs including trace * Logs including trace
*/ */
logs: { logs: {
endpoint: null,
lines: [], lines: [],
isLoading: false, isLoading: false,
isComplete: true, isComplete: true,
......
...@@ -164,7 +164,7 @@ describe('Api', () => { ...@@ -164,7 +164,7 @@ describe('Api', () => {
}); });
describe('getPodLogs', () => { describe('getPodLogs', () => {
const projectFullPath = 'root/test-project'; const projectPath = 'root/test-project';
const environmentId = 2; const environmentId = 2;
const podName = 'pod'; const podName = 'pod';
const containerName = 'container'; const containerName = 'container';
...@@ -180,9 +180,9 @@ describe('Api', () => { ...@@ -180,9 +180,9 @@ describe('Api', () => {
}); });
it('calls `axios.get` with pod_name and container_name', done => { it('calls `axios.get` with pod_name and container_name', done => {
const expectedUrl = `${dummyUrlRoot}/${projectFullPath}/environments/${environmentId}/pods/${podName}/containers/${containerName}/logs.json`; const expectedUrl = `${dummyUrlRoot}/${projectPath}/environments/${environmentId}/pods/${podName}/containers/${containerName}/logs.json`;
Api.getPodLogs({ projectFullPath, environmentId, podName, containerName }) Api.getPodLogs({ projectPath, environmentId, podName, containerName })
.then(() => { .then(() => {
expect(expectedUrl).toBe(lastUrl()); expect(expectedUrl).toBe(lastUrl());
}) })
...@@ -191,9 +191,9 @@ describe('Api', () => { ...@@ -191,9 +191,9 @@ describe('Api', () => {
}); });
it('calls `axios.get` without pod_name and container_name', done => { it('calls `axios.get` without pod_name and container_name', done => {
const expectedUrl = `${dummyUrlRoot}/${projectFullPath}/environments/${environmentId}/pods/containers/logs.json`; const expectedUrl = `${dummyUrlRoot}/${projectPath}/environments/${environmentId}/pods/containers/logs.json`;
Api.getPodLogs({ projectFullPath, environmentId }) Api.getPodLogs({ projectPath, environmentId })
.then(() => { .then(() => {
expect(expectedUrl).toBe(lastUrl()); expect(expectedUrl).toBe(lastUrl());
}) })
...@@ -202,9 +202,9 @@ describe('Api', () => { ...@@ -202,9 +202,9 @@ describe('Api', () => {
}); });
it('calls `axios.get` with pod_name', done => { it('calls `axios.get` with pod_name', done => {
const expectedUrl = `${dummyUrlRoot}/${projectFullPath}/environments/${environmentId}/pods/${podName}/containers/logs.json`; const expectedUrl = `${dummyUrlRoot}/${projectPath}/environments/${environmentId}/pods/${podName}/containers/logs.json`;
Api.getPodLogs({ projectFullPath, environmentId, podName }) Api.getPodLogs({ projectPath, environmentId, podName })
.then(() => { .then(() => {
expect(expectedUrl).toBe(lastUrl()); expect(expectedUrl).toBe(lastUrl());
}) })
......
...@@ -11,12 +11,14 @@ import { ...@@ -11,12 +11,14 @@ import {
import EnvironmentLogs from 'ee/logs/components/environment_logs.vue'; import EnvironmentLogs from 'ee/logs/components/environment_logs.vue';
import { createStore } from 'ee/logs/stores'; import { createStore } from 'ee/logs/stores';
import { import {
mockEnvironment, mockProjectPath,
mockEnvId,
mockEnvName,
mockEnvironments, mockEnvironments,
mockPods, mockPods,
mockEnvironmentsEndpoint,
mockLines, mockLines,
mockLogsEndpoint, mockPodName,
mockEnvironmentsEndpoint,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/lib/utils/scroll_utils'); jest.mock('~/lib/utils/scroll_utils');
...@@ -28,13 +30,16 @@ describe('EnvironmentLogs', () => { ...@@ -28,13 +30,16 @@ describe('EnvironmentLogs', () => {
let state; let state;
const propsData = { const propsData = {
currentEnvironmentName: mockEnvironment.name, environmentId: mockEnvId,
projectFullPath: mockProjectPath,
currentEnvironmentName: mockEnvName,
environmentsPath: mockEnvironmentsEndpoint, environmentsPath: mockEnvironmentsEndpoint,
logsEndpoint: mockLogsEndpoint,
}; };
const actionMocks = { const actionMocks = {
setInitData: jest.fn(),
showPodLogs: jest.fn(),
fetchEnvironments: jest.fn(), fetchEnvironments: jest.fn(),
fetchLogs: jest.fn(),
}; };
const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown'); const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown');
...@@ -60,6 +65,12 @@ describe('EnvironmentLogs', () => { ...@@ -60,6 +65,12 @@ describe('EnvironmentLogs', () => {
EnvironmentLogsComponent = Vue.extend(EnvironmentLogs); EnvironmentLogsComponent = Vue.extend(EnvironmentLogs);
}); });
afterEach(() => {
actionMocks.setInitData.mockReset();
actionMocks.showPodLogs.mockReset();
actionMocks.fetchEnvironments.mockReset();
});
it('displays UI elements', () => { it('displays UI elements', () => {
initWrapper(); initWrapper();
...@@ -76,17 +87,32 @@ describe('EnvironmentLogs', () => { ...@@ -76,17 +87,32 @@ describe('EnvironmentLogs', () => {
expect(findLogTrace().isEmpty()).toBe(false); expect(findLogTrace().isEmpty()).toBe(false);
}); });
it('mounted inits data', () => {
initWrapper();
expect(actionMocks.setInitData).toHaveBeenCalledTimes(1);
expect(actionMocks.setInitData).toHaveBeenLastCalledWith({
environmentId: mockEnvId,
projectPath: mockProjectPath,
podName: null,
});
expect(actionMocks.fetchEnvironments).toHaveBeenCalledTimes(1);
expect(actionMocks.fetchEnvironments).toHaveBeenLastCalledWith(mockEnvironmentsEndpoint);
expect(findEnvironmentsDropdown().props('text')).toBe(mockEnvName);
expect(findPodsDropdown().props('text').length).toBeGreaterThan(0);
});
describe('loading state', () => { describe('loading state', () => {
beforeEach(() => { beforeEach(() => {
actionMocks.fetchEnvironments.mockImplementation(() => { state.pods.options = [];
state.environments.options = [];
state.environments.isLoading = true; state.logs.lines = [];
}); state.logs.isLoading = true;
actionMocks.fetchLogs.mockImplementation(() => {
state.pods.options = []; state.environments.options = [];
state.logs.lines = []; state.environments.isLoading = true;
state.logs.isLoading = true;
});
initWrapper(); initWrapper();
}); });
...@@ -102,24 +128,34 @@ describe('EnvironmentLogs', () => { ...@@ -102,24 +128,34 @@ describe('EnvironmentLogs', () => {
}); });
it('shows a logs trace', () => { it('shows a logs trace', () => {
const trace = findLogTrace(); expect(findLogTrace().text()).toBe('');
expect(trace.text()).toBe(''); expect(
expect(trace.find('.js-build-loader-animation').isVisible()).toBe(true); findLogTrace()
.find('.js-build-loader-animation')
.isVisible(),
).toBe(true);
}); });
}); });
describe('state with data', () => { describe('state with data', () => {
beforeEach(() => { beforeEach(() => {
actionMocks.fetchEnvironments.mockImplementation(() => { actionMocks.setInitData.mockImplementation(() => {
state.environments.options = mockEnvironments;
});
actionMocks.fetchLogs.mockImplementation(() => {
state.pods.options = mockPods; state.pods.options = mockPods;
[state.pods.current] = state.pods.options; [state.pods.current] = state.pods.options;
state.logs.isComplete = false; state.logs.isComplete = false;
state.logs.lines = mockLines; state.logs.lines = mockLines;
}); });
actionMocks.showPodLogs.mockImplementation(podName => {
state.pods.options = mockPods;
[state.pods.current] = podName;
state.logs.isComplete = false;
state.logs.lines = mockLines;
});
actionMocks.fetchEnvironments.mockImplementation(() => {
state.environments.options = mockEnvironments;
});
initWrapper(); initWrapper();
}); });
...@@ -128,13 +164,15 @@ describe('EnvironmentLogs', () => { ...@@ -128,13 +164,15 @@ describe('EnvironmentLogs', () => {
scrollDown.mockReset(); scrollDown.mockReset();
scrollUp.mockReset(); scrollUp.mockReset();
actionMocks.setInitData.mockReset();
actionMocks.showPodLogs.mockReset();
actionMocks.fetchEnvironments.mockReset(); actionMocks.fetchEnvironments.mockReset();
actionMocks.fetchLogs.mockReset();
}); });
it('populates environments dropdown', () => { it('populates environments dropdown', () => {
const items = findEnvironmentsDropdown().findAll(GlDropdownItem); const items = findEnvironmentsDropdown().findAll(GlDropdownItem);
expect(findEnvironmentsDropdown().props('text')).toBe(mockEnvName);
expect(items.length).toBe(mockEnvironments.length); expect(items.length).toBe(mockEnvironments.length);
mockEnvironments.forEach((env, i) => { mockEnvironments.forEach((env, i) => {
const item = items.at(i); const item = items.at(i);
...@@ -147,6 +185,7 @@ describe('EnvironmentLogs', () => { ...@@ -147,6 +185,7 @@ describe('EnvironmentLogs', () => {
it('populates pods dropdown', () => { it('populates pods dropdown', () => {
const items = findPodsDropdown().findAll(GlDropdownItem); const items = findPodsDropdown().findAll(GlDropdownItem);
expect(findPodsDropdown().props('text')).toBe(mockPodName);
expect(items.length).toBe(mockPods.length); expect(items.length).toBe(mockPods.length);
mockPods.forEach((pod, i) => { mockPods.forEach((pod, i) => {
const item = items.at(i); const item = items.at(i);
...@@ -169,28 +208,26 @@ describe('EnvironmentLogs', () => { ...@@ -169,28 +208,26 @@ describe('EnvironmentLogs', () => {
const items = findPodsDropdown().findAll(GlDropdownItem); const items = findPodsDropdown().findAll(GlDropdownItem);
const index = 2; // any pod const index = 2; // any pod
expect(actionMocks.fetchLogs).toHaveBeenCalledTimes(1); expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
expect(actionMocks.fetchLogs).toHaveBeenLastCalledWith(null);
items.at(index).vm.$emit('click'); items.at(index).vm.$emit('click');
expect(actionMocks.fetchLogs).toHaveBeenCalledTimes(2); expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
expect(actionMocks.fetchLogs).toHaveBeenLastCalledWith(mockPods[index]); expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPods[index]);
}); });
it('refresh button, trace is refreshed', () => { it('refresh button, trace is refreshed', () => {
expect(actionMocks.fetchLogs).toHaveBeenCalledTimes(1); expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(0);
expect(actionMocks.fetchLogs).toHaveBeenLastCalledWith(null);
findRefreshLog().vm.$emit('click'); // works findRefreshLog().vm.$emit('click');
expect(actionMocks.fetchLogs).toHaveBeenCalledTimes(2); expect(actionMocks.showPodLogs).toHaveBeenCalledTimes(1);
expect(actionMocks.fetchLogs).toHaveBeenLastCalledWith(mockPods[0]); expect(actionMocks.showPodLogs).toHaveBeenLastCalledWith(mockPodName);
}); });
describe('when scrolling actions are enabled', () => { describe('when scrolling actions are enabled', () => {
beforeEach(done => { beforeEach(done => {
// simulate being in the middle of a long page // mock scrolled to the middle of a long page
canScroll.mockReturnValue(true); canScroll.mockReturnValue(true);
isScrolledToBottom.mockReturnValue(false); isScrolledToBottom.mockReturnValue(false);
isScrolledToTop.mockReturnValue(false); isScrolledToTop.mockReturnValue(false);
...@@ -215,13 +252,13 @@ describe('EnvironmentLogs', () => { ...@@ -215,13 +252,13 @@ describe('EnvironmentLogs', () => {
it('click on "scroll to bottom" scrolls down', () => { it('click on "scroll to bottom" scrolls down', () => {
expect(findScrollToBottom().is('[disabled]')).toBe(false); expect(findScrollToBottom().is('[disabled]')).toBe(false);
findScrollToBottom().vm.$emit('click'); findScrollToBottom().vm.$emit('click');
expect(scrollDown).toHaveBeenCalledTimes(2); // plus one time when loaded expect(scrollDown).toHaveBeenCalledTimes(2); // plus one time when trace was loaded
}); });
}); });
describe('when scrolling actions are disabled', () => { describe('when scrolling actions are disabled', () => {
beforeEach(() => { beforeEach(() => {
// a short page, without a scrollbar // mock a short page without a scrollbar
canScroll.mockReturnValue(false); canScroll.mockReturnValue(false);
isScrolledToBottom.mockReturnValue(true); isScrolledToBottom.mockReturnValue(true);
isScrolledToTop.mockReturnValue(true); isScrolledToTop.mockReturnValue(true);
......
const makeMockLogsPath = id => `/root/autodevops-deploy/environments/${id}/logs`; export const mockProjectPath = 'root/autodevops-deploy';
export const mockEnvName = 'production';
export const mockEnvironmentsEndpoint = `${mockProjectPath}/environments.json`;
export const mockEnvId = '99';
const makeMockEnvironment = (id, name) => ({ const makeMockEnvironment = (id, name) => ({
id, id,
logs_path: makeMockLogsPath(id), logs_path: `${mockProjectPath}/environments/${id}/logs`,
name, name,
}); });
export const mockEnvironment = makeMockEnvironment(99, 'production'); export const mockEnvironment = makeMockEnvironment(mockEnvId, mockEnvName);
export const mockEnvironmentsEndpoint = '/root/autodevops-deploy/environments.json';
export const mockEnvironments = [ export const mockEnvironments = [
mockEnvironment, mockEnvironment,
makeMockEnvironment(101, 'staging'), makeMockEnvironment(101, 'staging'),
...@@ -21,7 +24,6 @@ export const mockPods = [ ...@@ -21,7 +24,6 @@ export const mockPods = [
'production-764c58d697-ddddd', 'production-764c58d697-ddddd',
]; ];
export const mockLogsEndpoint = `/root/autodevops-deploy/environments/${mockEnvironment.id}/logs.json`;
export const mockLines = [ 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:48 UTC] "GET / HTTP/1.1" 200 13',
'- -> /', '- -> /',
......
...@@ -4,17 +4,18 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -4,17 +4,18 @@ import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import * as types from 'ee/logs/stores/mutation_types'; import * as types from 'ee/logs/stores/mutation_types';
import logsPageState from 'ee/logs/stores/state'; import logsPageState from 'ee/logs/stores/state';
import { setLogsEndpoint, fetchEnvironments, fetchLogs } from 'ee/logs/stores/actions'; import { setInitData, showPodLogs, fetchEnvironments, fetchLogs } from 'ee/logs/stores/actions';
import flash from '~/flash'; import flash from '~/flash';
import { import {
mockLogsEndpoint, mockProjectPath,
mockEnvId,
mockPodName,
mockEnvironmentsEndpoint,
mockEnvironments, mockEnvironments,
mockPods, mockPods,
mockPodName,
mockLines, mockLines,
mockEnvironmentsEndpoint,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -31,14 +32,33 @@ describe('Logs Store actions', () => { ...@@ -31,14 +32,33 @@ describe('Logs Store actions', () => {
flash.mockClear(); flash.mockClear();
}); });
describe('setLogsEndpoint', () => { describe('setInitData', () => {
it('should commit SET_LOGS_ENDPOINT mutation', done => { it('should commit environment and pod name mutation', done => {
testAction( testAction(
setLogsEndpoint, setInitData,
mockLogsEndpoint, { projectPath: mockProjectPath, environmentId: mockEnvId, podName: mockPodName },
state, state,
[{ type: types.SET_LOGS_ENDPOINT, payload: mockLogsEndpoint }], [
[], {
type: types.SET_PROJECT_ENVIRONMENT,
payload: { projectPath: mockProjectPath, environmentId: mockEnvId },
},
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
],
[{ type: 'fetchLogs' }],
done,
);
});
});
describe('showPodLogs', () => {
it('should commit pod name', done => {
testAction(
showPodLogs,
mockPodName,
state,
[{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName }],
[{ type: 'fetchLogs' }],
done, done,
); );
}); });
...@@ -81,24 +101,6 @@ describe('Logs Store actions', () => { ...@@ -81,24 +101,6 @@ describe('Logs Store actions', () => {
}, },
); );
}); });
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', () => { describe('fetchLogs', () => {
...@@ -106,22 +108,33 @@ describe('Logs Store actions', () => { ...@@ -106,22 +108,33 @@ describe('Logs Store actions', () => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
afterEach(() => {
mock.reset();
});
it('should commit logs and pod data when there is pod name defined', done => { it('should commit logs and pod data when there is pod name defined', done => {
state.logs.endpoint = mockLogsEndpoint; state.projectPath = mockProjectPath;
state.environments.current = mockEnvId;
state.pods.current = mockPodName;
mock.onGet(mockLogsEndpoint).replyOnce(200, { const endpoint = `/${mockProjectPath}/environments/${mockEnvId}/pods/${mockPodName}/containers/logs.json`;
mock.onGet(endpoint).reply(200, {
pod_name: mockPodName,
pods: mockPods, pods: mockPods,
logs: mockLines, logs: mockLines,
}); });
mock.onGet(endpoint).replyOnce(202); // mock reactive cache
testAction( testAction(
fetchLogs, fetchLogs,
mockPodName, null,
state, state,
[ [
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.REQUEST_PODS_DATA }, { type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods }, { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLines }, { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLines },
], ],
...@@ -131,12 +144,17 @@ describe('Logs Store actions', () => { ...@@ -131,12 +144,17 @@ describe('Logs Store actions', () => {
}); });
it('should commit logs and pod data when no pod name defined', done => { it('should commit logs and pod data when no pod name defined', done => {
state.logs.endpoint = mockLogsEndpoint; state.projectPath = mockProjectPath;
state.environments.current = mockEnvId;
mock.onGet(mockLogsEndpoint).replyOnce(200, { const endpoint = `/${mockProjectPath}/environments/${mockEnvId}/pods/containers/logs.json`;
mock.onGet(endpoint).reply(200, {
pod_name: mockPodName,
pods: mockPods, pods: mockPods,
logs: mockLines, logs: mockLines,
}); });
mock.onGet(endpoint).replyOnce(202); // mock reactive cache
testAction( testAction(
fetchLogs, fetchLogs,
...@@ -145,7 +163,7 @@ describe('Logs Store actions', () => { ...@@ -145,7 +163,7 @@ describe('Logs Store actions', () => {
[ [
{ type: types.REQUEST_PODS_DATA }, { type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPods[0] }, { type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods }, { type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLines }, { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLines },
], ],
...@@ -154,17 +172,18 @@ describe('Logs Store actions', () => { ...@@ -154,17 +172,18 @@ describe('Logs Store actions', () => {
); );
}); });
it('should commit logs and pod errors', done => { it('should commit logs and pod errors when backend fails', done => {
state.logs.endpoint = mockLogsEndpoint; state.projectPath = mockProjectPath;
state.environments.current = mockEnvId;
mock.onGet(mockLogsEndpoint).replyOnce(500); const endpoint = `/${mockProjectPath}/environments/${mockEnvId}/pods/containers/logs.json`;
mock.onGet(endpoint).replyOnce(500);
testAction( testAction(
fetchLogs, fetchLogs,
mockPodName, null,
state, state,
[ [
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.REQUEST_PODS_DATA }, { type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.RECEIVE_PODS_DATA_ERROR }, { type: types.RECEIVE_PODS_DATA_ERROR },
......
...@@ -2,7 +2,14 @@ import mutations from 'ee/logs/stores/mutations'; ...@@ -2,7 +2,14 @@ import mutations from 'ee/logs/stores/mutations';
import * as types from 'ee/logs/stores/mutation_types'; import * as types from 'ee/logs/stores/mutation_types';
import logsPageState from 'ee/logs/stores/state'; import logsPageState from 'ee/logs/stores/state';
import { mockLogsEndpoint, mockEnvironments, mockPods, mockPodName, mockLines } from '../mock_data'; import {
mockProjectPath,
mockEnvId,
mockEnvironments,
mockPods,
mockPodName,
mockLines,
} from '../mock_data';
describe('Logs Store Mutations', () => { describe('Logs Store Mutations', () => {
let state; let state;
...@@ -17,17 +24,14 @@ describe('Logs Store Mutations', () => { ...@@ -17,17 +24,14 @@ describe('Logs Store Mutations', () => {
}); });
}); });
describe('SET_LOGS_ENDPOINT', () => { describe('SET_PROJECT_ENVIRONMENT', () => {
it('sets the logs json endpoint', () => { it('sets the logs json endpoint', () => {
mutations[types.SET_LOGS_ENDPOINT](state, mockLogsEndpoint); mutations[types.SET_PROJECT_ENVIRONMENT](state, {
expect(state.logs.endpoint).toEqual(mockLogsEndpoint); projectPath: mockProjectPath,
}); environmentId: mockEnvId,
}); });
expect(state.projectPath).toEqual(mockProjectPath);
describe('SET_CURRENT_POD_NAME', () => { expect(state.environments.current).toEqual(mockEnvId);
it('sets current pod name', () => {
mutations[types.SET_CURRENT_POD_NAME](state, mockPodName);
expect(state.pods.current).toEqual(mockPodName);
}); });
}); });
...@@ -57,6 +61,7 @@ describe('Logs Store Mutations', () => { ...@@ -57,6 +61,7 @@ describe('Logs Store Mutations', () => {
expect(state.environments).toEqual({ expect(state.environments).toEqual({
options: [], options: [],
isLoading: false, isLoading: false,
current: null,
}); });
}); });
}); });
...@@ -103,6 +108,13 @@ describe('Logs Store Mutations', () => { ...@@ -103,6 +108,13 @@ describe('Logs Store Mutations', () => {
}); });
}); });
describe('SET_CURRENT_POD_NAME', () => {
it('set current pod name', () => {
mutations[types.SET_CURRENT_POD_NAME](state, mockPodName);
expect(state.pods.current).toEqual(mockPodName);
});
});
describe('REQUEST_PODS_DATA', () => { describe('REQUEST_PODS_DATA', () => {
it('receives log data error and stops loading', () => { it('receives log data error and stops loading', () => {
mutations[types.REQUEST_PODS_DATA](state); mutations[types.REQUEST_PODS_DATA](state);
......
...@@ -6442,6 +6442,9 @@ msgstr "" ...@@ -6442,6 +6442,9 @@ msgstr ""
msgid "Environments|No deployments yet" msgid "Environments|No deployments yet"
msgstr "" msgstr ""
msgid "Environments|No pods to display"
msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file." msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
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