Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-13 15:09:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-13 15:09:22 +0300
commit286fe61013674fe2d245ffc8d2233baf09923e70 (patch)
tree2037291f5863105e54e75be056b49f7d62007cae /app/assets/javascripts/logs
parent4cb5e5011abfe8d50ac3a7ebd0018c563c6d7af4 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/logs')
-rw-r--r--app/assets/javascripts/logs/components/environment_logs.vue98
-rw-r--r--app/assets/javascripts/logs/components/log_control_buttons.vue57
-rw-r--r--app/assets/javascripts/logs/stores/actions.js86
-rw-r--r--app/assets/javascripts/logs/stores/getters.js10
-rw-r--r--app/assets/javascripts/logs/stores/mutation_types.js3
-rw-r--r--app/assets/javascripts/logs/stores/mutations.js52
-rw-r--r--app/assets/javascripts/logs/stores/state.js13
-rw-r--r--app/assets/javascripts/logs/utils.js5
8 files changed, 231 insertions, 93 deletions
diff --git a/app/assets/javascripts/logs/components/environment_logs.vue b/app/assets/javascripts/logs/components/environment_logs.vue
index b94cd2bcec4..b0acd69bae0 100644
--- a/app/assets/javascripts/logs/components/environment_logs.vue
+++ b/app/assets/javascripts/logs/components/environment_logs.vue
@@ -1,23 +1,37 @@
<script>
+import { throttle } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
-import { GlDropdown, GlDropdownItem, GlFormGroup, GlSearchBoxByClick, GlAlert } from '@gitlab/ui';
+import {
+ GlSprintf,
+ GlAlert,
+ GlDropdown,
+ GlDropdownItem,
+ GlFormGroup,
+ GlSearchBoxByClick,
+ GlInfiniteScroll,
+} from '@gitlab/ui';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
-import { scrollDown } from '~/lib/utils/scroll_utils';
import LogControlButtons from './log_control_buttons.vue';
import { timeRanges, defaultTimeRange } from '~/monitoring/constants';
import { timeRangeFromUrl } from '~/monitoring/utils';
+import { formatDate } from '../utils';
export default {
components: {
+ GlSprintf,
GlAlert,
GlDropdown,
GlDropdownItem,
GlFormGroup,
GlSearchBoxByClick,
+ GlInfiniteScroll,
DateTimePicker,
LogControlButtons,
},
+ filters: {
+ formatDate,
+ },
props: {
environmentName: {
type: String,
@@ -39,11 +53,13 @@ export default {
required: true,
},
},
+ traceHeight: 600,
data() {
return {
searchQuery: '',
timeRanges,
isElasticStackCalloutDismissed: false,
+ scrollDownButtonDisabled: true,
};
},
computed: {
@@ -52,7 +68,7 @@ export default {
timeRangeModel: {
get() {
- return this.timeRange.current;
+ return this.timeRange.selected;
},
set(val) {
this.setTimeRange(val);
@@ -60,7 +76,7 @@ export default {
},
showLoader() {
- return this.logs.isLoading || !this.logs.isComplete;
+ return this.logs.isLoading;
},
advancedFeaturesEnabled() {
const environment = this.environments.options.find(
@@ -75,16 +91,6 @@ export default {
return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls;
},
},
- watch: {
- trace(val) {
- this.$nextTick(() => {
- if (val) {
- scrollDown();
- }
- this.$refs.scrollButtons.update();
- });
- },
- },
mounted() {
this.setInitData({
timeRange: timeRangeFromUrl() || defaultTimeRange,
@@ -102,12 +108,26 @@ export default {
'showPodLogs',
'showEnvironment',
'fetchEnvironments',
+ 'fetchMoreLogsPrepend',
]),
+
+ topReached() {
+ if (!this.logs.isLoading) {
+ this.fetchMoreLogsPrepend();
+ }
+ },
+ scrollDown() {
+ this.$refs.infiniteScroll.scrollDown();
+ },
+ scroll: throttle(function scrollThrottled({ target = {} }) {
+ const { scrollTop = 0, clientHeight = 0, scrollHeight = 0 } = target;
+ this.scrollDownButtonDisabled = scrollTop + clientHeight === scrollHeight;
+ }, 200),
},
};
</script>
<template>
- <div class="build-page-pod-logs mt-3">
+ <div class="environment-logs-viewer mt-3">
<gl-alert
v-if="shouldShowElasticStackCallout"
class="mb-3 js-elasticsearch-alert"
@@ -209,14 +229,50 @@ export default {
<log-control-buttons
ref="scrollButtons"
class="controllers align-self-end mb-1"
+ :scroll-down-button-disabled="scrollDownButtonDisabled"
@refresh="showPodLogs(pods.current)"
+ @scrollDown="scrollDown"
/>
</div>
- <pre class="build-trace js-log-trace"><code class="bash js-build-output">{{trace}}
- <div v-if="showLoader" class="build-loader-animation js-build-loader-animation">
- <div class="dot"></div>
- <div class="dot"></div>
- <div class="dot"></div>
- </div></code></pre>
+
+ <gl-infinite-scroll
+ ref="infiniteScroll"
+ class="log-lines"
+ :style="{ height: `${$options.traceHeight}px` }"
+ :max-list-height="$options.traceHeight"
+ :fetched-items="logs.lines.length"
+ @topReached="topReached"
+ @scroll="scroll"
+ >
+ <template #items>
+ <pre
+ class="build-trace js-log-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>
+ <div class="dot"></div>
+ </div>{{trace}}
+ </code></pre>
+ </template>
+ <template #default
+ ><div></div
+ ></template>
+ </gl-infinite-scroll>
+
+ <div ref="logFooter" class="log-footer py-2 px-3">
+ <gl-sprintf :message="s__('Environments|Logs from %{start} to %{end}.')">
+ <template #start>{{ timeRange.current.start | formatDate }}</template>
+ <template #end>{{ timeRange.current.end | formatDate }}</template>
+ </gl-sprintf>
+ <gl-sprintf
+ v-if="!logs.isComplete"
+ :message="s__('Environments|Currently showing %{fetched} results.')"
+ >
+ <template #fetched>{{ logs.lines.length }}</template>
+ </gl-sprintf>
+ <template v-else>
+ {{ s__('Environments|Currently showing all results.') }}</template
+ >
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/logs/components/log_control_buttons.vue b/app/assets/javascripts/logs/components/log_control_buttons.vue
index d55c2f7cd4c..170d0474447 100644
--- a/app/assets/javascripts/logs/components/log_control_buttons.vue
+++ b/app/assets/javascripts/logs/components/log_control_buttons.vue
@@ -1,12 +1,5 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import {
- canScroll,
- isScrolledToTop,
- isScrolledToBottom,
- scrollDown,
- scrollUp,
-} from '~/lib/utils/scroll_utils';
import Icon from '~/vue_shared/components/icon.vue';
export default {
@@ -17,32 +10,34 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ props: {
+ scrollUpButtonDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ scrollDownButtonDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
data() {
return {
- scrollToTopEnabled: false,
- scrollToBottomEnabled: false,
+ scrollUpAvailable: Boolean(this.$listeners.scrollUp),
+ scrollDownAvailable: Boolean(this.$listeners.scrollDown),
};
},
- created() {
- window.addEventListener('scroll', this.update);
- },
- destroyed() {
- window.removeEventListener('scroll', this.update);
- },
methods: {
- /**
- * Checks if page can be scrolled and updates
- * enabled/disabled state of buttons accordingly
- */
- update() {
- this.scrollToTopEnabled = canScroll() && !isScrolledToTop();
- this.scrollToBottomEnabled = canScroll() && !isScrolledToBottom();
- },
handleRefreshClick() {
this.$emit('refresh');
},
- scrollUp,
- scrollDown,
+ handleScrollUp() {
+ this.$emit('scrollUp');
+ },
+ handleScrollDown() {
+ this.$emit('scrollDown');
+ },
},
};
</script>
@@ -50,6 +45,7 @@ export default {
<template>
<div>
<div
+ v-if="scrollUpAvailable"
v-gl-tooltip
class="controllers-buttons"
:title="__('Scroll to top')"
@@ -59,13 +55,15 @@ export default {
id="scroll-to-top"
class="btn-blank js-scroll-to-top"
:aria-label="__('Scroll to top')"
- :disabled="!scrollToTopEnabled"
- @click="scrollUp()"
+ :disabled="scrollUpButtonDisabled"
+ @click="handleScrollUp()"
><icon name="scroll_up"
/></gl-button>
</div>
<div
+ v-if="scrollDownAvailable"
v-gl-tooltip
+ :disabled="scrollUpButtonDisabled"
class="controllers-buttons"
:title="__('Scroll to bottom')"
aria-labelledby="scroll-to-bottom"
@@ -74,8 +72,9 @@ export default {
id="scroll-to-bottom"
class="btn-blank js-scroll-to-bottom"
:aria-label="__('Scroll to bottom')"
- :disabled="!scrollToBottomEnabled"
- @click="scrollDown()"
+ :v-if="scrollDownAvailable"
+ :disabled="scrollDownButtonDisabled"
+ @click="handleScrollDown()"
><icon name="scroll_down"
/></gl-button>
</div>
diff --git a/app/assets/javascripts/logs/stores/actions.js b/app/assets/javascripts/logs/stores/actions.js
index 89a896b9dec..4544ebdfec1 100644
--- a/app/assets/javascripts/logs/stores/actions.js
+++ b/app/assets/javascripts/logs/stores/actions.js
@@ -1,4 +1,3 @@
-import Api from '~/api';
import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
@@ -16,9 +15,10 @@ const flashLogsError = () => {
flash(s__('Metrics|There was an error fetching the logs, please try again'));
};
-const requestLogsUntilData = params =>
+const requestUntilData = (url, params) =>
backOff((next, stop) => {
- Api.getPodLogs(params)
+ axios
+ .get(url, { params })
.then(res => {
if (res.status === httpStatusCodes.ACCEPTED) {
next();
@@ -31,10 +31,36 @@ const requestLogsUntilData = params =>
});
});
-export const setInitData = ({ commit }, { timeRange, environmentName, podName }) => {
- if (timeRange) {
- commit(types.SET_TIME_RANGE, timeRange);
+const requestLogsUntilData = state => {
+ const params = {};
+ const { logs_api_path } = state.environments.options.find(
+ ({ name }) => name === state.environments.current,
+ );
+
+ if (state.pods.current) {
+ params.pod_name = state.pods.current;
+ }
+ if (state.search) {
+ params.search = state.search;
+ }
+ if (state.timeRange.current) {
+ try {
+ const { start, end } = convertToFixedRange(state.timeRange.current);
+ params.start = start;
+ params.end = end;
+ } catch {
+ flashTimeRangeWarning();
+ }
+ }
+ if (state.logs.cursor) {
+ params.cursor = state.logs.cursor;
}
+
+ return requestUntilData(logs_api_path, params);
+};
+
+export const setInitData = ({ commit }, { timeRange, environmentName, podName }) => {
+ commit(types.SET_TIME_RANGE, timeRange);
commit(types.SET_PROJECT_ENVIRONMENT, environmentName);
commit(types.SET_CURRENT_POD_NAME, podName);
};
@@ -60,10 +86,15 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => {
dispatch('fetchLogs');
};
+/**
+ * Fetch environments data and initial logs
+ * @param {Object} store
+ * @param {String} environmentsPath
+ */
export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
commit(types.REQUEST_ENVIRONMENTS_DATA);
- axios
+ return axios
.get(environmentsPath)
.then(({ data }) => {
commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data.environments);
@@ -76,32 +107,16 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
};
export const fetchLogs = ({ commit, state }) => {
- const params = {
- environment: state.environments.options.find(({ name }) => name === state.environments.current),
- podName: state.pods.current,
- search: state.search,
- };
-
- if (state.timeRange.current) {
- try {
- const { start, end } = convertToFixedRange(state.timeRange.current);
- params.start = start;
- params.end = end;
- } catch {
- flashTimeRangeWarning();
- }
- }
-
commit(types.REQUEST_PODS_DATA);
commit(types.REQUEST_LOGS_DATA);
- return requestLogsUntilData(params)
+ return requestLogsUntilData(state)
.then(({ data }) => {
- const { pod_name, pods, logs } = data;
+ const { pod_name, pods, logs, cursor } = data;
commit(types.SET_CURRENT_POD_NAME, pod_name);
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
- commit(types.RECEIVE_LOGS_DATA_SUCCESS, logs);
+ commit(types.RECEIVE_LOGS_DATA_SUCCESS, { logs, cursor });
})
.catch(() => {
commit(types.RECEIVE_PODS_DATA_ERROR);
@@ -110,5 +125,24 @@ export const fetchLogs = ({ commit, state }) => {
});
};
+export const fetchMoreLogsPrepend = ({ commit, state }) => {
+ if (state.logs.isComplete) {
+ // return when all logs are loaded
+ return Promise.resolve();
+ }
+
+ commit(types.REQUEST_LOGS_DATA_PREPEND);
+
+ return requestLogsUntilData(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();
+ });
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/logs/stores/getters.js b/app/assets/javascripts/logs/stores/getters.js
index c7dbb72ce3d..58f2dbf4835 100644
--- a/app/assets/javascripts/logs/stores/getters.js
+++ b/app/assets/javascripts/logs/stores/getters.js
@@ -1,9 +1,9 @@
-import dateFormat from 'dateformat';
+import { formatDate } from '../utils';
-export const trace = state =>
- state.logs.lines
- .map(item => [dateFormat(item.timestamp, 'UTC:mmm dd HH:MM:ss.l"Z"'), item.message].join(' | '))
- .join('\n');
+const mapTrace = ({ timestamp = null, message = '' }) =>
+ [timestamp ? formatDate(timestamp) : '', message].join(' | ');
+
+export const trace = state => state.logs.lines.map(mapTrace).join('\n');
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/logs/stores/mutation_types.js b/app/assets/javascripts/logs/stores/mutation_types.js
index b8e70f95d92..5ff49135e41 100644
--- a/app/assets/javascripts/logs/stores/mutation_types.js
+++ b/app/assets/javascripts/logs/stores/mutation_types.js
@@ -10,6 +10,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR'
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR';
+export const REQUEST_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 REQUEST_PODS_DATA = 'REQUEST_PODS_DATA';
export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS';
diff --git a/app/assets/javascripts/logs/stores/mutations.js b/app/assets/javascripts/logs/stores/mutations.js
index ca31dd3bc20..d94d71cd25a 100644
--- a/app/assets/javascripts/logs/stores/mutations.js
+++ b/app/assets/javascripts/logs/stores/mutations.js
@@ -1,17 +1,24 @@
import * as types from './mutation_types';
+import { convertToFixedRange } from '~/lib/utils/datetime_range';
+
+const mapLine = ({ timestamp, message }) => ({
+ timestamp,
+ message,
+});
export default {
- /** Search data */
+ // Search Data
[types.SET_SEARCH](state, searchQuery) {
state.search = searchQuery;
},
- /** Time Range data */
+ // Time Range Data
[types.SET_TIME_RANGE](state, timeRange) {
- state.timeRange.current = timeRange;
+ state.timeRange.selected = timeRange;
+ state.timeRange.current = convertToFixedRange(timeRange);
},
- /** Environments data */
+ // Environments Data
[types.SET_PROJECT_ENVIRONMENT](state, environmentName) {
state.environments.current = environmentName;
},
@@ -28,24 +35,49 @@ export default {
state.environments.isLoading = false;
},
- /** Logs data */
+ // Logs data
[types.REQUEST_LOGS_DATA](state) {
+ state.timeRange.current = convertToFixedRange(state.timeRange.selected);
+
state.logs.lines = [];
state.logs.isLoading = true;
+
+ // start pagination from the beginning
+ state.logs.cursor = null;
state.logs.isComplete = false;
},
- [types.RECEIVE_LOGS_DATA_SUCCESS](state, lines) {
- state.logs.lines = lines;
+ [types.RECEIVE_LOGS_DATA_SUCCESS](state, { logs = [], cursor }) {
+ state.logs.lines = logs.map(mapLine);
state.logs.isLoading = false;
- state.logs.isComplete = true;
+ state.logs.cursor = cursor;
+
+ if (!cursor) {
+ state.logs.isComplete = true;
+ }
},
[types.RECEIVE_LOGS_DATA_ERROR](state) {
state.logs.lines = [];
state.logs.isLoading = false;
- state.logs.isComplete = true;
},
- /** Pods data */
+ [types.REQUEST_LOGS_DATA_PREPEND](state) {
+ state.logs.isLoading = true;
+ },
+ [types.RECEIVE_LOGS_DATA_PREPEND_SUCCESS](state, { logs = [], cursor }) {
+ const lines = logs.map(mapLine);
+ state.logs.lines = lines.concat(state.logs.lines);
+ state.logs.isLoading = false;
+ state.logs.cursor = cursor;
+
+ if (!cursor) {
+ state.logs.isComplete = true;
+ }
+ },
+ [types.RECEIVE_LOGS_DATA_PREPEND_ERROR](state) {
+ state.logs.isLoading = false;
+ },
+
+ // Pods data
[types.SET_CURRENT_POD_NAME](state, podName) {
state.pods.current = podName;
},
diff --git a/app/assets/javascripts/logs/stores/state.js b/app/assets/javascripts/logs/stores/state.js
index eaf1b1bdd93..e058f15b6b4 100644
--- a/app/assets/javascripts/logs/stores/state.js
+++ b/app/assets/javascripts/logs/stores/state.js
@@ -1,4 +1,5 @@
import { timeRanges, defaultTimeRange } from '~/monitoring/constants';
+import { convertToFixedRange } from '~/lib/utils/datetime_range';
export default () => ({
/**
@@ -11,7 +12,10 @@ export default () => ({
*/
timeRange: {
options: timeRanges,
- current: defaultTimeRange,
+ // Selected time range, can be fixed or relative
+ selected: defaultTimeRange,
+ // Current time range, must be fixed
+ current: convertToFixedRange(defaultTimeRange),
},
/**
@@ -29,7 +33,12 @@ export default () => ({
logs: {
lines: [],
isLoading: false,
- isComplete: true,
+ /**
+ * Logs `cursor` represents the current pagination position,
+ * Should be sent in next batch (page) of logs to be fetched
+ */
+ cursor: null,
+ isComplete: false,
},
/**
diff --git a/app/assets/javascripts/logs/utils.js b/app/assets/javascripts/logs/utils.js
index 668efee74e8..30213dbc130 100644
--- a/app/assets/javascripts/logs/utils.js
+++ b/app/assets/javascripts/logs/utils.js
@@ -1,4 +1,7 @@
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
+import dateFormat from 'dateformat';
+
+const dateFormatMask = 'UTC:mmm dd HH:MM:ss.l"Z"';
/**
* Returns a time range (`start`, `end`) where `start` is the
@@ -20,4 +23,6 @@ export const getTimeRange = (seconds = 0) => {
};
};
+export const formatDate = timestamp => dateFormat(timestamp, dateFormatMask);
+
export default {};