Commit b2d1f5ba authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 4332522354d0
......@@ -87,7 +87,7 @@ gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
# 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
# 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
......
......@@ -456,7 +456,7 @@ GEM
graphiql-rails (1.4.10)
railties
sprockets-rails
graphql (1.9.12)
graphql (1.9.19)
graphql-docs (1.6.0)
commonmarker (~> 0.16)
escape_utils (~> 1.2)
......@@ -1252,7 +1252,7 @@ DEPENDENCIES
grape-path-helpers (~> 1.2)
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.9.12)
graphql (~> 1.9.19)
graphql-docs (~> 1.6.0)
grpc (~> 1.24.0)
gssapi
......
......@@ -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;
......@@ -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>
......
<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>
......
......@@ -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>
......@@ -95,24 +78,6 @@ export default {
<icon v-once :name="iconName" :size="18" class="append-right-8" />
<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
......@@ -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>
......
......@@ -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">
......
<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;
......
......@@ -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" />
......
<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>
<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>
......