Commit 36ff6f33 authored by Miguel Rincon's avatar Miguel Rincon

Logs container uses the whole screen

Calculate the height of the log container using viewport units
to set the height it should have.
parent 7ebbf830
......@@ -56,7 +56,6 @@ export default {
required: true,
},
},
traceHeight: 600,
data() {
return {
isElasticStackCalloutDismissed: false,
......@@ -94,6 +93,9 @@ export default {
'showEnvironment',
'fetchEnvironments',
'fetchMoreLogsPrepend',
'dismissRequestEnvironmentsError',
'dismissInvalidTimeRangeWarning',
'dismissRequestLogsError',
]),
isCurrentEnvironment(envName) {
......@@ -115,7 +117,7 @@ export default {
};
</script>
<template>
<div class="environment-logs-viewer mt-3">
<div class="environment-logs-viewer d-flex flex-column py-3">
<gl-alert
v-if="shouldShowElasticStackCallout"
class="mb-3 js-elasticsearch-alert"
......@@ -132,6 +134,31 @@ export default {
</strong>
</a>
</gl-alert>
<gl-alert
v-if="environments.fetchError"
class="mb-3"
variant="danger"
@dismiss="dismissRequestEnvironmentsError"
>
{{ s__('Metrics|There was an error fetching the environments data, please try again') }}
</gl-alert>
<gl-alert
v-if="timeRange.invalidWarning"
class="mb-3"
variant="warning"
@dismiss="dismissInvalidTimeRangeWarning"
>
{{ s__('Metrics|Invalid time range, please verify.') }}
</gl-alert>
<gl-alert
v-if="logs.fetchError"
class="mb-3"
variant="danger"
@dismiss="dismissRequestLogsError"
>
{{ s__('Environments|There was an error fetching the logs. Please try again.') }}
</gl-alert>
<div class="top-bar d-md-flex border bg-secondary-50 pt-2 pr-1 pb-0 pl-2">
<div class="flex-grow-0">
<gl-dropdown
......@@ -183,16 +210,15 @@ export default {
<gl-infinite-scroll
ref="infiniteScroll"
class="log-lines"
:style="{ height: `${$options.traceHeight}px` }"
:max-list-height="$options.traceHeight"
class="log-lines overflow-auto flex-grow-1 min-height-0"
:fetched-items="logs.lines.length"
@topReached="topReached"
@scroll="scroll"
>
<template #items>
<pre
class="build-trace js-log-trace"
ref="logTrace"
class="build-trace"
><code class="bash js-build-output"><div v-if="showLoader" class="build-loader-animation js-build-loader-animation">
<div class="dot"></div>
<div class="dot"></div>
......@@ -205,7 +231,7 @@ export default {
></template>
</gl-infinite-scroll>
<div ref="logFooter" class="log-footer py-2 px-3">
<div ref="logFooter" class="py-2 px-3 text-white bg-secondary-900">
<gl-sprintf :message="s__('Environments|Logs from %{start} to %{end}.')">
<template #start>{{ timeRange.current.start | formatDate }}</template>
<template #end>{{ timeRange.current.end | formatDate }}</template>
......
import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { s__ } from '~/locale';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import * as types from './mutation_types';
const flashTimeRangeWarning = () => {
flash(s__('Metrics|Invalid time range, please verify.'), 'warning');
};
const flashLogsError = () => {
flash(s__('Metrics|There was an error fetching the logs, please try again'));
};
const requestUntilData = (url, params) =>
backOff((next, stop) => {
axios
......@@ -31,7 +21,7 @@ const requestUntilData = (url, params) =>
});
});
const requestLogsUntilData = state => {
const requestLogsUntilData = ({ commit, state }) => {
const params = {};
const { logs_api_path } = state.environments.options.find(
({ name }) => name === state.environments.current,
......@@ -49,7 +39,7 @@ const requestLogsUntilData = state => {
params.start_time = start;
params.end_time = end;
} catch {
flashTimeRangeWarning();
commit(types.SHOW_TIME_RANGE_INVALID_WARNING);
}
}
if (state.logs.cursor) {
......@@ -101,26 +91,22 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
})
.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 }) => {
commit(types.REQUEST_LOGS_DATA);
return requestLogsUntilData(state)
return requestLogsUntilData({ commit, state })
.then(({ data }) => {
const { pod_name, pods, logs, cursor } = data;
commit(types.RECEIVE_LOGS_DATA_SUCCESS, { logs, cursor });
commit(types.SET_CURRENT_POD_NAME, pod_name);
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
})
.catch(() => {
commit(types.RECEIVE_PODS_DATA_ERROR);
commit(types.RECEIVE_LOGS_DATA_ERROR);
flashLogsError();
});
};
......@@ -132,16 +118,27 @@ export const fetchMoreLogsPrepend = ({ commit, state }) => {
commit(types.REQUEST_LOGS_DATA_PREPEND);
return requestLogsUntilData(state)
return requestLogsUntilData({ commit, state })
.then(({ data }) => {
const { logs, cursor } = data;
commit(types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS, { logs, cursor });
})
.catch(() => {
commit(types.RECEIVE_LOGS_DATA_PREPEND_ERROR);
flashLogsError();
});
};
export const dismissRequestEnvironmentsError = ({ commit }) => {
commit(types.HIDE_REQUEST_ENVIRONMENTS_ERROR);
};
export const dismissRequestLogsError = ({ commit }) => {
commit(types.HIDE_REQUEST_LOGS_ERROR);
};
export const dismissInvalidTimeRangeWarning = ({ commit }) => {
commit(types.HIDE_TIME_RANGE_INVALID_WARNING);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const SET_PROJECT_ENVIRONMENT = 'SET_PROJECT_ENVIRONMENT';
export const SET_SEARCH = 'SET_SEARCH';
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export const SHOW_TIME_RANGE_INVALID_WARNING = 'SHOW_TIME_RANGE_INVALID_WARNING';
export const HIDE_TIME_RANGE_INVALID_WARNING = 'HIDE_TIME_RANGE_INVALID_WARNING';
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 HIDE_REQUEST_ENVIRONMENTS_ERROR = 'HIDE_REQUEST_ENVIRONMENTS_ERROR';
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
......@@ -13,6 +18,7 @@ export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
export const REQUEST_LOGS_DATA_PREPEND = 'REQUEST_LOGS_DATA_PREPEND';
export const RECEIVE_LOGS_DATA_PREPEND_SUCCESS = 'RECEIVE_LOGS_DATA_PREPEND_SUCCESS';
export const RECEIVE_LOGS_DATA_PREPEND_ERROR = 'RECEIVE_LOGS_DATA_PREPEND_ERROR';
export const HIDE_REQUEST_LOGS_ERROR = 'HIDE_REQUEST_LOGS_ERROR';
export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS';
export const RECEIVE_PODS_DATA_ERROR = 'RECEIVE_PODS_DATA_ERROR';
......@@ -18,6 +18,12 @@ export default {
state.timeRange.selected = timeRange;
state.timeRange.current = convertToFixedRange(timeRange);
},
[types.SHOW_TIME_RANGE_INVALID_WARNING](state) {
state.timeRange.invalidWarning = true;
},
[types.HIDE_TIME_RANGE_INVALID_WARNING](state) {
state.timeRange.invalidWarning = false;
},
// Environments Data
[types.SET_PROJECT_ENVIRONMENT](state, environmentName) {
......@@ -38,6 +44,10 @@ export default {
[types.RECEIVE_ENVIRONMENTS_DATA_ERROR](state) {
state.environments.options = [];
state.environments.isLoading = false;
state.environments.fetchError = true;
},
[types.HIDE_REQUEST_ENVIRONMENTS_ERROR](state) {
state.environments.fetchError = false;
},
// Logs data
......@@ -63,6 +73,7 @@ export default {
[types.RECEIVE_LOGS_DATA_ERROR](state) {
state.logs.lines = [];
state.logs.isLoading = false;
state.logs.fetchError = true;
},
[types.REQUEST_LOGS_DATA_PREPEND](state) {
......@@ -80,6 +91,10 @@ export default {
},
[types.RECEIVE_LOGS_DATA_PREPEND_ERROR](state) {
state.logs.isLoading = false;
state.logs.fetchError = true;
},
[types.HIDE_REQUEST_LOGS_ERROR](state) {
state.logs.fetchError = false;
},
// Pods data
......
......@@ -16,6 +16,8 @@ export default () => ({
selected: defaultTimeRange,
// Current time range, must be fixed
current: convertToFixedRange(defaultTimeRange),
invalidWarning: false,
},
/**
......@@ -25,6 +27,7 @@ export default () => ({
options: [],
isLoading: false,
current: null,
fetchError: false,
},
/**
......@@ -39,6 +42,8 @@ export default () => ({
*/
cursor: null,
isComplete: false,
fetchError: false,
},
/**
......
......@@ -474,6 +474,9 @@ img.emoji {
.mw-70p { max-width: 70%; }
.mw-90p { max-width: 90%; }
// By default flex items don't shrink below their minimum content size.
// To change this, these clases set a min-width or min-height
.min-width-0 { min-width: 0; }
.min-height-0 { min-height: 0; }
.svg-w-100 {
......
......@@ -199,8 +199,8 @@
/*
* Mixin that handles the container for the job logs (CI/CD and kubernetes pod logs)
*/
@mixin build-trace {
background: $black;
@mixin build-trace($background: $black) {
background: $background;
color: $gray-darkest;
white-space: pre;
overflow-x: auto;
......@@ -243,7 +243,7 @@
/*
* Mixin that handles the position of the controls placed on the top bar
*/
@mixin build-controllers($control-font-size, $flex-direction, $with-grow, $flex-grow-size, $svg-display: 'block', $svg-top: '2px') {
@mixin build-controllers($control-font-size, $flex-direction, $with-grow, $flex-grow-size, $svg-display: block, $svg-top: 2px) {
display: flex;
font-size: $control-font-size;
justify-content: $flex-direction;
......
......@@ -641,6 +641,14 @@ $issue-boards-breadcrumbs-height-xs: 63px;
$issue-board-list-difference-xs: $header-height + $issue-boards-breadcrumbs-height-xs;
$issue-board-list-difference-sm: $header-height + $breadcrumb-min-height;
$issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards-filter-height;
/*
The following heights are used in environment_logs.scss and are used for calculation of the log viewer height.
*/
$environment-logs-breadcrumbs-height: 63px;
$environment-logs-breadcrumbs-height-md: $breadcrumb-min-height;
$environment-logs-difference-xs-up: $header-height + $environment-logs-breadcrumbs-height;
$environment-logs-difference-md-up: $header-height + $environment-logs-breadcrumbs-height-md;
/*
* Avatar
......
......@@ -356,54 +356,3 @@
}
}
}
.environment-logs-viewer {
.build-trace-container {
position: relative;
}
.log-lines,
.gl-infinite-scroll-container {
// makes scrollbar visible by creating contrast
background: $black;
}
.gl-infinite-scroll-legend {
margin: 0;
}
.build-trace {
@include build-trace();
margin: 0;
}
.top-bar {
.date-time-picker-wrapper,
.dropdown-toggle {
@include media-breakpoint-up(md) {
width: 140px;
}
@include media-breakpoint-up(lg) {
width: 160px;
}
}
.controllers {
@include build-controllers(16px, flex-end, false, 2);
}
}
.btn-refresh svg {
top: 0;
}
.build-loader-animation {
@include build-loader-animation;
}
.log-footer {
color: $white-normal;
background-color: $gray-900;
}
}
.environment-logs-page {
.content-wrapper {
padding-bottom: 0;
}
}
.environment-logs-viewer {
height: calc(100vh - #{$environment-logs-difference-xs-up});
min-height: 700px;
@include media-breakpoint-up(md) {
height: calc(100vh - #{$environment-logs-difference-md-up});
}
.with-performance-bar & {
height: calc(100vh - #{$environment-logs-difference-xs-up} - #{$performance-bar-height});
@include media-breakpoint-up(md) {
height: calc(100vh - #{$environment-logs-difference-md-up} - #{$performance-bar-height});
}
}
.top-bar {
.date-time-picker-wrapper,
.dropdown-toggle {
@include media-breakpoint-up(md) {
width: 140px;
}
@include media-breakpoint-up(lg) {
width: 160px;
}
}
.controllers {
@include build-controllers(16px, flex-end, false, 2, inline);
}
}
.log-lines,
.gl-infinite-scroll-container {
// makes scrollbar visible by creating contrast
background: $black;
height: 100%;
}
.build-trace {
@include build-trace($black);
}
.gl-infinite-scroll-legend {
margin: 0;
}
.build-loader-animation {
@include build-loader-animation;
}
}
......@@ -256,6 +256,7 @@ module ApplicationHelper
def page_class
class_names = []
class_names << 'issue-boards-page' if current_controller?(:boards)
class_names << 'environment-logs-page' if current_controller?(:logs)
class_names << 'with-performance-bar' if performance_bar_enabled?
class_names << system_message_class
class_names
......
---
title: Enable log explorer to use the full height of the screen
merge_request: 28312
author:
type: added
......@@ -7898,6 +7898,9 @@ msgstr ""
msgid "Environments|Stopping"
msgstr ""
msgid "Environments|There was an error fetching the logs. Please try again."
msgstr ""
msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
msgstr ""
......@@ -12811,9 +12814,6 @@ msgstr ""
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr ""
msgid "Metrics|There was an error fetching the logs, please try again"
msgstr ""
msgid "Metrics|There was an error getting deployment information."
msgstr ""
......
......@@ -47,7 +47,7 @@ describe('EnvironmentLogs', () => {
const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' });
const findInfiniteScroll = () => wrapper.find({ ref: 'infiniteScroll' });
const findLogTrace = () => wrapper.find('.js-log-trace');
const findLogTrace = () => wrapper.find({ ref: 'logTrace' });
const findLogFooter = () => wrapper.find({ ref: 'logFooter' });
const getInfiniteScrollAttr = attr => parseInt(findInfiniteScroll().attributes(attr), 10);
......@@ -169,16 +169,12 @@ describe('EnvironmentLogs', () => {
expect(updateControlBtnsMock).not.toHaveBeenCalled();
});
it('shows an infinite scroll with height and no content', () => {
expect(getInfiniteScrollAttr('max-list-height')).toBeGreaterThan(0);
it('shows an infinite scroll with no content', () => {
expect(getInfiniteScrollAttr('fetched-items')).toBe(0);
});
it('shows an infinite scroll container with equal height and max-height ', () => {
const height = getInfiniteScrollAttr('max-list-height');
expect(height).toEqual(expect.any(Number));
expect(findInfiniteScroll().attributes('style')).toMatch(`height: ${height}px;`);
it('shows an infinite scroll container with no set max-height ', () => {
expect(findInfiniteScroll().attributes('max-list-height')).toBeUndefined();
});
it('shows a logs trace', () => {
......@@ -270,8 +266,7 @@ describe('EnvironmentLogs', () => {
expect(findAdvancedFilters().exists()).toBe(true);
});
it('shows infinite scroll with height and no content', () => {
expect(getInfiniteScrollAttr('max-list-height')).toBeGreaterThan(0);
it('shows infinite scroll with content', () => {
expect(getInfiniteScrollAttr('fetched-items')).toBe(mockTrace.length);
});
......
......@@ -38,7 +38,7 @@ jest.mock('~/logs/utils');
const mockDefaultRange = {
start: '2020-01-10T18:00:00.000Z',
end: '2020-01-10T10:00:00.000Z',
end: '2020-01-10T19:00:00.000Z',
};
const mockFixedRange = {
start: '2020-01-09T18:06:20.000Z',
......@@ -145,9 +145,6 @@ describe('Logs Store actions', () => {
{ type: types.RECEIVE_ENVIRONMENTS_DATA_ERROR },
],
[],
() => {
expect(flash).toHaveBeenCalledTimes(1);
},
);
});
});
......@@ -186,6 +183,7 @@ describe('Logs Store actions', () => {
it('should commit logs and pod data when there is pod name defined', () => {
state.pods.current = mockPodName;
state.timeRange.current = mockFixedRange;
return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
expect(latestGetParams()).toMatchObject({
......@@ -214,22 +212,26 @@ describe('Logs Store actions', () => {
state.search = mockSearch;
state.timeRange.current = 'INVALID_TIME_RANGE';
expectedMutations.splice(1, 0, {
type: types.SHOW_TIME_RANGE_INVALID_WARNING,
});
return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
expect(latestGetParams()).toEqual({
pod_name: mockPodName,
search: mockSearch,
});
// Warning about time ranges was issued
expect(flash).toHaveBeenCalledTimes(1);
expect(flash).toHaveBeenCalledWith(expect.any(String), 'warning');
});
});
it('should commit logs and pod data when no pod name defined', () => {
state.timeRange.current = mockDefaultRange;
state.timeRange.current = defaultTimeRange;
return testAction(fetchLogs, null, state, expectedMutations, expectedActions, () => {
expect(latestGetParams()).toEqual({});
expect(latestGetParams()).toEqual({
start_time: expect.any(String),
end_time: expect.any(String),
});
});
});
});
......@@ -249,6 +251,7 @@ describe('Logs Store actions', () => {
it('should commit logs and pod data when there is pod name defined', () => {
state.pods.current = mockPodName;
state.timeRange.current = mockFixedRange;
expectedActions = [];
......@@ -293,6 +296,10 @@ describe('Logs Store actions', () => {
state.search = mockSearch;
state.timeRange.current = 'INVALID_TIME_RANGE';
expectedMutations.splice(1, 0, {
type: types.SHOW_TIME_RANGE_INVALID_WARNING,
});
return testAction(
fetchMoreLogsPrepend,
null,
......@@ -304,15 +311,12 @@ describe('Logs Store actions', () => {
pod_name: mockPodName,
search: mockSearch,
});
// Warning about time ranges was issued
expect(flash).toHaveBeenCalledTimes(1);
expect(flash).toHaveBeenCalledWith(expect.any(String), 'warning');
},
);
});
it('should commit logs and pod data when no pod name defined', () => {
state.timeRange.current = mockDefaultRange;
state.timeRange.current = defaultTimeRange;
return testAction(
fetchMoreLogsPrepend,
......@@ -321,7 +325,10 @@ describe('Logs Store actions', () => {
expectedMutations,
expectedActions,
() => {
expect(latestGetParams()).toEqual({});
expect(latestGetParams()).toEqual({
start_time: expect.any(String),
end_time: expect.any(String),
});
},
);
});
......@@ -357,6 +364,7 @@ describe('Logs Store actions', () => {
it('fetchLogs should commit logs and pod errors', () => {
state.environments.options = mockEnvironments;
state.environments.current = mockEnvName;
state.timeRange.current = defaultTimeRange;
return testAction(
fetchLogs,
......@@ -377,6 +385,7 @@ describe('Logs Store actions', () => {
it('fetchMoreLogsPrepend should commit logs and pod errors', () => {
state.environments.options = mockEnvironments;
state.environments.current = mockEnvName;
state.timeRange.current = defaultTimeRange;
return testAction(
fetchMoreLogsPrepend,
......
......@@ -67,6 +67,7 @@ describe('Logs Store Mutations', () => {
options: [],
isLoading: false,
current: null,
fetchError: true,
});
});
});
......@@ -83,6 +84,7 @@ describe('Logs Store Mutations', () => {
expect(state.logs).toEqual({
lines: [],
cursor: null,
fetchError: false,
isLoading: true,
isComplete: false,
});
......@@ -101,6 +103,7 @@ describe('Logs Store Mutations', () => {
isLoading: false,
cursor: mockCursor,
isComplete: false,
fetchError: false,
});
});
......@@ -115,6 +118,7 @@ describe('Logs Store Mutations', () => {
isLoading: false,
cursor: null,
isComplete: true,
fetchError: false,
});
});
});
......@@ -128,6 +132,7 @@ describe('Logs Store Mutations', () => {
isLoading: false,
cursor: null,
isComplete: false,
fetchError: true,
});
});
});
......@@ -152,6 +157,7 @@ describe('Logs Store Mutations', () => {
isLoading: false,
cursor: mockCursor,
isComplete: false,
fetchError: false,
});
});
......@@ -171,6 +177,7 @@ describe('Logs Store Mutations', () => {
isLoading: false,
cursor: mockNextCursor,
isComplete: false,
fetchError: false,
});
});
......@@ -185,6 +192,7 @@ describe('Logs Store Mutations', () => {
isLoading: false,
cursor: null,
isComplete: true,
fetchError: false,
});
});
});
......@@ -194,6 +202,7 @@ describe('Logs Store Mutations', () => {
mutations[types.RECEIVE_LOGS_DATA_PREPEND_ERROR](state);
expect(state.logs.isLoading).toBe(false);
expect(state.logs.fetchError).toBe(true);
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment