Skip to content
Snippets Groups Projects
Commit 286fe610 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 4cb5e501
No related branches found
No related tags found
No related merge requests found
Showing
with 299 additions and 272 deletions
Loading
@@ -87,7 +87,7 @@ gem 'grape-entity', '~> 0.7.1'
Loading
@@ -87,7 +87,7 @@ gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors' gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
   
# GraphQL API # GraphQL API
gem 'graphql', '~> 1.9.12' gem 'graphql', '~> 1.9.19'
# NOTE: graphiql-rails v1.5+ doesn't work: https://gitlab.com/gitlab-org/gitlab/issues/31771 # NOTE: graphiql-rails v1.5+ doesn't work: https://gitlab.com/gitlab-org/gitlab/issues/31771
# TODO: remove app/views/graphiql/rails/editors/show.html.erb when https://github.com/rmosolgo/graphiql-rails/pull/71 is released: # TODO: remove app/views/graphiql/rails/editors/show.html.erb when https://github.com/rmosolgo/graphiql-rails/pull/71 is released:
# https://gitlab.com/gitlab-org/gitlab/issues/31747 # https://gitlab.com/gitlab-org/gitlab/issues/31747
Loading
Loading
Loading
@@ -456,7 +456,7 @@ GEM
Loading
@@ -456,7 +456,7 @@ GEM
graphiql-rails (1.4.10) graphiql-rails (1.4.10)
railties railties
sprockets-rails sprockets-rails
graphql (1.9.12) graphql (1.9.19)
graphql-docs (1.6.0) graphql-docs (1.6.0)
commonmarker (~> 0.16) commonmarker (~> 0.16)
escape_utils (~> 1.2) escape_utils (~> 1.2)
Loading
@@ -1252,7 +1252,7 @@ DEPENDENCIES
Loading
@@ -1252,7 +1252,7 @@ DEPENDENCIES
grape-path-helpers (~> 1.2) grape-path-helpers (~> 1.2)
grape_logging (~> 1.7) grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphql (~> 1.9.12) graphql (~> 1.9.19)
graphql-docs (~> 1.6.0) graphql-docs (~> 1.6.0)
grpc (~> 1.24.0) grpc (~> 1.24.0)
gssapi gssapi
Loading
Loading
Loading
@@ -492,41 +492,6 @@ const Api = {
Loading
@@ -492,41 +492,6 @@ const Api = {
buildUrl(url) { buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version)); 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; export default Api;
Loading
@@ -24,25 +24,19 @@ export default {
Loading
@@ -24,25 +24,19 @@ export default {
discardModalTitle() { discardModalTitle() {
return sprintf(__('Discard changes to %{path}?'), { path: this.activeFile.path }); return sprintf(__('Discard changes to %{path}?'), { path: this.activeFile.path });
}, },
actionButtonText() {
return this.activeFile.staged ? __('Unstage') : __('Stage');
},
isStaged() { isStaged() {
return !this.activeFile.changed && this.activeFile.staged; return !this.activeFile.changed && this.activeFile.staged;
}, },
}, },
methods: { methods: {
...mapActions(['stageChange', 'unstageChange', 'discardFileChanges']), ...mapActions(['stageChange', 'unstageChange', 'discardFileChanges']),
actionButtonClicked() {
if (this.activeFile.staged) {
this.unstageChange(this.activeFile.path);
} else {
this.stageChange(this.activeFile.path);
}
},
showDiscardModal() { showDiscardModal() {
this.$refs.discardModal.show(); this.$refs.discardModal.show();
}, },
discardChanges(path) {
this.unstageChange(path);
this.discardFileChanges(path);
},
}, },
}; };
</script> </script>
Loading
@@ -65,19 +59,7 @@ export default {
Loading
@@ -65,19 +59,7 @@ export default {
class="btn btn-remove btn-inverted append-right-8" class="btn btn-remove btn-inverted append-right-8"
@click="showDiscardModal" @click="showDiscardModal"
> >
{{ __('Discard') }} {{ __('Discard changes') }}
</button>
<button
ref="actionButton"
:class="{
'btn-success': !isStaged,
'btn-warning': isStaged,
}"
type="button"
class="btn btn-inverted"
@click="actionButtonClicked"
>
{{ actionButtonText }}
</button> </button>
</div> </div>
<gl-modal <gl-modal
Loading
@@ -87,7 +69,7 @@ export default {
Loading
@@ -87,7 +69,7 @@ export default {
:ok-title="__('Discard changes')" :ok-title="__('Discard changes')"
:modal-id="discardModalId" :modal-id="discardModalId"
:title="discardModalTitle" :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.") }} {{ __("You will lose all changes you've made to this file. This action cannot be undone.") }}
</gl-modal> </gl-modal>
Loading
Loading
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { sprintf, __ } from '~/locale'; import { n__, __ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue';
import CommitMessageField from './message_field.vue'; import CommitMessageField from './message_field.vue';
import Actions from './actions.vue'; import Actions from './actions.vue';
Loading
@@ -26,15 +26,7 @@ export default {
Loading
@@ -26,15 +26,7 @@ export default {
...mapGetters(['hasChanges']), ...mapGetters(['hasChanges']),
...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']), ...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']),
overviewText() { overviewText() {
return sprintf( return n__('%d changed file', '%d changed files', this.stagedFiles.length);
__(
'<strong>%{stagedFilesLength} staged</strong> and <strong>%{changedFilesLength} unstaged</strong> changes',
),
{
stagedFilesLength: this.stagedFiles.length,
changedFilesLength: this.changedFiles.length,
},
);
}, },
commitButtonText() { commitButtonText() {
return this.stagedFiles.length ? __('Commit') : __('Stage & Commit'); return this.stagedFiles.length ? __('Commit') : __('Stage & Commit');
Loading
@@ -125,7 +117,7 @@ export default {
Loading
@@ -125,7 +117,7 @@ export default {
> >
{{ __('Commit…') }} {{ __('Commit…') }}
</button> </button>
<p class="text-center" v-html="overviewText"></p> <p class="text-center bold">{{ overviewText }}</p>
</div> </div>
<form v-if="!isCompact" ref="formEl" @submit.prevent.stop="commitChanges"> <form v-if="!isCompact" ref="formEl" @submit.prevent.stop="commitChanges">
<transition name="fade"> <success-message v-show="lastCommitMsg" /> </transition> <transition name="fade"> <success-message v-show="lastCommitMsg" /> </transition>
Loading
Loading
Loading
@@ -17,10 +17,6 @@ export default {
Loading
@@ -17,10 +17,6 @@ export default {
tooltip, tooltip,
}, },
props: { props: {
title: {
type: String,
required: true,
},
fileList: { fileList: {
type: Array, type: Array,
required: true, required: true,
Loading
@@ -29,18 +25,6 @@ export default {
Loading
@@ -29,18 +25,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
action: {
type: String,
required: true,
},
actionBtnText: {
type: String,
required: true,
},
actionBtnIcon: {
type: String,
required: true,
},
stagedList: { stagedList: {
type: Boolean, type: Boolean,
required: false, required: false,
Loading
@@ -63,9 +47,9 @@ export default {
Loading
@@ -63,9 +47,9 @@ export default {
}, },
computed: { computed: {
titleText() { titleText() {
return sprintf(__('%{title} changes'), { if (!this.title) return __('Changes');
title: this.title,
}); return sprintf(__('%{title} changes'), { title: this.title });
}, },
filesLength() { filesLength() {
return this.fileList.length; return this.fileList.length;
Loading
@@ -73,17 +57,16 @@ export default {
Loading
@@ -73,17 +57,16 @@ export default {
}, },
methods: { methods: {
...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']), ...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
actionBtnClicked() {
this[this.action]();
$(this.$refs.actionBtn).tooltip('hide');
},
openDiscardModal() { openDiscardModal() {
$('#discard-all-changes').modal('show'); $('#discard-all-changes').modal('show');
}, },
unstageAndDiscardAllChanges() {
this.unstageAllChanges();
this.discardAllChanges();
},
}, },
discardModalText: __( 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> </script>
Loading
@@ -95,24 +78,6 @@ export default {
Loading
@@ -95,24 +78,6 @@ export default {
<icon v-once :name="iconName" :size="18" class="append-right-8" /> <icon v-once :name="iconName" :size="18" class="append-right-8" />
<strong> {{ titleText }} </strong> <strong> {{ titleText }} </strong>
<div class="d-flex ml-auto"> <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 <button
v-if="!stagedList" v-if="!stagedList"
v-tooltip v-tooltip
Loading
@@ -151,9 +116,9 @@ export default {
Loading
@@ -151,9 +116,9 @@ export default {
v-if="!stagedList" v-if="!stagedList"
id="discard-all-changes" id="discard-all-changes"
:footer-primary-button-text="__('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" footer-primary-button-variant="danger"
@submit="discardAllChanges" @submit="unstageAndDiscardAllChanges"
> >
{{ $options.discardModalText }} {{ $options.discardModalText }}
</gl-modal> </gl-modal>
Loading
Loading
Loading
@@ -57,13 +57,7 @@ export default {
Loading
@@ -57,13 +57,7 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions([ ...mapActions(['discardFileChanges', 'updateViewer', 'openPendingTab']),
'discardFileChanges',
'updateViewer',
'openPendingTab',
'unstageChange',
'stageChange',
]),
openFileInEditor() { openFileInEditor() {
if (this.file.type === 'tree') return null; if (this.file.type === 'tree') return null;
   
Loading
@@ -76,13 +70,6 @@ export default {
Loading
@@ -76,13 +70,6 @@ export default {
} }
}); });
}, },
fileAction() {
if (this.file.staged) {
this.unstageChange(this.file.path);
} else {
this.stageChange(this.file.path);
}
},
}, },
}; };
</script> </script>
Loading
@@ -97,7 +84,6 @@ export default {
Loading
@@ -97,7 +84,6 @@ export default {
}" }"
class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0" class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0"
role="button" role="button"
@dblclick="fileAction"
@click="openFileInEditor" @click="openFileInEditor"
> >
<span class="multi-file-commit-list-file-path d-flex align-items-center"> <span class="multi-file-commit-list-file-path d-flex align-items-center">
Loading
Loading
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { n__, __, sprintf } from '~/locale'; import { n__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue'; import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
Loading
@@ -49,16 +49,7 @@ export default {
Loading
@@ -49,16 +49,7 @@ export default {
folderChangesTooltip() { folderChangesTooltip() {
if (this.changesCount === 0) return undefined; if (this.changesCount === 0) return undefined;
   
if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) { return n__('%d changed file', '%d changed files', this.changesCount);
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,
});
}, },
showTreeChangesCount() { showTreeChangesCount() {
return this.isTree && this.changesCount > 0 && !this.file.opened; return this.isTree && this.changesCount > 0 && !this.file.opened;
Loading
Loading
Loading
@@ -86,28 +86,12 @@ export default {
Loading
@@ -86,28 +86,12 @@ export default {
</deprecated-modal> </deprecated-modal>
<template v-if="showStageUnstageArea"> <template v-if="showStageUnstageArea">
<commit-files-list <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" :key-prefix="$options.stageKeys.staged"
:file-list="stagedFiles" :file-list="stagedFiles"
:action-btn-text="__('Unstage all changes')"
:staged-list="true"
:active-file-key="activeFileKey" :active-file-key="activeFileKey"
:empty-state-text="__('There are no staged changes')" :empty-state-text="__('There are no changes')"
action="unstageAllChanges" class="is-first"
action-btn-icon="unstage-all" icon-name="unstaged"
icon-name="staged"
/> />
</template> </template>
<empty-state v-if="unusedSeal" /> <empty-state v-if="unusedSeal" />
Loading
Loading
<script> <script>
import { throttle } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex'; 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 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 LogControlButtons from './log_control_buttons.vue';
   
import { timeRanges, defaultTimeRange } from '~/monitoring/constants'; import { timeRanges, defaultTimeRange } from '~/monitoring/constants';
import { timeRangeFromUrl } from '~/monitoring/utils'; import { timeRangeFromUrl } from '~/monitoring/utils';
import { formatDate } from '../utils';
   
export default { export default {
components: { components: {
GlSprintf,
GlAlert, GlAlert,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlFormGroup, GlFormGroup,
GlSearchBoxByClick, GlSearchBoxByClick,
GlInfiniteScroll,
DateTimePicker, DateTimePicker,
LogControlButtons, LogControlButtons,
}, },
filters: {
formatDate,
},
props: { props: {
environmentName: { environmentName: {
type: String, type: String,
Loading
@@ -39,11 +53,13 @@ export default {
Loading
@@ -39,11 +53,13 @@ export default {
required: true, required: true,
}, },
}, },
traceHeight: 600,
data() { data() {
return { return {
searchQuery: '', searchQuery: '',
timeRanges, timeRanges,
isElasticStackCalloutDismissed: false, isElasticStackCalloutDismissed: false,
scrollDownButtonDisabled: true,
}; };
}, },
computed: { computed: {
Loading
@@ -52,7 +68,7 @@ export default {
Loading
@@ -52,7 +68,7 @@ export default {
   
timeRangeModel: { timeRangeModel: {
get() { get() {
return this.timeRange.current; return this.timeRange.selected;
}, },
set(val) { set(val) {
this.setTimeRange(val); this.setTimeRange(val);
Loading
@@ -60,7 +76,7 @@ export default {
Loading
@@ -60,7 +76,7 @@ export default {
}, },
   
showLoader() { showLoader() {
return this.logs.isLoading || !this.logs.isComplete; return this.logs.isLoading;
}, },
advancedFeaturesEnabled() { advancedFeaturesEnabled() {
const environment = this.environments.options.find( const environment = this.environments.options.find(
Loading
@@ -75,16 +91,6 @@ export default {
Loading
@@ -75,16 +91,6 @@ export default {
return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls; return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls;
}, },
}, },
watch: {
trace(val) {
this.$nextTick(() => {
if (val) {
scrollDown();
}
this.$refs.scrollButtons.update();
});
},
},
mounted() { mounted() {
this.setInitData({ this.setInitData({
timeRange: timeRangeFromUrl() || defaultTimeRange, timeRange: timeRangeFromUrl() || defaultTimeRange,
Loading
@@ -102,12 +108,26 @@ export default {
Loading
@@ -102,12 +108,26 @@ export default {
'showPodLogs', 'showPodLogs',
'showEnvironment', 'showEnvironment',
'fetchEnvironments', '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> </script>
<template> <template>
<div class="build-page-pod-logs mt-3"> <div class="environment-logs-viewer mt-3">
<gl-alert <gl-alert
v-if="shouldShowElasticStackCallout" v-if="shouldShowElasticStackCallout"
class="mb-3 js-elasticsearch-alert" class="mb-3 js-elasticsearch-alert"
Loading
@@ -209,14 +229,50 @@ export default {
Loading
@@ -209,14 +229,50 @@ export default {
<log-control-buttons <log-control-buttons
ref="scrollButtons" ref="scrollButtons"
class="controllers align-self-end mb-1" class="controllers align-self-end mb-1"
:scroll-down-button-disabled="scrollDownButtonDisabled"
@refresh="showPodLogs(pods.current)" @refresh="showPodLogs(pods.current)"
@scrollDown="scrollDown"
/> />
</div> </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"> <gl-infinite-scroll
<div class="dot"></div> ref="infiniteScroll"
<div class="dot"></div> class="log-lines"
<div class="dot"></div> :style="{ height: `${$options.traceHeight}px` }"
</div></code></pre> :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> </div>
</template> </template>
<script> <script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui'; 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'; import Icon from '~/vue_shared/components/icon.vue';
   
export default { export default {
Loading
@@ -17,32 +10,34 @@ export default {
Loading
@@ -17,32 +10,34 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
props: {
scrollUpButtonDisabled: {
type: Boolean,
required: false,
default: false,
},
scrollDownButtonDisabled: {
type: Boolean,
required: false,
default: false,
},
},
data() { data() {
return { return {
scrollToTopEnabled: false, scrollUpAvailable: Boolean(this.$listeners.scrollUp),
scrollToBottomEnabled: false, scrollDownAvailable: Boolean(this.$listeners.scrollDown),
}; };
}, },
created() {
window.addEventListener('scroll', this.update);
},
destroyed() {
window.removeEventListener('scroll', this.update);
},
methods: { 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() { handleRefreshClick() {
this.$emit('refresh'); this.$emit('refresh');
}, },
scrollUp, handleScrollUp() {
scrollDown, this.$emit('scrollUp');
},
handleScrollDown() {
this.$emit('scrollDown');
},
}, },
}; };
</script> </script>
Loading
@@ -50,6 +45,7 @@ export default {
Loading
@@ -50,6 +45,7 @@ export default {
<template> <template>
<div> <div>
<div <div
v-if="scrollUpAvailable"
v-gl-tooltip v-gl-tooltip
class="controllers-buttons" class="controllers-buttons"
:title="__('Scroll to top')" :title="__('Scroll to top')"
Loading
@@ -59,13 +55,15 @@ export default {
Loading
@@ -59,13 +55,15 @@ export default {
id="scroll-to-top" id="scroll-to-top"
class="btn-blank js-scroll-to-top" class="btn-blank js-scroll-to-top"
:aria-label="__('Scroll to top')" :aria-label="__('Scroll to top')"
:disabled="!scrollToTopEnabled" :disabled="scrollUpButtonDisabled"
@click="scrollUp()" @click="handleScrollUp()"
><icon name="scroll_up" ><icon name="scroll_up"
/></gl-button> /></gl-button>
</div> </div>
<div <div
v-if="scrollDownAvailable"
v-gl-tooltip v-gl-tooltip
:disabled="scrollUpButtonDisabled"
class="controllers-buttons" class="controllers-buttons"
:title="__('Scroll to bottom')" :title="__('Scroll to bottom')"
aria-labelledby="scroll-to-bottom" aria-labelledby="scroll-to-bottom"
Loading
@@ -74,8 +72,9 @@ export default {
Loading
@@ -74,8 +72,9 @@ export default {
id="scroll-to-bottom" id="scroll-to-bottom"
class="btn-blank js-scroll-to-bottom" class="btn-blank js-scroll-to-bottom"
:aria-label="__('Scroll to bottom')" :aria-label="__('Scroll to bottom')"
:disabled="!scrollToBottomEnabled" :v-if="scrollDownAvailable"
@click="scrollDown()" :disabled="scrollDownButtonDisabled"
@click="handleScrollDown()"
><icon name="scroll_down" ><icon name="scroll_down"
/></gl-button> /></gl-button>
</div> </div>
Loading
Loading
import Api from '~/api';
import { backOff } from '~/lib/utils/common_utils'; import { backOff } from '~/lib/utils/common_utils';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
Loading
@@ -16,9 +15,10 @@ const flashLogsError = () => {
Loading
@@ -16,9 +15,10 @@ const flashLogsError = () => {
flash(s__('Metrics|There was an error fetching the logs, please try again')); flash(s__('Metrics|There was an error fetching the logs, please try again'));
}; };
   
const requestLogsUntilData = params => const requestUntilData = (url, params) =>
backOff((next, stop) => { backOff((next, stop) => {
Api.getPodLogs(params) axios
.get(url, { params })
.then(res => { .then(res => {
if (res.status === httpStatusCodes.ACCEPTED) { if (res.status === httpStatusCodes.ACCEPTED) {
next(); next();
Loading
@@ -31,10 +31,36 @@ const requestLogsUntilData = params =>
Loading
@@ -31,10 +31,36 @@ const requestLogsUntilData = params =>
}); });
}); });
   
export const setInitData = ({ commit }, { timeRange, environmentName, podName }) => { const requestLogsUntilData = state => {
if (timeRange) { const params = {};
commit(types.SET_TIME_RANGE, timeRange); 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_PROJECT_ENVIRONMENT, environmentName);
commit(types.SET_CURRENT_POD_NAME, podName); commit(types.SET_CURRENT_POD_NAME, podName);
}; };
Loading
@@ -60,10 +86,15 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => {
Loading
@@ -60,10 +86,15 @@ export const showEnvironment = ({ dispatch, commit }, environmentName) => {
dispatch('fetchLogs'); dispatch('fetchLogs');
}; };
   
/**
* Fetch environments data and initial logs
* @param {Object} store
* @param {String} environmentsPath
*/
export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => { export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
commit(types.REQUEST_ENVIRONMENTS_DATA); commit(types.REQUEST_ENVIRONMENTS_DATA);
   
axios return axios
.get(environmentsPath) .get(environmentsPath)
.then(({ data }) => { .then(({ data }) => {
commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data.environments); commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data.environments);
Loading
@@ -76,32 +107,16 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
Loading
@@ -76,32 +107,16 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
}; };
   
export const fetchLogs = ({ commit, state }) => { 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_PODS_DATA);
commit(types.REQUEST_LOGS_DATA); commit(types.REQUEST_LOGS_DATA);
   
return requestLogsUntilData(params) return requestLogsUntilData(state)
.then(({ data }) => { .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.SET_CURRENT_POD_NAME, pod_name);
   
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods); commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
commit(types.RECEIVE_LOGS_DATA_SUCCESS, logs); commit(types.RECEIVE_LOGS_DATA_SUCCESS, { logs, cursor });
}) })
.catch(() => { .catch(() => {
commit(types.RECEIVE_PODS_DATA_ERROR); commit(types.RECEIVE_PODS_DATA_ERROR);
Loading
@@ -110,5 +125,24 @@ export const fetchLogs = ({ commit, state }) => {
Loading
@@ -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 // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
import dateFormat from 'dateformat'; import { formatDate } from '../utils';
   
export const trace = state => const mapTrace = ({ timestamp = null, message = '' }) =>
state.logs.lines [timestamp ? formatDate(timestamp) : '', message].join(' | ');
.map(item => [dateFormat(item.timestamp, 'UTC:mmm dd HH:MM:ss.l"Z"'), item.message].join(' | '))
.join('\n'); export const trace = state => state.logs.lines.map(mapTrace).join('\n');
   
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
Loading
@@ -10,6 +10,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR'
Loading
@@ -10,6 +10,9 @@ export const RECEIVE_ENVIRONMENTS_DATA_ERROR = 'RECEIVE_ENVIRONMENTS_DATA_ERROR'
export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA'; export const REQUEST_LOGS_DATA = 'REQUEST_LOGS_DATA';
export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS'; export const RECEIVE_LOGS_DATA_SUCCESS = 'RECEIVE_LOGS_DATA_SUCCESS';
export const RECEIVE_LOGS_DATA_ERROR = 'RECEIVE_LOGS_DATA_ERROR'; 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 REQUEST_PODS_DATA = 'REQUEST_PODS_DATA';
export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS'; export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS';
Loading
Loading
import * as types from './mutation_types'; import * as types from './mutation_types';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
const mapLine = ({ timestamp, message }) => ({
timestamp,
message,
});
   
export default { export default {
/** Search data */ // Search Data
[types.SET_SEARCH](state, searchQuery) { [types.SET_SEARCH](state, searchQuery) {
state.search = searchQuery; state.search = searchQuery;
}, },
   
/** Time Range data */ // Time Range Data
[types.SET_TIME_RANGE](state, timeRange) { [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) { [types.SET_PROJECT_ENVIRONMENT](state, environmentName) {
state.environments.current = environmentName; state.environments.current = environmentName;
}, },
Loading
@@ -28,24 +35,49 @@ export default {
Loading
@@ -28,24 +35,49 @@ export default {
state.environments.isLoading = false; state.environments.isLoading = false;
}, },
   
/** Logs data */ // Logs data
[types.REQUEST_LOGS_DATA](state) { [types.REQUEST_LOGS_DATA](state) {
state.timeRange.current = convertToFixedRange(state.timeRange.selected);
state.logs.lines = []; state.logs.lines = [];
state.logs.isLoading = true; state.logs.isLoading = true;
// start pagination from the beginning
state.logs.cursor = null;
state.logs.isComplete = false; state.logs.isComplete = false;
}, },
[types.RECEIVE_LOGS_DATA_SUCCESS](state, lines) { [types.RECEIVE_LOGS_DATA_SUCCESS](state, { logs = [], cursor }) {
state.logs.lines = lines; state.logs.lines = logs.map(mapLine);
state.logs.isLoading = false; state.logs.isLoading = false;
state.logs.isComplete = true; state.logs.cursor = cursor;
if (!cursor) {
state.logs.isComplete = true;
}
}, },
[types.RECEIVE_LOGS_DATA_ERROR](state) { [types.RECEIVE_LOGS_DATA_ERROR](state) {
state.logs.lines = []; state.logs.lines = [];
state.logs.isLoading = false; 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) { [types.SET_CURRENT_POD_NAME](state, podName) {
state.pods.current = podName; state.pods.current = podName;
}, },
Loading
Loading
import { timeRanges, defaultTimeRange } from '~/monitoring/constants'; import { timeRanges, defaultTimeRange } from '~/monitoring/constants';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
   
export default () => ({ export default () => ({
/** /**
Loading
@@ -11,7 +12,10 @@ export default () => ({
Loading
@@ -11,7 +12,10 @@ export default () => ({
*/ */
timeRange: { timeRange: {
options: timeRanges, options: timeRanges,
current: defaultTimeRange, // Selected time range, can be fixed or relative
selected: defaultTimeRange,
// Current time range, must be fixed
current: convertToFixedRange(defaultTimeRange),
}, },
   
/** /**
Loading
@@ -29,7 +33,12 @@ export default () => ({
Loading
@@ -29,7 +33,12 @@ export default () => ({
logs: { logs: {
lines: [], lines: [],
isLoading: false, 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,
}, },
   
/** /**
Loading
Loading
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; 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 * Returns a time range (`start`, `end`) where `start` is the
Loading
@@ -20,4 +23,6 @@ export const getTimeRange = (seconds = 0) => {
Loading
@@ -20,4 +23,6 @@ export const getTimeRange = (seconds = 0) => {
}; };
}; };
   
export const formatDate = timestamp => dateFormat(timestamp, dateFormatMask);
export default {}; export default {};
import '~/pages/sessions/index';
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { GlSkeletonLoading, GlEmptyState } from '@gitlab/ui'; import { GlSkeletonLoading, GlEmptyState, GlLink } from '@gitlab/ui';
import { import {
getParameterByName, getParameterByName,
historyPushState, historyPushState,
buildUrlWithCurrentLocation, buildUrlWithCurrentLocation,
} from '~/lib/utils/common_utils'; } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import ReleaseBlock from './release_block.vue'; import ReleaseBlock from './release_block.vue';
   
Loading
@@ -16,13 +17,14 @@ export default {
Loading
@@ -16,13 +17,14 @@ export default {
GlEmptyState, GlEmptyState,
ReleaseBlock, ReleaseBlock,
TablePagination, TablePagination,
GlLink,
}, },
props: { props: {
projectId: { projectId: {
type: String, type: String,
required: true, required: true,
}, },
documentationLink: { documentationPath: {
type: String, type: String,
required: true, required: true,
}, },
Loading
@@ -30,6 +32,11 @@ export default {
Loading
@@ -30,6 +32,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
newReleasePath: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
...mapState('list', ['isLoading', 'releases', 'hasError', 'pageInfo']), ...mapState('list', ['isLoading', 'releases', 'hasError', 'pageInfo']),
Loading
@@ -39,6 +46,11 @@ export default {
Loading
@@ -39,6 +46,11 @@ export default {
shouldRenderSuccessState() { shouldRenderSuccessState() {
return this.releases.length && !this.isLoading && !this.hasError; 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() { created() {
this.fetchReleases({ this.fetchReleases({
Loading
@@ -56,7 +68,16 @@ export default {
Loading
@@ -56,7 +68,16 @@ export default {
}; };
</script> </script>
<template> <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-skeleton-loading v-if="isLoading" class="js-loading" />
   
<gl-empty-state <gl-empty-state
Loading
@@ -64,14 +85,20 @@ export default {
Loading
@@ -64,14 +85,20 @@ export default {
class="js-empty-state" class="js-empty-state"
:title="__('Getting started with releases')" :title="__('Getting started with releases')"
:svg-path="illustrationPath" :svg-path="illustrationPath"
:description=" >
__( <template #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.', <span id="releases-description">
) {{ emptyStateText }}
" <gl-link
:primary-button-link="documentationLink" :href="documentationPath"
:primary-button-text="__('Open Documentation')" :aria-label="__('Releases documentation')"
/> target="_blank"
>
{{ __('More information') }}
</gl-link>
</span>
</template>
</gl-empty-state>
   
<div v-else-if="shouldRenderSuccessState" class="js-success-state"> <div v-else-if="shouldRenderSuccessState" class="js-success-state">
<release-block <release-block
Loading
Loading
Loading
@@ -15,11 +15,7 @@ export default () => {
Loading
@@ -15,11 +15,7 @@ export default () => {
}), }),
render: h => render: h =>
h(ReleaseListApp, { h(ReleaseListApp, {
props: { props: el.dataset,
projectId: el.dataset.projectId,
documentationLink: el.dataset.documentationPath,
illustrationPath: el.dataset.illustrationPath,
},
}), }),
}); });
}; };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment