Commit 16be6f5d authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '37276-add-time-filtering-to-log-view' into 'master'

Add time filters to log view

Closes #37986

See merge request gitlab-org/gitlab!22638
parents bb1e7015 2f4adf82
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
.mh-50vh { max-height: 50vh; } .mh-50vh { max-height: 50vh; }
.gl-w-64 { width: px-to-rem($grid-size * 8); } .gl-w-64 { width: px-to-rem($grid-size * 8); }
.gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); } .gl-h-64 { height: px-to-rem($grid-size * 8); }
.gl-text-purple { color: $purple; } .gl-text-purple { color: $purple; }
...@@ -59,4 +60,3 @@ ...@@ -59,4 +60,3 @@
.gl-text-red-700 { @include gl-text-red-700; } .gl-text-red-700 { @include gl-text-red-700; }
.gl-text-orange-700 { @include gl-text-orange-700; } .gl-text-orange-700 { @include gl-text-orange-700; }
.gl-text-green-700 { @include gl-text-green-700; } .gl-text-green-700 { @include gl-text-green-700; }
...@@ -88,13 +88,15 @@ export default { ...@@ -88,13 +88,15 @@ export default {
* Returns pods logs for an environment with an optional pod and container * Returns pods logs for an environment with an optional pod and container
* *
* @param {Object} params * @param {Object} params
* @param {string} param.projectFullPath - Path of the project, in format `/<namespace>/<project-key>` * @param {string} params.projectFullPath - Path of the project, in format `/<namespace>/<project-key>`
* @param {number} param.environmentId - Id of the environment * @param {number} params.environmentId - Id of the environment
* @param {string=} params.podName - Pod name, if not set the backend assumes a default one * @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 * @param {string=} params.containerName - Container name, if not set the backend assumes a default one
* @param {string=} params.start - Starting date to query the logs in ISO format
* @param {string=} params.end - Ending date to query the logs in ISO format
* @returns {Promise} Axios promise for the result of a GET request of logs * @returns {Promise} Axios promise for the result of a GET request of logs
*/ */
getPodLogs({ projectPath, environmentName, podName, containerName, search }) { getPodLogs({ projectPath, environmentName, podName, containerName, search, start, end }) {
const url = this.buildUrl(this.podLogsPath).replace(':project_full_path', projectPath); const url = this.buildUrl(this.podLogsPath).replace(':project_full_path', projectPath);
const params = { const params = {
...@@ -110,6 +112,12 @@ export default { ...@@ -110,6 +112,12 @@ export default {
if (search) { if (search) {
params.search = search; params.search = search;
} }
if (start) {
params.start = start;
}
if (end) {
params.end = end;
}
return axios.get(url, { params }); return axios.get(url, { params });
}, },
......
...@@ -45,7 +45,13 @@ export default { ...@@ -45,7 +45,13 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('environmentLogs', ['environments', 'logs', 'pods', 'enableAdvancedQuerying']), ...mapState('environmentLogs', [
'environments',
'timeWindow',
'logs',
'pods',
'enableAdvancedQuerying',
]),
...mapGetters('environmentLogs', ['trace']), ...mapGetters('environmentLogs', ['trace']),
showLoader() { showLoader() {
return this.logs.isLoading || !this.logs.isComplete; return this.logs.isLoading || !this.logs.isComplete;
...@@ -60,6 +66,7 @@ export default { ...@@ -60,6 +66,7 @@ export default {
return ( return (
!this.isElasticStackCalloutDismissed && !this.isElasticStackCalloutDismissed &&
!this.environments.isLoading && !this.environments.isLoading &&
!this.logs.isLoading &&
!this.advancedFeaturesEnabled !this.advancedFeaturesEnabled
); );
}, },
...@@ -87,6 +94,7 @@ export default { ...@@ -87,6 +94,7 @@ export default {
...mapActions('environmentLogs', [ ...mapActions('environmentLogs', [
'setInitData', 'setInitData',
'setSearch', 'setSearch',
'setTimeWindow',
'showPodLogs', 'showPodLogs',
'showEnvironment', 'showEnvironment',
'fetchEnvironments', 'fetchEnvironments',
...@@ -113,19 +121,20 @@ export default { ...@@ -113,19 +121,20 @@ export default {
</a> </a>
</gl-alert> </gl-alert>
<div class="top-bar js-top-bar d-flex"> <div class="top-bar js-top-bar d-flex">
<div class="row"> <div class="row mx-n1">
<gl-form-group <gl-form-group
id="environments-dropdown-fg" id="environments-dropdown-fg"
:label="s__('Environments|Environment')" :label="s__('Environments|Environment')"
label-size="sm" label-size="sm"
label-for="environments-dropdown" label-for="environments-dropdown"
:class="featureElasticEnabled ? 'col-4' : 'col-6'" class="px-1"
:class="featureElasticEnabled ? 'col-3' : 'col-6'"
> >
<gl-dropdown <gl-dropdown
id="environments-dropdown" id="environments-dropdown"
:text="environments.current" :text="environments.current"
:disabled="environments.isLoading" :disabled="environments.isLoading"
class="d-flex js-environments-dropdown" class="d-flex gl-h-32 js-environments-dropdown"
toggle-class="dropdown-menu-toggle" toggle-class="dropdown-menu-toggle"
> >
<gl-dropdown-item <gl-dropdown-item
...@@ -142,13 +151,14 @@ export default { ...@@ -142,13 +151,14 @@ export default {
:label="s__('Environments|Pod logs from')" :label="s__('Environments|Pod logs from')"
label-size="sm" label-size="sm"
label-for="pods-dropdown" label-for="pods-dropdown"
:class="featureElasticEnabled ? 'col-4' : 'col-6'" class="px-1"
:class="featureElasticEnabled ? 'col-3' : 'col-6'"
> >
<gl-dropdown <gl-dropdown
id="pods-dropdown" id="pods-dropdown"
:text="pods.current || s__('Environments|No pods to display')" :text="pods.current || s__('Environments|No pods to display')"
:disabled="logs.isLoading" :disabled="logs.isLoading"
class="d-flex js-pods-dropdown" class="d-flex gl-h-32 js-pods-dropdown"
toggle-class="dropdown-menu-toggle" toggle-class="dropdown-menu-toggle"
> >
<gl-dropdown-item <gl-dropdown-item
...@@ -160,29 +170,57 @@ export default { ...@@ -160,29 +170,57 @@ export default {
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
</gl-form-group> </gl-form-group>
<gl-form-group
v-if="featureElasticEnabled" <template v-if="featureElasticEnabled">
id="search-fg" <gl-form-group
:label="s__('Environments|Search')" id="dates-fg"
label-size="sm" :label="s__('Environments|Show last')"
label-for="search" label-size="sm"
class="col-4" label-for="time-window-dropdown"
> class="col-3 px-1"
<gl-search-box-by-click >
v-model.trim="searchQuery" <gl-dropdown
:disabled="environments.isLoading || !advancedFeaturesEnabled" id="time-window-dropdown"
:placeholder="s__('Environments|Search')" ref="time-window-dropdown"
class="js-logs-search" :disabled="environments.isLoading || !advancedFeaturesEnabled"
type="search" :text="timeWindow.options[timeWindow.current].label"
autofocus class="d-flex gl-h-32"
@submit="setSearch(searchQuery)" toggle-class="dropdown-menu-toggle"
/> >
</gl-form-group> <gl-dropdown-item
v-for="(option, key) in timeWindow.options"
:key="key"
@click="setTimeWindow(key)"
>
{{ option.label }}
</gl-dropdown-item>
</gl-dropdown>
</gl-form-group>
<gl-form-group
id="search-fg"
:label="s__('Environments|Search')"
label-size="sm"
label-for="search"
class="col-3 px-1"
>
<gl-search-box-by-click
v-model.trim="searchQuery"
:disabled="environments.isLoading || !advancedFeaturesEnabled"
:placeholder="s__('Environments|Search')"
class="js-logs-search"
type="search"
autofocus
@submit="
(environments.isLoading || !advancedFeaturesEnabled) && setSearch(searchQuery)
"
/>
</gl-form-group>
</template>
</div> </div>
<log-control-buttons <log-control-buttons
ref="scrollButtons" ref="scrollButtons"
class="controllers align-self-end" class="controllers align-self-end mb-1"
@refresh="showPodLogs(pods.current)" @refresh="showPodLogs(pods.current)"
/> />
</div> </div>
......
import { __ } from '~/locale';
export const defaultTimeWindow = 'oneHour';
export const timeWindows = {
oneHour: {
label: __('1 hour'),
seconds: 60 * 60,
},
fourHours: {
label: __('4 hours'),
seconds: 60 * 60 * 4,
},
oneDay: {
label: __('1 day'),
seconds: 60 * 60 * 24,
},
twoDays: {
label: __('2 days'),
seconds: 60 * 60 * 24 * 3,
},
pastWeek: {
label: __('Past week'),
seconds: 60 * 60 * 24 * 7,
},
twoWeeks: {
label: __('2 weeks'),
seconds: 60 * 60 * 24 * 15,
},
};
...@@ -6,6 +6,9 @@ import flash from '~/flash'; ...@@ -6,6 +6,9 @@ import flash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { getTimeRange } from '../utils';
import { timeWindows } from '../constants';
const requestLogsUntilData = params => const requestLogsUntilData = params =>
backOff((next, stop) => { backOff((next, stop) => {
Api.getPodLogs(params) Api.getPodLogs(params)
...@@ -38,6 +41,11 @@ export const setSearch = ({ dispatch, commit }, searchQuery) => { ...@@ -38,6 +41,11 @@ export const setSearch = ({ dispatch, commit }, searchQuery) => {
dispatch('fetchLogs'); dispatch('fetchLogs');
}; };
export const setTimeWindow = ({ dispatch, commit }, timeWindowKey) => {
commit(types.SET_TIME_WINDOW, timeWindowKey);
dispatch('fetchLogs');
};
export const showEnvironment = ({ dispatch, commit }, environmentName) => { export const showEnvironment = ({ dispatch, commit }, environmentName) => {
commit(types.SET_PROJECT_ENVIRONMENT, environmentName); commit(types.SET_PROJECT_ENVIRONMENT, environmentName);
commit(types.SET_CURRENT_POD_NAME, null); commit(types.SET_CURRENT_POD_NAME, null);
...@@ -66,6 +74,14 @@ export const fetchLogs = ({ commit, state }) => { ...@@ -66,6 +74,14 @@ export const fetchLogs = ({ commit, state }) => {
search: state.search, search: state.search,
}; };
if (state.timeWindow.current) {
const { current } = state.timeWindow;
const { start, end } = getTimeRange(timeWindows[current].seconds);
params.start = start;
params.end = end;
}
commit(types.REQUEST_PODS_DATA); commit(types.REQUEST_PODS_DATA);
commit(types.REQUEST_LOGS_DATA); commit(types.REQUEST_LOGS_DATA);
......
...@@ -2,6 +2,7 @@ export const SET_PROJECT_PATH = 'SET_PROJECT_PATH'; ...@@ -2,6 +2,7 @@ export const SET_PROJECT_PATH = 'SET_PROJECT_PATH';
export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT'; export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT';
export const SET_SEARCH = 'SET_SEARCH'; export const SET_SEARCH = 'SET_SEARCH';
export const ENABLE_ADVANCED_QUERYING = 'ENABLE_ADVANCED_QUERYING'; export const ENABLE_ADVANCED_QUERYING = 'ENABLE_ADVANCED_QUERYING';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
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';
......
...@@ -15,6 +15,10 @@ export default { ...@@ -15,6 +15,10 @@ export default {
[types.ENABLE_ADVANCED_QUERYING](state, enableAdvancedQuerying) { [types.ENABLE_ADVANCED_QUERYING](state, enableAdvancedQuerying) {
state.enableAdvancedQuerying = enableAdvancedQuerying; state.enableAdvancedQuerying = enableAdvancedQuerying;
}, },
/** Time Range data */
[types.SET_TIME_WINDOW](state, timeWindowKey) {
state.timeWindow.current = timeWindowKey;
},
/** Environments data */ /** Environments data */
[types.SET_PROJECT_ENVIRONMENT](state, environmentName) { [types.SET_PROJECT_ENVIRONMENT](state, environmentName) {
......
import { defaultTimeWindow, timeWindows } from '../constants';
export default () => ({ export default () => ({
/** /**
* Current project path * Current project path
...@@ -14,6 +16,14 @@ export default () => ({ ...@@ -14,6 +16,14 @@ export default () => ({
*/ */
enableAdvancedQuerying: false, enableAdvancedQuerying: false,
/**
* Time range (Show last)
*/
timeWindow: {
options: { ...timeWindows },
current: defaultTimeWindow,
},
/** /**
* Environments list information * Environments list information
*/ */
......
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
/**
* Returns a time range (`start`, `end`) where `start` is the
* current time minus a given number of seconds and `end`
* is the current time (`now()`).
*
* @param {Number} seconds Seconds duration, defaults to 0.
* @returns {Object} range Time range
* @returns {String} range.start ISO String of current time minus given seconds
* @returns {String} range.end ISO String of current time
*/
export const getTimeRange = (seconds = 0) => {
const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
const start = end - seconds;
return {
start: new Date(secondsToMilliseconds(start)).toISOString(),
end: new Date(secondsToMilliseconds(end)).toISOString(),
};
};
export default {};
import Vue from 'vue'; import Vue from 'vue';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import EnvironmentLogs from 'ee/logs/components/environment_logs.vue'; import EnvironmentLogs from 'ee/logs/components/environment_logs.vue';
...@@ -44,9 +44,33 @@ describe('EnvironmentLogs', () => { ...@@ -44,9 +44,33 @@ describe('EnvironmentLogs', () => {
const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown'); const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown');
const findPodsDropdown = () => wrapper.find('.js-pods-dropdown'); const findPodsDropdown = () => wrapper.find('.js-pods-dropdown');
const findSearchBar = () => wrapper.find('.js-logs-search'); const findSearchBar = () => wrapper.find('.js-logs-search');
const findTimeWindowDropdown = () => wrapper.find({ ref: 'time-window-dropdown' });
const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' }); const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' });
const findLogTrace = () => wrapper.find('.js-log-trace'); const findLogTrace = () => wrapper.find('.js-log-trace');
const mockSetInitData = () => {
state.pods.options = mockPods;
state.environments.current = mockEnvName;
[state.pods.current] = state.pods.options;
state.enableAdvancedQuerying = true;
state.logs.isComplete = false;
state.logs.lines = mockLogsResult;
};
const mockShowPodLogs = podName => {
state.pods.options = mockPods;
[state.pods.current] = podName;
state.logs.isComplete = false;
state.logs.lines = mockLogsResult;
};
const mockFetchEnvs = () => {
state.environments.options = mockEnvironments;
};
const initWrapper = () => { const initWrapper = () => {
wrapper = shallowMount(EnvironmentLogsComponent, { wrapper = shallowMount(EnvironmentLogsComponent, {
propsData, propsData,
...@@ -87,12 +111,21 @@ describe('EnvironmentLogs', () => { ...@@ -87,12 +111,21 @@ describe('EnvironmentLogs', () => {
expect(wrapper.isVueInstance()).toBe(true); expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.isEmpty()).toBe(false); expect(wrapper.isEmpty()).toBe(false);
expect(findLogTrace().isEmpty()).toBe(false);
// top bar
expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true); expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true);
expect(findPodsDropdown().is(GlDropdown)).toBe(true); expect(findPodsDropdown().is(GlDropdown)).toBe(true);
expect(findLogControlButtons().exists()).toBe(true); expect(findLogControlButtons().exists()).toBe(true);
expect(findSearchBar().exists()).toBe(false); // behind ff
expect(findTimeWindowDropdown().exists()).toBe(false); // behind ff
// log trace
expect(findLogTrace().isEmpty()).toBe(false);
// layout
expect(wrapper.find('#environments-dropdown-fg').attributes('class')).toMatch('col-6');
expect(wrapper.find('#pods-dropdown-fg').attributes('class')).toMatch('col-6');
}); });
it('mounted inits data', () => { it('mounted inits data', () => {
...@@ -119,9 +152,6 @@ describe('EnvironmentLogs', () => { ...@@ -119,9 +152,6 @@ describe('EnvironmentLogs', () => {
state.environments.options = []; state.environments.options = [];
state.environments.isLoading = true; state.environments.isLoading = true;
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true;
initWrapper(); initWrapper();
}); });
...@@ -135,11 +165,6 @@ describe('EnvironmentLogs', () => { ...@@ -135,11 +165,6 @@ describe('EnvironmentLogs', () => {
expect(findPodsDropdown().findAll(GlDropdownItem).length).toBe(0); expect(findPodsDropdown().findAll(GlDropdownItem).length).toBe(0);
}); });
it('displays a disabled search bar', () => {
expect(findSearchBar().exists()).toEqual(true);
expect(findSearchBar().attributes('disabled')).toEqual('true');
});
it('does not update buttons state', () => { it('does not update buttons state', () => {
expect(updateControlBtnsMock).not.toHaveBeenCalled(); expect(updateControlBtnsMock).not.toHaveBeenCalled();
}); });
...@@ -154,69 +179,11 @@ describe('EnvironmentLogs', () => { ...@@ -154,69 +179,11 @@ describe('EnvironmentLogs', () => {
}); });
}); });
describe('elastic stack disabled', () => {
beforeEach(() => {
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = false;
initWrapper();
});
it("doesn't display the search bar", () => {
expect(findSearchBar().exists()).toEqual(false);
expect(wrapper.find('#environments-dropdown-fg').attributes('class')).toEqual('col-6');
expect(wrapper.find('#pods-dropdown-fg').attributes('class')).toEqual('col-6');
});
});
describe('ES enabled and legacy environment', () => {
beforeEach(() => {
state.pods.options = [];
state.logs.lines = [];
state.logs.isLoading = false;
state.environments.options = [];
state.environments.isLoading = false;
state.enableAdvancedQuerying = false;
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true;
initWrapper();
});
it('displays a disabled search bar', () => {
expect(findSearchBar().exists()).toEqual(true);
expect(findSearchBar().attributes('disabled')).toEqual('true');
});
});
describe('state with data', () => { describe('state with data', () => {
beforeEach(() => { beforeEach(() => {
actionMocks.setInitData.mockImplementation(() => { actionMocks.setInitData.mockImplementation(mockSetInitData);
state.pods.options = mockPods; actionMocks.showPodLogs.mockImplementation(mockShowPodLogs);
state.environments.current = mockEnvName; actionMocks.fetchEnvironments.mockImplementation(mockFetchEnvs);
[state.pods.current] = state.pods.options;
state.enableAdvancedQuerying = true;
state.logs.isComplete = false;
state.logs.lines = mockLogsResult;
});
actionMocks.showPodLogs.mockImplementation(podName => {
state.pods.options = mockPods;
[state.pods.current] = podName;
state.logs.isComplete = false;
state.logs.lines = mockLogsResult;
});
actionMocks.fetchEnvironments.mockImplementation(() => {
state.environments.options = mockEnvironments;
});
gon.features = gon.features || {};
gon.features.enableClusterApplicationElasticStack = true;
initWrapper(); initWrapper();
}); });
...@@ -238,7 +205,6 @@ describe('EnvironmentLogs', () => { ...@@ -238,7 +205,6 @@ describe('EnvironmentLogs', () => {
const item = items.at(i); const item = items.at(i);
expect(item.text()).toBe(env.name); expect(item.text()).toBe(env.name);
}); });
expect(wrapper.find('#environments-dropdown-fg').attributes('class')).toEqual('col-4');
}); });
it('populates pods dropdown', () => { it('populates pods dropdown', () => {
...@@ -250,7 +216,6 @@ describe('EnvironmentLogs', () => { ...@@ -250,7 +216,6 @@ describe('EnvironmentLogs', () => {
const item = items.at(i); const item = items.at(i);
expect(item.text()).toBe(pod); expect(item.text()).toBe(pod);
}); });
expect(wrapper.find('#pods-dropdown-fg').attributes('class')).toEqual('col-4');
}); });
it('populates logs trace', () => { it('populates logs trace', () => {
...@@ -259,12 +224,6 @@ describe('EnvironmentLogs', () => { ...@@ -259,12 +224,6 @@ describe('EnvironmentLogs', () => {
expect(trace.text().split('\n')).toEqual(mockTrace); expect(trace.text().split('\n')).toEqual(mockTrace);
}); });
it('displays an enabled search bar', () => {
expect(findSearchBar().exists()).toEqual(true);
expect(findSearchBar().attributes('disabled')).toEqual(undefined);
expect(wrapper.find('#search-fg').attributes('class')).toEqual('col-4');
});
it('update control buttons state', () => { it('update control buttons state', () => {
expect(updateControlBtnsMock).toHaveBeenCalledTimes(1); expect(updateControlBtnsMock).toHaveBeenCalledTimes(1);
}); });
...@@ -308,4 +267,95 @@ describe('EnvironmentLogs', () => { ...@@ -308,4 +267,95 @@ describe('EnvironmentLogs', () => {
}); });
}); });
}); });
describe('when feature flag enable_cluster_application_elastic_stack is enabled', () => {
let originalGon;
beforeEach(() => {
originalGon = window.gon;
window.gon = { features: { enableClusterApplicationElasticStack: true } };
});
afterEach(() => {
window.gon = originalGon;
});
it('displays UI elements', () => {
initWrapper();
// elements
expect(findSearchBar().exists()).toBe(true);
expect(findSearchBar().is(GlSearchBoxByClick)).toBe(true);
expect(findTimeWindowDropdown().exists()).toBe(true);
expect(findTimeWindowDropdown().is(GlDropdown)).toBe(true);
// layout
expect(wrapper.find('#environments-dropdown-fg').attributes('class')).toMatch('col-3');
expect(wrapper.find('#pods-dropdown-fg').attributes('class')).toMatch('col-3');
expect(wrapper.find('#dates-fg').attributes('class')).toMatch('col-3');
expect(wrapper.find('#search-fg').attributes('class')).toMatch('col-3');
});
describe('loading state', () => {
beforeEach(() => {
state.pods.options = [];
state.logs.lines = [];
state.logs.isLoading = true;
state.environments.options = [];
state.environments.isLoading = true;
initWrapper();
});
it('displays a disabled search bar', () => {
expect(findSearchBar().exists()).toEqual(true);
expect(findSearchBar().attributes('disabled')).toEqual('true');
});
it('displays a disabled time window dropdown', () => {
expect(findTimeWindowDropdown().attributes('disabled')).toEqual('true');
});
});
describe('when advanced querying is disabled', () => {
beforeEach(() => {
state.pods.options = [];
state.logs.lines = [];
state.logs.isLoading = false;
state.environments.options = [];
state.environments.isLoading = false;
state.enableAdvancedQuerying = false;
initWrapper();
});
it('search bar and time window dropdown are disabled', () => {
expect(findSearchBar().attributes('disabled')).toEqual('true');
expect(findTimeWindowDropdown().attributes('disabled')).toEqual('true');
});
});
describe('state with data', () => {
beforeEach(() => {
actionMocks.setInitData.mockImplementation(mockSetInitData);
actionMocks.showPodLogs.mockImplementation(mockShowPodLogs);
actionMocks.fetchEnvironments.mockImplementation(mockFetchEnvs);
initWrapper();
});
it('displays an enabled search bar', () => {
expect(findSearchBar().attributes('disabled')).toBeFalsy();
});
it('displays an enabled time window dropdown', () => {
expect(findTimeWindowDropdown().attributes('disabled')).toBeFalsy();
});
});
});
}); });
...@@ -10,8 +10,10 @@ import { ...@@ -10,8 +10,10 @@ import {
fetchEnvironments, fetchEnvironments,
fetchLogs, fetchLogs,
} from 'ee/logs/stores/actions'; } from 'ee/logs/stores/actions';
import axios from '~/lib/utils/axios_utils'; import { getTimeRange } from 'ee/logs/utils';
import { timeWindows } from 'ee/logs/constants';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash'; import flash from '~/flash';
import { import {
...@@ -27,13 +29,21 @@ import { ...@@ -27,13 +29,21 @@ import {
} from '../mock_data'; } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('ee/logs/utils');
describe('Logs Store actions', () => { describe('Logs Store actions', () => {
let state; let state;
let mock; let mock;
const mockThirtyMinutesSeconds = 3600;
const mockThirtyMinutes = {
start: '2020-01-09T18:06:20.000Z',
end: '2020-01-09T18:36:20.000Z',
};
beforeEach(() => { beforeEach(() => {
state = logsPageState(); state = logsPageState();
getTimeRange.mockReturnValue(mockThirtyMinutes);
}); });
afterEach(() => { afterEach(() => {
...@@ -139,7 +149,9 @@ describe('Logs Store actions', () => { ...@@ -139,7 +149,9 @@ describe('Logs Store actions', () => {
const endpoint = `/${mockProjectPath}/-/logs/k8s.json`; const endpoint = `/${mockProjectPath}/-/logs/k8s.json`;
mock mock
.onGet(endpoint, { params: { environment_name: mockEnvName, pod_name: mockPodName } }) .onGet(endpoint, {
params: { environment_name: mockEnvName, pod_name: mockPodName, ...mockThirtyMinutes },
})
.reply(200, { .reply(200, {
pod_name: mockPodName, pod_name: mockPodName,
pods: mockPods, pods: mockPods,
...@@ -162,7 +174,57 @@ describe('Logs Store actions', () => { ...@@ -162,7 +174,57 @@ describe('Logs Store actions', () => {
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult }, { type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
], ],
[], [],
done, () => {
expect(getTimeRange).toHaveBeenCalledWith(mockThirtyMinutesSeconds);
done();
},
);
});
it('should commit logs and pod data when there is pod name defined and a non-default date range', done => {
const mockOneDaySeconds = timeWindows.oneDay.seconds;
const mockOneDay = {
start: '2020-01-08T18:41:39.000Z',
end: '2020-01-09T18:41:39.000Z',
};
getTimeRange.mockReturnValueOnce(mockOneDay);
state.projectPath = mockProjectPath;
state.environments.current = mockEnvName;
state.pods.current = mockPodName;
state.timeWindow.current = 'oneDay';
const endpoint = `/${mockProjectPath}/-/logs/k8s.json`;
mock
.onGet(endpoint, {
params: { environment_name: mockEnvName, pod_name: mockPodName, ...mockOneDay },
})
.reply(200, {
pod_name: mockPodName,
pods: mockPods,
enable_advanced_querying: true,
logs: mockLogsResult,
});
testAction(
fetchLogs,
null,
state,
[
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA },
{ type: types.ENABLE_ADVANCED_QUERYING, payload: true },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ type: types.RECEIVE_LOGS_DATA_SUCCESS, payload: mockLogsResult },
],
[],
() => {
expect(getTimeRange).toHaveBeenCalledWith(mockOneDaySeconds);
done();
},
); );
}); });
...@@ -177,7 +239,12 @@ describe('Logs Store actions', () => { ...@@ -177,7 +239,12 @@ describe('Logs Store actions', () => {
mock mock
.onGet(endpoint, { .onGet(endpoint, {
params: { environment_name: mockEnvName, pod_name: mockPodName, search: mockSearch }, params: {
environment_name: mockEnvName,
pod_name: mockPodName,
search: mockSearch,
...mockThirtyMinutes,
},
}) })
.reply(200, { .reply(200, {
pod_name: mockPodName, pod_name: mockPodName,
...@@ -211,12 +278,14 @@ describe('Logs Store actions', () => { ...@@ -211,12 +278,14 @@ describe('Logs Store actions', () => {
const endpoint = `/${mockProjectPath}/-/logs/k8s.json`; const endpoint = `/${mockProjectPath}/-/logs/k8s.json`;
mock.onGet(endpoint, { params: { environment_name: mockEnvName } }).reply(200, { mock
pod_name: mockPodName, .onGet(endpoint, { params: { environment_name: mockEnvName, ...mockThirtyMinutes } })
pods: mockPods, .reply(200, {
logs: mockLogsResult, pod_name: mockPodName,
enable_advanced_querying: mockEnableAdvancedQuerying, pods: mockPods,
}); logs: mockLogsResult,
enable_advanced_querying: mockEnableAdvancedQuerying,
});
mock.onGet(endpoint).replyOnce(202); // mock reactive cache mock.onGet(endpoint).replyOnce(202); // mock reactive cache
testAction( testAction(
......
...@@ -136,6 +136,15 @@ describe('Logs Store Mutations', () => { ...@@ -136,6 +136,15 @@ describe('Logs Store Mutations', () => {
}); });
}); });
describe('SET_TIME_WINDOW', () => {
it('sets a time window Key', () => {
const mockKey = 'fourHours';
mutations[types.SET_TIME_WINDOW](state, mockKey);
expect(state.timeWindow.current).toEqual(mockKey);
});
});
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);
......
import { getTimeRange } from 'ee/logs/utils';
describe('logs/utils', () => {
describe('getTimeRange', () => {
const nowTimestamp = 1577836800000;
const nowString = '2020-01-01T00:00:00.000Z';
beforeEach(() => {
jest.spyOn(Date, 'now').mockImplementation(() => nowTimestamp);
});
afterEach(() => {
Date.now.mockRestore();
});
it('returns the right values', () => {
expect(getTimeRange(0)).toEqual({
start: '2020-01-01T00:00:00.000Z',
end: nowString,
});
expect(getTimeRange(60 * 30)).toEqual({
start: '2019-12-31T23:30:00.000Z',
end: nowString,
});
expect(getTimeRange(60 * 60 * 24 * 7 * 1)).toEqual({
start: '2019-12-25T00:00:00.000Z',
end: nowString,
});
expect(getTimeRange(60 * 60 * 24 * 7 * 4)).toEqual({
start: '2019-12-04T00:00:00.000Z',
end: nowString,
});
});
});
});
...@@ -638,6 +638,12 @@ msgstr "" ...@@ -638,6 +638,12 @@ msgstr ""
msgid "1st contribution!" msgid "1st contribution!"
msgstr "" msgstr ""
msgid "2 days"
msgstr ""
msgid "2 weeks"
msgstr ""
msgid "20-29 contributions" msgid "20-29 contributions"
msgstr "" msgstr ""
...@@ -665,6 +671,9 @@ msgstr "" ...@@ -665,6 +671,9 @@ msgstr ""
msgid "30+ contributions" msgid "30+ contributions"
msgstr "" msgstr ""
msgid "4 hours"
msgstr ""
msgid "403|Please contact your GitLab administrator to get permission." msgid "403|Please contact your GitLab administrator to get permission."
msgstr "" msgstr ""
...@@ -7183,6 +7192,9 @@ msgstr "" ...@@ -7183,6 +7192,9 @@ msgstr ""
msgid "Environments|Show all" msgid "Environments|Show all"
msgstr "" msgstr ""
msgid "Environments|Show last"
msgstr ""
msgid "Environments|Stop" msgid "Environments|Stop"
msgstr "" msgstr ""
...@@ -13082,6 +13094,9 @@ msgstr "" ...@@ -13082,6 +13094,9 @@ msgstr ""
msgid "Past due" msgid "Past due"
msgstr "" msgstr ""
msgid "Past week"
msgstr ""
msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}" msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
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