Commit c158fa8d authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖

Add latest changes from gitlab-org/gitlab@master

parent b806264d
Pipeline #109903418 passed with stages
in 95 minutes and 36 seconds
......@@ -374,7 +374,7 @@ export default {
<div
:data-can-create-note="getNoteableData.current_user.can_create_note"
class="files d-flex prepend-top-default"
class="files d-flex"
>
<div
v-show="showTreeList"
......
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
......@@ -63,9 +62,6 @@ export default {
showDropdowns() {
return !this.commit && this.mergeRequestDiffs.length;
},
fileTreeIcon() {
return this.showTreeList ? 'collapse-left' : 'expand-left';
},
toggleFileBrowserTitle() {
return this.showTreeList ? __('Hide file browser') : __('Show file browser');
},
......@@ -91,7 +87,7 @@ export default {
</script>
<template>
<div class="mr-version-controls border-top border-bottom">
<div class="mr-version-controls border-top">
<div
class="mr-version-menus-container content-block"
:class="{
......@@ -108,17 +104,17 @@ export default {
:title="toggleFileBrowserTitle"
@click="toggleShowTreeList"
>
<icon :name="fileTreeIcon" />
<icon name="file-tree" />
</button>
<div v-if="showDropdowns" class="d-flex align-items-center compare-versions-container">
Changes between
{{ __('Compare') }}
<compare-versions-dropdown
:other-versions="mergeRequestDiffs"
:merge-request-version="mergeRequestDiff"
:show-commit-count="true"
class="mr-version-dropdown"
/>
and
{{ __('and') }}
<compare-versions-dropdown
:other-versions="comparableDiffs"
:base-version-path="baseVersionPath"
......
......@@ -123,6 +123,20 @@ export default {
}
return s__('MRDiff|Show full file');
},
changedFile() {
const {
new_path: changed,
deleted_file: deleted,
new_file: tempFile,
...diffFile
} = this.diffFile;
return {
...diffFile,
changed: Boolean(changed),
deleted,
tempFile,
};
},
},
mounted() {
polyfillSticky(this.$refs.header);
......@@ -221,7 +235,7 @@ export default {
<div
v-if="!diffFile.submodule && addMergeRequestButtons"
class="file-actions d-none d-sm-block"
class="file-actions d-none d-sm-flex align-items-center"
>
<diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
<div class="btn-group" role="group">
......
<script>
import Icon from '~/vue_shared/components/icon.vue';
import { n__ } from '~/locale';
export default {
components: { Icon },
props: {
addedLines: {
type: Number,
......@@ -21,7 +19,7 @@ export default {
},
computed: {
filesText() {
return n__('File', 'Files', this.diffFilesLength);
return n__('file', 'files', this.diffFilesLength);
},
isCompareVersionsHeader() {
return Boolean(this.diffFilesLength);
......@@ -39,14 +37,21 @@ export default {
}"
>
<div v-if="diffFilesLength !== null" class="diff-stats-group">
<icon name="doc-code" class="diff-stats-icon text-secondary" />
<strong>{{ diffFilesLength }} {{ filesText }}</strong>
<span class="text-secondary bold">{{ diffFilesLength }} {{ filesText }}</span>
</div>
<div class="diff-stats-group cgreen">
<icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong>
<div
class="diff-stats-group cgreen d-flex align-items-center"
:class="{ bold: isCompareVersionsHeader }"
>
<span>+</span>
<span class="js-file-addition-line">{{ addedLines }}</span>
</div>
<div class="diff-stats-group cred">
<icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong>
<div
class="diff-stats-group cred d-flex align-items-center"
:class="{ bold: isCompareVersionsHeader }"
>
<span>-</span>
<span class="js-file-deletion-line">{{ removedLines }}</span>
</div>
</div>
</template>
......@@ -4,7 +4,6 @@ import { GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
export default {
directives: {
......@@ -48,9 +47,6 @@ export default {
return acc;
}, []);
},
fileRowExtraComponent() {
return this.hideFileStats ? null : FileRowStats;
},
},
methods: {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
......@@ -58,8 +54,8 @@ export default {
this.search = '';
},
},
searchPlaceholder: sprintf(s__('MergeRequest|Filter files or search with %{modifier_key}+p'), {
modifier_key: /Mac/i.test(navigator.userAgent) ? 'cmd' : 'ctrl',
searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), {
modifier_key: /Mac/i.test(navigator.userAgent) ? '' : 'Ctrl+',
}),
};
</script>
......@@ -97,7 +93,6 @@ export default {
:file="file"
:level="0"
:hide-extra-on-tree="true"
:extra-component="fileRowExtraComponent"
:show-changed-icon="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
......
......@@ -6,6 +6,7 @@ import CommitMessageField from './message_field.vue';
import Actions from './actions.vue';
import SuccessMessage from './success_message.vue';
import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
......@@ -14,6 +15,7 @@ export default {
CommitMessageField,
SuccessMessage,
},
mixins: [glFeatureFlagsMixin()],
data() {
return {
isCompact: true,
......@@ -27,9 +29,13 @@ export default {
...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']),
overviewText() {
return sprintf(
__(
'<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes',
),
this.glFeatures.stageAllByDefault
? __(
'<strong>%{stagedFilesLength} staged</strong> and <strong>%{changedFilesLength} unstaged</strong> changes',
)
: __(
'<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes',
),
{
stagedFilesLength: this.stagedFiles.length,
changedFilesLength: this.changedFiles.length,
......@@ -39,6 +45,10 @@ export default {
commitButtonText() {
return this.stagedFiles.length ? __('Commit') : __('Stage & Commit');
},
currentViewIsCommitView() {
return this.currentActivityView === activityBarViews.commit;
},
},
watch: {
currentActivityView() {
......@@ -46,27 +56,26 @@ export default {
this.isCompact = false;
} else {
this.isCompact = !(
this.currentActivityView === activityBarViews.commit &&
window.innerHeight >= MAX_WINDOW_HEIGHT_COMPACT
this.currentViewIsCommitView && window.innerHeight >= MAX_WINDOW_HEIGHT_COMPACT
);
}
},
lastCommitMsg() {
this.isCompact =
this.currentActivityView !== activityBarViews.commit && this.lastCommitMsg === '';
},
},
methods: {
...mapActions(['updateActivityBarView']),
...mapActions('commit', ['updateCommitMessage', 'discardDraft', 'commitChanges']),
toggleIsSmall() {
this.updateActivityBarView(activityBarViews.commit)
.then(() => {
this.isCompact = !this.isCompact;
})
.catch(e => {
throw e;
});
toggleIsCompact() {
if (this.currentViewIsCommitView) {
this.isCompact = !this.isCompact;
} else {
this.updateActivityBarView(activityBarViews.commit)
.then(() => {
this.isCompact = false;
})
.catch(e => {
throw e;
});
}
},
beforeEnterTransition() {
const elHeight = this.isCompact
......@@ -114,7 +123,7 @@ export default {
:disabled="!hasChanges"
type="button"
class="btn btn-primary btn-sm btn-block qa-begin-commit-button"
@click="toggleIsSmall"
@click="toggleIsCompact"
>
{{ __('Commit…') }}
</button>
......@@ -148,7 +157,7 @@ export default {
v-else
type="button"
class="btn btn-default btn-sm float-right"
@click="toggleIsSmall"
@click="toggleIsCompact"
>
{{ __('Collapse') }}
</button>
......
......@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import NewDropdown from './new_dropdown/index.vue';
import MrFileIcon from './mr_file_icon.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'FileRowExtra',
......@@ -18,6 +19,7 @@ export default {
ChangedFileIcon,
MrFileIcon,
},
mixins: [glFeatureFlagsMixin()],
props: {
file: {
type: Object,
......@@ -55,10 +57,15 @@ export default {
return n__('%d staged change', '%d staged changes', this.folderStagedCount);
}
return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), {
unstaged: this.folderUnstagedCount,
staged: this.folderStagedCount,
});
return sprintf(
this.glFeatures.stageAllByDefault
? __('%{staged} staged and %{unstaged} unstaged changes')
: __('%{unstaged} unstaged and %{staged} staged changes'),
{
unstaged: this.folderUnstagedCount,
staged: this.folderStagedCount,
},
);
},
showTreeChangesCount() {
return this.isTree && this.changesCount > 0 && !this.file.opened;
......
......@@ -51,7 +51,7 @@ export const setResizingStatus = ({ commit }, resizing) => {
};
export const createTempEntry = (
{ state, commit, dispatch },
{ state, commit, dispatch, getters },
{ name, type, content = '', base64 = false, binary = false, rawPath = '' },
) => {
const fullName = name.slice(-1) !== '/' && type === 'tree' ? `${name}/` : name;
......@@ -92,7 +92,11 @@ export const createTempEntry = (
if (type === 'blob') {
commit(types.TOGGLE_FILE_OPEN, file.path);
commit(types.ADD_FILE_TO_CHANGED, file.path);
if (gon.features?.stageAllByDefault)
commit(types.STAGE_CHANGE, { path: file.path, diffInfo: getters.getDiffInfo(file.path) });
else commit(types.ADD_FILE_TO_CHANGED, file.path);
dispatch('setFileActive', file.path);
dispatch('triggerFilesChange');
dispatch('burstUnusedSeal');
......@@ -238,7 +242,7 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => {
export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
export const renameEntry = ({ dispatch, commit, state }, { path, name, parentPath }) => {
export const renameEntry = ({ dispatch, commit, state, getters }, { path, name, parentPath }) => {
const entry = state.entries[path];
const newPath = parentPath ? `${parentPath}/${name}` : name;
const existingParent = parentPath && state.entries[parentPath];
......@@ -268,7 +272,10 @@ export const renameEntry = ({ dispatch, commit, state }, { path, name, parentPat
if (isReset) {
commit(types.REMOVE_FILE_FROM_STAGED_AND_CHANGED, newEntry);
} else if (!isInChanges) {
commit(types.ADD_FILE_TO_CHANGED, newPath);
if (gon.features?.stageAllByDefault)
commit(types.STAGE_CHANGE, { path: newPath, diffInfo: getters.getDiffInfo(newPath) });
else commit(types.ADD_FILE_TO_CHANGED, newPath);
dispatch('burstUnusedSeal');
}
......
......@@ -147,7 +147,7 @@ export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) =
});
};
export const changeFileContent = ({ commit, dispatch, state }, { path, content }) => {
export const changeFileContent = ({ commit, dispatch, state, getters }, { path, content }) => {
const file = state.entries[path];
commit(types.UPDATE_FILE_CONTENT, {
path,
......@@ -157,7 +157,9 @@ export const changeFileContent = ({ commit, dispatch, state }, { path, content }
const indexOfChangedFile = state.changedFiles.findIndex(f => f.path === path);
if (file.changed && indexOfChangedFile === -1) {
commit(types.ADD_FILE_TO_CHANGED, path);
if (gon.features?.stageAllByDefault)
commit(types.STAGE_CHANGE, { path, diffInfo: getters.getDiffInfo(path) });
else commit(types.ADD_FILE_TO_CHANGED, path);
} else if (!file.changed && !file.tempFile && indexOfChangedFile !== -1) {
commit(types.REMOVE_FILE_FROM_CHANGED, path);
}
......
......@@ -37,7 +37,7 @@ export default {
}}
</li>
</ul>
<gl-loading-icon v-if="isLoading" ref="loading-icon" />
<gl-loading-icon v-if="isLoading" ref="loading-icon" size="xl" />
<settings-form v-else ref="settings-form" />
</div>
</template>
......@@ -46,7 +46,7 @@ export default {
regexHelpText() {
return sprintf(
s__(
'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported',
'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
),
{
codeStart: '<code>',
......@@ -61,7 +61,7 @@ export default {
nameRegexState() {
return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null;
},
formIsValid() {
formIsInvalid() {
return this.nameRegexState === false;
},
},
......@@ -124,7 +124,7 @@ export default {
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-latest"
:label="s__('ContainerRegistry|Expiration latest:')"
:label="s__('ContainerRegistry|Number of tags to retain:')"
>
<gl-form-select id="expiration-policy-latest" v-model="keep_n" :disabled="!enabled">
<option v-for="option in formOptions.keepN" :key="option.key" :value="option.key">
......@@ -138,7 +138,7 @@ export default {
:label-cols="$options.labelsConfig.cols"
:label-align="$options.labelsConfig.align"
label-for="expiration-policy-name-matching"
:label="s__('ContainerRegistry|Expire Docker tags with name matching:')"
:label="s__('ContainerRegistry|Expire Docker tags that match this regex:')"
:state="nameRegexState"
:invalid-feedback="
s__('ContainerRegistry|The value of this input should be less than 255 characters')
......@@ -165,7 +165,7 @@ export default {
<gl-button
ref="save-button"
type="submit"
:disabled="formIsValid"
:disabled="formIsInvalid"
variant="success"
class="d-block"
>
......
......@@ -18,8 +18,8 @@ export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS);
export const fetchSettings = ({ dispatch, state }) => {
dispatch('toggleLoading');
return Api.project(state.projectId)
.then(({ tag_expiration_policies }) =>
dispatch('receiveSettingsSuccess', tag_expiration_policies),
.then(({ data: { container_expiration_policy } }) =>
dispatch('receiveSettingsSuccess', container_expiration_policy),
)
.catch(() => dispatch('receiveSettingsError'))
.finally(() => dispatch('toggleLoading'));
......@@ -27,10 +27,12 @@ export const fetchSettings = ({ dispatch, state }) => {
export const saveSettings = ({ dispatch, state }) => {
dispatch('toggleLoading');
return Api.updateProject(state.projectId, { tag_expiration_policies: state.settings })
.then(({ tag_expiration_policies }) => {
dispatch('receiveSettingsSuccess', tag_expiration_policies);
createFlash(UPDATE_SETTINGS_SUCCESS_MESSAGE);
return Api.updateProject(state.projectId, {
container_expiration_policy_attributes: state.settings,
})
.then(({ data: { container_expiration_policy } }) => {
dispatch('receiveSettingsSuccess', container_expiration_policy);
createFlash(UPDATE_SETTINGS_SUCCESS_MESSAGE, 'success');
})
.catch(() => dispatch('updateSettingsError'))
.finally(() => dispatch('toggleLoading'));
......
......@@ -104,7 +104,11 @@ export default {
</span>
<div class="commit-detail flex-list">
<div class="commit-content qa-commit-content">
<gl-link :href="commit.webUrl" class="commit-row-message item-title">
<gl-link
:href="commit.webUrl"
:class="{ 'font-italic': !commit.message }"
class="commit-row-message item-title"
>
{{ commit.title }}
</gl-link>
<gl-button
......
......@@ -6,6 +6,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
sha
title
description
message
webUrl
authoredDate
authorName
......
......@@ -36,12 +36,17 @@ export default {
required: false,
default: true,
},
showChangedStatus: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
changedIcon() {
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
const suffix = !this.file.changed && this.file.staged && this.showStagedIcon ? '-solid' : '';
const suffix = this.showStagedIcon ? '-solid' : '';
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
......@@ -86,8 +91,8 @@ export default {
<span
v-gl-tooltip.right
:title="tooltipTitle"
:class="{ 'ml-auto': isCentered }"
class="file-changed-icon d-inline-block"
:class="[{ 'ml-auto': isCentered }, changedIconClass]"
class="file-changed-icon d-flex align-items-center "
>
<icon v-if="showIcon" :name="changedIcon" :size="size" :class="changedIconClass" />
</span>
......
<script>
import Icon from '~/vue_shared/components/icon.vue';
import FileHeader from '~/vue_shared/components/file_row_header.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
......@@ -9,7 +8,6 @@ export default {
components: {
FileHeader,
FileIcon,
Icon,
ChangedFileIcon,
},
props: {
......@@ -26,6 +24,7 @@ export default {
required: false,
default: null,
},
hideExtraOnTree: {
type: Boolean,
required: false,
......@@ -143,17 +142,17 @@ export default {
@mouseleave="toggleDropdown(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated d-flex">
<file-icon
v-if="!showChangedIcon || file.type === 'tree'"
class="file-row-icon"
class="file-row-icon text-secondary mr-1"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
:opened="file.opened"
:size="16"
/>
<changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
<file-icon v-else :file-name="file.name" :size="16" css-classes="top mr-1" />
{{ file.name }}
</span>
<component
......@@ -163,6 +162,7 @@ export default {
:dropdown-open="dropdownOpen"
@toggle="toggleDropdown($event)"
/>
<changed-file-icon :file="file" :size="16" class="append-right-5" />
</div>
</div>
<template v-if="file.opened || file.isHeader">
......@@ -172,7 +172,6 @@ export default {
:file="childFile"
:level="childFilesLevel"
:hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
......
......@@ -14,9 +14,9 @@
cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) {
// The `-1` below is to prevent two borders from clashing up against eachother -
// The `+11` is to ensure the file header border shows when scrolled -
// the bottom of the compare-versions header and the top of the file header
$mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height - 1;
$mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height + 11;
position: -webkit-sticky;
position: sticky;
......@@ -552,7 +552,7 @@ table.code {
.diff-stats {
align-items: center;
padding: 0 0.25rem;
padding: 0 1rem;
.diff-stats-group {
padding: 0 0.25rem;
......@@ -564,7 +564,7 @@ table.code {
&.is-compare-versions-header {
.diff-stats-group {
padding: 0 0.5rem;
padding: 0 0.25rem;
}
}
}
......@@ -1059,8 +1059,8 @@ table.code {
.diff-tree-list {
position: -webkit-sticky;
position: sticky;
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 11px;
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 11px;
max-height: calc(100vh - #{$top-pos});
z-index: 202;
......@@ -1097,10 +1097,7 @@ table.code {
.tree-list-scroll {
max-height: 100%;
padding-top: $grid-size;
padding-bottom: $grid-size;
border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color;
overflow-y: scroll;
overflow-x: auto;
}
......
......@@ -708,7 +708,7 @@
.mr-version-controls {
position: relative;
z-index: 203;
background: $gray-light;
background: $white-light;
color: $gl-text-color;
margin-top: -1px;
......@@ -732,7 +732,7 @@
}
.content-block {
padding: $gl-padding-top $gl-padding;
padding: $gl-padding;
border-bottom: 0;
}
......
......@@ -11,7 +11,7 @@ module SpammableActions
end
def mark_as_spam
if SpamService.new(spammable: spammable).mark_as_spam!
if Spam::MarkAsSpamService.new(spammable: spammable).execute
redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase }
else
redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.')
......
......@@ -119,7 +119,9 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def search_params
params.permit(:state, :search_title).merge(group_ids: group.id)
groups = request.format.json? ? group.self_and_ancestors.select(:id) : group.id
params.permit(:state, :search_title).merge(group_ids: groups)
end
end
......
......@@ -3,6 +3,10 @@
class IdeController < ApplicationController
layout 'fullscreen'
before_action do
push_frontend_feature_flag(:stage_all_by_default, default_enabled: true)
end
def index
Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count