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
parent4cb5e5011abfe8d50ac3a7ebd0018c563c6d7af4 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/api.js35
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue30
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue14
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list.vue55
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue16
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue13
-rw-r--r--app/assets/javascripts/ide/components/repo_commit_section.vue22
-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
-rw-r--r--app/assets/javascripts/pages/admin/sessions/index.js1
-rw-r--r--app/assets/javascripts/releases/components/app_index.vue49
-rw-r--r--app/assets/javascripts/releases/mount_index.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue14
-rw-r--r--app/assets/stylesheets/framework/mixins.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss20
21 files changed, 317 insertions, 283 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index c85e5b68f5f..dc6ea148047 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -492,41 +492,6 @@ const Api = {
buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
},
-
- /**
- * Returns pods logs for an environment with an optional pod and container
- *
- * @param {Object} params
- * @param {Object} param.environment - Environment object
- * @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.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
- */
- getPodLogs({ environment, podName, containerName, search, start, end }) {
- const url = this.buildUrl(environment.logs_api_path);
-
- const params = {};
-
- if (podName) {
- params.pod_name = podName;
- }
- if (containerName) {
- params.container_name = containerName;
- }
- if (search) {
- params.search = search;
- }
- if (start) {
- params.start = start;
- }
- if (end) {
- params.end = end;
- }
-
- return axios.get(url, { params });
- },
};
export default Api;
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
index 3398cd091ba..e618fb3daae 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue
@@ -24,25 +24,19 @@ export default {
discardModalTitle() {
return sprintf(__('Discard changes to %{path}?'), { path: this.activeFile.path });
},
- actionButtonText() {
- return this.activeFile.staged ? __('Unstage') : __('Stage');
- },
isStaged() {
return !this.activeFile.changed && this.activeFile.staged;
},
},
methods: {
...mapActions(['stageChange', 'unstageChange', 'discardFileChanges']),
- actionButtonClicked() {
- if (this.activeFile.staged) {
- this.unstageChange(this.activeFile.path);
- } else {
- this.stageChange(this.activeFile.path);
- }
- },
showDiscardModal() {
this.$refs.discardModal.show();
},
+ discardChanges(path) {
+ this.unstageChange(path);
+ this.discardFileChanges(path);
+ },
},
};
</script>
@@ -65,19 +59,7 @@ export default {
class="btn btn-remove btn-inverted append-right-8"
@click="showDiscardModal"
>
- {{ __('Discard') }}
- </button>
- <button
- ref="actionButton"
- :class="{
- 'btn-success': !isStaged,
- 'btn-warning': isStaged,
- }"
- type="button"
- class="btn btn-inverted"
- @click="actionButtonClicked"
- >
- {{ actionButtonText }}
+ {{ __('Discard changes') }}
</button>
</div>
<gl-modal
@@ -87,7 +69,7 @@ export default {
:ok-title="__('Discard changes')"
:modal-id="discardModalId"
:title="discardModalTitle"
- @ok="discardFileChanges(activeFile.path)"
+ @ok="discardChanges(activeFile.path)"
>
{{ __("You will lose all changes you've made to this file. This action cannot be undone.") }}
</gl-modal>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index 5ec3fc4041b..f6ca728defc 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -1,6 +1,6 @@
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
-import { sprintf, __ } from '~/locale';
+import { n__, __ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import CommitMessageField from './message_field.vue';
import Actions from './actions.vue';
@@ -26,15 +26,7 @@ export default {
...mapGetters(['hasChanges']),
...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']),
overviewText() {
- return sprintf(
- __(
- '<strong>%{stagedFilesLength} staged</strong> and <strong>%{changedFilesLength} unstaged</strong> changes',
- ),
- {
- stagedFilesLength: this.stagedFiles.length,
- changedFilesLength: this.changedFiles.length,
- },
- );
+ return n__('%d changed file', '%d changed files', this.stagedFiles.length);
},
commitButtonText() {
return this.stagedFiles.length ? __('Commit') : __('Stage & Commit');
@@ -125,7 +117,7 @@ export default {
>
{{ __('Commit…') }}
</button>
- <p class="text-center" v-html="overviewText"></p>
+ <p class="text-center bold">{{ overviewText }}</p>
</div>
<form v-if="!isCompact" ref="formEl" @submit.prevent.stop="commitChanges">
<transition name="fade"> <success-message v-show="lastCommitMsg" /> </transition>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
index d9a385a9d31..2e273d45506 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue
@@ -17,10 +17,6 @@ export default {
tooltip,
},
props: {
- title: {
- type: String,
- required: true,
- },
fileList: {
type: Array,
required: true,
@@ -29,18 +25,6 @@ export default {
type: String,
required: true,
},
- action: {
- type: String,
- required: true,
- },
- actionBtnText: {
- type: String,
- required: true,
- },
- actionBtnIcon: {
- type: String,
- required: true,
- },
stagedList: {
type: Boolean,
required: false,
@@ -63,9 +47,9 @@ export default {
},
computed: {
titleText() {
- return sprintf(__('%{title} changes'), {
- title: this.title,
- });
+ if (!this.title) return __('Changes');
+
+ return sprintf(__('%{title} changes'), { title: this.title });
},
filesLength() {
return this.fileList.length;
@@ -73,17 +57,16 @@ export default {
},
methods: {
...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
- actionBtnClicked() {
- this[this.action]();
-
- $(this.$refs.actionBtn).tooltip('hide');
- },
openDiscardModal() {
$('#discard-all-changes').modal('show');
},
+ unstageAndDiscardAllChanges() {
+ this.unstageAllChanges();
+ this.discardAllChanges();
+ },
},
discardModalText: __(
- "You will lose all the unstaged changes you've made in this project. This action cannot be undone.",
+ "You will lose all uncommitted changes you've made in this project. This action cannot be undone.",
),
};
</script>
@@ -96,24 +79,6 @@ export default {
<strong> {{ titleText }} </strong>
<div class="d-flex ml-auto">
<button
- ref="actionBtn"
- v-tooltip
- :title="actionBtnText"
- :aria-label="actionBtnText"
- :disabled="!filesLength"
- :class="{
- 'disabled-content': !filesLength,
- }"
- type="button"
- class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
- data-placement="bottom"
- data-container="body"
- data-boundary="viewport"
- @click="actionBtnClicked"
- >
- <icon :name="actionBtnIcon" :size="16" class="ml-auto mr-auto" />
- </button>
- <button
v-if="!stagedList"
v-tooltip
:title="__('Discard all changes')"
@@ -151,9 +116,9 @@ export default {
v-if="!stagedList"
id="discard-all-changes"
:footer-primary-button-text="__('Discard all changes')"
- :header-title-text="__('Discard all unstaged changes?')"
+ :header-title-text="__('Discard all changes?')"
footer-primary-button-variant="danger"
- @submit="discardAllChanges"
+ @submit="unstageAndDiscardAllChanges"
>
{{ $options.discardModalText }}
</gl-modal>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 726e2b7e1fc..e49d96efe50 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -57,13 +57,7 @@ export default {
},
},
methods: {
- ...mapActions([
- 'discardFileChanges',
- 'updateViewer',
- 'openPendingTab',
- 'unstageChange',
- 'stageChange',
- ]),
+ ...mapActions(['discardFileChanges', 'updateViewer', 'openPendingTab']),
openFileInEditor() {
if (this.file.type === 'tree') return null;
@@ -76,13 +70,6 @@ export default {
}
});
},
- fileAction() {
- if (this.file.staged) {
- this.unstageChange(this.file.path);
- } else {
- this.stageChange(this.file.path);
- }
- },
},
};
</script>
@@ -97,7 +84,6 @@ export default {
}"
class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0"
role="button"
- @dblclick="fileAction"
@click="openFileInEditor"
>
<span class="multi-file-commit-list-file-path d-flex align-items-center">
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 3ef7d863bd5..32822a75772 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -1,6 +1,6 @@
<script>
import { mapGetters } from 'vuex';
-import { n__, __, sprintf } from '~/locale';
+import { n__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
@@ -49,16 +49,7 @@ export default {
folderChangesTooltip() {
if (this.changesCount === 0) return undefined;
- if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) {
- return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount);
- } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) {
- return n__('%d staged change', '%d staged changes', this.folderStagedCount);
- }
-
- return sprintf(__('%{staged} staged and %{unstaged} unstaged changes'), {
- unstaged: this.folderUnstagedCount,
- staged: this.folderStagedCount,
- });
+ return n__('%d changed file', '%d changed files', this.changesCount);
},
showTreeChangesCount() {
return this.isTree && this.changesCount > 0 && !this.file.opened;
diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue
index 62fb0b03975..b8dca2709c8 100644
--- a/app/assets/javascripts/ide/components/repo_commit_section.vue
+++ b/app/assets/javascripts/ide/components/repo_commit_section.vue
@@ -86,28 +86,12 @@ export default {
</deprecated-modal>
<template v-if="showStageUnstageArea">
<commit-files-list
- :title="__('Unstaged')"
- :key-prefix="$options.stageKeys.unstaged"
- :file-list="changedFiles"
- :action-btn-text="__('Stage all changes')"
- :active-file-key="activeFileKey"
- :empty-state-text="__('There are no unstaged changes')"
- action="stageAllChanges"
- action-btn-icon="stage-all"
- class="is-first"
- icon-name="unstaged"
- />
- <commit-files-list
- :title="__('Staged')"
:key-prefix="$options.stageKeys.staged"
:file-list="stagedFiles"
- :action-btn-text="__('Unstage all changes')"
- :staged-list="true"
:active-file-key="activeFileKey"
- :empty-state-text="__('There are no staged changes')"
- action="unstageAllChanges"
- action-btn-icon="unstage-all"
- icon-name="staged"
+ :empty-state-text="__('There are no changes')"
+ class="is-first"
+ icon-name="unstaged"
/>
</template>
<empty-state v-if="unusedSeal" />
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 {};
diff --git a/app/assets/javascripts/pages/admin/sessions/index.js b/app/assets/javascripts/pages/admin/sessions/index.js
new file mode 100644
index 00000000000..680ebd19a9f
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/sessions/index.js
@@ -0,0 +1 @@
+import '~/pages/sessions/index';
diff --git a/app/assets/javascripts/releases/components/app_index.vue b/app/assets/javascripts/releases/components/app_index.vue
index b9e80899e25..511b3cda9c8 100644
--- a/app/assets/javascripts/releases/components/app_index.vue
+++ b/app/assets/javascripts/releases/components/app_index.vue
@@ -1,11 +1,12 @@
<script>
import { mapState, mapActions } from 'vuex';
-import { GlSkeletonLoading, GlEmptyState } from '@gitlab/ui';
+import { GlSkeletonLoading, GlEmptyState, GlLink } from '@gitlab/ui';
import {
getParameterByName,
historyPushState,
buildUrlWithCurrentLocation,
} from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import ReleaseBlock from './release_block.vue';
@@ -16,13 +17,14 @@ export default {
GlEmptyState,
ReleaseBlock,
TablePagination,
+ GlLink,
},
props: {
projectId: {
type: String,
required: true,
},
- documentationLink: {
+ documentationPath: {
type: String,
required: true,
},
@@ -30,6 +32,11 @@ export default {
type: String,
required: true,
},
+ newReleasePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapState('list', ['isLoading', 'releases', 'hasError', 'pageInfo']),
@@ -39,6 +46,11 @@ export default {
shouldRenderSuccessState() {
return this.releases.length && !this.isLoading && !this.hasError;
},
+ emptyStateText() {
+ return __(
+ "Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software.",
+ );
+ },
},
created() {
this.fetchReleases({
@@ -56,7 +68,16 @@ export default {
};
</script>
<template>
- <div class="prepend-top-default">
+ <div class="flex flex-column mt-2">
+ <gl-link
+ v-if="newReleasePath"
+ :href="newReleasePath"
+ :aria-describedby="shouldRenderEmptyState && 'releases-description'"
+ class="btn btn-success align-self-end mb-2 js-new-release-btn"
+ >
+ {{ __('New release') }}
+ </gl-link>
+
<gl-skeleton-loading v-if="isLoading" class="js-loading" />
<gl-empty-state
@@ -64,14 +85,20 @@ export default {
class="js-empty-state"
:title="__('Getting started with releases')"
:svg-path="illustrationPath"
- :description="
- __(
- 'Releases are based on Git tags and mark specific points in a project\'s development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software.',
- )
- "
- :primary-button-link="documentationLink"
- :primary-button-text="__('Open Documentation')"
- />
+ >
+ <template #description>
+ <span id="releases-description">
+ {{ emptyStateText }}
+ <gl-link
+ :href="documentationPath"
+ :aria-label="__('Releases documentation')"
+ target="_blank"
+ >
+ {{ __('More information') }}
+ </gl-link>
+ </span>
+ </template>
+ </gl-empty-state>
<div v-else-if="shouldRenderSuccessState" class="js-success-state">
<release-block
diff --git a/app/assets/javascripts/releases/mount_index.js b/app/assets/javascripts/releases/mount_index.js
index ad82d9a65d6..5f0bf3b6459 100644
--- a/app/assets/javascripts/releases/mount_index.js
+++ b/app/assets/javascripts/releases/mount_index.js
@@ -15,11 +15,7 @@ export default () => {
}),
render: h =>
h(ReleaseListApp, {
- props: {
- projectId: el.dataset.projectId,
- documentationLink: el.dataset.documentationPath,
- illustrationPath: el.dataset.illustrationPath,
- },
+ props: el.dataset,
}),
});
};
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index 9ec99ac93d7..7acbe949151 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -1,7 +1,7 @@
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
-import { __, sprintf } from '~/locale';
+import { __ } from '~/locale';
import { getCommitIconMap } from '~/ide/utils';
export default {
@@ -51,17 +51,7 @@ export default {
tooltipTitle() {
if (!this.showTooltip || !this.file.changed) return undefined;
- const type = this.file.tempFile ? 'addition' : 'modification';
-
- if (this.file.staged) {
- return sprintf(__('Staged %{type}'), {
- type,
- });
- }
-
- return sprintf(__('Unstaged %{type}'), {
- type,
- });
+ return this.file.tempFile ? __('Added') : __('Modified');
},
showIcon() {
return (
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index d54648cc34b..fd448ee24ed 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -257,7 +257,6 @@
width: 15px;
height: 15px;
display: $svg-display;
- fill: $gl-text-color;
top: $svg-top;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 59266af96b4..c829695621c 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -358,17 +358,30 @@
}
}
-.build-page-pod-logs {
+.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 {
@include build-trace-top-bar($gl-line-height * 5);
+ position: relative;
+ top: 0;
.dropdown-menu-toggle {
width: 200px;
@@ -395,4 +408,9 @@
.build-loader-animation {
@include build-loader-animation;
}
+
+ .log-footer {
+ color: $white-normal;
+ background-color: $gray-900;
+ }
}