Commit 0f9003c7 authored by Phil Hughes's avatar Phil Hughes

Merge branch '210502-restore-full-height-of-logs-explorer' into 'master'

Enable log explorer to use the full height of the screen

See merge request gitlab-org/gitlab!28312
parents 93968328 36ff6f33
......@@ -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
......@@ -7964,6 +7964,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 ""
......@@ -12838,9 +12841,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