diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-16 12:08:32 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-16 12:08:32 +0000 |
commit | c158fa8d69c704663d289341a014c44c062cda88 (patch) | |
tree | d0cac82a9ac9e9ad28bb0030266eb8d5dc91fbbc /app | |
parent | b806264d29b8d52ccb78a41dcc3d67f2b040700c (diff) | |
download | gitlab-ce-c158fa8d69c704663d289341a014c44c062cda88.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
31 files changed, 203 insertions, 202 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 463d1427805..878b54f7d53 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -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" diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue index 24542126b07..63ce43a193d 100644 --- a/app/assets/javascripts/diffs/components/compare_versions.vue +++ b/app/assets/javascripts/diffs/components/compare_versions.vue @@ -1,5 +1,4 @@ <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" diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 5d27c6eb865..e78bea789c3 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -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"> diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue index 2e5855380af..1fa1fda7bd7 100644 --- a/app/assets/javascripts/diffs/components/diff_stats.vue +++ b/app/assets/javascripts/diffs/components/diff_stats.vue @@ -1,9 +1,7 @@ <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> diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index 30be2e68e76..7956d05b4f1 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -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" diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue index f7ed7006874..002c00599bb 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -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> diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue index f0bedcfbd6b..33098eb1af0 100644 --- a/app/assets/javascripts/ide/components/file_row_extra.vue +++ b/app/assets/javascripts/ide/components/file_row_extra.vue @@ -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; diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index 3445ef7a75f..cb027358d46 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -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'); } diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index 1bfee7b6be4..70a966afa66 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -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); } diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue index c770fd70260..ca495cd2eca 100644 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue @@ -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> diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue index 55a6a1ace55..457bf35daab 100644 --- a/app/assets/javascripts/registry/settings/components/settings_form.vue +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -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" > diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js index b161373dd0a..5e46d564121 100644 --- a/app/assets/javascripts/registry/settings/store/actions.js +++ b/app/assets/javascripts/registry/settings/store/actions.js @@ -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')); diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 8e7529899c0..fe1724acf89 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -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 diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql index 9be025afe39..c812614e94d 100644 --- a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql +++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql @@ -6,6 +6,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) { sha title description + message webUrl authoredDate authorName 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 75c3c544c77..09cffc57688 100644 --- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue @@ -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> diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index 611001df32f..0c9f6ea94d5 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -1,5 +1,4 @@ <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" diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index f394e4ab58a..d1053570093 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -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; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c023c9e5cbd..84daec4fb43 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -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; } diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index 36fdbfd6eff..9ec8f930a78 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -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.') diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 1e9d51cf970..7eba73daa3c 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -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 diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb index 4c9aac9a327..ca35b07111c 100644 --- a/app/controllers/ide_controller.rb +++ b/app/controllers/ide_controller.rb @@ -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 end diff --git a/app/graphql/mutations/snippets/mark_as_spam.rb b/app/graphql/mutations/snippets/mark_as_spam.rb index 96cb208651f..8cfbbae7c08 100644 --- a/app/graphql/mutations/snippets/mark_as_spam.rb +++ b/app/graphql/mutations/snippets/mark_as_spam.rb @@ -24,7 +24,7 @@ module Mutations private def mark_as_spam(snippet) - SpamService.new(spammable: snippet).mark_as_spam! + Spam::MarkAsSpamService.new(spammable: snippet).execute end def authorized_resource?(snippet) diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index 719de095faf..d6e466d4678 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -154,7 +154,9 @@ module MarkupHelper else other_markup_unsafe(file_name, text, context) end - rescue RuntimeError + rescue StandardError => e + Gitlab::ErrorTracking.track_exception(e, project_id: @project&.id, file_name: file_name, context: context) + simple_format(text) end diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index 45fbbef9225..4b9896343c6 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -1,78 +1,7 @@ # frozen_string_literal: true -# The ReactiveCaching concern is used to fetch some data in the background and -# store it in the Rails cache, keeping it up-to-date for as long as it is being -# requested. If the data hasn't been requested for +reactive_cache_lifetime+, -# it stop being refreshed, and then be removed. -# -# Example of use: -# -# class Foo < ApplicationRecord -# include ReactiveCaching -# -# after_save :clear_reactive_cache! -# -# def calculate_reactive_cache -# # Expensive operation here. The return value of this method is cached -# end -# -# def result -# with_reactive_cache do |data| -# # ... -# end -# end -# end -# -# In this example, the first time `#result` is called, it will return `nil`. -# However, it will enqueue a background worker to call `#calculate_reactive_cache` -# and set an initial cache lifetime of ten minutes. -# -# The background worker needs to find or generate the object on which -# `with_reactive_cache` was called. -# The default behaviour can be overridden by defining a custom -# `reactive_cache_worker_finder`. -# Otherwise the background worker will use the class name and primary key to get -# the object using the ActiveRecord find_by method. -# -# class Bar -# include ReactiveCaching -# -# self.reactive_cache_key = ->() { ["bar", "thing"] } -# self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) } -# -# def self.from_cache(var1, var2) -# # This method will be called by the background worker with "bar1" and -# # "bar2" as arguments. -# new(var1, var2) -# end -# -# def initialize(var1, var2) -# # ... -# end -# -# def calculate_reactive_cache -# # Expensive operation here. The return value of this method is cached -# end -# -# def result -# with_reactive_cache("bar1", "bar2") do |data| -# # ... -# end -# end -# end -# -# Each time the background job completes, it stores the return value of -# `#calculate_reactive_cache`. It is also re-enqueued to run again after -# `reactive_cache_refresh_interval`, so keeping the stored value up to date. -# Calculations are never run concurrently. -# -# Calling `#result` while a value is in the cache will call the block given to -# `#with_reactive_cache`, yielding the cached value. It will also extend the -# lifetime by `reactive_cache_lifetime`. -# -# Once the lifetime has expired, no more background jobs will be enqueued and -# calling `#result` will again return `nil` - starting the process all over -# again +# The usage of the ReactiveCaching module is documented here: +# https://docs.gitlab.com/ee/development/utilities.html#reactivecaching module ReactiveCaching extend ActiveSupport::Concern diff --git a/app/services/concerns/akismet_methods.rb b/app/services/concerns/akismet_methods.rb new file mode 100644 index 00000000000..1cbcf0d47b9 --- /dev/null +++ b/app/services/concerns/akismet_methods.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module AkismetMethods + def spammable_owner + @user ||= User.find(spammable_owner_id) + end + + def spammable_owner_id + @owner_id ||= + if spammable.respond_to?(:author_id) + spammable.author_id + elsif spammable.respond_to?(:creator_id) + spammable.creator_id + end + end + + def akismet + @akismet ||= AkismetService.new( + spammable_owner.name, + spammable_owner.email, + spammable.spammable_text, + options + ) + end +end diff --git a/app/services/notes/destroy_service.rb b/app/services/notes/destroy_service.rb index fa0c2c5c86b..ee8a680fcb4 100644 --- a/app/services/notes/destroy_service.rb +++ b/app/services/notes/destroy_service.rb @@ -11,3 +11,5 @@ module Notes end end end + +Notes::DestroyService.prepend_if_ee('EE::Notes::DestroyService') diff --git a/app/services/spam/mark_as_spam_service.rb b/app/services/spam/mark_as_spam_service.rb new file mode 100644 index 00000000000..0ebcf17927a --- /dev/null +++ b/app/services/spam/mark_as_spam_service.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Spam + class MarkAsSpamService + include ::AkismetMethods + + attr_accessor :spammable, :options + + def initialize(spammable:) + @spammable = spammable + @options = {} + + @options[:ip_address] = @spammable.ip_address + @options[:user_agent] = @spammable.user_agent + end + + def execute + return unless spammable.submittable_as_spam? + return unless akismet.submit_spam + + spammable.user_agent_detail.update_attribute(:submitted, true) + end + end +end diff --git a/app/services/spam_service.rb b/app/services/spam_service.rb index a546c834603..ba9b812a01c 100644 --- a/app/services/spam_service.rb +++ b/app/services/spam_service.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true class SpamService + include AkismetMethods + attr_accessor :spammable, :request, :options attr_reader :spam_log - def initialize(spammable:, request: nil) + def initialize(spammable:, request:) @spammable = spammable @request = request @options = {} @@ -19,16 +21,6 @@ class SpamService end end - def mark_as_spam! - return false unless spammable.submittable_as_spam? - - if akismet.submit_spam - spammable.user_agent_detail.update_attribute(:submitted, true) - else - false - end - end - def when_recaptcha_verified(recaptcha_verified, api = false) # In case it's a request which is already verified through recaptcha, yield # block. @@ -54,28 +46,6 @@ class SpamService true end - def akismet - @akismet ||= AkismetService.new( - spammable_owner.name, - spammable_owner.email, - spammable.spammable_text, - options - ) - end - - def spammable_owner - @user ||= User.find(spammable_owner_id) - end - - def spammable_owner_id - @owner_id ||= - if spammable.respond_to?(:author_id) - spammable.author_id - elsif spammable.respond_to?(:creator_id) - spammable.creator_id - end - end - def check_for_spam? spammable.check_for_spam? end diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 08675c16124..f49cdfbf8da 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -8,7 +8,7 @@ = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) %div = f.label 'Two-Factor Authentication code', name: :otp_attempt - = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.', inputmode: 'numeric', pattern: '[0-9]*' + = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' %p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. .prepend-top-20 = f.submit "Verify code", class: "btn btn-success" diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 3a9c7a8bec5..8b659034fe6 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -21,9 +21,9 @@ .commit-detail.flex-list .commit-content.qa-commit-content - if view_details && merge_request - = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title js-onboarding-commit-item" + = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: ["commit-row-message item-title js-onboarding-commit-item", ("font-italic" if commit.message.empty?)] - else - = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title js-onboarding-commit-item") + = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title js-onboarding-commit-item #{"font-italic" if commit.message.empty?}") %span.commit-row-message.d-inline.d-sm-none · = commit.short_id diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 9e7652fe663..5a6c8079543 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -60,7 +60,7 @@ = render 'projects/triggers/index' - if Feature.enabled?(:registry_retention_policies_settings, @project) - %section.settings.no-animate#js-registry-polcies{ class: ('expanded' if expanded) } + %section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) } .settings-header %h4 = _("Container Registry tag expiration policy") |