diff options
author | Rémy Coutable <remy@rymai.me> | 2018-07-16 16:02:55 +0000 |
---|---|---|
committer | Rémy Coutable <remy@rymai.me> | 2018-07-16 16:02:55 +0000 |
commit | 8048f99c21d0299313dcda172aae22fda8c519d3 (patch) | |
tree | edf02d0adf376ee0cce7caa9c541c687bc25f43f /app | |
parent | 815947bf6785e1c458bad8d2d891e99279cab602 (diff) | |
parent | 2c5e6b272e7984400ac4d297553b3f4f50a8d5c4 (diff) | |
download | gitlab-ce-8048f99c21d0299313dcda172aae22fda8c519d3.tar.gz |
Merge branch 'master' into 'qa-48464-add-edit-delete-file-scenario'sliaquat/gitlab-ce-qa-48464-add-edit-delete-file-scenario
# Conflicts:
# qa/qa.rb
Diffstat (limited to 'app')
313 files changed, 2443 insertions, 1959 deletions
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index 1638e09132b..b0c85c2572e 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -2,13 +2,16 @@ import $ from 'jquery'; import { rstrip } from './lib/utils/common_utils'; function openConfirmDangerModal($form, text) { + const $input = $('.js-confirm-danger-input'); + $input.val(''); + $('.js-confirm-text').text(text || ''); - $('.js-confirm-danger-input').val(''); $('#modal-confirm-danger').modal('show'); const confirmTextMatch = $('.js-confirm-danger-match').text(); const $submit = $('.js-confirm-danger-submit'); $submit.disable(); + $input.focus(); $('.js-confirm-danger-input').off('input').on('input', function handleInput() { const confirmText = rstrip($(this).val()); diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index eb0985e5603..1d1415fe6ca 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -41,11 +41,6 @@ export default { required: true, }, }, - data() { - return { - activeFile: '', - }; - }, computed: { ...mapState({ isLoading: state => state.diffs.isLoading, @@ -63,7 +58,8 @@ export default { plainDiffPath: state => state.diffs.plainDiffPath, emailPatchPath: state => state.diffs.emailPatchPath, }), - ...mapGetters(['isParallelView', 'isNotesFetched']), + ...mapGetters('diffs', ['isParallelView']), + ...mapGetters(['isNotesFetched']), targetBranch() { return { branchName: this.targetBranchName, @@ -115,7 +111,7 @@ export default { this.adjustView(); }, methods: { - ...mapActions(['setBaseConfig', 'fetchDiffFiles']), + ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']), fetchData() { this.fetchDiffFiles().catch(() => { createFlash(__('Something went wrong on our end. Please try again!')); @@ -125,14 +121,6 @@ export default { eventHub.$emit('fetchNotesData'); } }, - setActive(filePath) { - this.activeFile = filePath; - }, - unsetActive(filePath) { - if (this.activeFile === filePath) { - this.activeFile = ''; - } - }, adjustView() { if (this.shouldShow && this.isParallelView) { window.mrTabs.expandViewContainer(); @@ -194,7 +182,6 @@ export default { <changed-files :diff-files="diffFiles" - :active-file="activeFile" /> <div @@ -206,8 +193,6 @@ export default { :key="file.newPath" :file="file" :current-user="currentUser" - @setActive="setActive(file.filePath)" - @unsetActive="unsetActive(file.filePath)" /> </div> <no-changes v-else /> diff --git a/app/assets/javascripts/diffs/components/changed_files.vue b/app/assets/javascripts/diffs/components/changed_files.vue index c5ef9fefc2f..97751db1254 100644 --- a/app/assets/javascripts/diffs/components/changed_files.vue +++ b/app/assets/javascripts/diffs/components/changed_files.vue @@ -16,13 +16,6 @@ export default { ClipboardButton, }, mixins: [changedFilesMixin], - props: { - activeFile: { - type: String, - required: false, - default: '', - }, - }, data() { return { isStuck: false, @@ -31,7 +24,7 @@ export default { }; }, computed: { - ...mapGetters(['isInlineView', 'isParallelView', 'areAllFilesCollapsed']), + ...mapGetters('diffs', ['isInlineView', 'isParallelView', 'areAllFilesCollapsed']), sumAddedLines() { return this.sumValues('addedLines'); }, @@ -66,11 +59,11 @@ export default { document.removeEventListener('scroll', this.handleScroll); }, methods: { - ...mapActions(['setInlineDiffViewType', 'setParallelDiffViewType', 'expandAllFiles']), + ...mapActions('diffs', ['setInlineDiffViewType', 'setParallelDiffViewType', 'expandAllFiles']), pluralize, handleScroll() { if (!this.updating) { - requestAnimationFrame(this.updateIsStuck); + this.$nextTick(this.updateIsStuck); this.updating = true; } }, @@ -148,25 +141,8 @@ export default { /> <span - v-show="activeFile" - class="prepend-left-5" - > - <strong class="prepend-right-5"> - {{ truncatedDiffPath(activeFile) }} - </strong> - <clipboard-button - :text="activeFile" - :title="s__('Copy file name to clipboard')" - tooltip-placement="bottom" - tooltip-container="body" - class="btn btn-default btn-transparent btn-clipboard" - /> - </span> - - <span - v-show="!isStuck" - id="diff-stats" - class="diff-stats-additions-deletions-expanded" + class="js-diff-stats-additions-deletions-expanded + diff-stats-additions-deletions-expanded" > with <strong class="cgreen"> @@ -177,6 +153,17 @@ export default { {{ pluralize(`${sumRemovedLines} deletion`, sumRemovedLines) }} </strong> </span> + <div + class="js-diff-stats-additions-deletions-collapsed + diff-stats-additions-deletions-collapsed float-right d-sm-none" + > + <strong class="cgreen"> + +{{ sumAddedLines }} + </strong> + <strong class="cred"> + -{{ sumRemovedLines }} + </strong> + </div> </div> </div> </div> diff --git a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue index b38d217fbe3..045688a32bf 100644 --- a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue +++ b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue @@ -40,7 +40,7 @@ export default { {{ n__('%d changed file', '%d changed files', diffFiles.length) }} </span> <icon - :size="8" + class="caret-icon" name="chevron-down" /> </button> diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index e9f24b7277e..02d5be1821b 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -22,7 +22,7 @@ export default { projectPath: state => state.diffs.projectPath, endpoint: state => state.diffs.endpoint, }), - ...mapGetters(['isInlineView', 'isParallelView']), + ...mapGetters('diffs', ['isInlineView', 'isParallelView']), diffMode() { const diffModeKey = Object.keys(diffModes).find(key => this.diffFile[`${key}File`]); return diffModes[diffModeKey] || diffModes.replaced; diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue index 39d535036f6..20483161033 100644 --- a/app/assets/javascripts/diffs/components/diff_discussions.vue +++ b/app/assets/javascripts/diffs/components/diff_discussions.vue @@ -15,9 +15,7 @@ export default { </script> <template> - <div - v-if="discussions.length" - > + <div> <div v-for="discussion in discussions" :key="discussion.id" diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 108eefdac5f..944084f05c9 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -25,15 +25,11 @@ export default { }, data() { return { - isActive: false, isLoadingCollapsedDiff: false, forkMessageVisible: false, }; }, computed: { - isDiscussionsExpanded() { - return true; // TODO: @fatihacet - Fix this. - }, isCollapsed() { return this.file.collapsed || false; }, @@ -47,15 +43,12 @@ export default { false, ); }, - }, - mounted() { - document.addEventListener('scroll', this.handleScroll); - }, - beforeDestroy() { - document.removeEventListener('scroll', this.handleScroll); + showExpandMessage() { + return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge; + }, }, methods: { - ...mapActions(['loadCollapsedDiff']), + ...mapActions('diffs', ['loadCollapsedDiff']), handleToggle() { const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file; @@ -65,36 +58,6 @@ export default { this.file.collapsed = !this.file.collapsed; } }, - handleScroll() { - if (!this.updating) { - requestAnimationFrame(this.scrollUpdate.bind(this)); - this.updating = true; - } - }, - scrollUpdate() { - const header = document.querySelector('.js-diff-files-changed'); - if (!header) { - this.updating = false; - return; - } - - const { top, bottom } = this.$el.getBoundingClientRect(); - const { top: topOfFixedHeader, bottom: bottomOfFixedHeader } = header.getBoundingClientRect(); - - const headerOverlapsContent = top < topOfFixedHeader && bottom > bottomOfFixedHeader; - const fullyAboveHeader = bottom < bottomOfFixedHeader; - const fullyBelowHeader = top > topOfFixedHeader; - - if (headerOverlapsContent && !this.isActive) { - this.$emit('setActive'); - this.isActive = true; - } else if (this.isActive && (fullyAboveHeader || fullyBelowHeader)) { - this.$emit('unsetActive'); - this.isActive = false; - } - - this.updating = false; - }, handleLoadCollapsedDiff() { this.isLoadingCollapsedDiff = true; @@ -128,7 +91,6 @@ export default { :diff-file="file" :collapsible="true" :expanded="!isCollapsed" - :discussions-expanded="isDiscussionsExpanded" :add-merge-request-buttons="true" class="js-file-title file-title" @toggleFile="handleToggle" @@ -159,7 +121,7 @@ export default { </div> <diff-content - v-show="!isCollapsed" + v-if="!isCollapsed" :class="{ hidden: isCollapsed || file.tooLarge }" :diff-file="file" /> @@ -168,7 +130,7 @@ export default { class="diff-content loading" /> <div - v-show="isCollapsed && !isLoadingCollapsedDiff && !file.tooLarge" + v-if="showExpandMessage" class="nothing-here-block diff-collapsed" > {{ __('This diff is collapsed.') }} diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index a8e8732053b..c5abd0a9568 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -1,5 +1,6 @@ <script> import _ from 'underscore'; +import { mapActions, mapGetters } from 'vuex'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import Icon from '~/vue_shared/components/icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue'; @@ -38,11 +39,6 @@ export default { required: false, default: true, }, - discussionsExpanded: { - type: Boolean, - required: false, - default: true, - }, currentUser: { type: Object, required: true, @@ -54,6 +50,10 @@ export default { }; }, computed: { + ...mapGetters('diffs', ['diffHasExpandedDiscussions']), + hasExpandedDiscussions() { + return this.diffHasExpandedDiscussions(this.diffFile); + }, icon() { if (this.diffFile.submodule) { return 'archive'; @@ -88,9 +88,6 @@ export default { collapseIcon() { return this.expanded ? 'chevron-down' : 'chevron-right'; }, - isDiscussionsExpanded() { - return this.discussionsExpanded && this.expanded; - }, viewFileButtonText() { const truncatedContentSha = _.escape(truncateSha(this.diffFile.contentSha)); return sprintf( @@ -113,7 +110,8 @@ export default { }, }, methods: { - handleToggle(e, checkTarget) { + ...mapActions('diffs', ['toggleFileDiscussions']), + handleToggleFile(e, checkTarget) { if ( !checkTarget || e.target === this.$refs.header || @@ -125,6 +123,9 @@ export default { showForkMessage() { this.$emit('showForkMessage'); }, + handleToggleDiscussions() { + this.toggleFileDiscussions(this.diffFile); + }, }, }; </script> @@ -133,7 +134,7 @@ export default { <div ref="header" class="js-file-title file-title file-title-flex-parent" - @click="handleToggle($event, true)" + @click="handleToggleFile($event, true)" > <div class="file-header-content"> <icon @@ -145,6 +146,7 @@ export default { @click.stop="handleToggle" /> <a + v-once ref="titleWrapper" :href="titleLink" class="append-right-4" @@ -215,10 +217,11 @@ export default { v-if="diffFile.blob && diffFile.blob.readableText" > <button - :class="{ active: isDiscussionsExpanded }" + :class="{ active: hasExpandedDiscussions }" :title="s__('MergeRequests|Toggle comments for this file')" - class="btn js-toggle-diff-comments" + class="js-btn-vue-toggle-comments btn" type="button" + @click="handleToggleDiscussions" > <icon name="comment" /> </button> diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue index a74ea4bfaaf..ad838a32518 100644 --- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue +++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue @@ -108,7 +108,7 @@ export default { }, }, methods: { - ...mapActions(['loadMoreLines', 'showCommentForm']), + ...mapActions('diffs', ['loadMoreLines', 'showCommentForm']), handleCommentButton() { this.showCommentForm({ lineCode: this.lineCode }); }, @@ -189,6 +189,7 @@ export default { </button> <a v-if="lineNumber" + v-once :data-linenumber="lineNumber" :href="lineHref" > diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue index 6943b462e86..db380e68bd1 100644 --- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue +++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue @@ -59,7 +59,8 @@ export default { } }, methods: { - ...mapActions(['cancelCommentForm', 'saveNote', 'fetchDiscussions']), + ...mapActions('diffs', ['cancelCommentForm']), + ...mapActions(['saveNote', 'refetchDiscussionById']), handleCancelCommentForm() { this.autosave.reset(); this.cancelCommentForm({ @@ -78,10 +79,10 @@ export default { }); this.saveNote(postData) - .then(() => { + .then(result => { const endpoint = this.getNotesDataByProp('discussionsPath'); - this.fetchDiscussions(endpoint) + this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id }) .then(() => { this.handleCancelCommentForm(); }) diff --git a/app/assets/javascripts/diffs/components/diff_table_cell.vue b/app/assets/javascripts/diffs/components/diff_table_cell.vue index 5b08b161114..bd02b45a63c 100644 --- a/app/assets/javascripts/diffs/components/diff_table_cell.vue +++ b/app/assets/javascripts/diffs/components/diff_table_cell.vue @@ -117,14 +117,6 @@ export default { <template> <td - v-if="isContentLine" - :class="lineType" - class="line_content" - v-html="normalizedLine.richText" - > - </td> - <td - v-else :class="classNameMap" > <diff-line-gutter-content diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue index 0e935f1d68e..1e8f2eecd76 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue @@ -31,22 +31,9 @@ export default { diffLineCommentForms: state => state.diffs.diffLineCommentForms, }), ...mapGetters(['discussionsByLineCode']), - isDiscussionExpanded() { - if (!this.discussions.length) { - return false; - } - - return this.discussions.every(discussion => discussion.expanded); - }, - hasCommentForm() { - return this.diffLineCommentForms[this.line.lineCode]; - }, discussions() { return this.discussionsByLineCode[this.line.lineCode] || []; }, - shouldRender() { - return this.isDiscussionExpanded || this.hasCommentForm; - }, className() { return this.discussions.length ? '' : 'js-temp-notes-holder'; }, @@ -56,7 +43,6 @@ export default { <template> <tr - v-if="shouldRender" :class="className" class="notes_holder" > @@ -67,6 +53,7 @@ export default { <td class="notes_content"> <div class="content"> <diff-discussions + v-if="discussions.length" :discussions="discussions" /> <diff-line-note-form diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue index a2470843ca6..8e4715c9862 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue @@ -36,7 +36,7 @@ export default { }; }, computed: { - ...mapGetters(['isInlineView']), + ...mapGetters('diffs', ['isInlineView']), isContextLine() { return this.line.type === CONTEXT_LINE_TYPE; }, @@ -94,11 +94,12 @@ export default { :is-hover="isHover" class="diff-line-num new_line" /> - <diff-table-cell + <td + v-once :class="line.type" - :diff-file="diffFile" - :line="line" - :is-content-line="true" - /> + class="line_content" + v-html="line.richText" + > + </td> </tr> </template> diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue index 83569346cf8..9c1359f7c89 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_view.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue @@ -1,5 +1,5 @@ <script> -import { mapGetters } from 'vuex'; +import { mapGetters, mapState } from 'vuex'; import inlineDiffTableRow from './inline_diff_table_row.vue'; import inlineDiffCommentRow from './inline_diff_comment_row.vue'; import { trimFirstCharOfLineContent } from '../store/utils'; @@ -20,7 +20,11 @@ export default { }, }, computed: { - ...mapGetters(['commitId']), + ...mapGetters('diffs', ['commitId']), + ...mapGetters(['discussionsByLineCode']), + ...mapState({ + diffLineCommentForms: state => state.diffs.diffLineCommentForms, + }), normalizedDiffLines() { return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line)); }, @@ -31,6 +35,18 @@ export default { return window.gon.user_color_scheme; }, }, + methods: { + shouldRenderCommentRow(line) { + if (this.diffLineCommentForms[line.lineCode]) return true; + + const lineDiscussions = this.discussionsByLineCode[line.lineCode]; + if (lineDiscussions === undefined) { + return false; + } + + return lineDiscussions.every(discussion => discussion.expanded); + }, + }, }; </script> @@ -50,6 +66,7 @@ export default { :key="line.lineCode" /> <inline-diff-comment-row + v-if="shouldRenderCommentRow(line)" :diff-file="diffFile" :diff-lines="normalizedDiffLines" :line="line" diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue index 5f33ec7a3c2..1e20792b647 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue @@ -55,13 +55,6 @@ export default { hasAnyExpandedDiscussion() { return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight; }, - shouldRenderDiscussionsRow() { - const hasDiscussion = this.hasDiscussion && this.hasAnyExpandedDiscussion; - const hasCommentFormOnLeft = this.diffLineCommentForms[this.leftLineCode]; - const hasCommentFormOnRight = this.diffLineCommentForms[this.rightLineCode]; - - return hasDiscussion || hasCommentFormOnLeft || hasCommentFormOnRight; - }, shouldRenderDiscussionsOnLeft() { return this.discussionsByLineCode[this.leftLineCode] && this.hasExpandedDiscussionOnLeft; }, @@ -81,7 +74,6 @@ export default { <template> <tr - v-if="shouldRenderDiscussionsRow" :class="className" class="notes_holder" > @@ -92,6 +84,7 @@ export default { class="content" > <diff-discussions + v-if="discussionsByLineCode[leftLineCode].length" :discussions="discussionsByLineCode[leftLineCode]" /> </div> @@ -112,6 +105,7 @@ export default { class="content" > <diff-discussions + v-if="discussionsByLineCode[rightLineCode].length" :discussions="discussionsByLineCode[rightLineCode]" /> </div> diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue index eb769584d74..b76fc63205b 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue @@ -40,7 +40,7 @@ export default { }; }, computed: { - ...mapGetters(['isParallelView']), + ...mapGetters('diffs', ['isParallelView']), isContextLine() { return this.line.left.type === CONTEXT_LINE_TYPE; }, @@ -113,17 +113,15 @@ export default { :diff-view-type="parallelDiffViewType" class="diff-line-num old_line" /> - <diff-table-cell + <td + v-once :id="line.left.lineCode" - :diff-file="diffFile" - :line="line" - :is-content-line="true" - :line-position="linePositionLeft" - :line-type="parallelViewLeftLineType" - :diff-view-type="parallelDiffViewType" + :class="parallelViewLeftLineType" class="line_content parallel left-side" @mousedown.native="handleParallelLineMouseDown" - /> + v-html="line.left.richText" + > + </td> <diff-table-cell :diff-file="diffFile" :line="line" @@ -135,16 +133,14 @@ export default { :diff-view-type="parallelDiffViewType" class="diff-line-num new_line" /> - <diff-table-cell + <td + v-once :id="line.right.lineCode" - :diff-file="diffFile" - :line="line" - :is-content-line="true" - :line-position="linePositionRight" - :line-type="line.right.type" - :diff-view-type="parallelDiffViewType" + :class="line.right.type" class="line_content parallel right-side" @mousedown.native="handleParallelLineMouseDown" - /> + v-html="line.right.richText" + > + </td> </tr> </template> diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue index 89148eb5e18..216865474a6 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue @@ -1,5 +1,5 @@ <script> -import { mapGetters } from 'vuex'; +import { mapState, mapGetters } from 'vuex'; import parallelDiffTableRow from './parallel_diff_table_row.vue'; import parallelDiffCommentRow from './parallel_diff_comment_row.vue'; import { EMPTY_CELL_TYPE } from '../constants'; @@ -21,7 +21,11 @@ export default { }, }, computed: { - ...mapGetters(['commitId']), + ...mapGetters('diffs', ['commitId']), + ...mapGetters(['discussionsByLineCode']), + ...mapState({ + diffLineCommentForms: state => state.diffs.diffLineCommentForms, + }), parallelDiffLines() { return this.diffLines.map(line => { const parallelLine = Object.assign({}, line); @@ -48,6 +52,32 @@ export default { return window.gon.user_color_scheme; }, }, + methods: { + shouldRenderCommentRow(line) { + const leftLineCode = line.left.lineCode; + const rightLineCode = line.right.lineCode; + const discussions = this.discussionsByLineCode; + const leftDiscussions = discussions[leftLineCode]; + const rightDiscussions = discussions[rightLineCode]; + const hasDiscussion = leftDiscussions || rightDiscussions; + + const hasExpandedDiscussionOnLeft = leftDiscussions + ? leftDiscussions.every(discussion => discussion.expanded) + : false; + const hasExpandedDiscussionOnRight = rightDiscussions + ? rightDiscussions.every(discussion => discussion.expanded) + : false; + + if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) { + return true; + } + + const hasCommentFormOnLeft = this.diffLineCommentForms[leftLineCode]; + const hasCommentFormOnRight = this.diffLineCommentForms[rightLineCode]; + + return hasCommentFormOnLeft || hasCommentFormOnRight; + }, + }, }; </script> @@ -69,6 +99,7 @@ export default { :key="index" /> <parallel-diff-comment-row + v-if="shouldRenderCommentRow(line)" :key="line.left.lineCode || line.right.lineCode" :line="line" :diff-file="diffFile" diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 5e0fd5109bb..27001142257 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -82,14 +82,32 @@ export const expandAllFiles = ({ commit }) => { commit(types.EXPAND_ALL_FILES); }; -export default { - setBaseConfig, - fetchDiffFiles, - setInlineDiffViewType, - setParallelDiffViewType, - showCommentForm, - cancelCommentForm, - loadMoreLines, - loadCollapsedDiff, - expandAllFiles, +/** + * Toggles the file discussions after user clicked on the toggle discussions button. + * + * Gets the discussions for the provided diff. + * + * If all discussions are expanded, it will collapse all of them + * If all discussions are collapsed, it will expand all of them + * If some discussions are open and others closed, it will expand the closed ones. + * + * @param {Object} diff + */ +export const toggleFileDiscussions = ({ getters, dispatch }, diff) => { + const discussions = getters.getDiffFileDiscussions(diff); + const shouldCloseAll = getters.diffHasAllExpandedDiscussions(diff); + const shouldExpandAll = getters.diffHasAllCollpasedDiscussions(diff); + + discussions.forEach(discussion => { + const data = { discussionId: discussion.id }; + + if (shouldCloseAll) { + dispatch('collapseDiscussion', data, { root: true }); + } else if (shouldExpandAll || (!shouldCloseAll && !shouldExpandAll && !discussion.expanded)) { + dispatch('expandDiscussion', data, { root: true }); + } + }); }; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index f3c2d7427e7..f89acb73ed8 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '../constants'; export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE; @@ -8,5 +9,52 @@ export const areAllFilesCollapsed = state => state.diffFiles.every(file => file. export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null); -// prevent babel-plugin-rewire from generating an invalid default during karma tests +/** + * Checks if the diff has all discussions expanded + * @param {Object} diff + * @returns {Boolean} + */ +export const diffHasAllExpandedDiscussions = (state, getters) => diff => { + const discussions = getters.getDiffFileDiscussions(diff); + + return (discussions.length && discussions.every(discussion => discussion.expanded)) || false; +}; + +/** + * Checks if the diff has all discussions collpased + * @param {Object} diff + * @returns {Boolean} + */ +export const diffHasAllCollpasedDiscussions = (state, getters) => diff => { + const discussions = getters.getDiffFileDiscussions(diff); + + return (discussions.length && discussions.every(discussion => !discussion.expanded)) || false; +}; + +/** + * Checks if the diff has any open discussions + * @param {Object} diff + * @returns {Boolean} + */ +export const diffHasExpandedDiscussions = (state, getters) => diff => { + const discussions = getters.getDiffFileDiscussions(diff); + + return ( + (discussions.length && discussions.find(discussion => discussion.expanded) !== undefined) || + false + ); +}; + +/** + * Returns an array with the discussions of the given diff + * @param {Object} diff + * @returns {Array} + */ +export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) => diff => + rootGetters.discussions.filter( + discussion => + discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash), + ) || []; + +// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests export default () => {}; diff --git a/app/assets/javascripts/diffs/store/index.js b/app/assets/javascripts/diffs/store/index.js deleted file mode 100644 index e6aa8f5b12a..00000000000 --- a/app/assets/javascripts/diffs/store/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import diffsModule from './modules'; - -Vue.use(Vuex); - -export default new Vuex.Store({ - modules: { - diffs: diffsModule, - }, -}); diff --git a/app/assets/javascripts/diffs/store/modules/index.js b/app/assets/javascripts/diffs/store/modules/index.js index c745320d532..20d1ebbe049 100644 --- a/app/assets/javascripts/diffs/store/modules/index.js +++ b/app/assets/javascripts/diffs/store/modules/index.js @@ -1,9 +1,10 @@ -import actions from '../actions'; +import * as actions from '../actions'; import * as getters from '../getters'; import mutations from '../mutations'; import createState from './diff_state'; export default { + namespaced: true, state: createState(), getters, actions, diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 17ea3bdb179..8abd8bc581a 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -171,6 +171,8 @@ export default class DueDateSelectors { initMilestoneDatePicker() { $('.datepicker').each(function initPikadayMilestone() { const $datePicker = $(this); + const datePickerVal = $datePicker.val(); + const calendar = new Pikaday({ field: $datePicker.get(0), theme: 'gitlab-theme animate-picker', @@ -183,7 +185,7 @@ export default class DueDateSelectors { }, }); - calendar.setDate(parsePikadayDate($datePicker.val())); + calendar.setDate(parsePikadayDate(datePickerVal)); $datePicker.data('pikaday', calendar); }); diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index e3652fe739e..63d83e307ee 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -1,50 +1,50 @@ <script> - import Icon from '~/vue_shared/components/icon.vue'; - import eventHub from '../event_hub'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; +import Icon from '~/vue_shared/components/icon.vue'; +import eventHub from '../event_hub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import tooltip from '../../vue_shared/directives/tooltip'; - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + components: { + loadingIcon, + Icon, + }, + props: { + actions: { + type: Array, + required: false, + default: () => [], }, - components: { - loadingIcon, - Icon, + }, + data() { + return { + isLoading: false, + }; + }, + computed: { + title() { + return 'Deploy to...'; }, - props: { - actions: { - type: Array, - required: false, - default: () => [], - }, - }, - data() { - return { - isLoading: false, - }; - }, - computed: { - title() { - return 'Deploy to...'; - }, - }, - methods: { - onClickAction(endpoint) { - this.isLoading = true; + }, + methods: { + onClickAction(endpoint) { + this.isLoading = true; - eventHub.$emit('postAction', endpoint); - }, + eventHub.$emit('postAction', { endpoint }); + }, - isActionDisabled(action) { - if (action.playable === undefined) { - return false; - } + isActionDisabled(action) { + if (action.playable === undefined) { + return false; + } - return !action.playable; - }, + return !action.playable; }, - }; + }, +}; </script> <template> <div @@ -61,10 +61,7 @@ data-toggle="dropdown" > <span> - <icon - :size="12" - name="play" - /> + <icon name="play" /> <i class="fa fa-caret-down" aria-hidden="true" @@ -85,10 +82,6 @@ class="js-manual-action-link no-btn btn" @click="onClickAction(action.play_path)" > - <icon - :size="12" - name="play" - /> <span> {{ action.name }} </span> diff --git a/app/assets/javascripts/environments/components/environment_external_url.vue b/app/assets/javascripts/environments/components/environment_external_url.vue index 68195225d50..7446196de13 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.vue +++ b/app/assets/javascripts/environments/components/environment_external_url.vue @@ -1,30 +1,30 @@ <script> - import Icon from '~/vue_shared/components/icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; - import { s__ } from '../../locale'; +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '../../vue_shared/directives/tooltip'; +import { s__ } from '../../locale'; - /** - * Renders the external url link in environments table. - */ - export default { - components: { - Icon, +/** + * Renders the external url link in environments table. + */ +export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + externalUrl: { + type: String, + required: true, }, - directives: { - tooltip, + }, + computed: { + title() { + return s__('Environments|Open live environment'); }, - props: { - externalUrl: { - type: String, - required: true, - }, - }, - computed: { - title() { - return s__('Environments|Open'); - }, - }, - }; + }, +}; </script> <template> <a @@ -37,9 +37,6 @@ target="_blank" rel="noopener noreferrer nofollow" > - <icon - :size="12" - name="external-link" - /> + <icon name="external-link" /> </a> </template> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 5ecdccf63ad..39f3790a286 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -1,429 +1,450 @@ <script> - import Timeago from 'timeago.js'; - import _ from 'underscore'; - import tooltip from '~/vue_shared/directives/tooltip'; - import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; - import { humanize } from '~/lib/utils/text_utility'; - import ActionsComponent from './environment_actions.vue'; - import ExternalUrlComponent from './environment_external_url.vue'; - import StopComponent from './environment_stop.vue'; - import RollbackComponent from './environment_rollback.vue'; - import TerminalButtonComponent from './environment_terminal_button.vue'; - import MonitoringButtonComponent from './environment_monitoring.vue'; - import CommitComponent from '../../vue_shared/components/commit.vue'; - import eventHub from '../event_hub'; - - /** - * Envrionment Item Component - * - * Renders a table row for each environment. - */ - const timeagoInstance = new Timeago(); - - export default { - components: { - UserAvatarLink, - CommitComponent, - ActionsComponent, - ExternalUrlComponent, - StopComponent, - RollbackComponent, - TerminalButtonComponent, - MonitoringButtonComponent, +import Timeago from 'timeago.js'; +import _ from 'underscore'; +import tooltip from '~/vue_shared/directives/tooltip'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import { humanize } from '~/lib/utils/text_utility'; +import ActionsComponent from './environment_actions.vue'; +import ExternalUrlComponent from './environment_external_url.vue'; +import StopComponent from './environment_stop.vue'; +import RollbackComponent from './environment_rollback.vue'; +import TerminalButtonComponent from './environment_terminal_button.vue'; +import MonitoringButtonComponent from './environment_monitoring.vue'; +import CommitComponent from '../../vue_shared/components/commit.vue'; +import eventHub from '../event_hub'; + +/** + * Envrionment Item Component + * + * Renders a table row for each environment. + */ +const timeagoInstance = new Timeago(); + +export default { + components: { + UserAvatarLink, + CommitComponent, + ActionsComponent, + ExternalUrlComponent, + StopComponent, + RollbackComponent, + TerminalButtonComponent, + MonitoringButtonComponent, + }, + + directives: { + tooltip, + }, + + props: { + model: { + type: Object, + required: true, + default: () => ({}), }, - directives: { - tooltip, + canCreateDeployment: { + type: Boolean, + required: false, + default: false, }, - props: { - model: { - type: Object, - required: true, - default: () => ({}), - }, - - canCreateDeployment: { - type: Boolean, - required: false, - default: false, - }, - - canReadEnvironment: { - type: Boolean, - required: false, - default: false, - }, + canReadEnvironment: { + type: Boolean, + required: false, + default: false, + }, + }, + + computed: { + /** + * Verifies if `last_deployment` key exists in the current Envrionment. + * This key is required to render most of the html - this method works has + * an helper. + * + * @returns {Boolean} + */ + hasLastDeploymentKey() { + if (this.model && this.model.last_deployment && !_.isEmpty(this.model.last_deployment)) { + return true; + } + return false; + }, + + /** + * Verifies is the given environment has manual actions. + * Used to verify if we should render them or nor. + * + * @returns {Boolean|Undefined} + */ + hasManualActions() { + return ( + this.model && + this.model.last_deployment && + this.model.last_deployment.manual_actions && + this.model.last_deployment.manual_actions.length > 0 + ); + }, + + /** + * Returns whether the environment can be stopped. + * + * @returns {Boolean} + */ + canStopEnvironment() { + return this.model && this.model.can_stop; + }, + + /** + * Verifies if the `deployable` key is present in `last_deployment` key. + * Used to verify whether we should or not render the rollback partial. + * + * @returns {Boolean|Undefined} + */ + canRetry() { + return ( + this.model && + this.hasLastDeploymentKey && + this.model.last_deployment && + this.model.last_deployment.deployable + ); + }, + + /** + * Verifies if the date to be shown is present. + * + * @returns {Boolean|Undefined} + */ + canShowDate() { + return ( + this.model && + this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable !== undefined + ); + }, + + /** + * Human readable date. + * + * @returns {String} + */ + createdDate() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable.created_at + ) { + return timeagoInstance.format(this.model.last_deployment.deployable.created_at); + } + return ''; + }, + + /** + * Returns the manual actions with the name parsed. + * + * @returns {Array.<Object>|Undefined} + */ + manualActions() { + if (this.hasManualActions) { + return this.model.last_deployment.manual_actions.map(action => { + const parsedAction = { + name: humanize(action.name), + play_path: action.play_path, + playable: action.playable, + }; + return parsedAction; + }); + } + return []; + }, + + /** + * Builds the string used in the user image alt attribute. + * + * @returns {String} + */ + userImageAltDescription() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.user && + this.model.last_deployment.user.username + ) { + return `${this.model.last_deployment.user.username}'s avatar'`; + } + return ''; + }, + + /** + * If provided, returns the commit tag. + * + * @returns {String|Undefined} + */ + commitTag() { + if (this.model && this.model.last_deployment && this.model.last_deployment.tag) { + return this.model.last_deployment.tag; + } + return undefined; + }, + + /** + * If provided, returns the commit ref. + * + * @returns {Object|Undefined} + */ + commitRef() { + if (this.model && this.model.last_deployment && this.model.last_deployment.ref) { + return this.model.last_deployment.ref; + } + return undefined; + }, + + /** + * If provided, returns the commit url. + * + * @returns {String|Undefined} + */ + commitUrl() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.commit && + this.model.last_deployment.commit.commit_path + ) { + return this.model.last_deployment.commit.commit_path; + } + return undefined; + }, + + /** + * If provided, returns the commit short sha. + * + * @returns {String|Undefined} + */ + commitShortSha() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.commit && + this.model.last_deployment.commit.short_id + ) { + return this.model.last_deployment.commit.short_id; + } + return undefined; + }, + + /** + * If provided, returns the commit title. + * + * @returns {String|Undefined} + */ + commitTitle() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.commit && + this.model.last_deployment.commit.title + ) { + return this.model.last_deployment.commit.title; + } + return undefined; + }, + + /** + * If provided, returns the commit tag. + * + * @returns {Object|Undefined} + */ + commitAuthor() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.commit && + this.model.last_deployment.commit.author + ) { + return this.model.last_deployment.commit.author; + } + + return undefined; + }, + + /** + * Verifies if the `retry_path` key is present and returns its value. + * + * @returns {String|Undefined} + */ + retryUrl() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable.retry_path + ) { + return this.model.last_deployment.deployable.retry_path; + } + return undefined; + }, + + /** + * Verifies if the `last?` key is present and returns its value. + * + * @returns {Boolean|Undefined} + */ + isLastDeployment() { + return this.model && this.model.last_deployment && this.model.last_deployment['last?']; + }, + + /** + * Builds the name of the builds needed to display both the name and the id. + * + * @returns {String} + */ + buildName() { + if (this.model && this.model.last_deployment && this.model.last_deployment.deployable) { + const { deployable } = this.model.last_deployment; + return `${deployable.name} #${deployable.id}`; + } + return ''; + }, + + /** + * Builds the needed string to show the internal id. + * + * @returns {String} + */ + deploymentInternalId() { + if (this.model && this.model.last_deployment && this.model.last_deployment.iid) { + return `#${this.model.last_deployment.iid}`; + } + return ''; }, - computed: { - /** - * Verifies if `last_deployment` key exists in the current Envrionment. - * This key is required to render most of the html - this method works has - * an helper. - * - * @returns {Boolean} - */ - hasLastDeploymentKey() { - if (this.model && - this.model.last_deployment && - !_.isEmpty(this.model.last_deployment)) { - return true; - } - return false; - }, - - /** - * Verifies is the given environment has manual actions. - * Used to verify if we should render them or nor. - * - * @returns {Boolean|Undefined} - */ - hasManualActions() { - return this.model && - this.model.last_deployment && - this.model.last_deployment.manual_actions && - this.model.last_deployment.manual_actions.length > 0; - }, - - /** - * Returns the value of the `stop_action?` key provided in the response. - * - * @returns {Boolean} - */ - hasStopAction() { - return this.model && this.model['stop_action?']; - }, - - /** - * Verifies if the `deployable` key is present in `last_deployment` key. - * Used to verify whether we should or not render the rollback partial. - * - * @returns {Boolean|Undefined} - */ - canRetry() { - return this.model && - this.hasLastDeploymentKey && - this.model.last_deployment && - this.model.last_deployment.deployable; - }, - - /** - * Verifies if the date to be shown is present. - * - * @returns {Boolean|Undefined} - */ - canShowDate() { - return this.model && - this.model.last_deployment && - this.model.last_deployment.deployable && - this.model.last_deployment.deployable !== undefined; - }, - - /** - * Human readable date. - * - * @returns {String} - */ - createdDate() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.deployable && - this.model.last_deployment.deployable.created_at) { - return timeagoInstance.format(this.model.last_deployment.deployable.created_at); - } - return ''; - }, - - /** - * Returns the manual actions with the name parsed. - * - * @returns {Array.<Object>|Undefined} - */ - manualActions() { - if (this.hasManualActions) { - return this.model.last_deployment.manual_actions.map((action) => { - const parsedAction = { - name: humanize(action.name), - play_path: action.play_path, - playable: action.playable, - }; - return parsedAction; - }); - } - return []; - }, - - /** - * Builds the string used in the user image alt attribute. - * - * @returns {String} - */ - userImageAltDescription() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.user && - this.model.last_deployment.user.username) { - return `${this.model.last_deployment.user.username}'s avatar'`; - } - return ''; - }, - - /** - * If provided, returns the commit tag. - * - * @returns {String|Undefined} - */ - commitTag() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.tag) { - return this.model.last_deployment.tag; - } - return undefined; - }, - - /** - * If provided, returns the commit ref. - * - * @returns {Object|Undefined} - */ - commitRef() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.ref) { - return this.model.last_deployment.ref; - } - return undefined; - }, - - /** - * If provided, returns the commit url. - * - * @returns {String|Undefined} - */ - commitUrl() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.commit_path) { - return this.model.last_deployment.commit.commit_path; - } - return undefined; - }, - - /** - * If provided, returns the commit short sha. - * - * @returns {String|Undefined} - */ - commitShortSha() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.short_id) { - return this.model.last_deployment.commit.short_id; - } - return undefined; - }, - - /** - * If provided, returns the commit title. - * - * @returns {String|Undefined} - */ - commitTitle() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.title) { - return this.model.last_deployment.commit.title; - } - return undefined; - }, - - /** - * If provided, returns the commit tag. - * - * @returns {Object|Undefined} - */ - commitAuthor() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.commit && - this.model.last_deployment.commit.author) { - return this.model.last_deployment.commit.author; - } - - return undefined; - }, - - /** - * Verifies if the `retry_path` key is present and returns its value. - * - * @returns {String|Undefined} - */ - retryUrl() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.deployable && - this.model.last_deployment.deployable.retry_path) { - return this.model.last_deployment.deployable.retry_path; - } - return undefined; - }, - - /** - * Verifies if the `last?` key is present and returns its value. - * - * @returns {Boolean|Undefined} - */ - isLastDeployment() { - return this.model && this.model.last_deployment && - this.model.last_deployment['last?']; - }, - - /** - * Builds the name of the builds needed to display both the name and the id. - * - * @returns {String} - */ - buildName() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.deployable) { - const { deployable } = this.model.last_deployment; - return `${deployable.name} #${deployable.id}`; - } - return ''; - }, - - /** - * Builds the needed string to show the internal id. - * - * @returns {String} - */ - deploymentInternalId() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.iid) { - return `#${this.model.last_deployment.iid}`; - } - return ''; - }, - - /** - * Verifies if the user object is present under last_deployment object. - * - * @returns {Boolean} - */ - deploymentHasUser() { - return this.model && - !_.isEmpty(this.model.last_deployment) && - !_.isEmpty(this.model.last_deployment.user); - }, - - /** - * Returns the user object nested with the last_deployment object. - * Used to render the template. - * - * @returns {Object} - */ - deploymentUser() { - if (this.model && - !_.isEmpty(this.model.last_deployment) && - !_.isEmpty(this.model.last_deployment.user)) { - return this.model.last_deployment.user; - } - return {}; - }, - - /** - * Verifies if the build name column should be rendered by verifing - * if all the information needed is present - * and if the environment is not a folder. - * - * @returns {Boolean} - */ - shouldRenderBuildName() { - return !this.model.isFolder && - !_.isEmpty(this.model.last_deployment) && - !_.isEmpty(this.model.last_deployment.deployable); - }, - - /** - * Verifies the presence of all the keys needed to render the buil_path. - * - * @return {String} - */ - buildPath() { - if (this.model && - this.model.last_deployment && - this.model.last_deployment.deployable && - this.model.last_deployment.deployable.build_path) { - return this.model.last_deployment.deployable.build_path; - } - - return ''; - }, - - /** - * Verifies the presence of all the keys needed to render the external_url. - * - * @return {String} - */ - externalURL() { - if (this.model && this.model.external_url) { - return this.model.external_url; - } - - return ''; - }, - - /** - * Verifies if deplyment internal ID should be rendered by verifing - * if all the information needed is present - * and if the environment is not a folder. - * - * @returns {Boolean} - */ - shouldRenderDeploymentID() { - return !this.model.isFolder && - !_.isEmpty(this.model.last_deployment) && - this.model.last_deployment.iid !== undefined; - }, - - environmentPath() { - if (this.model && this.model.environment_path) { - return this.model.environment_path; - } - - return ''; - }, - - monitoringUrl() { - if (this.model && this.model.metrics_path) { - return this.model.metrics_path; - } - - return ''; - }, - - displayEnvironmentActions() { - return this.hasManualActions || - this.externalURL || - this.monitoringUrl || - this.hasStopAction || - this.canRetry; - }, + /** + * Verifies if the user object is present under last_deployment object. + * + * @returns {Boolean} + */ + deploymentHasUser() { + return ( + this.model && + !_.isEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment.user) + ); }, - methods: { - onClickFolder() { - eventHub.$emit('toggleFolder', this.model); - }, + /** + * Returns the user object nested with the last_deployment object. + * Used to render the template. + * + * @returns {Object} + */ + deploymentUser() { + if ( + this.model && + !_.isEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment.user) + ) { + return this.model.last_deployment.user; + } + return {}; }, - }; + + /** + * Verifies if the build name column should be rendered by verifing + * if all the information needed is present + * and if the environment is not a folder. + * + * @returns {Boolean} + */ + shouldRenderBuildName() { + return ( + !this.model.isFolder && + !_.isEmpty(this.model.last_deployment) && + !_.isEmpty(this.model.last_deployment.deployable) + ); + }, + + /** + * Verifies the presence of all the keys needed to render the buil_path. + * + * @return {String} + */ + buildPath() { + if ( + this.model && + this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable.build_path + ) { + return this.model.last_deployment.deployable.build_path; + } + + return ''; + }, + + /** + * Verifies the presence of all the keys needed to render the external_url. + * + * @return {String} + */ + externalURL() { + if (this.model && this.model.external_url) { + return this.model.external_url; + } + + return ''; + }, + + /** + * Verifies if deplyment internal ID should be rendered by verifing + * if all the information needed is present + * and if the environment is not a folder. + * + * @returns {Boolean} + */ + shouldRenderDeploymentID() { + return ( + !this.model.isFolder && + !_.isEmpty(this.model.last_deployment) && + this.model.last_deployment.iid !== undefined + ); + }, + + environmentPath() { + if (this.model && this.model.environment_path) { + return this.model.environment_path; + } + + return ''; + }, + + monitoringUrl() { + if (this.model && this.model.metrics_path) { + return this.model.metrics_path; + } + + return ''; + }, + + displayEnvironmentActions() { + return ( + this.hasManualActions || + this.externalURL || + this.monitoringUrl || + this.canStopEnvironment || + this.canRetry + ); + }, + }, + + methods: { + onClickFolder() { + eventHub.$emit('toggleFolder', this.model); + }, + }, +}; </script> <template> <div @@ -580,11 +601,6 @@ class="btn-group table-action-buttons" role="group"> - <actions-component - v-if="hasManualActions && canCreateDeployment" - :actions="manualActions" - /> - <external-url-component v-if="externalURL && canReadEnvironment" :external-url="externalURL" @@ -595,21 +611,26 @@ :monitoring-url="monitoringUrl" /> + <actions-component + v-if="hasManualActions && canCreateDeployment" + :actions="manualActions" + /> + <terminal-button-component v-if="model && model.terminal_path" :terminal-path="model.terminal_path" /> - <stop-component - v-if="hasStopAction && canCreateDeployment" - :stop-url="model.stop_path" - /> - <rollback-component v-if="canRetry && canCreateDeployment" :is-last-deployment="isLastDeployment" :retry-url="retryUrl" /> + + <stop-component + v-if="canStopEnvironment" + :environment="model" + /> </div> </div> </div> diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue index 947e8c901e9..ccc8419ca6d 100644 --- a/app/assets/javascripts/environments/components/environment_monitoring.vue +++ b/app/assets/javascripts/environments/components/environment_monitoring.vue @@ -1,29 +1,29 @@ <script> - /** - * Renders the Monitoring (Metrics) link in environments table. - */ - import Icon from '~/vue_shared/components/icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; +/** + * Renders the Monitoring (Metrics) link in environments table. + */ +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '../../vue_shared/directives/tooltip'; - export default { - components: { - Icon, +export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + monitoringUrl: { + type: String, + required: true, }, - directives: { - tooltip, + }, + computed: { + title() { + return 'Monitoring'; }, - props: { - monitoringUrl: { - type: String, - required: true, - }, - }, - computed: { - title() { - return 'Monitoring'; - }, - }, - }; + }, +}; </script> <template> <a @@ -35,9 +35,6 @@ data-container="body" rel="noopener noreferrer nofollow" > - <icon - :size="12" - name="chart" - /> + <icon name="chart" /> </a> </template> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 310835c5ea9..4deeef4beb9 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -1,56 +1,74 @@ <script> - /** - * Renders Rollback or Re deploy button in environments table depending - * of the provided property `isLastDeployment`. - * - * Makes a post request when the button is clicked. - */ - import eventHub from '../event_hub'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - - export default { - components: { - loadingIcon, +/** + * Renders Rollback or Re deploy button in environments table depending + * of the provided property `isLastDeployment`. + * + * Makes a post request when the button is clicked. + */ +import { s__ } from '~/locale'; +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import eventHub from '../event_hub'; +import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; + +export default { + components: { + Icon, + LoadingIcon, + }, + + directives: { + tooltip, + }, + + props: { + retryUrl: { + type: String, + default: '', }, - props: { - retryUrl: { - type: String, - default: '', - }, - - isLastDeployment: { - type: Boolean, - default: true, - }, + + isLastDeployment: { + type: Boolean, + default: true, }, - data() { - return { - isLoading: false, - }; + }, + data() { + return { + isLoading: false, + }; + }, + + computed: { + title() { + return this.isLastDeployment ? s__('Environments|Re-deploy to environment') : s__('Environments|Rollback environment'); }, - methods: { - onClick() { - this.isLoading = true; + }, + + methods: { + onClick() { + this.isLoading = true; - eventHub.$emit('postAction', this.retryUrl); - }, + eventHub.$emit('postAction', { endpoint: this.retryUrl }); }, - }; + }, +}; </script> <template> <button + v-tooltip :disabled="isLoading" + :title="title" type="button" class="btn d-none d-sm-none d-md-block" @click="onClick" > - <span v-if="isLastDeployment"> - {{ s__("Environments|Re-deploy") }} - </span> - <span v-else> - {{ s__("Environments|Rollback") }} - </span> + <icon + v-if="isLastDeployment" + name="repeat" /> + <icon + v-else + name="redo"/> <loading-icon v-if="isLoading" /> </button> diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue index eba58bedd6d..a814b3405f5 100644 --- a/app/assets/javascripts/environments/components/environment_stop.vue +++ b/app/assets/javascripts/environments/components/environment_stop.vue @@ -1,72 +1,78 @@ <script> - /** - * Renders the stop "button" that allows stop an environment. - * Used in environments table. - */ +/** + * Renders the stop "button" that allows stop an environment. + * Used in environments table. + */ - import $ from 'jquery'; - import eventHub from '../event_hub'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +import { s__ } from '~/locale'; +import eventHub from '../event_hub'; +import LoadingButton from '../../vue_shared/components/loading_button.vue'; +import tooltip from '../../vue_shared/directives/tooltip'; - export default { - components: { - loadingIcon, - }, +export default { + components: { + Icon, + LoadingButton, + }, - directives: { - tooltip, - }, + directives: { + tooltip, + }, - props: { - stopUrl: { - type: String, - default: '', - }, + props: { + environment: { + type: Object, + required: true, }, + }, - data() { - return { - isLoading: false, - }; - }, + data() { + return { + isLoading: false, + }; + }, - computed: { - title() { - return 'Stop'; - }, + computed: { + title() { + return s__('Environments|Stop environment'); }, + }, - methods: { - onClick() { - // eslint-disable-next-line no-alert - if (window.confirm('Are you sure you want to stop this environment?')) { - this.isLoading = true; + mounted() { + eventHub.$on('stopEnvironment', this.onStopEnvironment); + }, - $(this.$el).tooltip('dispose'); + beforeDestroy() { + eventHub.$off('stopEnvironment', this.onStopEnvironment); + }, - eventHub.$emit('postAction', this.stopUrl); - } - }, + methods: { + onClick() { + $(this.$el).tooltip('dispose'); + eventHub.$emit('requestStopEnvironment', this.environment); + }, + onStopEnvironment(environment) { + if (this.environment.id === environment.id) { + this.isLoading = true; + } }, - }; + }, +}; </script> <template> - <button + <loading-button v-tooltip - :disabled="isLoading" + :loading="isLoading" :title="title" :aria-label="title" - type="button" - class="btn stop-env-link d-none d-sm-none d-md-block" + container-class="btn btn-danger d-none d-sm-none d-md-block" data-container="body" + data-toggle="modal" + data-target="#stop-environment-modal" @click="onClick" > - <i - class="fa fa-stop stop-env-icon" - aria-hidden="true" - > - </i> - <loading-icon v-if="isLoading" /> - </button> + <icon name="stop"/> + </loading-button> </template> diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue index f8e3165f8cd..350417e5ad0 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.vue +++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue @@ -1,31 +1,31 @@ <script> - /** - * Renders a terminal button to open a web terminal. - * Used in environments table. - */ - import Icon from '~/vue_shared/components/icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; +/** + * Renders a terminal button to open a web terminal. + * Used in environments table. + */ +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '../../vue_shared/directives/tooltip'; - export default { - components: { - Icon, +export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + terminalPath: { + type: String, + required: false, + default: '', }, - directives: { - tooltip, + }, + computed: { + title() { + return 'Terminal'; }, - props: { - terminalPath: { - type: String, - required: false, - default: '', - }, - }, - computed: { - title() { - return 'Terminal'; - }, - }, - }; + }, +}; </script> <template> <a @@ -36,9 +36,6 @@ class="btn terminal-button d-none d-sm-none d-md-block" data-container="body" > - <icon - :size="12" - name="terminal" - /> + <icon name="terminal" /> </a> </template> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index b18f02343d6..8efdfb8abe0 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -5,10 +5,12 @@ import eventHub from '../event_hub'; import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; + import StopEnvironmentModal from './stop_environment_modal.vue'; export default { components: { emptyState, + StopEnvironmentModal, }, mixins: [ @@ -90,6 +92,8 @@ </script> <template> <div :class="cssContainerClass"> + <stop-environment-modal :environment="environmentInStopModal" /> + <div class="top-area"> <tabs :tabs="tabs" diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue new file mode 100644 index 00000000000..657cc8cd1aa --- /dev/null +++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue @@ -0,0 +1,92 @@ +<script> +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import { s__, sprintf } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import LoadingButton from '~/vue_shared/components/loading_button.vue'; +import eventHub from '../event_hub'; + +export default { + id: 'stop-environment-modal', + name: 'StopEnvironmentModal', + + components: { + GlModal, + LoadingButton, + }, + + directives: { + tooltip, + }, + + props: { + environment: { + type: Object, + required: true, + }, + }, + + computed: { + noStopActionMessage() { + return sprintf( + s__( + `Environments|Note that this action will stop the environment, + but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment + due to no “stop environment action” being defined + in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file.`, + ), + { + emphasisStart: '<strong>', + emphasisEnd: '</strong>', + ciConfigLinkStart: + '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">', + ciConfigLinkEnd: '</a>', + }, + false, + ); + }, + }, + + methods: { + onSubmit() { + eventHub.$emit('stopEnvironment', this.environment); + }, + }, +}; +</script> + +<template> + <gl-modal + :id="$options.id" + :footer-primary-button-text="s__('Environments|Stop environment')" + footer-primary-button-variant="danger" + @submit="onSubmit" + > + <template slot="header"> + <h4 + class="modal-title d-flex mw-100" + > + Stopping + <span + v-tooltip + :title="environment.name" + class="text-truncate ml-1 mr-1 flex-fill" + >{{ environment.name }}</span> + ? + </h4> + </template> + + <p>{{ s__('Environments|Are you sure you want to stop this environment?') }}</p> + + <div + v-if="!environment.has_stop_action" + class="warning_message" + > + <p v-html="noStopActionMessage"></p> + <a + href="https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment" + target="_blank" + rel="noopener noreferrer" + >{{ s__('Environments|Learn more about stopping environments') }}</a> + </div> + </gl-modal> +</template> diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 5f72a39c5cb..e69bfa0b2cc 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,12 +1,18 @@ <script> import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; + import StopEnvironmentModal from '../components/stop_environment_modal.vue'; export default { + components: { + StopEnvironmentModal, + }, + mixins: [ environmentsMixin, CIPaginationMixin, ], + props: { endpoint: { type: String, @@ -38,6 +44,8 @@ </script> <template> <div :class="cssContainerClass"> + <stop-environment-modal :environment="environmentInStopModal" /> + <div v-if="!isLoading" class="top-area" diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index a7a79dbca70..d88624f7f8d 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -40,6 +40,7 @@ export default { scope: getParameterByName('scope') || 'available', page: getParameterByName('page') || '1', requestData: {}, + environmentInStopModal: {}, }; }, @@ -85,7 +86,7 @@ export default { Flash(s__('Environments|An error occurred while fetching the environments.')); }, - postAction(endpoint) { + postAction({ endpoint, errorMessage }) { if (!this.isMakingRequest) { this.isLoading = true; @@ -93,7 +94,7 @@ export default { .then(() => this.fetchEnvironments()) .catch(() => { this.isLoading = false; - Flash(s__('Environments|An error occurred while making the request.')); + Flash(errorMessage || s__('Environments|An error occurred while making the request.')); }); } }, @@ -106,6 +107,15 @@ export default { .catch(this.errorCallback); }, + updateStopModal(environment) { + this.environmentInStopModal = environment; + }, + + stopEnvironment(environment) { + const endpoint = environment.stop_path; + const errorMessage = s__('Environments|An error occurred while stopping the environment, please try again'); + this.postAction({ endpoint, errorMessage }); + }, }, computed: { @@ -162,9 +172,13 @@ export default { }); eventHub.$on('postAction', this.postAction); + eventHub.$on('requestStopEnvironment', this.updateStopModal); + eventHub.$on('stopEnvironment', this.stopEnvironment); }, - beforeDestroyed() { - eventHub.$off('postAction'); + beforeDestroy() { + eventHub.$off('postAction', this.postAction); + eventHub.$off('requestStopEnvironment', this.updateStopModal); + eventHub.$off('stopEnvironment', this.stopEnvironment); }, }; diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index 3b121551aca..4e07ccba91a 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -13,7 +13,7 @@ export default class EnvironmentsService { // eslint-disable-next-line class-methods-use-this postAction(endpoint) { - return axios.post(endpoint, {}, { emulateJSON: true }); + return axios.post(endpoint, {}); } getFolderContent(folderUrl) { diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 9f016e0338f..257a7432c20 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -1,6 +1,7 @@ <script> import Mousetrap from 'mousetrap'; import { mapActions, mapState, mapGetters } from 'vuex'; +import NewModal from './new_dropdown/modal.vue'; import IdeSidebar from './ide_side_bar.vue'; import RepoTabs from './repo_tabs.vue'; import IdeStatusBar from './ide_status_bar.vue'; @@ -13,6 +14,7 @@ const originalStopCallback = Mousetrap.stopCallback; export default { components: { + NewModal, IdeSidebar, RepoTabs, IdeStatusBar, @@ -137,5 +139,6 @@ export default { /> </div> <ide-status-bar :file="activeFile"/> + <new-modal /> </article> </template> diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 0582ad32e92..715dc1bfb42 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -5,6 +5,7 @@ import tooltip from '~/vue_shared/directives/tooltip'; import timeAgoMixin from '~/vue_shared/mixins/timeago'; import CiIcon from '../../vue_shared/components/ci_icon.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import { rightSidebarViews } from '../constants'; export default { components: { @@ -49,6 +50,7 @@ export default { this.stopPipelinePolling(); }, methods: { + ...mapActions(['setRightPane']), ...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']), startTimer() { this.intervalId = setInterval(() => { @@ -69,24 +71,31 @@ export default { return `${this.currentProject.web_url}/commit/${shortSha}`; }, }, + rightSidebarViews, }; </script> <template> <footer class="ide-status-bar"> <div - v-if="lastCommit && lastCommitFormatedAge" + v-if="lastCommit" class="ide-status-branch" > <span v-if="latestPipeline && latestPipeline.details" class="ide-status-pipeline" > - <ci-icon - v-tooltip - :status="latestPipeline.details.status" - :title="latestPipeline.details.status.text" - /> + <button + type="button" + class="p-0 border-0 h-50" + @click="setRightPane($options.rightSidebarViews.pipelines)" + > + <ci-icon + v-tooltip + :status="latestPipeline.details.status" + :title="latestPipeline.details.status.text" + /> + </button> Pipeline <a :href="latestPipeline.details.status.details_path" diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue index 8fc4ebe6ca6..0a95c0bb30d 100644 --- a/app/assets/javascripts/ide/components/ide_tree.vue +++ b/app/assets/javascripts/ide/components/ide_tree.vue @@ -1,12 +1,16 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; -import NewDropdown from './new_dropdown/index.vue'; +import Icon from '~/vue_shared/components/icon.vue'; import IdeTreeList from './ide_tree_list.vue'; +import Upload from './new_dropdown/upload.vue'; +import NewEntryButton from './new_dropdown/button.vue'; export default { components: { - NewDropdown, + Icon, + Upload, IdeTreeList, + NewEntryButton, }, computed: { ...mapState(['currentBranchId']), @@ -20,23 +24,42 @@ export default { } }, methods: { - ...mapActions(['updateViewer']), + ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry']), }, }; </script> <template> <ide-tree-list + header-class="d-flex w-100" viewer-type="editor" > <template slot="header" > {{ __('Edit') }} - <new-dropdown - :project-id="currentProject.name_with_namespace" - :branch="currentBranchId" - /> + <div class="ml-auto d-flex"> + <new-entry-button + :label="__('New file')" + :show-label="false" + class="d-flex border-0 p-0 mr-3" + icon="doc-new" + @click="openNewEntryModal({ type: 'blob' })" + /> + <upload + :show-label="false" + class="d-flex mr-3" + button-css-classes="border-0 p-0" + @create="createTempEntry" + /> + <new-entry-button + :label="__('New directory')" + :show-label="false" + class="d-flex border-0 p-0" + icon="folder-new" + @click="openNewEntryModal({ type: 'tree' })" + /> + </div> </template> </ide-tree-list> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/button.vue b/app/assets/javascripts/ide/components/new_dropdown/button.vue new file mode 100644 index 00000000000..7682b34ce4d --- /dev/null +++ b/app/assets/javascripts/ide/components/new_dropdown/button.vue @@ -0,0 +1,51 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { + Icon, + }, + props: { + label: { + type: String, + required: false, + default: null, + }, + icon: { + type: String, + required: true, + }, + iconClasses: { + type: String, + required: false, + default: null, + }, + showLabel: { + type: Boolean, + required: false, + default: true, + }, + }, + methods: { + clicked() { + this.$emit('click'); + }, + }, +}; +</script> + +<template> + <button + :aria-label="label" + type="button" + @click.stop.prevent="clicked" + > + <icon + :name="icon" + :css-classes="iconClasses" + /> + <template v-if="showLabel"> + {{ label }} + </template> + </button> +</template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index 1e398d7e1aa..c29e49ba766 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -3,12 +3,14 @@ import { mapActions } from 'vuex'; import icon from '~/vue_shared/components/icon.vue'; import newModal from './modal.vue'; import upload from './upload.vue'; +import ItemButton from './button.vue'; export default { components: { icon, newModal, upload, + ItemButton, }, props: { branch: { @@ -20,11 +22,13 @@ export default { required: false, default: '', }, + mouseOver: { + type: Boolean, + required: true, + }, }, data() { return { - openModal: false, - modalType: '', dropdownOpen: false, }; }, @@ -34,17 +38,18 @@ export default { this.$refs.dropdownMenu.scrollIntoView(); }); }, + mouseOver() { + if (!this.mouseOver) { + this.dropdownOpen = false; + } + }, }, methods: { - ...mapActions(['createTempEntry']), + ...mapActions(['createTempEntry', 'openNewEntryModal']), createNewItem(type) { - this.modalType = type; - this.openModal = true; + this.openNewEntryModal({ type, path: this.path }); this.dropdownOpen = false; }, - hideModal() { - this.openModal = false; - }, openDropdown() { this.dropdownOpen = !this.dropdownOpen; }, @@ -58,23 +63,19 @@ export default { :class="{ show: dropdownOpen, }" - class="dropdown" + class="dropdown d-flex" > <button + :aria-label="__('Create new file or directory')" type="button" - class="btn btn-sm btn-default dropdown-toggle add-to-tree" - aria-label="Create new file or directory" + class="rounded border-0 d-flex ide-entry-dropdown-toggle" @click.stop="openDropdown()" > <icon - :size="12" - name="plus" - css-classes="float-left" + name="hamburger" /> <icon - :size="12" name="arrow-down" - css-classes="float-left" /> </button> <ul @@ -82,39 +83,30 @@ export default { class="dropdown-menu dropdown-menu-right" > <li> - <a - href="#" - role="button" - @click.stop.prevent="createNewItem('blob')" - > - {{ __('New file') }} - </a> + <item-button + :label="__('New file')" + class="d-flex" + icon="doc-new" + icon-classes="mr-2" + @click="createNewItem('blob')" + /> </li> <li> <upload - :branch-id="branch" :path="path" @create="createTempEntry" /> </li> <li> - <a - href="#" - role="button" - @click.stop.prevent="createNewItem('tree')" - > - {{ __('New directory') }} - </a> + <item-button + :label="__('New directory')" + class="d-flex" + icon="folder-new" + icon-classes="mr-2" + @click="createNewItem('tree')" + /> </li> </ul> </div> - <new-modal - v-if="openModal" - :type="modalType" - :branch-id="branch" - :path="path" - @hide="hideModal" - @create="createTempEntry" - /> </div> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 1e9668d5154..1867b7980d2 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -1,78 +1,70 @@ <script> import { __ } from '~/locale'; -import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; +import { mapActions, mapState } from 'vuex'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; export default { components: { - DeprecatedModal, - }, - props: { - branchId: { - type: String, - required: true, - }, - type: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, + GlModal, }, data() { return { - entryName: this.path !== '' ? `${this.path}/` : '', + name: '', }; }, computed: { + ...mapState(['newEntryModal']), + entryName: { + get() { + return this.name || (this.newEntryModal.path !== '' ? `${this.newEntryModal.path}/` : ''); + }, + set(val) { + this.name = val; + }, + }, modalTitle() { - if (this.type === 'tree') { + if (this.newEntryModal.type === 'tree') { return __('Create new directory'); } return __('Create new file'); }, buttonLabel() { - if (this.type === 'tree') { + if (this.newEntryModal.type === 'tree') { return __('Create directory'); } return __('Create file'); }, }, - mounted() { - this.$refs.fieldName.focus(); - }, methods: { + ...mapActions(['createTempEntry']), createEntryInStore() { - this.$emit('create', { - branchId: this.branchId, - name: this.entryName, - type: this.type, + this.createTempEntry({ + name: this.name, + type: this.newEntryModal.type, }); - - this.hideModal(); }, - hideModal() { - this.$emit('hide'); + focusInput() { + setTimeout(() => { + this.$refs.fieldName.focus(); + }); }, }, }; </script> <template> - <deprecated-modal - :title="modalTitle" - :primary-button-label="buttonLabel" - kind="success" - @cancel="hideModal" + <gl-modal + id="ide-new-entry" + :header-title-text="modalTitle" + :footer-primary-button-text="buttonLabel" + footer-primary-button-variant="success" @submit="createEntryInStore" + @open="focusInput" > - <form - slot="body" + <div class="form-group row" - @submit.prevent="createEntryInStore" > <label class="label-light col-form-label col-sm-3"> {{ __('Name') }} @@ -85,6 +77,6 @@ export default { class="form-control" /> </div> - </form> - </deprecated-modal> + </div> + </gl-modal> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue index 677b282bd61..5b1743bb30e 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue @@ -1,71 +1,85 @@ <script> - export default { - props: { - branchId: { - type: String, - required: true, - }, - path: { - type: String, - required: false, - default: '', - }, +import Icon from '~/vue_shared/components/icon.vue'; +import ItemButton from './button.vue'; + +export default { + components: { + Icon, + ItemButton, + }, + props: { + path: { + type: String, + required: false, + default: '', }, - mounted() { - this.$refs.fileUpload.addEventListener('change', this.openFile); + showLabel: { + type: Boolean, + required: false, + default: true, }, - beforeDestroy() { - this.$refs.fileUpload.removeEventListener('change', this.openFile); + buttonCssClasses: { + type: String, + required: false, + default: null, }, - methods: { - createFile(target, file, isText) { - const { name } = file; - let { result } = target; + }, + mounted() { + this.$refs.fileUpload.addEventListener('change', this.openFile); + }, + beforeDestroy() { + this.$refs.fileUpload.removeEventListener('change', this.openFile); + }, + methods: { + createFile(target, file, isText) { + const { name } = file; + let { result } = target; - if (!isText) { - // eslint-disable-next-line prefer-destructuring - result = result.split('base64,')[1]; - } + if (!isText) { + // eslint-disable-next-line prefer-destructuring + result = result.split('base64,')[1]; + } - this.$emit('create', { - name: `${(this.path ? `${this.path}/` : '')}${name}`, - branchId: this.branchId, - type: 'blob', - content: result, - base64: !isText, - }); - }, - readFile(file) { - const reader = new FileReader(); - const isText = file.type.match(/text.*/) !== null; + this.$emit('create', { + name: `${this.path ? `${this.path}/` : ''}${name}`, + type: 'blob', + content: result, + base64: !isText, + }); + }, + readFile(file) { + const reader = new FileReader(); + const isText = file.type.match(/text.*/) !== null; - reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); + reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); - if (isText) { - reader.readAsText(file); - } else { - reader.readAsDataURL(file); - } - }, - openFile() { - Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); - }, - startFileUpload() { - this.$refs.fileUpload.click(); - }, + if (isText) { + reader.readAsText(file); + } else { + reader.readAsDataURL(file); + } + }, + openFile() { + Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); }, - }; + startFileUpload() { + this.$refs.fileUpload.click(); + }, + }, +}; </script> <template> <div> - <a - href="#" - role="button" - @click.stop.prevent="startFileUpload" - > - {{ __('Upload file') }} - </a> + <item-button + :class="buttonCssClasses" + :show-label="showLabel" + :icon-classes="showLabel ? 'mr-2' : ''" + :label="__('Upload file')" + class="d-flex" + icon="upload" + @click="startFileUpload" + /> <input id="file-upload" ref="fileUpload" diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index f490a3a2a39..3b4dd5ae9aa 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -40,6 +40,11 @@ export default { default: false, }, }, + data() { + return { + mouseOver: false, + }; + }, computed: { ...mapGetters([ 'getChangesInFolder', @@ -142,6 +147,9 @@ export default { hasUrlAtCurrentRoute() { return this.$router.currentRoute.path === `/project${this.file.url}`; }, + toggleHover(over) { + this.mouseOver = over; + }, }, }; </script> @@ -153,6 +161,8 @@ export default { class="file" role="button" @click="clickFile" + @mouseover="toggleHover(true)" + @mouseout="toggleHover(false)" > <div class="file-name" @@ -206,6 +216,7 @@ export default { :project-id="file.projectId" :branch="file.branchId" :path="file.path" + :mouse-over="mouseOver" class="float-right prepend-left-8" /> </div> diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index cc8dbb942d8..44c35e9a5a5 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => { store .dispatch('getMergeRequestData', { projectId: fullProjectId, + targetProjectId: to.query.target_project, mergeRequestId: to.params.mrid, }) .then(mr => { @@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => { .then(() => store.dispatch('getMergeRequestVersions', { projectId: fullProjectId, + targetProjectId: to.query.target_project, mergeRequestId: to.params.mrid, }), ) .then(() => store.dispatch('getMergeRequestChanges', { projectId: fullProjectId, + targetProjectId: to.query.target_project, mergeRequestId: to.params.mrid, }), ) diff --git a/app/assets/javascripts/ide/lib/themes/gl_theme.js b/app/assets/javascripts/ide/lib/themes/gl_theme.js index 2fc96250c7d..439ae50448a 100644 --- a/app/assets/javascripts/ide/lib/themes/gl_theme.js +++ b/app/assets/javascripts/ide/lib/themes/gl_theme.js @@ -9,6 +9,7 @@ export default { 'diffEditor.insertedTextBackground': '#ddfbe6', 'diffEditor.removedTextBackground': '#f9d7dc', 'editor.selectionBackground': '#aad6f8', + 'editorIndentGuide.activeBackground': '#cccccc', }, }, }; diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index 5e91fa915ff..b5bd6f5a6bb 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -52,7 +52,7 @@ export const setResizingStatus = ({ commit }, resizing) => { export const createTempEntry = ( { state, commit, dispatch }, - { branchId, name, type, content = '', base64 = false }, + { name, type, content = '', base64 = false }, ) => new Promise(resolve => { const worker = new FilesDecoratorWorker(); @@ -81,7 +81,7 @@ export const createTempEntry = ( commit(types.CREATE_TMP_ENTRY, { data, projectId: state.currentProjectId, - branchId, + branchId: state.currentBranchId, }); if (type === 'blob') { @@ -100,7 +100,7 @@ export const createTempEntry = ( worker.postMessage({ data: [fullName], projectId: state.currentProjectId, - branchId, + branchId: state.currentBranchId, type, tempFile: true, base64, @@ -178,6 +178,13 @@ export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links); export const setErrorMessage = ({ commit }, errorMessage) => commit(types.SET_ERROR_MESSAGE, errorMessage); +export const openNewEntryModal = ({ commit }, { type, path = '' }) => { + commit(types.OPEN_NEW_ENTRY_MODAL, { type, path }); + + // open the modal manually so we don't mess around with dropdown/rows + $('#ide-new-entry').modal('show'); +}; + export * from './actions/tree'; export * from './actions/file'; export * from './actions/project'; diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js index 6bdf9dc3028..1887b77b00b 100644 --- a/app/assets/javascripts/ide/stores/actions/merge_request.js +++ b/app/assets/javascripts/ide/stores/actions/merge_request.js @@ -4,12 +4,14 @@ import * as types from '../mutation_types'; export const getMergeRequestData = ( { commit, dispatch, state }, - { projectId, mergeRequestId, force = false } = {}, + { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, ) => new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) { service - .getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true }) + .getProjectMergeRequestData(targetProjectId || projectId, mergeRequestId, { + render_html: true, + }) .then(({ data }) => { commit(types.SET_MERGE_REQUEST, { projectPath: projectId, @@ -38,12 +40,12 @@ export const getMergeRequestData = ( export const getMergeRequestChanges = ( { commit, dispatch, state }, - { projectId, mergeRequestId, force = false } = {}, + { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, ) => new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) { service - .getProjectMergeRequestChanges(projectId, mergeRequestId) + .getProjectMergeRequestChanges(targetProjectId || projectId, mergeRequestId) .then(({ data }) => { commit(types.SET_MERGE_REQUEST_CHANGES, { projectPath: projectId, @@ -71,12 +73,12 @@ export const getMergeRequestChanges = ( export const getMergeRequestVersions = ( { commit, dispatch, state }, - { projectId, mergeRequestId, force = false } = {}, + { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, ) => new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) { service - .getProjectMergeRequestVersions(projectId, mergeRequestId) + .getProjectMergeRequestVersions(targetProjectId || projectId, mergeRequestId) .then(res => res.data) .then(data => { commit(types.SET_MERGE_REQUEST_VERSIONS, { diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js index cdd8076952f..6ef938b0ae2 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js @@ -31,7 +31,7 @@ export const fetchMergeRequests = ({ dispatch, state: { state } }, { type, searc dispatch('requestMergeRequests', type); dispatch('resetMergeRequests', type); - Api.mergeRequests({ scope, state, search }) + return Api.mergeRequests({ scope, state, search }) .then(({ data }) => dispatch('receiveMergeRequestsSuccess', { type, data })) .catch(() => dispatch('receiveMergeRequestsError', { type, search })); }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 8cb01f25223..3e67b222e66 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -102,7 +102,7 @@ export const receiveJobsSuccess = ({ commit }, { id, data }) => export const fetchJobs = ({ dispatch }, stage) => { dispatch('requestJobs', stage.id); - axios + return axios .get(stage.dropdownPath) .then(({ data }) => dispatch('receiveJobsSuccess', { id: stage.id, data })) .catch(() => dispatch('receiveJobsError', stage)); diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index 555802e1811..8d6f9ccaf34 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -74,3 +74,5 @@ export const CLEAR_PROJECTS = 'CLEAR_PROJECTS'; export const RESET_OPEN_FILES = 'RESET_OPEN_FILES'; export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE'; + +export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 702be2140e2..f8091f5b5e0 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -166,6 +166,11 @@ export default { [types.SET_ERROR_MESSAGE](state, errorMessage) { Object.assign(state, { errorMessage }); }, + [types.OPEN_NEW_ENTRY_MODAL](state, { type, path }) { + Object.assign(state, { + newEntryModal: { type, path }, + }); + }, ...projectMutations, ...mergeRequestMutation, ...fileMutations, diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index be229b2c723..0f32a267469 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -26,4 +26,8 @@ export default () => ({ rightPane: null, links: {}, errorMessage: null, + newEntryModal: { + type: '', + path: '', + }, }); diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 963e3a37b39..26482a02e00 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -200,7 +200,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" class="btn btn-cancel note-edit-cancel js-close-discussion-note-form" type="button" @click="cancelHandler()"> - {{ __('Discard draft') }} + Cancel </button> </div> </form> diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 671fa4d7d22..3eefbe11c37 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -15,6 +15,8 @@ let eTagPoll; export const expandDiscussion = ({ commit }, data) => commit(types.EXPAND_DISCUSSION, data); +export const collapseDiscussion = ({ commit }, data) => commit(types.COLLAPSE_DISCUSSION, data); + export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data); export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data); @@ -41,6 +43,15 @@ export const fetchDiscussions = ({ commit }, path) => commit(types.SET_INITIAL_DISCUSSIONS, discussions); }); +export const refetchDiscussionById = ({ commit }, { path, discussionId }) => + service + .fetchDiscussions(path) + .then(res => res.json()) + .then(discussions => { + const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId); + if (selectedDiscussion) commit(types.UPDATE_DISCUSSION, selectedDiscussion); + }); + export const deleteNote = ({ commit }, note) => service.deleteNote(note.path).then(() => { commit(types.DELETE_NOTE, note); diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index a25098fbc06..6f374f78691 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -1,7 +1,6 @@ export const ADD_NEW_NOTE = 'ADD_NEW_NOTE'; export const ADD_NEW_REPLY_TO_DISCUSSION = 'ADD_NEW_REPLY_TO_DISCUSSION'; export const DELETE_NOTE = 'DELETE_NOTE'; -export const EXPAND_DISCUSSION = 'EXPAND_DISCUSSION'; export const REMOVE_PLACEHOLDER_NOTES = 'REMOVE_PLACEHOLDER_NOTES'; export const SET_NOTES_DATA = 'SET_NOTES_DATA'; export const SET_NOTEABLE_DATA = 'SET_NOTEABLE_DATA'; @@ -11,12 +10,16 @@ export const SET_LAST_FETCHED_AT = 'SET_LAST_FETCHED_AT'; export const SET_TARGET_NOTE_HASH = 'SET_TARGET_NOTE_HASH'; export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE'; export const TOGGLE_AWARD = 'TOGGLE_AWARD'; -export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION'; export const UPDATE_NOTE = 'UPDATE_NOTE'; export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; +// DISCUSSION +export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; +export const EXPAND_DISCUSSION = 'EXPAND_DISCUSSION'; +export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION'; + // Issue export const CLOSE_ISSUE = 'CLOSE_ISSUE'; export const REOPEN_ISSUE = 'REOPEN_ISSUE'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index e5e40ce07fa..ab6a95e2601 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -58,6 +58,11 @@ export default { discussion.expanded = true; }, + [types.COLLAPSE_DISCUSSION](state, { discussionId }) { + const discussion = utils.findNoteObjectById(state.discussions, discussionId); + discussion.expanded = false; + }, + [types.REMOVE_PLACEHOLDER_NOTES](state) { const { discussions } = state; @@ -114,7 +119,6 @@ export default { Object.assign(state, { discussions }); }, - [types.SET_LAST_FETCHED_AT](state, fetchedAt) { Object.assign(state, { lastFetchedAt: fetchedAt }); }, diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 9aa83ce6269..ff19b9a9c30 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -39,7 +39,6 @@ export default class Todos { } initFilters() { - this.initFilterDropdown($('.js-group-search'), 'group_id', ['text']); this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); this.initFilterDropdown($('.js-type-search'), 'type'); this.initFilterDropdown($('.js-action-search'), 'action_id'); @@ -54,16 +53,7 @@ export default class Todos { filterable: searchFields ? true : false, search: { fields: searchFields }, data: $dropdown.data('data'), - clicked: () => { - const $formEl = $dropdown.closest('form.filter-form'); - const mutexDropdowns = { - group_id: 'project_id', - project_id: 'group_id', - }; - - $formEl.find(`input[name="${mutexDropdowns[fieldName]}"]`).remove(); - $formEl.submit(); - }, + clicked: () => $dropdown.closest('form.filter-form').submit(), }); } diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index b76965f280b..0fdb0a080cf 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -1,13 +1,10 @@ <script> import $ from 'jquery'; -import PerformanceBarService from '../services/performance_bar_service'; import detailedMetric from './detailed_metric.vue'; import requestSelector from './request_selector.vue'; import simpleMetric from './simple_metric.vue'; -import Flash from '../../flash'; - export default { components: { detailedMetric, @@ -69,37 +66,13 @@ export default { }, }, mounted() { - this.interceptor = PerformanceBarService.registerInterceptor( - this.peekUrl, - this.loadRequestDetails, - ); - - this.loadRequestDetails(this.requestId, window.location.href); this.currentRequest = this.requestId; if (this.lineProfileModal.length) { this.lineProfileModal.modal('toggle'); } }, - beforeDestroy() { - PerformanceBarService.removeInterceptor(this.interceptor); - }, methods: { - loadRequestDetails(requestId, requestUrl) { - if (!this.store.canTrackRequest(requestUrl)) { - return; - } - - this.store.addRequest(requestId, requestUrl); - - PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId) - .then(res => { - this.store.addRequestDetails(requestId, res.data.data); - }) - .catch(() => - Flash(`Error getting performance bar results for ${requestId}`), - ); - }, changeCurrentRequest(newRequestId) { this.currentRequest = newRequestId; }, diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index 4a98aed7679..41880d27516 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -1,12 +1,13 @@ import Vue from 'vue'; -import performanceBarApp from './components/performance_bar_app.vue'; +import Flash from '../flash'; +import PerformanceBarService from './services/performance_bar_service'; import PerformanceBarStore from './stores/performance_bar_store'; export default ({ container }) => new Vue({ el: container, components: { - performanceBarApp, + performanceBarApp: () => import('./components/performance_bar_app.vue'), }, data() { const performanceBarData = document.querySelector(this.$options.el) @@ -21,6 +22,34 @@ export default ({ container }) => profileUrl: performanceBarData.profileUrl, }; }, + mounted() { + this.interceptor = PerformanceBarService.registerInterceptor( + this.peekUrl, + this.loadRequestDetails, + ); + + this.loadRequestDetails(this.requestId, window.location.href); + }, + beforeDestroy() { + PerformanceBarService.removeInterceptor(this.interceptor); + }, + methods: { + loadRequestDetails(requestId, requestUrl) { + if (!this.store.canTrackRequest(requestUrl)) { + return; + } + + this.store.addRequest(requestId, requestUrl); + + PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId) + .then(res => { + this.store.addRequestDetails(requestId, res.data.data); + }) + .catch(() => + Flash(`Error getting performance bar results for ${requestId}`), + ); + }, + }, render(createElement) { return createElement('performance-bar-app', { props: { diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue deleted file mode 100644 index ffaed9c7193..00000000000 --- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue +++ /dev/null @@ -1,98 +0,0 @@ -<script> -import { __ } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; - -import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; - -const MARK_TEXT = __('Mark todo as done'); -const TODO_TEXT = __('Add todo'); - -export default { - directives: { - tooltip, - }, - components: { - Icon, - LoadingIcon, - }, - props: { - issuableId: { - type: Number, - required: true, - }, - issuableType: { - type: String, - required: true, - }, - isTodo: { - type: Boolean, - required: false, - default: true, - }, - isActionActive: { - type: Boolean, - required: false, - default: false, - }, - collapsed: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - buttonClasses() { - return this.collapsed ? - 'btn-blank btn-todo sidebar-collapsed-icon dont-change-state' : - 'btn btn-default btn-todo issuable-header-btn float-right'; - }, - buttonLabel() { - return this.isTodo ? MARK_TEXT : TODO_TEXT; - }, - collapsedButtonIconClasses() { - return this.isTodo ? 'todo-undone' : ''; - }, - collapsedButtonIcon() { - return this.isTodo ? 'todo-done' : 'todo-add'; - }, - }, - methods: { - handleButtonClick() { - this.$emit('toggleTodo'); - }, - }, -}; -</script> - -<template> - <button - v-tooltip - :class="buttonClasses" - :title="buttonLabel" - :aria-label="buttonLabel" - :data-issuable-id="issuableId" - :data-issuable-type="issuableType" - type="button" - data-container="body" - data-placement="left" - data-boundary="viewport" - @click="handleButtonClick" - > - <icon - v-show="collapsed" - :css-classes="collapsedButtonIconClasses" - :name="collapsedButtonIcon" - /> - <span - v-show="!collapsed" - class="issuable-todo-inner" - > - {{ buttonLabel }} - </span> - <loading-icon - v-show="isActionActive" - :inline="true" - /> - </button> -</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index c18b74743e4..a4c2289c590 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -1,7 +1,7 @@ <script> import tooltip from '~/vue_shared/directives/tooltip'; import { n__ } from '~/locale'; -import { webIDEUrl } from '~/lib/utils/url_utility'; +import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; import Icon from '~/vue_shared/components/icon.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; @@ -43,7 +43,10 @@ export default { return this.isBranchTitleLong(this.mr.targetBranch); }, webIdePath() { - return webIDEUrl(this.mr.statusPath.replace('.json', '')); + return mergeUrlParams({ + target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ? + this.mr.targetProjectFullPath : '', + }, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`)); }, }, methods: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 55b87f3a8ec..9aff95dcfec 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -32,7 +32,7 @@ }; </script> <template> - <div class="space-children d-flex append-right-10"> + <div class="space-children d-flex append-right-10 widget-status-icon"> <div v-if="isLoading" class="mr-widget-icon" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index fe777a07189..a5ca7b719a1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -233,7 +233,7 @@ export default { <status-icon :status="iconClass" /> <div class="media-body"> <div class="mr-widget-body-controls media space-children"> - <span class="btn-group append-bottom-5"> + <span class="btn-group"> <button :disabled="isMergeButtonDisabled" :class="mergeButtonClass" diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index c881cd496d1..e84c436905d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -16,10 +16,11 @@ export default class MergeRequestStore { const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; this.squash = data.squash; - this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath || - data.squash_before_merge_help_path; + this.squashBeforeMergeHelpPath = + this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path; this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true; + this.iid = data.iid; this.title = data.title; this.targetBranch = data.target_branch; this.sourceBranch = data.source_branch; @@ -85,6 +86,8 @@ export default class MergeRequestStore { this.isMergeAllowed = data.mergeable || false; this.mergeOngoing = data.merge_ongoing; this.allowCollaboration = data.allow_collaboration; + this.targetProjectFullPath = data.target_project_full_path; + this.sourceProjectFullPath = data.source_project_full_path; // Cherry-pick and Revert actions related this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; @@ -97,7 +100,8 @@ export default class MergeRequestStore { this.hasCI = data.has_ci; this.ciStatus = data.ci_status; this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; - this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings'; + this.isPipelinePassing = + this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings'; this.isPipelineSkipped = this.ciStatus === 'skipped'; this.pipelineDetailedStatus = pipelineStatus; this.isPipelineActive = data.pipeline ? data.pipeline.active : false; diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue index b298b989203..416eda796a7 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -45,6 +45,11 @@ export default { emitSubmit(event) { this.$emit('submit', event); }, + opened({ propertyName }) { + if (propertyName === 'opacity') { + this.$emit('open'); + } + }, }, }; </script> @@ -55,6 +60,7 @@ export default { class="modal fade" tabindex="-1" role="dialog" + @transitionend="opened" > <div :class="modalSizeClass" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue index 80dc7d3557c..ac2e99abe77 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue @@ -12,11 +12,6 @@ export default { type: Boolean, required: true, }, - cssClasses: { - type: String, - required: false, - default: '', - }, }, computed: { tooltipLabel() { @@ -35,12 +30,10 @@ export default { <button v-tooltip :title="tooltipLabel" - :class="cssClasses" type="button" class="btn btn-blank gutter-toggle btn-sidebar-action" data-container="body" data-placement="left" - data-boundary="viewport" @click="toggle" > <i diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 551a7e852ae..5d79610b21e 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -224,7 +224,10 @@ .form-control { position: relative; min-width: 200px; - padding: 5px 25px 6px 0; + padding-right: 25px; + padding-left: 0; + height: $input-height; + line-height: inherit; border-color: transparent; &:focus, diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 9dbb04e5443..8bab8cf36b1 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -321,11 +321,18 @@ } &.activities { + display: flex; border-bottom: 1px solid $border-color; + overflow: hidden; .nav-links { border-bottom: 0; } + + @include media-breakpoint-down(xs) { + display: block; + overflow: visible; + } } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 7e89f8998fb..5e39bbb9890 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -518,6 +518,12 @@ outline: none; color: $gl-link-hover-color; } + + .caret-icon { + position: relative; + top: 2px; + left: -1px; + } } // Mobile diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 199039f38f7..3144dcc4dc0 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -23,7 +23,7 @@ } .btn-group { - > a { + > .btn:not(.btn-danger) { color: $gl-text-color-secondary; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index f6617380cc0..f9fd9f1ab8b 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -449,7 +449,6 @@ .todo-undone { color: $gl-link-color; - fill: $gl-link-color; } .author { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c32049e1b33..5835b8b8c9b 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -116,10 +116,8 @@ .modify-merge-commit-link { padding: 0; - background-color: transparent; border: 0; - color: $gl-text-color; &:hover, @@ -216,6 +214,10 @@ } } + .widget-status-icon { + align-self: flex-start; + } + .mr-widget-body { line-height: 28px; @@ -501,10 +503,6 @@ } } -.merge-request-details .content-block { - border-bottom: 0; -} - .mr-source-target { display: flex; flex-wrap: wrap; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 6e2b285285a..8b1227b9131 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -44,6 +44,7 @@ padding-bottom: $grid-size; .file { + height: 32px; cursor: pointer; &.file-active { @@ -716,32 +717,6 @@ justify-content: center; } -.ide-new-btn { - .btn { - padding-top: 3px; - padding-bottom: 3px; - } - - .dropdown { - display: flex; - } - - .dropdown-toggle svg { - top: 0; - } - - .dropdown-menu { - left: auto; - right: 0; - - label { - font-weight: $gl-font-weight-normal; - padding: 5px 8px; - margin-bottom: 0; - } - } -} - .ide { overflow: hidden; @@ -1340,3 +1315,24 @@ overflow: auto; } } + +.ide-entry-dropdown-toggle { + padding: $gl-padding-4; + background-color: $theme-gray-100; + + &:hover { + background-color: $theme-gray-200; + } + + &:active, + &:focus { + color: $white-normal; + background-color: $blue-500; + outline: 0; + } +} + +.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle { + color: $white-normal; + background-color: $blue-500; +} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 010a2c05a1c..e5d7dd13915 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -174,18 +174,6 @@ } } -@include media-breakpoint-down(lg) { - .todos-filters { - .filter-categories { - width: 75%; - - .filter-item { - margin-bottom: 10px; - } - } - } -} - @include media-breakpoint-down(xs) { .todo { .avatar { @@ -211,10 +199,6 @@ } .todos-filters { - .filter-categories { - width: auto; - } - .dropdown-menu-toggle { width: 100%; } diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index b0c4c31cffc..5c2025c1988 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -22,7 +22,7 @@ class Admin::DeployKeysController < Admin::ApplicationController end def update - if deploy_key.update_attributes(update_params) + if deploy_key.update(update_params) flash[:notice] = 'Deploy key was successfully updated.' redirect_to admin_deploy_keys_path else @@ -34,7 +34,7 @@ class Admin::DeployKeysController < Admin::ApplicationController deploy_key.destroy respond_to do |format| - format.html { redirect_to admin_deploy_keys_path, status: 302 } + format.html { redirect_to admin_deploy_keys_path, status: :found } format.json { head :ok } end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 96b7bc65ac9..d7a5b745d3f 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -39,7 +39,7 @@ class Admin::GroupsController < Admin::ApplicationController end def update - if @group.update_attributes(group_params) + if @group.update(group_params) redirect_to [:admin, @group], notice: 'Group was successfully updated.' else render "edit" diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 6944857bd33..a98c355c7ba 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -23,7 +23,7 @@ class Admin::HooksController < Admin::ApplicationController end def update - if hook.update_attributes(hook_params) + if hook.update(hook_params) flash[:notice] = 'System hook was successfully updated.' redirect_to admin_hooks_path else @@ -34,7 +34,7 @@ class Admin::HooksController < Admin::ApplicationController def destroy hook.destroy - redirect_to admin_hooks_path, status: 302 + redirect_to admin_hooks_path, status: :found end def test diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb index 43b4e3a2cc3..ceb45865804 100644 --- a/app/controllers/admin/identities_controller.rb +++ b/app/controllers/admin/identities_controller.rb @@ -25,7 +25,7 @@ class Admin::IdentitiesController < Admin::ApplicationController end def update - if @identity.update_attributes(identity_params) + if @identity.update(identity_params) RepairLdapBlockedUserService.new(@user).execute redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.' else diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb index 39dbf85f6c0..d2f947d2c66 100644 --- a/app/controllers/admin/impersonations_controller.rb +++ b/app/controllers/admin/impersonations_controller.rb @@ -11,7 +11,7 @@ class Admin::ImpersonationsController < Admin::ApplicationController session[:impersonator_id] = nil - redirect_to admin_user_path(original_user), status: 302 + redirect_to admin_user_path(original_user), status: :found end private diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb index ae7a7f6279c..ac1ae0f16b3 100644 --- a/app/controllers/admin/jobs_controller.rb +++ b/app/controllers/admin/jobs_controller.rb @@ -20,6 +20,6 @@ class Admin::JobsController < Admin::ApplicationController def cancel_all Ci::Build.running_or_pending.each(&:cancel) - redirect_to admin_jobs_path, status: 303 + redirect_to admin_jobs_path, status: :see_other end end diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb index 7aba77d8129..51d5799cd89 100644 --- a/app/controllers/admin/runner_projects_controller.rb +++ b/app/controllers/admin/runner_projects_controller.rb @@ -16,7 +16,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController runner = rp.runner rp.destroy - redirect_to admin_runner_path(runner), status: 302 + redirect_to admin_runner_path(runner), status: :found end private diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 4b01904f2a1..6c76c55a9d4 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -28,7 +28,7 @@ class Admin::RunnersController < Admin::ApplicationController def destroy @runner.destroy - redirect_to admin_runners_path, status: 302 + redirect_to admin_runners_path, status: :found end def resume diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index a7025b62ad7..e70aa549140 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -16,7 +16,7 @@ class Admin::ServicesController < Admin::ApplicationController end def update - if service.update_attributes(service_params[:service]) + if service.update(service_params[:service]) PropagateServiceTemplateWorker.perform_async(service.id) if service.active? redirect_to admin_application_settings_services_path, diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 653f3dfffc4..a51a8c3ed4a 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -163,7 +163,7 @@ class Admin::UsersController < Admin::ApplicationController format.json { head :ok } else format.html { redirect_back_or_admin_user(alert: 'There was an error removing the e-mail.') } - format.json { render json: 'There was an error removing the e-mail.', status: 400 } + format.json { render json: 'There was an error removing the e-mail.', status: :bad_request } end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 21cc6dfdd16..f45fcd4d900 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -30,7 +30,13 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception, prepend: true helper_method :can? - helper_method :import_sources_enabled?, :github_import_enabled?, :gitea_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, + :gitea_import_enabled?, :github_import_configured?, + :gitlab_import_enabled?, :gitlab_import_configured?, + :bitbucket_import_enabled?, :bitbucket_import_configured?, + :google_code_import_enabled?, :fogbugz_import_enabled?, + :git_import_enabled?, :gitlab_project_import_enabled?, + :manifest_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -351,6 +357,10 @@ class ApplicationController < ActionController::Base Gitlab::CurrentSettings.import_sources.include?('gitlab_project') end + def manifest_import_enabled? + Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest') + end + # U2F (universal 2nd factor) devices need a unique identifier for the application # to perform authentication. # https://developers.yubico.com/U2F/App_ID.html diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index ba510968684..37e03d70b6f 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -127,7 +127,7 @@ module IssuableActions errors: [ "Someone edited this #{issuable.human_class_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs." ] - }, status: 409 + }, status: :conflict end end end diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 5e4e8a87153..79ee5b2f91e 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -27,7 +27,7 @@ module LfsRequest message: 'Git LFS is not enabled on this GitLab server, contact your admin.', documentation_url: help_url }, - status: 501 + status: :not_implemented ) end diff --git a/app/controllers/concerns/todos_actions.rb b/app/controllers/concerns/todos_actions.rb deleted file mode 100644 index c0acdb3498d..00000000000 --- a/app/controllers/concerns/todos_actions.rb +++ /dev/null @@ -1,12 +0,0 @@ -module TodosActions - extend ActiveSupport::Concern - - def create - todo = TodoService.new.mark_todo(issuable, current_user) - - render json: { - count: TodosFinder.new(current_user, state: :pending).execute.count, - delete_path: dashboard_todo_path(todo) - } - end -end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index bd7111e28bc..f9e8fe624e8 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -70,7 +70,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def todo_params - params.permit(:action_id, :author_id, :project_id, :type, :sort, :state, :group_id) + params.permit(:action_id, :author_id, :project_id, :type, :sort, :state) end def redirect_out_of_range(todos) diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb index cc5ba5878f8..35a61b359c8 100644 --- a/app/controllers/groups/avatars_controller.rb +++ b/app/controllers/groups/avatars_controller.rb @@ -7,6 +7,6 @@ class Groups::AvatarsController < Groups::ApplicationController @group.remove_avatar! @group.save - redirect_to edit_group_path(@group), status: 302 + redirect_to edit_group_path(@group), status: :found end end diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb index 78992ec7f46..1036b4e6ed3 100644 --- a/app/controllers/groups/runners_controller.rb +++ b/app/controllers/groups/runners_controller.rb @@ -23,7 +23,7 @@ class Groups::RunnersController < Groups::ApplicationController def destroy @runner.destroy - redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: 302 + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found end def resume diff --git a/app/controllers/import/manifest_controller.rb b/app/controllers/import/manifest_controller.rb new file mode 100644 index 00000000000..e5a719fa0df --- /dev/null +++ b/app/controllers/import/manifest_controller.rb @@ -0,0 +1,93 @@ +class Import::ManifestController < Import::BaseController + before_action :whitelist_query_limiting, only: [:create] + before_action :verify_import_enabled + before_action :ensure_import_vars, only: [:create, :status] + + def new + end + + def status + @already_added_projects = find_already_added_projects + already_added_import_urls = @already_added_projects.pluck(:import_url) + + @pending_repositories = repositories.to_a.reject do |repository| + already_added_import_urls.include?(repository[:url]) + end + end + + def upload + group = Group.find(params[:group_id]) + + unless can?(current_user, :create_projects, group) + @errors = ["You don't have enough permissions to create projects in the selected group"] + + render :new && return + end + + manifest = Gitlab::ManifestImport::Manifest.new(params[:manifest].tempfile) + + if manifest.valid? + session[:manifest_import_repositories] = manifest.projects + session[:manifest_import_group_id] = group.id + + redirect_to status_import_manifest_path + else + @errors = manifest.errors + + render :new + end + end + + def jobs + render json: find_jobs + end + + def create + repository = repositories.find do |project| + project[:id] == params[:repo_id].to_i + end + + project = Gitlab::ManifestImport::ProjectCreator.new(repository, group, current_user).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project_save_error(project) }, status: :unprocessable_entity + end + end + + private + + def ensure_import_vars + unless group && repositories.present? + redirect_to(new_import_manifest_path) + end + end + + def group + @group ||= Group.find_by(id: session[:manifest_import_group_id]) + end + + def repositories + @repositories ||= session[:manifest_import_repositories] + end + + def find_jobs + find_already_added_projects.to_json(only: [:id], methods: [:import_status]) + end + + def find_already_added_projects + group.all_projects + .where(import_type: 'manifest') + .where(creator_id: current_user) + .includes(:import_state) + end + + def verify_import_enabled + render_404 unless manifest_import_enabled? + end + + def whitelist_query_limiting + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/48939') + end +end diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 67057b5b126..3cb9e46b548 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -41,7 +41,7 @@ class JwtController < ApplicationController "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}" } ] - }, status: 401 + }, status: :unauthorized end def render_unauthorized @@ -50,7 +50,7 @@ class JwtController < ApplicationController { code: 'UNAUTHORIZED', message: 'HTTP Basic: Access denied' } ] - }, status: 401 + }, status: :unauthorized end def auth_params diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index 8ec4bb1233f..ed20302487c 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -5,14 +5,14 @@ class NotificationSettingsController < ApplicationController return render_404 unless can_read?(resource) @notification_setting = current_user.notification_settings_for(resource) - @saved = @notification_setting.update_attributes(notification_setting_params) + @saved = @notification_setting.update(notification_setting_params) render_response end def update @notification_setting = current_user.notification_settings.find(params[:id]) - @saved = @notification_setting.update_attributes(notification_setting_params) + @saved = @notification_setting.update(notification_setting_params) render_response end diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb index f0cdc228366..f1e77d68acd 100644 --- a/app/controllers/profiles/active_sessions_controller.rb +++ b/app/controllers/profiles/active_sessions_controller.rb @@ -7,7 +7,7 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController ActiveSession.destroy(current_user, params[:id]) respond_to do |format| - format.html { redirect_to profile_active_sessions_url, status: 302 } + format.html { redirect_to profile_active_sessions_url, status: :found } format.js { head :ok } end end diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb index 39b9f8a84d1..4f030ded80f 100644 --- a/app/controllers/profiles/avatars_controller.rb +++ b/app/controllers/profiles/avatars_controller.rb @@ -4,6 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController Users::UpdateService.new(current_user, user: @user).execute { |user| user.remove_avatar! } - redirect_to profile_path, status: 302 + redirect_to profile_path, status: :found end end diff --git a/app/controllers/profiles/chat_names_controller.rb b/app/controllers/profiles/chat_names_controller.rb index 2353f0840d6..a186c5f36a8 100644 --- a/app/controllers/profiles/chat_names_controller.rb +++ b/app/controllers/profiles/chat_names_controller.rb @@ -39,7 +39,7 @@ class Profiles::ChatNamesController < Profiles::ApplicationController flash[:alert] = "Could not delete chat nickname #{@chat_name.chat_name}." end - redirect_to profile_chat_names_path, status: 302 + redirect_to profile_chat_names_path, status: :found end private diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb index bbd7ba49d77..a39824ec9c8 100644 --- a/app/controllers/profiles/emails_controller.rb +++ b/app/controllers/profiles/emails_controller.rb @@ -19,7 +19,7 @@ class Profiles::EmailsController < Profiles::ApplicationController Emails::DestroyService.new(current_user, user: current_user).execute(@email) respond_to do |format| - format.html { redirect_to profile_emails_url, status: 302 } + format.html { redirect_to profile_emails_url, status: :found } format.js { head :ok } end end diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb index 38e3eacd229..c32507756e8 100644 --- a/app/controllers/profiles/gpg_keys_controller.rb +++ b/app/controllers/profiles/gpg_keys_controller.rb @@ -21,7 +21,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController @gpg_key.destroy respond_to do |format| - format.html { redirect_to profile_gpg_keys_url, status: 302 } + format.html { redirect_to profile_gpg_keys_url, status: :found } format.js { head :ok } end end @@ -30,7 +30,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController @gpg_key.revoke respond_to do |format| - format.html { redirect_to profile_gpg_keys_url, status: 302 } + format.html { redirect_to profile_gpg_keys_url, status: :found } format.js { head :ok } end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 12a6cd11f80..6035258667e 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -26,7 +26,7 @@ class Profiles::KeysController < Profiles::ApplicationController Keys::DestroyService.new(current_user).execute(@key) respond_to do |format| - format.html { redirect_to profile_keys_url, status: 302 } + format.html { redirect_to profile_keys_url, status: :found } format.js { head :ok } end end diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index aa9789f8a0f..29ff18a1219 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -78,7 +78,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController def destroy current_user.disable_two_factor! - redirect_to profile_account_path, status: 302 + redirect_to profile_account_path, status: :found end def skip diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 5ab6d103c89..b4f814fd3a4 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -61,7 +61,7 @@ class Projects::ApplicationController < ApplicationController def require_non_empty_project # Be sure to return status code 303 to avoid a double DELETE: # http://api.rubyonrails.org/classes/ActionController/Redirecting.html - redirect_to project_path(@project), status: 303 if @project.empty_repo? + redirect_to project_path(@project), status: :see_other if @project.empty_repo? end def require_branch_head diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index 21a403f3765..a13d552dbd8 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -21,6 +21,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.save - redirect_to edit_project_path(@project), status: 302 + redirect_to edit_project_path(@project), status: :found end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index cd7250b10fc..d1dc9fe9600 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -98,7 +98,7 @@ class Projects::BranchesController < Projects::ApplicationController flash_type = result[:status] == :error ? :alert : :notice flash[flash_type] = result[:message] - redirect_to project_branches_path(@project), status: 303 + redirect_to project_branches_path(@project), status: :see_other end format.js { render nothing: true, status: result[:return_code] } diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 62193257940..358fe59618b 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -62,7 +62,7 @@ class Projects::ClustersController < Projects::ApplicationController def destroy if cluster.destroy flash[:notice] = _('Kubernetes cluster integration was successfully removed.') - redirect_to project_clusters_path(project), status: 302 + redirect_to project_clusters_path(project), status: :found else flash[:notice] = _('Kubernetes cluster integration was not removed.') render :show diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index f43ef2e5f2f..06739d8fd4a 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -35,7 +35,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def update - if deploy_key.update_attributes(update_params) + if deploy_key.update(update_params) flash[:notice] = 'Deploy key was successfully updated.' redirect_to_repository_settings(@project) else diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 27b7425b965..68353e6a210 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,7 +2,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_create_deployment!, only: [:stop] + before_action :authorize_stop_environment!, only: [:stop] before_action :authorize_update_environment!, only: [:edit, :update] before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize] before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics] @@ -116,7 +116,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController set_workhorse_internal_api_content_type render json: Gitlab::Workhorse.terminal_websocket(terminal) else - render text: 'Not found', status: 404 + render text: 'Not found', status: :not_found end end @@ -175,4 +175,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController def environment @environment ||= project.environments.find(params[:id]) end + + def authorize_stop_environment! + access_denied! unless can?(current_user, :stop_environment, environment) + end end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 07249fe3182..a52814e6e52 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -53,7 +53,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController end send_challenges - render plain: "HTTP Basic: Access denied\n", status: 401 + render plain: "HTTP Basic: Access denied\n", status: :unauthorized rescue Gitlab::Auth::MissingPersonalAccessTokenError render_missing_personal_access_token end @@ -83,7 +83,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController render plain: "HTTP Basic: Access denied\n" \ "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}", - status: 401 + status: :unauthorized end def repository diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index f58ee3e9109..bc5f38f3c2b 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -24,7 +24,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - @group_link.update_attributes(group_link_params) + @group_link.update(group_link_params) end def destroy @@ -34,7 +34,7 @@ class Projects::GroupLinksController < Projects::ApplicationController respond_to do |format| format.html do - redirect_to project_project_members_path(project), status: 302 + redirect_to project_project_members_path(project), status: :found end format.js { head :ok } end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 6800d742b0a..2da2aad9b33 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -29,7 +29,7 @@ class Projects::HooksController < Projects::ApplicationController end def update - if hook.update_attributes(hook_params) + if hook.update(hook_params) flash[:notice] = 'Hook was successfully updated.' redirect_to project_settings_integrations_path(@project) else @@ -48,7 +48,7 @@ class Projects::HooksController < Projects::ApplicationController def destroy hook.destroy - redirect_to project_settings_integrations_path(@project), status: 302 + redirect_to project_settings_integrations_path(@project), status: :found end private diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 91016f6494e..21d3c918581 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -39,7 +39,7 @@ class Projects::LabelsController < Projects::ApplicationController else respond_to do |format| format.html { render :new } - format.json { render json: { message: @label.errors.messages }, status: 400 } + format.json { render json: { message: @label.errors.messages }, status: :bad_request } end end end @@ -115,7 +115,7 @@ class Projects::LabelsController < Projects::ApplicationController flash[:notice] = "#{@label.title} promoted to <a href=\"#{group_labels_path(@project.group)}\">group label</a>.".html_safe respond_to do |format| format.html do - redirect_to(project_labels_path(@project), status: 303) + redirect_to(project_labels_path(@project), status: :see_other) end format.json do render json: { url: project_labels_path(@project) } diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 3f4962b543d..c64ccc3d473 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -25,7 +25,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.', documentation_url: "#{Gitlab.config.gitlab.url}/help" }, - status: 501 + status: :not_implemented ) end diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 45c98d60822..dd7e673ec75 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -28,7 +28,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController if store_file!(oid, size) head 200 else - render plain: 'Unprocessable entity', status: 422 + render plain: 'Unprocessable entity', status: :unprocessable_entity end rescue ActiveRecord::RecordInvalid render_lfs_forbidden diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a7c5f858c42..dc6551fc761 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -192,7 +192,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo deployment = environment.first_deployment_for(@merge_request.diff_head_sha) stop_url = - if environment.stop_action? && can?(current_user, :create_deployment, environment) + if can?(current_user, :stop_environment, environment) stop_project_environment_path(project, environment) end @@ -227,7 +227,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo def rebase RebaseWorker.perform_async(@merge_request.id, current_user.id) - render nothing: true, status: 200 + render nothing: true, status: :ok end protected diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 594563d1f6f..5e86ec93f34 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -96,7 +96,7 @@ class Projects::MilestonesController < Projects::ApplicationController Milestones::DestroyService.new(project, current_user).execute(milestone) respond_to do |format| - format.html { redirect_to namespace_project_milestones_path, status: 303 } + format.html { redirect_to namespace_project_milestones_path, status: :see_other } format.js { head :ok } end end diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb index 5698ff4e706..3b24d231f3d 100644 --- a/app/controllers/projects/mirrors_controller.rb +++ b/app/controllers/projects/mirrors_controller.rb @@ -13,7 +13,7 @@ class Projects::MirrorsController < Projects::ApplicationController end def update - if project.update_attributes(mirror_params) + if project.update(mirror_params) flash[:notice] = 'Mirroring settings were successfully updated.' else flash[:alert] = project.errors.full_messages.join(', ').html_safe diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb index fa258f3d9af..aeda7b3edf5 100644 --- a/app/controllers/projects/pipeline_schedules_controller.rb +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -64,7 +64,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController def destroy if schedule.destroy - redirect_to pipeline_schedules_path(@project), status: 302 + redirect_to pipeline_schedules_path(@project), status: :found else redirect_to pipeline_schedules_path(@project), status: :forbidden, diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 3e0a530fdb9..19e09b3af6f 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -14,7 +14,7 @@ class Projects::ReleasesController < Projects::ApplicationController # it exists only to save a description to each Tag. # If description is empty we should destroy the existing record. if release_params[:description].present? - release.update_attributes(release_params) + release.update(release_params) else release.destroy end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index d01f324e6fd..ecb2ece7532 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -24,7 +24,7 @@ class Projects::RepositoriesController < Projects::ApplicationController send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha rescue => ex logger.error("#{self.class.name}: #{ex}") - return git_not_found! + git_not_found! end def assign_archive_vars diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb index a080724634b..c098c82081e 100644 --- a/app/controllers/projects/runner_projects_controller.rb +++ b/app/controllers/projects/runner_projects_controller.rb @@ -21,6 +21,6 @@ class Projects::RunnerProjectsController < Projects::ApplicationController runner_project = project.runner_projects.find(params[:id]) runner_project.destroy - redirect_to project_runners_path(project), status: 302 + redirect_to project_runners_path(project), status: :found end end diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index bef94cea989..cc7cce887bf 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -24,7 +24,7 @@ class Projects::RunnersController < Projects::ApplicationController @runner.destroy end - redirect_to project_runners_path(@project), status: 302 + redirect_to project_runners_path(@project), status: :found end def resume diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 690596b12db..d55046047ae 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -34,7 +34,7 @@ class Projects::ServicesController < Projects::ApplicationController private def service_test_response - if @service.update_attributes(service_params[:service]) + if @service.update(service_params[:service]) data = @service.test_data(project, current_user) outcome = @service.test(data) diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 208a1d19862..f742d7edf83 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -82,7 +82,7 @@ class Projects::SnippetsController < Projects::ApplicationController @snippet.destroy - redirect_to project_snippets_path(@project), status: 302 + redirect_to project_snippets_path(@project), status: :found end protected diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index b62d7d9b7c5..b17753222a0 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -50,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController respond_to do |format| if result[:status] == :success format.html do - redirect_to project_tags_path(@project), status: 303 + redirect_to project_tags_path(@project), status: :see_other end format.js diff --git a/app/controllers/projects/templates_controller.rb b/app/controllers/projects/templates_controller.rb index 694b468c8d3..52d6fb82093 100644 --- a/app/controllers/projects/templates_controller.rb +++ b/app/controllers/projects/templates_controller.rb @@ -14,6 +14,6 @@ class Projects::TemplatesController < Projects::ApplicationController def get_template_class template_types = { issue: Gitlab::Template::IssueTemplate, merge_request: Gitlab::Template::MergeRequestTemplate }.with_indifferent_access @template_type = template_types[params[:template_type]] - render json: [], status: 404 unless @template_type + render json: [], status: :not_found unless @template_type end end diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 93fb9da6510..a41fcb85c40 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -1,13 +1,19 @@ class Projects::TodosController < Projects::ApplicationController - include Gitlab::Utils::StrongMemoize - include TodosActions - before_action :authenticate_user!, only: [:create] + def create + todo = TodoService.new.mark_todo(issuable, current_user) + + render json: { + count: TodosFinder.new(current_user, state: :pending).execute.count, + delete_path: dashboard_todo_path(todo) + } + end + private def issuable - strong_memoize(:issuable) do + @issuable ||= begin case params[:issuable_type] when "issue" IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index e04145dd0b3..6f3de43f85a 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController flash[:alert] = "Could not remove the trigger." end - redirect_to project_settings_ci_cd_path(@project), status: 302 + redirect_to project_settings_ci_cd_path(@project), status: :found end private diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index aa844e94d89..c01066c688a 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -120,7 +120,7 @@ class Projects::WikisController < Projects::ApplicationController rescue ProjectWiki::CouldNotCreateWikiError flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." redirect_to project_path(@project) - return false + false end def wiki_params @@ -129,7 +129,7 @@ class Projects::WikisController < Projects::ApplicationController def build_page(args) WikiPage.new(@project_wiki).tap do |page| - page.update_attributes(args) + page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases end end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f2abe27f60e..9d1c44db137 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -133,7 +133,7 @@ class ProjectsController < Projects::ApplicationController ::Projects::DestroyService.new(@project, current_user, {}).async_execute flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name } - redirect_to dashboard_projects_path, status: 302 + redirect_to dashboard_projects_path, status: :found rescue Projects::DestroyService::DestroyError => ex redirect_to edit_project_path(@project), status: 302, alert: ex.message end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 1de6ae24622..9dd652206fe 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -32,8 +32,8 @@ class SessionsController < Devise::SessionsController super do |resource| # User has successfully signed in, so clear any unused reset token if resource.reset_password_token.present? - resource.update_attributes(reset_password_token: nil, - reset_password_sent_at: nil) + resource.update(reset_password_token: nil, + reset_password_sent_at: nil) end # hide the signed-in notification diff --git a/app/controllers/sherlock/transactions_controller.rb b/app/controllers/sherlock/transactions_controller.rb index cb6c3a7cd98..ae4953c3259 100644 --- a/app/controllers/sherlock/transactions_controller.rb +++ b/app/controllers/sherlock/transactions_controller.rb @@ -13,7 +13,7 @@ module Sherlock def destroy_all Gitlab::Sherlock.collection.clear - redirect_to :back, status: 302 + redirect_to :back, status: :found end end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 3d51520ddf4..1d6d0943674 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -89,7 +89,7 @@ class SnippetsController < ApplicationController @snippet.destroy - redirect_to snippets_path, status: 302 + redirect_to snippets_path, status: :found end protected diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 2156413fb26..09e2c586f2a 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -15,7 +15,6 @@ class TodosFinder prepend FinderWithCrossProjectAccess include FinderMethods - include Gitlab::Utils::StrongMemoize requires_cross_project_access unless: -> { project? } @@ -35,11 +34,9 @@ class TodosFinder items = by_author(items) items = by_state(items) items = by_type(items) - items = by_group(items) # Filtering by project HAS TO be the last because we use # the project IDs yielded by the todos query thus far items = by_project(items) - items = visible_to_user(items) sort(items) end @@ -85,10 +82,6 @@ class TodosFinder params[:project_id].present? end - def group? - params[:group_id].present? - end - def project return @project if defined?(@project) @@ -107,14 +100,18 @@ class TodosFinder @project end - def group - strong_memoize(:group) do - Group.find(params[:group_id]) + def project_ids(items) + ids = items.except(:order).select(:project_id) + if Gitlab::Database.mysql? + # To make UPDATE work on MySQL, wrap it in a SELECT with an alias + ids = Todo.except(:order).select('*').from("(#{ids.to_sql}) AS t") end + + ids end def type? - type.present? && %w(Issue MergeRequest Epic).include?(type) + type.present? && %w(Issue MergeRequest).include?(type) end def type @@ -151,37 +148,12 @@ class TodosFinder def by_project(items) if project? - items = items.where(project: project) - end - - items - end + items.where(project: project) + else + projects = Project.public_or_visible_to_user(current_user) - def by_group(items) - if group? - groups = group.self_and_descendants - items = items.where( - 'project_id IN (?) OR group_id IN (?)', - Project.where(group: groups).select(:id), - groups.select(:id) - ) + items.joins(:project).merge(projects) end - - items - end - - def visible_to_user(items) - projects = Project.public_or_visible_to_user(current_user) - groups = Group.public_or_visible_to_user(current_user) - - items - .joins('LEFT JOIN namespaces ON namespaces.id = todos.group_id') - .joins('LEFT JOIN projects ON projects.id = todos.project_id') - .where( - 'project_id IN (?) OR group_id IN (?)', - projects.select(:id), - groups.select(:id) - ) end def by_state(items) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index ef1bf283d0c..358b896702b 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -251,6 +251,7 @@ module ApplicationSettingsHelper :user_oauth_applications, :version_check_enabled, :allow_local_requests_from_hooks_and_services, + :hide_third_party_offers, :enforce_terms, :terms, :mirror_available diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index c24d340d184..8fd0b6f14c6 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -4,6 +4,7 @@ module ClustersHelper end def render_gcp_signup_offer + return if Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? return unless show_gcp_signup_offer? content_tag :section, class: 'no-animate expanded' do diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 7bbdc798ddd..8766bb43cac 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -131,19 +131,6 @@ module IssuablesHelper end end - def group_dropdown_label(group_id, default_label) - return default_label if group_id.nil? - return "Any group" if group_id == "0" - - group = ::Group.find_by(id: group_id) - - if group - group.full_name - else - default_label - end - end - def milestone_dropdown_label(milestone_title, default_label = "Milestone") title = case milestone_title diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 9be93fa69ae..9008db1b300 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -3,7 +3,7 @@ module NamespacesHelper params.dig(:project, :namespace_id) || params[:namespace_id] end - def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) + def namespaces_options(selected = :current_user, display_path: false, extra_group: nil, groups_only: false) groups = current_user.manageable_groups .joins(:route) .includes(:route) @@ -20,10 +20,13 @@ module NamespacesHelper options = [] options << options_for_group(groups, display_path: display_path, type: 'group') - options << options_for_group(users, display_path: display_path, type: 'user') - if selected == :current_user && current_user.namespace - selected = current_user.namespace.id + unless groups_only + options << options_for_group(users, display_path: display_path, type: 'user') + + if selected == :current_user && current_user.namespace + selected = current_user.namespace.id + end end grouped_options_for_select(options, selected) diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb index 6edaf78de1b..4b9f6bd2caf 100644 --- a/app/helpers/pipeline_schedules_helper.rb +++ b/app/helpers/pipeline_schedules_helper.rb @@ -3,7 +3,7 @@ module PipelineSchedulesHelper ActiveSupport::TimeZone.all.map do |timezone| { name: timezone.name, - offset: timezone.utc_offset, + offset: timezone.now.utc_offset, identifier: timezone.tzinfo.identifier } end diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb index 271e839692a..336385f6798 100644 --- a/app/helpers/time_helper.rb +++ b/app/helpers/time_helper.rb @@ -5,9 +5,13 @@ module TimeHelper seconds = interval_in_seconds - minutes * 60 if minutes >= 1 - "#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}" + if seconds % 60 == 0 + pluralize(minutes, "minute") + else + [pluralize(minutes, "minute"), pluralize(seconds, "second")].to_sentence + end else - "#{pluralize(seconds, "second")}" + pluralize(seconds, "second") end end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 7cd74358168..f7620e0b6b8 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -43,7 +43,7 @@ module TodosHelper project_commit_path(todo.project, todo.target, anchor: anchor) else - path = [todo.parent, todo.target] + path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target] path.unshift(:pipelines) if todo.build_failed? @@ -167,12 +167,4 @@ module TodosHelper def show_todo_state?(todo) (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state) end - - def todo_group_options - groups = current_user.authorized_groups.map do |group| - { id: group.id, text: group.full_name } - end - - groups.unshift({ id: '', text: 'Any Group' }).to_json - end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index bddeb8b0352..f770b219422 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -294,6 +294,7 @@ class ApplicationSetting < ActiveRecord::Base gitaly_timeout_medium: 30, gitaly_timeout_default: 55, allow_local_requests_from_hooks_and_services: false, + hide_third_party_offers: false, mirror_available: true } end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 19949f83351..db86400128c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -371,7 +371,7 @@ module Ci def update_coverage coverage = trace.extract_coverage(coverage_regex) - update_attributes(coverage: coverage) if coverage.present? + update(coverage: coverage) if coverage.present? end def parse_trace_sections! @@ -437,9 +437,9 @@ module Ci end def artifacts_metadata_entry(path, **options) - artifacts_metadata.use_file do |metadata_path| + artifacts_metadata.open do |metadata_stream| metadata = Gitlab::Ci::Build::Artifacts::Metadata.new( - metadata_path, + metadata_stream, path, **options) @@ -653,6 +653,7 @@ module Ci variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_COMMIT_SHA', value: sha) + variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 7a459078151..b93c1145f82 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -243,12 +243,6 @@ module Issuable opened? end - def overdue? - return false unless respond_to?(:due_date) - - due_date.try(:past?) || false - end - def user_notes_count if notes.loaded? # Use the in-memory association to select and count to avoid hitting the db diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index 94eef4ff7cd..dbe8d31de37 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -23,7 +23,7 @@ module ProtectedRef # If we don't `protected_branch` or `protected_tag` would be empty and # `project` cannot be delegated to it, which in turn would cause validations # to fail. - has_many :"#{type}_access_levels", inverse_of: self.model_name.singular # rubocop:disable Cop/ActiveRecordDependent + has_many :"#{type}_access_levels", inverse_of: self.model_name.singular validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." } diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb index e3a7f2d5498..71b0c3468b9 100644 --- a/app/models/concerns/protected_ref_access.rb +++ b/app/models/concerns/protected_ref_access.rb @@ -2,19 +2,20 @@ module ProtectedRefAccess extend ActiveSupport::Concern ALLOWED_ACCESS_LEVELS = [ - Gitlab::Access::MASTER, + Gitlab::Access::MAINTAINER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS ].freeze HUMAN_ACCESS_LEVELS = { - Gitlab::Access::MASTER => "Maintainers".freeze, + Gitlab::Access::MAINTAINER => "Maintainers".freeze, Gitlab::Access::DEVELOPER => "Developers + Maintainers".freeze, Gitlab::Access::NO_ACCESS => "No one".freeze }.freeze included do - scope :master, -> { where(access_level: Gitlab::Access::MASTER) } + scope :master, -> { maintainer } # @deprecated + scope :maintainer, -> { where(access_level: Gitlab::Access::MAINTAINER) } scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) } validates :access_level, presence: true, if: :role?, inclusion: { diff --git a/app/models/concerns/select_for_project_authorization.rb b/app/models/concerns/select_for_project_authorization.rb index 58194b0ea13..7af0fdbd618 100644 --- a/app/models/concerns/select_for_project_authorization.rb +++ b/app/models/concerns/select_for_project_authorization.rb @@ -6,8 +6,11 @@ module SelectForProjectAuthorization select("projects.id AS project_id, members.access_level") end - def select_as_master_for_project_authorization - select(["projects.id AS project_id", "#{Gitlab::Access::MASTER} AS access_level"]) + def select_as_maintainer_for_project_authorization + select(["projects.id AS project_id", "#{Gitlab::Access::MAINTAINER} AS access_level"]) end + + # @deprecated + alias_method :select_as_master_for_project_authorization, :select_as_maintainer_for_project_authorization end end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index ac86e9e8de0..687246b47b2 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -92,10 +92,6 @@ class Deployment < ActiveRecord::Base @stop_action ||= manual_actions.find_by(name: on_stop) end - def stop_action? - stop_action.present? - end - def formatted_deployment_time created_at.to_time.in_time_zone.to_s(:medium) end diff --git a/app/models/environment.rb b/app/models/environment.rb index 8d523dae324..4856d313318 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -117,7 +117,7 @@ class Environment < ActiveRecord::Base external_url.gsub(%r{\A.*?://}, '') end - def stop_action? + def stop_action_available? available? && stop_action.present? end diff --git a/app/models/group.rb b/app/models/group.rb index b0392774379..ddebaff50b0 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -39,8 +39,6 @@ class Group < Namespace has_many :boards has_many :badges, class_name: 'GroupBadge' - has_many :todos - accepts_nested_attributes_for :variables, allow_destroy: true validate :visibility_level_allowed_by_projects @@ -84,12 +82,6 @@ class Group < Namespace where(id: user.authorized_groups.select(:id).reorder(nil)) end - def public_or_visible_to_user(user) - where('id IN (?) OR namespaces.visibility_level IN (?)', - user.authorized_groups.select(:id), - Gitlab::VisibilityLevel.levels_for_user(user)) - end - def select_for_project_authorization if current_scope.joins_values.include?(:shared_projects) joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id') @@ -186,10 +178,13 @@ class Group < Namespace add_user(user, :developer, current_user: current_user) end - def add_master(user, current_user = nil) - add_user(user, :master, current_user: current_user) + def add_maintainer(user, current_user = nil) + add_user(user, :maintainer, current_user: current_user) end + # @deprecated + alias_method :add_master, :add_maintainer + def add_owner(user, current_user = nil) add_user(user, :owner, current_user: current_user) end @@ -206,12 +201,15 @@ class Group < Namespace members_with_parents.owners.where(user_id: user).any? end - def has_master?(user) + def has_maintainer?(user) return false unless user - members_with_parents.masters.where(user_id: user).any? + members_with_parents.maintainers.where(user_id: user).any? end + # @deprecated + alias_method :has_master?, :has_maintainer? + # Check if user is a last owner of the group. # Parent owners are ignored for nested groups. def last_owner?(user) diff --git a/app/models/issue.rb b/app/models/issue.rb index 983684a5e05..4715d942c8d 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -275,6 +275,10 @@ class Issue < ActiveRecord::Base user ? readable_by?(user) : publicly_visible? end + def overdue? + due_date.try(:past?) || false + end + def check_for_spam? project.public? && (title_changed? || description_changed?) end diff --git a/app/models/member.rb b/app/models/member.rb index 68572f2e33a..00a13a279a9 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -69,9 +69,11 @@ class Member < ActiveRecord::Base scope :guests, -> { active.where(access_level: GUEST) } scope :reporters, -> { active.where(access_level: REPORTER) } scope :developers, -> { active.where(access_level: DEVELOPER) } - scope :masters, -> { active.where(access_level: MASTER) } + scope :maintainers, -> { active.where(access_level: MAINTAINER) } + scope :masters, -> { maintainers } # @deprecated scope :owners, -> { active.where(access_level: OWNER) } - scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) } + scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) } + scope :owners_and_masters, -> { owners_and_maintainers } # @deprecated scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) } scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) } diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 024106056b4..4f27d0aeaf8 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -17,19 +17,19 @@ class ProjectMember < Member # Add users to projects with passed access option # # access can be an integer representing a access code - # or symbol like :master representing role + # or symbol like :maintainer representing role # # Ex. # add_users_to_projects( # project_ids, # user_ids, - # ProjectMember::MASTER + # ProjectMember::MAINTAINER # ) # # add_users_to_projects( # project_ids, # user_ids, - # :master + # :maintainer # ) # def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil) diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb index 22d48c9e661..d667948deae 100644 --- a/app/models/network/commit.rb +++ b/app/models/network/commit.rb @@ -11,8 +11,8 @@ module Network @parent_spaces = [] end - def method_missing(m, *args, &block) - @commit.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend + def method_missing(msg, *args, &block) + @commit.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend end def space diff --git a/app/models/note.rb b/app/models/note.rb index 3918bbee194..abc40d9016e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -229,10 +229,6 @@ class Note < ActiveRecord::Base !for_personal_snippet? end - def for_issuable? - for_issue? || for_merge_request? - end - def skip_project_check? !for_project_noteable? end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 9195408551f..1933c46ee44 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -32,6 +32,7 @@ class NotificationSetting < ActiveRecord::Base :reopen_issue, :close_issue, :reassign_issue, + :issue_due, :new_merge_request, :push_to_merge_request, :reopen_merge_request, diff --git a/app/models/project.rb b/app/models/project.rb index 770262f6193..e29bca365a4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -269,7 +269,8 @@ class Project < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true delegate :add_user, :add_users, to: :team - delegate :add_guest, :add_reporter, :add_developer, :add_master, :add_role, to: :team + delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team + delegate :add_master, to: :team # @deprecated delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings # Validations @@ -367,8 +368,10 @@ class Project < ActiveRecord::Base chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 validates :build_timeout, allow_nil: true, - numericality: { greater_than_or_equal_to: 600, - message: 'needs to be at least 10 minutes' } + numericality: { greater_than_or_equal_to: 10.minutes, + less_than: 1.month, + only_integer: true, + message: 'needs to be beetween 10 minutes and 1 month' } # Returns a collection of projects that is either public or visible to the # logged in user. @@ -1647,10 +1650,10 @@ class Project < ActiveRecord::Base params = { name: default_branch, push_access_levels_attributes: [{ - access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER }], merge_access_levels_attributes: [{ - access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER }] } diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index ac1e9ab2b0b..cf8fc41e870 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -4,7 +4,8 @@ class ProjectGroupLink < ActiveRecord::Base GUEST = 10 REPORTER = 20 DEVELOPER = 30 - MASTER = 40 + MAINTAINER = 40 + MASTER = MAINTAINER # @deprecated belongs_to :project belongs_to :group diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 9a38806baab..c7d0f49d837 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -19,10 +19,13 @@ class ProjectTeam add_user(user, :developer, current_user: current_user) end - def add_master(user, current_user: nil) - add_user(user, :master, current_user: current_user) + def add_maintainer(user, current_user: nil) + add_user(user, :maintainer, current_user: current_user) end + # @deprecated + alias_method :add_master, :add_maintainer + def add_role(user, role, current_user: nil) public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend end @@ -81,10 +84,13 @@ class ProjectTeam @developers ||= fetch_members(Gitlab::Access::DEVELOPER) end - def masters - @masters ||= fetch_members(Gitlab::Access::MASTER) + def maintainers + @maintainers ||= fetch_members(Gitlab::Access::MAINTAINER) end + # @deprecated + alias_method :masters, :maintainers + def owners @owners ||= if group @@ -136,10 +142,13 @@ class ProjectTeam max_member_access(user.id) == Gitlab::Access::DEVELOPER end - def master?(user) - max_member_access(user.id) == Gitlab::Access::MASTER + def maintainer?(user) + max_member_access(user.id) == Gitlab::Access::MAINTAINER end + # @deprecated + alias_method :master?, :maintainer? + # Checks if `user` is authorized for this project, with at least the # `min_access_level` (if given). def member?(user, min_access_level = Gitlab::Access::GUEST) diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index a6f94b3e3b0..9ae2fb0013a 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -20,7 +20,6 @@ class ProjectWiki @user = user end - delegate :empty?, to: :pages delegate :repository_storage, :hashed_storage?, to: :project def path @@ -74,6 +73,10 @@ class ProjectWiki !!find_page('home') end + def empty? + pages(limit: 1).empty? + end + # Returns an Array of Gitlab WikiPage instances or an # empty Array if this Wiki has no pages. def pages(limit: nil) @@ -107,7 +110,7 @@ class ProjectWiki update_project_activity rescue Gitlab::Git::Wiki::DuplicatePageError => e @error_message = "Duplicate page: #{e.message}" - return false + false end def update_page(page, content:, title: nil, format: :markdown, message: nil) diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index c4b5dd2dc96..976b501e297 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -57,7 +57,7 @@ class RemoteMirror < ActiveRecord::Base Gitlab::Metrics.add_event(:remote_mirrors_finished, path: remote_mirror.project.full_path) timestamp = Time.now - remote_mirror.update_attributes!( + remote_mirror.update!( last_update_at: timestamp, last_successful_update_at: timestamp, last_error: nil ) end diff --git a/app/models/repository.rb b/app/models/repository.rb index 7cd600fec5b..a96c73e6ab7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -174,8 +174,8 @@ class Repository CommitCollection.new(project, commits, ref) end - def find_branch(name, fresh_repo: true) - raw_repository.find_branch(name, fresh_repo) + def find_branch(name) + raw_repository.find_branch(name) end def find_tag(name) @@ -462,12 +462,12 @@ class Repository expire_branches_cache end - def method_missing(m, *args, &block) - if m == :lookup && !block_given? - lookup_cache[m] ||= {} - lookup_cache[m][args.join(":")] ||= raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend + def method_missing(msg, *args, &block) + if msg == :lookup && !block_given? + lookup_cache[msg] ||= {} + lookup_cache[msg][args.join(":")] ||= raw_repository.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend else - raw_repository.__send__(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend + raw_repository.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 942cbb754e3..a2ab405fdbe 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -22,18 +22,15 @@ class Todo < ActiveRecord::Base belongs_to :author, class_name: "User" belongs_to :note belongs_to :project - belongs_to :group belongs_to :target, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations belongs_to :user delegate :name, :email, to: :author, prefix: true, allow_nil: true - validates :action, :target_type, :user, presence: true + validates :action, :project, :target_type, :user, presence: true validates :author, presence: true validates :target_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - validates :project, presence: true, unless: :group_id - validates :group, presence: true, unless: :project_id scope :pending, -> { with_state(:pending) } scope :done, -> { with_state(:done) } @@ -47,7 +44,7 @@ class Todo < ActiveRecord::Base state :done end - after_save :keep_around_commit, if: :commit_id + after_save :keep_around_commit class << self # Priority sorting isn't displayed in the dropdown, because we don't show @@ -82,10 +79,6 @@ class Todo < ActiveRecord::Base end end - def parent - project - end - def unmergeable? action == UNMERGEABLE end diff --git a/app/models/user.rb b/app/models/user.rb index 27a5d0278b7..4987d01aac6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -99,7 +99,8 @@ class User < ActiveRecord::Base has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember' has_many :groups, through: :group_members has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group - has_many :masters_groups, -> { where(members: { access_level: Gitlab::Access::MASTER }) }, through: :group_members, source: :group + has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group + alias_attribute :masters_groups, :maintainers_groups # Projects has_many :groups_projects, through: :groups, source: :projects @@ -496,7 +497,7 @@ class User < ActiveRecord::Base def disable_two_factor! transaction do - update_attributes( + update( otp_required_for_login: false, encrypted_otp_secret: nil, encrypted_otp_secret_iv: nil, @@ -728,7 +729,7 @@ class User < ActiveRecord::Base end def several_namespaces? - owned_groups.any? || masters_groups.any? + owned_groups.any? || maintainers_groups.any? end def namespace_id @@ -974,15 +975,15 @@ class User < ActiveRecord::Base end def manageable_groups - union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), masters_groups.select(:id)]).to_sql + union_sql = Gitlab::SQL::Union.new([owned_groups.select(:id), maintainers_groups.select(:id)]).to_sql # Update this line to not use raw SQL when migrated to Rails 5.2. # Either ActiveRecord or Arel constructions are fine. # This was replaced with the raw SQL construction because of bugs in the arel gem. # Bugs were fixed in arel 9.0.0 (Rails 5.2). - owned_and_master_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection + owned_and_maintainer_groups = Group.where("namespaces.id IN (#{union_sql})") # rubocop:disable GitlabSecurity/SqlInjection - Gitlab::GroupHierarchy.new(owned_and_master_groups).base_and_descendants + Gitlab::GroupHierarchy.new(owned_and_maintainer_groups).base_and_descendants end def namespaces @@ -1023,11 +1024,11 @@ class User < ActiveRecord::Base def ci_owned_runners @ci_owned_runners ||= begin project_runner_ids = Ci::RunnerProject - .where(project: authorized_projects(Gitlab::Access::MASTER)) + .where(project: authorized_projects(Gitlab::Access::MAINTAINER)) .select(:runner_id) group_runner_ids = Ci::RunnerNamespace - .where(namespace_id: owned_or_masters_groups.select(:id)) + .where(namespace_id: owned_or_maintainers_groups.select(:id)) .select(:runner_id) union = Gitlab::SQL::Union.new([project_runner_ids, group_runner_ids]) @@ -1053,7 +1054,7 @@ class User < ActiveRecord::Base return @global_notification_setting if defined?(@global_notification_setting) @global_notification_setting = notification_settings.find_or_initialize_by(source: nil) - @global_notification_setting.update_attributes(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted? + @global_notification_setting.update(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted? @global_notification_setting end @@ -1236,11 +1237,14 @@ class User < ActiveRecord::Base !terms_accepted? end - def owned_or_masters_groups - union = Gitlab::SQL::Union.new([owned_groups, masters_groups]) + def owned_or_maintainers_groups + union = Gitlab::SQL::Union.new([owned_groups, maintainers_groups]) Group.from("(#{union.to_sql}) namespaces") end + # @deprecated + alias_method :owned_or_masters_groups, :owned_or_maintainers_groups + protected # override, from Devise::Validatable @@ -1333,8 +1337,8 @@ class User < ActiveRecord::Base end end - def self.unique_internal(scope, username, email_pattern, &b) - scope.first || create_unique_internal(scope, username, email_pattern, &b) + def self.unique_internal(scope, username, email_pattern, &block) + scope.first || create_unique_internal(scope, username, email_pattern, &block) end def self.create_unique_internal(scope, username, email_pattern, &creation_block) diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index cde79b95062..4b49edb01a5 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -1,3 +1,4 @@ +# rubocop:disable Rails/ActiveRecordAliases class WikiPage PageChangedError = Class.new(StandardError) PageRenameError = Class.new(StandardError) diff --git a/app/policies/clusters/cluster_policy.rb b/app/policies/clusters/cluster_policy.rb index 1f7c13072b9..b5b24491655 100644 --- a/app/policies/clusters/cluster_policy.rb +++ b/app/policies/clusters/cluster_policy.rb @@ -4,7 +4,7 @@ module Clusters delegate { cluster.project } - rule { can?(:master_access) }.policy do + rule { can?(:maintainer_access) }.policy do enable :update_cluster enable :admin_cluster end diff --git a/app/policies/deploy_token_policy.rb b/app/policies/deploy_token_policy.rb index 7aa9106e8b1..d1b459cfc90 100644 --- a/app/policies/deploy_token_policy.rb +++ b/app/policies/deploy_token_policy.rb @@ -1,10 +1,10 @@ class DeployTokenPolicy < BasePolicy with_options scope: :subject, score: 0 - condition(:master) { @subject.project.team.master?(@user) } + condition(:maintainer) { @subject.project.team.maintainer?(@user) } rule { anonymous }.prevent_all - rule { master }.policy do + rule { maintainer }.policy do enable :create_deploy_token enable :update_deploy_token end diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb index 375a5535359..2d07311db72 100644 --- a/app/policies/environment_policy.rb +++ b/app/policies/environment_policy.rb @@ -1,9 +1,14 @@ class EnvironmentPolicy < BasePolicy delegate { @subject.project } - condition(:stop_action_allowed) do - @subject.stop_action? && can?(:update_build, @subject.stop_action) + condition(:stop_with_deployment_allowed) do + @subject.stop_action_available? && + can?(:create_deployment) && can?(:update_build, @subject.stop_action) end - rule { can?(:create_deployment) & stop_action_allowed }.enable :stop_environment + condition(:stop_with_update_allowed) do + !@subject.stop_action_available? && can?(:update_environment, @subject) + end + + rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index ded9fe30eff..dc339b71ec7 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -11,7 +11,7 @@ class GroupPolicy < BasePolicy condition(:guest) { access_level >= GroupMember::GUEST } condition(:developer) { access_level >= GroupMember::DEVELOPER } condition(:owner) { access_level >= GroupMember::OWNER } - condition(:master) { access_level >= GroupMember::MASTER } + condition(:maintainer) { access_level >= GroupMember::MAINTAINER } condition(:reporter) { access_level >= GroupMember::REPORTER } condition(:nested_groups_supported, scope: :global) { Group.supports_nested_groups? } @@ -59,7 +59,7 @@ class GroupPolicy < BasePolicy enable :admin_issue end - rule { master }.policy do + rule { maintainer }.policy do enable :create_projects enable :admin_pipeline enable :admin_build diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 199bcf92b21..bc49092633f 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -46,7 +46,7 @@ class ProjectPolicy < BasePolicy condition(:developer) { team_access_level >= Gitlab::Access::DEVELOPER } desc "User has maintainer access" - condition(:master) { team_access_level >= Gitlab::Access::MASTER } + condition(:maintainer) { team_access_level >= Gitlab::Access::MAINTAINER } desc "Project is public" condition(:public_project, scope: :subject, score: 0) { project.public? } @@ -123,14 +123,14 @@ class ProjectPolicy < BasePolicy rule { guest }.enable :guest_access rule { reporter }.enable :reporter_access rule { developer }.enable :developer_access - rule { master }.enable :master_access + rule { maintainer }.enable :maintainer_access rule { owner | admin }.enable :owner_access rule { can?(:owner_access) }.policy do enable :guest_access enable :reporter_access enable :developer_access - enable :master_access + enable :maintainer_access enable :change_namespace enable :change_visibility_level @@ -228,7 +228,7 @@ class ProjectPolicy < BasePolicy enable :create_deployment end - rule { can?(:master_access) }.policy do + rule { can?(:maintainer_access) }.policy do enable :push_to_delete_protected_branch enable :update_project_snippet enable :update_environment diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index ba0ae6ba8a0..83558fc6659 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -7,7 +7,7 @@ class EnvironmentEntity < Grape::Entity expose :external_url expose :environment_type expose :last_deployment, using: DeploymentEntity - expose :stop_action? + expose :stop_action_available?, as: :has_stop_action expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment| metrics_project_environment_path(environment.project, environment) @@ -31,4 +31,14 @@ class EnvironmentEntity < Grape::Entity end expose :created_at, :updated_at + + expose :can_stop do |environment| + environment.available? && can?(current_user, :stop_environment, environment) + end + + private + + def current_user + request.current_user + end end diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 5d72ebdd7fd..a78bd77cf7c 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -10,9 +10,15 @@ class MergeRequestWidgetEntity < IssuableEntity expose :merge_when_pipeline_succeeds expose :source_branch expose :source_project_id + expose :source_project_full_path do |merge_request| + merge_request.source_project&.full_path + end expose :squash expose :target_branch expose :target_project_id + expose :target_project_full_path do |merge_request| + merge_request.project&.full_path + end expose :allow_collaboration expose :should_be_rebased?, as: :should_be_rebased diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb index 46e19230328..2a337918d21 100644 --- a/app/services/access_token_validation_service.rb +++ b/app/services/access_token_validation_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AccessTokenValidationService # Results: VALID = :valid diff --git a/app/services/after_branch_delete_service.rb b/app/services/after_branch_delete_service.rb index 227e9ea9c6d..e7eb74d3e7d 100644 --- a/app/services/after_branch_delete_service.rb +++ b/app/services/after_branch_delete_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # Branch can be deleted either by DeleteBranchService # or by GitPushService. diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb index 0521393dd27..82ae66ab0f5 100644 --- a/app/services/akismet_service.rb +++ b/app/services/akismet_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AkismetService attr_accessor :owner, :text, :options diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index 5ad9a50687c..4c5e22bdd7e 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AuditEventService def initialize(author, entity, details = {}) @author, @entity, @details = author, entity, details diff --git a/app/services/badges/update_service.rb b/app/services/badges/update_service.rb index 7ca84b5df31..495a4a2c99d 100644 --- a/app/services/badges/update_service.rb +++ b/app/services/badges/update_service.rb @@ -3,7 +3,7 @@ module Badges # returns the updated badge def execute(badge) if params.present? - badge.update_attributes(params) + badge.update(params) end badge diff --git a/app/services/base_count_service.rb b/app/services/base_count_service.rb index 975e288301c..ad1647842b8 100644 --- a/app/services/base_count_service.rb +++ b/app/services/base_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for services that count a single resource such as the number of # issues for a project. class BaseCountService diff --git a/app/services/base_renderer.rb b/app/services/base_renderer.rb index d6e30bd7008..30a6e8c62dd 100644 --- a/app/services/base_renderer.rb +++ b/app/services/base_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseRenderer attr_reader :current_user diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 3519b7c5e7d..3e968c8f707 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseService include Gitlab::Allowable diff --git a/app/services/ci/stop_environments_service.rb b/app/services/ci/stop_environments_service.rb index 43c9a065fcf..439746e82bd 100644 --- a/app/services/ci/stop_environments_service.rb +++ b/app/services/ci/stop_environments_service.rb @@ -8,7 +8,7 @@ module Ci return unless @ref.present? environments.each do |environment| - next unless environment.stop_action? + next unless environment.stop_action_available? next unless can?(current_user, :stop_environment, environment) environment.stop_with_action!(current_user) diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb index 6781533af28..7a14e97f749 100644 --- a/app/services/cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CohortsService MONTHS_INCLUDED = 12 diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index b9d0173a2d0..1ce6ab36cbf 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -13,8 +13,6 @@ module Commits # rubocop:disable GitlabSecurity/PublicSend message = @commit.public_send(:"#{action}_message", current_user) - - # rubocop:disable GitlabSecurity/PublicSend repository.public_send( action, current_user, diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 2a69a205629..3adf8a0c1a1 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' # Compare 2 refs for one repo or between repositories diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 9b1a4d960e2..65208b07e27 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateBranchService < BaseService def execute(branch_name, ref) create_master_branch if project.empty_repo? diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 7e5a77fb056..bb3f605da28 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateDeploymentService attr_reader :job diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb index 54ff1f74126..09c68390007 100644 --- a/app/services/create_release_service.rb +++ b/app/services/create_release_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateReleaseService < BaseService def execute(tag_name, release_description) repository = project.repository diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb index 40286dbf3bf..6f1fce4989e 100644 --- a/app/services/create_snippet_service.rb +++ b/app/services/create_snippet_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateSnippetService < BaseService include SpamCheckService diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index e1499dcee64..44252f7b0a6 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeleteBranchService < BaseService def execute(branch_name) repository = project.repository diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb index c98d1e3c540..ff3e4783fe3 100644 --- a/app/services/delete_merged_branches_service.rb +++ b/app/services/delete_merged_branches_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeleteMergedBranchesService < BaseService def async_execute DeleteMergedBranchesWorker.perform_async(project.id, current_user.id) diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 44dc90b3462..e7464fd9d5f 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # EventCreateService class # # Used for creating events feed on dashboard after certain user action diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f3bfc53dcd3..29c8ce5fea3 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GitPushService < BaseService attr_accessor :push_data, :push_commits include Gitlab::Access diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 9917a39b795..3ff2d1d107d 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GitTagPushService < BaseService attr_accessor :push_data diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb index c6e52c3bb91..2a7a5dae291 100644 --- a/app/services/gravatar_service.rb +++ b/app/services/gravatar_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GravatarService def execute(email, size = nil, scale = 2, username: nil) return unless Gitlab::CurrentSettings.gravatar_enabled? diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb index 5c337a9faa5..c2dfbac5414 100644 --- a/app/services/groups/nested_create_service.rb +++ b/app/services/groups/nested_create_service.rb @@ -1,11 +1,12 @@ module Groups class NestedCreateService < Groups::BaseService - attr_reader :group_path + attr_reader :group_path, :visibility_level def initialize(user, params) @current_user, @params = user, params.dup - @group_path = @params.delete(:group_path) + @visibility_level = @params.delete(:visibility_level) || + Gitlab::CurrentSettings.current_application_settings.default_group_visibility end def execute @@ -36,11 +37,12 @@ module Groups new_params = params.reverse_merge( path: subgroup_name, name: subgroup_name, - parent: last_group + parent: last_group, + visibility_level: visibility_level ) - new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility - last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute + last_group = namespace_or_group(partial_path) || + Groups::CreateService.new(current_user, new_params).execute end last_group diff --git a/app/services/ham_service.rb b/app/services/ham_service.rb index b0e1799b489..794eb34d9ca 100644 --- a/app/services/ham_service.rb +++ b/app/services/ham_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class HamService attr_accessor :spam_log diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb index 3702c3742ef..e75a951944e 100644 --- a/app/services/import_export_clean_up_service.rb +++ b/app/services/import_export_clean_up_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ImportExportCleanUpService LAST_MODIFIED_TIME_IN_MINUTES = 1440 diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 683f64e82ad..7d60c65bb79 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssuableBaseService < BaseService private @@ -130,7 +132,7 @@ class IssuableBaseService < BaseService def create_issuable(issuable, attributes, label_ids:) issuable.with_transaction_returning_status do if issuable.save - issuable.update_attributes(label_ids: label_ids) + issuable.update(label_ids: label_ids) end end end diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index 48b3d59f7bd..cb19cf01dd7 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -6,7 +6,7 @@ module Members old_access_level = member.human_access - if member.update_attributes(params) + if member.update(params) after_execute(action: permission, old_access_level: old_access_level, member: member) end diff --git a/app/services/merge_request_metrics_service.rb b/app/services/merge_request_metrics_service.rb index 9248de14a53..4e88b77c855 100644 --- a/app/services/merge_request_metrics_service.rb +++ b/app/services/merge_request_metrics_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestMetricsService delegate :update!, to: :@merge_request_metrics diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb index 5b4bc86b9ba..c741e913860 100644 --- a/app/services/merge_requests/rebase_service.rb +++ b/app/services/merge_requests/rebase_service.rb @@ -26,7 +26,7 @@ module MergeRequests Gitlab::GitLogger.info("#{log_prefix} rebased to #{rebase_sha}") - merge_request.update_attributes(rebase_commit_sha: rebase_sha) + merge_request.update(rebase_commit_sha: rebase_sha) Gitlab::GitLogger.info("#{log_prefix} rebase SHA saved: #{rebase_sha}") diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index 51ff9eff5e4..222a5c8c79c 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -1,35 +1,18 @@ +# frozen_string_literal: true + require 'prometheus/client/formats/text' class MetricsService - CHECKS = [ - Gitlab::HealthChecks::DbCheck, - Gitlab::HealthChecks::Redis::RedisCheck, - Gitlab::HealthChecks::Redis::CacheCheck, - Gitlab::HealthChecks::Redis::QueuesCheck, - Gitlab::HealthChecks::Redis::SharedStateCheck, - Gitlab::HealthChecks::GitalyCheck - ].freeze - def prometheus_metrics_text Prometheus::Client::Formats::Text.marshal_multiprocess(multiprocess_metrics_path) end - def health_metrics_text - metrics = CHECKS.flat_map(&:metrics) - - formatter.marshal(metrics) - end - def metrics_text - prometheus_metrics_text.concat(health_metrics_text) + prometheus_metrics_text end private - def formatter - @formatter ||= Gitlab::HealthChecks::PrometheusTextFormat.new - end - def multiprocess_metrics_path ::Prometheus::Client.configuration.multiprocess_files_dir end diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb index 31b441ed476..74edbf9b41d 100644 --- a/app/services/milestones/update_service.rb +++ b/app/services/milestones/update_service.rb @@ -11,7 +11,7 @@ module Milestones end if params.present? - milestone.update_attributes(params.except(:state_event)) + milestone.update(params.except(:state_event)) end milestone diff --git a/app/services/note_summary.rb b/app/services/note_summary.rb index a6f6320d573..81f6f92f75c 100644 --- a/app/services/note_summary.rb +++ b/app/services/note_summary.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NoteSummary attr_reader :note attr_reader :metadata diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 75fd08ea0a9..e16ef398184 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -5,7 +5,7 @@ module Notes old_mentioned_users = note.mentioned_users.to_a - note.update_attributes(params.merge(updated_by: current_user)) + note.update(params.merge(updated_by: current_user)) note.create_new_cross_references!(current_user) if note.previous_changes.include?('note') diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 4fa38665abc..4389fd89538 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # Used by NotificationService to determine who should receive notification # @@ -10,16 +12,16 @@ module NotificationRecipientService NotificationRecipient.new(user, *args).notifiable? end - def self.build_recipients(*a) - Builder::Default.new(*a).notification_recipients + def self.build_recipients(*args) + Builder::Default.new(*args).notification_recipients end - def self.build_new_note_recipients(*a) - Builder::NewNote.new(*a).notification_recipients + def self.build_new_note_recipients(*args) + Builder::NewNote.new(*args).notification_recipients end - def self.build_merge_request_unmergeable_recipients(*a) - Builder::MergeRequestUnmergeable.new(*a).notification_recipients + def self.build_merge_request_unmergeable_recipients(*args) + Builder::MergeRequestUnmergeable.new(*args).notification_recipients end module Builder @@ -44,7 +46,6 @@ module NotificationRecipientService raise 'abstract' end - # rubocop:disable Rails/Delegate def project target.project end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 8c6221af788..4511c500fca 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop:disable GitlabSecurity/PublicSend # NotificationService class @@ -274,9 +276,9 @@ class NotificationService def new_access_request(member) return true unless member.notifiable?(:subscription) - recipients = member.source.members.active_without_invites_and_requests.owners_and_masters - if fallback_to_group_owners_masters?(recipients, member) - recipients = member.source.group.members.active_without_invites_and_requests.owners_and_masters + recipients = member.source.members.active_without_invites_and_requests.owners_and_maintainers + if fallback_to_group_owners_maintainers?(recipients, member) + recipients = member.source.group.members.active_without_invites_and_requests.owners_and_maintainers end recipients.each { |recipient| deliver_access_request_email(recipient, member) } @@ -519,7 +521,7 @@ class NotificationService return [] unless project - notifiable_users(project.team.masters, :watch, target: project) + notifiable_users(project.team.maintainers, :watch, target: project) end def notifiable?(*args) @@ -534,7 +536,7 @@ class NotificationService mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.notification_email).deliver_later end - def fallback_to_group_owners_masters?(recipients, member) + def fallback_to_group_owners_maintainers?(recipients, member) return false if recipients.present? member.source.respond_to?(:group) && member.source.group diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb index 6da4d9523cf..a15ee4911ef 100644 --- a/app/services/preview_markdown_service.rb +++ b/app/services/preview_markdown_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PreviewMarkdownService < BaseService def execute text, commands = explain_quick_actions(params[:text]) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 172497b8e67..85491089d8e 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -115,7 +115,7 @@ module Projects @project.group.refresh_members_authorized_projects(blocking: false) current_user.refresh_authorized_projects else - @project.add_master(@project.namespace.owner, current_user: current_user) + @project.add_maintainer(@project.namespace.owner, current_user: current_user) end end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 02769e72229..87173cc79ec 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -124,7 +124,7 @@ module Projects # It's possible that the project was destroyed, but some after_commit # hook failed and caused us to end up here. A destroyed model will be a frozen hash, # which cannot be altered. - project.update_attributes(delete_error: message, pending_delete: false) unless project.destroyed? + project.update(delete_error: message, pending_delete: false) unless project.destroyed? log_error("Deletion failed on #{project.full_path} with the following message: #{message}") end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 348eb0bf8d8..a8aafa9fb4f 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -37,7 +37,7 @@ module Projects return new_project unless new_project.persisted? builds_access_level = @project.project_feature.builds_access_level - new_project.project_feature.update_attributes(builds_access_level: builds_access_level) + new_project.project_feature.update(builds_access_level: builds_access_level) link_fork_network(new_project) diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb index 6ea43561d61..618c30b971f 100644 --- a/app/services/projects/lfs_pointers/lfs_download_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_service.rb @@ -22,7 +22,7 @@ module Projects private def download_and_save_file(file, sanitized_uri) - IO.copy_stream(open(sanitized_uri.sanitized_url, headers(sanitized_uri)), file) + IO.copy_stream(open(sanitized_uri.sanitized_url, headers(sanitized_uri)), file) # rubocop:disable Security/Open end def headers(sanitized_uri) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index d8250cd8102..f4fbaacc08b 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -22,7 +22,7 @@ module Projects # If the block added errors, don't try to save the project return validation_failed! if project.errors.any? - if project.update_attributes(params.except(:default_branch)) + if project.update(params.except(:default_branch)) if project.previous_changes.include?('path') project.rename_repo else diff --git a/app/services/protected_branches/access_level_params.rb b/app/services/protected_branches/access_level_params.rb index 253ae8b0124..4658b0e850d 100644 --- a/app/services/protected_branches/access_level_params.rb +++ b/app/services/protected_branches/access_level_params.rb @@ -14,7 +14,7 @@ module ProtectedBranches private def params_with_default(params) - params[:"#{type}_access_level"] ||= Gitlab::Access::MASTER if use_default_access_level?(params) + params[:"#{type}_access_level"] ||= Gitlab::Access::MAINTAINER if use_default_access_level?(params) params end diff --git a/app/services/protected_branches/legacy_api_create_service.rb b/app/services/protected_branches/legacy_api_create_service.rb index e358fd0374e..bb7656489c5 100644 --- a/app/services/protected_branches/legacy_api_create_service.rb +++ b/app/services/protected_branches/legacy_api_create_service.rb @@ -9,14 +9,14 @@ module ProtectedBranches if params.delete(:developers_can_push) Gitlab::Access::DEVELOPER else - Gitlab::Access::MASTER + Gitlab::Access::MAINTAINER end merge_access_level = if params.delete(:developers_can_merge) Gitlab::Access::DEVELOPER else - Gitlab::Access::MASTER + Gitlab::Access::MAINTAINER end @params.merge!(push_access_levels_attributes: [{ access_level: push_access_level }], diff --git a/app/services/protected_branches/legacy_api_update_service.rb b/app/services/protected_branches/legacy_api_update_service.rb index 33176253ca2..1df38de0e4a 100644 --- a/app/services/protected_branches/legacy_api_update_service.rb +++ b/app/services/protected_branches/legacy_api_update_service.rb @@ -17,14 +17,14 @@ module ProtectedBranches when true params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }] when false - params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::MASTER }] + params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::MAINTAINER }] end case @developers_can_merge when true params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }] when false - params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::MASTER }] + params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::MAINTAINER }] end service = ProtectedBranches::UpdateService.new(@project, @current_user, @params) diff --git a/app/services/push_event_payload_service.rb b/app/services/push_event_payload_service.rb index b0a389c85f9..bb1259787af 100644 --- a/app/services/push_event_payload_service.rb +++ b/app/services/push_event_payload_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Service class for creating push event payloads as stored in the # "push_event_payloads" table. # diff --git a/app/services/repair_ldap_blocked_user_service.rb b/app/services/repair_ldap_blocked_user_service.rb index 863cef7ff61..6ed42054ac3 100644 --- a/app/services/repair_ldap_blocked_user_service.rb +++ b/app/services/repair_ldap_blocked_user_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepairLdapBlockedUserService attr_accessor :user diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb index ba7be4b3f89..99a9c834352 100644 --- a/app/services/repository_archive_clean_up_service.rb +++ b/app/services/repository_archive_clean_up_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryArchiveCleanUpService LAST_MODIFIED_TIME_IN_MINUTES = 120 diff --git a/app/services/reset_project_cache_service.rb b/app/services/reset_project_cache_service.rb index a162a6eedb9..676d367a1c1 100644 --- a/app/services/reset_project_cache_service.rb +++ b/app/services/reset_project_cache_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ResetProjectCacheService < BaseService def execute @project.increment!(:jobs_cache_index) diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 1d4d03a8b7d..1b707d79b43 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SearchService include Gitlab::Allowable diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb index d4ade869777..895261925ba 100644 --- a/app/services/spam_check_service.rb +++ b/app/services/spam_check_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # SpamCheckService # # Provide helper methods for checking if a given spammable object has diff --git a/app/services/spam_service.rb b/app/services/spam_service.rb index 73ea3018fbd..f2f133dae28 100644 --- a/app/services/spam_service.rb +++ b/app/services/spam_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SpamService attr_accessor :spammable, :request, :options attr_reader :spam_log diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index ac029fad7ea..93c2e222963 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SubmitUsagePingService URL = 'https://version.gitlab.com/usage_data'.freeze diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index ba7946fd23c..bd3907cdf8e 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SystemHooksService def execute_hooks_for(model, event) data = build_event_data(model, event) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 00bf5434b7f..77494295f14 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # SystemNoteService # # Used for creating system notes (e.g., when a user references a merge request @@ -21,9 +23,11 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "added #{commits_text}\n\n" - body << commits_list(noteable, new_commits, existing_commits, oldrev) - body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" + text_parts = ["added #{commits_text}"] + text_parts << commits_list(noteable, new_commits, existing_commits, oldrev) + text_parts << "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" + + body = text_parts.join("\n\n") create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count)) end @@ -103,18 +107,19 @@ module SystemNoteService added_labels = added_labels.map(&references).join(' ') removed_labels = removed_labels.map(&references).join(' ') - body = '' + text_parts = [] if added_labels.present? - body << "added #{added_labels}" - body << ' and ' if removed_labels.present? + text_parts << "added #{added_labels}" + text_parts << 'and' if removed_labels.present? end if removed_labels.present? - body << "removed #{removed_labels}" + text_parts << "removed #{removed_labels}" end - body << ' ' << 'label'.pluralize(labels_count) + text_parts << 'label'.pluralize(labels_count) + body = text_parts.join(' ') create_note(NoteSummary.new(noteable, project, author, body, action: 'label')) end @@ -188,8 +193,10 @@ module SystemNoteService spent_at = noteable.spent_at parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) action = time_spent > 0 ? 'added' : 'subtracted' - body = "#{action} #{parsed_time} of time spent" - body << " at #{spent_at}" if spent_at + + text_parts = ["#{action} #{parsed_time} of time spent"] + text_parts << "at #{spent_at}" if spent_at + body = text_parts.join(' ') end create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) @@ -268,17 +275,19 @@ module SystemNoteService diff_refs = change_position.diff_refs version_index = merge_request.merge_request_diffs.viewable.count - body = "changed this line in" + text_parts = ["changed this line in"] if version_params = merge_request.version_params_for(diff_refs) line_code = change_position.line_code(project.repository) url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code)) - body << " [version #{version_index} of the diff](#{url})" + text_parts << "[version #{version_index} of the diff](#{url})" else - body << " version #{version_index} of the diff" + text_parts << "version #{version_index} of the diff" end + body = text_parts.join(' ') note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body) + note = Note.create(note_attributes.merge(system: true)) note.system_note_metadata = SystemNoteMetadata.new(action: 'outdated') diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 46f12086555..0bcd53c76a9 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # TodoService class # # Used for creating/updating todos after certain user actions @@ -260,15 +262,15 @@ class TodoService end end - def create_mention_todos(parent, target, author, note = nil, skip_users = []) + def create_mention_todos(project, target, author, note = nil, skip_users = []) # Create Todos for directly addressed users - directly_addressed_users = filter_directly_addressed_users(parent, note || target, author, skip_users) - attributes = attributes_for_todo(parent, target, author, Todo::DIRECTLY_ADDRESSED, note) + directly_addressed_users = filter_directly_addressed_users(project, note || target, author, skip_users) + attributes = attributes_for_todo(project, target, author, Todo::DIRECTLY_ADDRESSED, note) create_todos(directly_addressed_users, attributes) # Create Todos for mentioned users - mentioned_users = filter_mentioned_users(parent, note || target, author, skip_users) - attributes = attributes_for_todo(parent, target, author, Todo::MENTIONED, note) + mentioned_users = filter_mentioned_users(project, note || target, author, skip_users) + attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note) create_todos(mentioned_users, attributes) end @@ -299,36 +301,36 @@ class TodoService def attributes_for_todo(project, target, author, action, note = nil) attributes_for_target(target).merge!( - project_id: project&.id, + project_id: project.id, author_id: author.id, action: action, note: note ) end - def filter_todo_users(users, parent, target) - reject_users_without_access(users, parent, target).uniq + def filter_todo_users(users, project, target) + reject_users_without_access(users, project, target).uniq end - def filter_mentioned_users(parent, target, author, skip_users = []) + def filter_mentioned_users(project, target, author, skip_users = []) mentioned_users = target.mentioned_users(author) - skip_users - filter_todo_users(mentioned_users, parent, target) + filter_todo_users(mentioned_users, project, target) end - def filter_directly_addressed_users(parent, target, author, skip_users = []) + def filter_directly_addressed_users(project, target, author, skip_users = []) directly_addressed_users = target.directly_addressed_users(author) - skip_users - filter_todo_users(directly_addressed_users, parent, target) + filter_todo_users(directly_addressed_users, project, target) end - def reject_users_without_access(users, parent, target) - if target.is_a?(Note) && target.for_issuable? + def reject_users_without_access(users, project, target) + if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?) target = target.noteable end if target.is_a?(Issuable) select_users(users, :"read_#{target.to_ability_name}", target) else - select_users(users, :read_project, parent) + select_users(users, :read_project, project) end end diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb index b7c36651968..422ba668e35 100644 --- a/app/services/update_release_service.rb +++ b/app/services/update_release_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UpdateReleaseService < BaseService def execute(tag_name, release_description) repository = project.repository @@ -7,7 +9,7 @@ class UpdateReleaseService < BaseService release = project.releases.find_by(tag: tag_name) if release - release.update_attributes(description: release_description) + release.update(description: release_description) success(release) else diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index 358bca73aec..15bc1046a4e 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UpdateSnippetService < BaseService include SpamCheckService diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb index d5a9b344905..39909ee4f82 100644 --- a/app/services/upload_service.rb +++ b/app/services/upload_service.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + class UploadService - def initialize(model, file, uploader_class = FileUploader) - @model, @file, @uploader_class = model, file, uploader_class + def initialize(model, file, uploader_class = FileUploader, **uploader_context) + @model, @file, @uploader_class, @uploader_context = model, file, uploader_class, uploader_context end def execute return nil unless @file && @file.size <= max_attachment_size - uploader = @uploader_class.new(@model) + uploader = @uploader_class.new(@model, nil, @uploader_context) uploader.store!(@file) uploader.to_h diff --git a/app/services/user_agent_detail_service.rb b/app/services/user_agent_detail_service.rb index a1ee3df5fe1..5cb42e879a0 100644 --- a/app/services/user_agent_detail_service.rb +++ b/app/services/user_agent_detail_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UserAgentDetailService attr_accessor :spammable, :request diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb index 8630e572624..adca43660e8 100644 --- a/app/services/user_project_access_changed_service.rb +++ b/app/services/user_project_access_changed_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UserProjectAccessChangedService def initialize(user_ids) @user_ids = Array.wrap(user_ids) diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb index 643f2ce1481..c19e2ec2043 100644 --- a/app/services/validate_new_branch_service.rb +++ b/app/services/validate_new_branch_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'base_service' class ValidateNewBranchService < BaseService diff --git a/app/services/verify_pages_domain_service.rb b/app/services/verify_pages_domain_service.rb index 13cb53dee01..07f7391f877 100644 --- a/app/services/verify_pages_domain_service.rb +++ b/app/services/verify_pages_domain_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'resolv' class VerifyPagesDomainService < BaseService diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 8a86e47f0ea..34724e0250d 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class WebHookService class InternalErrorResponse attr_reader :body, :headers, :code diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 21292ddcf44..83f7b99d2a5 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -15,7 +15,7 @@ class FileUploader < GitlabUploader prepend ObjectStorage::Extension::RecordsUploads MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)} - DYNAMIC_PATH_PATTERN = %r{(?<secret>\h{32})/(?<identifier>.*)} + DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\h{32})/(?<identifier>.*)} after :remove, :prune_store_dir @@ -67,6 +67,10 @@ class FileUploader < GitlabUploader SecureRandom.hex end + def self.extract_dynamic_path(path) + DYNAMIC_PATH_PATTERN.match(path) + end + def upload_paths(identifier) [ File.join(secret, identifier), @@ -143,7 +147,7 @@ class FileUploader < GitlabUploader return if apply_context!(value.uploader_context) # fallback to the regex based extraction - if matches = DYNAMIC_PATH_PATTERN.match(value.path) + if matches = self.class.extract_dynamic_path(value.path) @secret = matches[:secret] @identifier = matches[:identifier] end diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index 7919f126075..719bd6ef418 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -71,6 +71,28 @@ class GitlabUploader < CarrierWave::Uploader::Base File.join('/', self.class.base_dir, dynamic_segment, filename) end + def cached_size + size + end + + def open + stream = + if file_storage? + File.open(path, "rb") if path + else + ::Gitlab::HttpIO.new(url, cached_size) if url + end + + return unless stream + return stream unless block_given? + + begin + yield(stream) + ensure + stream.close + end + end + private # Designed to be overridden by child uploaders that have a dynamic path diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 855cf2fc21c..f6af023e0f9 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -18,14 +18,6 @@ class JobArtifactUploader < GitlabUploader dynamic_segment end - def open - if file_storage? - File.open(path, "rb") if path - else - ::Gitlab::Ci::Trace::HttpIO.new(url, cached_size) if url - end - end - private def dynamic_segment diff --git a/app/views/admin/application_settings/_third_party_offers.html.haml b/app/views/admin/application_settings/_third_party_offers.html.haml new file mode 100644 index 00000000000..c5d775d4bf5 --- /dev/null +++ b/app/views/admin/application_settings/_third_party_offers.html.haml @@ -0,0 +1,13 @@ +- application_setting = local_assigns.fetch(:application_setting) + += form_for application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| + = form_errors(application_setting) + + %fieldset + .form-group + .form-check + = f.check_box :hide_third_party_offers, class: 'form-check-input' + = f.label :hide_third_party_offers, class: 'form-check-label' do + Do not display offers from third parties within GitLab + + = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index bd43504dd37..5cb8001a364 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -325,5 +325,16 @@ .settings-content = render partial: 'repository_mirrors_form' +%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('Third party offers') + %button.btn.btn-default.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + = _('Control the display of third party offers.') + .settings-content + = render 'third_party_offers', application_setting: @application_setting + = render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index c8008771236..a3773e90cfb 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -6,7 +6,7 @@ = render_if_exists 'admin/namespace_plan', f: f .form-group.row.group-description-holder - = f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2' + = f.label :avatar, _("Group avatar"), class: 'col-form-label col-sm-2' .col-sm-10 = render 'shared/choose_group_avatar_button', f: f @@ -26,12 +26,12 @@ .alert.alert-info = render 'shared/group_tips' .form-actions - = f.submit 'Create group', class: "btn btn-create" - = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" + = f.submit _('Create group'), class: "btn btn-create" + = link_to _('Cancel'), admin_groups_path, class: "btn btn-cancel" - else .form-actions - = f.submit 'Save changes', class: "btn btn-save" - = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" + = f.submit _('Save changes'), class: "btn btn-save" + = link_to _('Cancel'), admin_group_path(@group), class: "btn btn-cancel" = render_if_exists 'ldap_group_links/ldap_syncrhonizations', group: @group diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 3f96988c203..0a688b90f3a 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -3,8 +3,8 @@ %li.group-row{ class: css_class } .controls - = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' - = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove' + = link_to _('Edit'), admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' + = link_to _('Delete'), [:admin, group], data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } }, method: :delete, class: 'btn btn-remove' .stats %span.badge.badge-pill = storage_counter(group.storage_size) diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index c2b9807015d..8e9e1a58a17 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", @group.name, "Groups" -%h3.page-title Edit group: #{@group.name} +- page_title _("Edit"), @group.name, _("Groups") +%h3.page-title= _('Edit group: %{group_name}') % { group_name: @group.name } %hr = render 'form', visibility_level: @group.visibility_level diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 25946ba6eaf..6a9b85b4109 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- page_title "Groups" +- page_title _("Groups") %div{ class: container_class } .top-area @@ -13,7 +13,7 @@ = icon("search", class: "search-icon") = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash = link_to new_admin_group_path, class: "btn btn-new" do - New group + = _('New group') %ul.content-list = render @groups diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index 8f9fe96249f..553e8638e52 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -1,4 +1,4 @@ -- page_title "New Group" -%h3.page-title New group +- page_title _("New Group") +%h3.page-title= _('New group') %hr = render 'form', visibility_level: default_group_visibility diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index a40f98ad24f..72b068ea6b5 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -1,61 +1,58 @@ -- add_to_breadcrumbs "Groups", admin_groups_path +- add_to_breadcrumbs _("Groups"), admin_groups_path - breadcrumb_title @group.name -- page_title @group.name, "Groups" +- page_title @group.name, _("Groups") %h3.page-title - Group: #{@group.full_name} + = _('Group: %{group_name}') % { group_name: @group.full_name } = link_to admin_group_edit_path(@group), class: "btn float-right" do %i.fa.fa-pencil-square-o - Edit + = _('Edit') %hr .row .col-md-6 .card .card-header - Group info: + = _('Group info:') %ul.content-list %li .avatar-container.s60 = group_icon(@group, class: "avatar s60") %li - %span.light Name: + %span.light= _('Name:') %strong= @group.name %li - %span.light Path: + %span.light= _('Path:') %strong = @group.path %li - %span.light Description: + %span.light= _('Description:') %strong = @group.description %li - %span.light Visibility level: + %span.light= _('Visibility level:') %strong = visibility_level_label(@group.visibility_level) %li - %span.light Created on: + %span.light= _('Created on:') %strong = @group.created_at.to_s(:medium) = render_if_exists 'admin/namespace_plan_info', namespace: @group %li - %span.light Storage: - %strong= storage_counter(@group.storage_size) - ( - = storage_counter(@group.repository_size) - repositories, - = storage_counter(@group.build_artifacts_size) - build artifacts, - = storage_counter(@group.lfs_objects_size) - LFS - ) + %span.light= _('Storage:') + - counter_storage = storage_counter(@group.storage_size) + - counter_repositories = storage_counter(@group.repository_size) + - counter_build_artifacts = storage_counter(@group.build_artifacts_size) + - counter_lfs_objects = storage_counter(@group.lfs_objects_size) + %strong + = _("%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)") % { counter_storage: counter_storage, counter_repositories: counter_repositories, counter_build_artifacts: counter_build_artifacts, counter_lfs_objects: counter_lfs_objects } %li - %span.light Group Git LFS status: + %span.light= _('Group Git LFS status:') %strong = group_lfs_status(@group) = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') @@ -67,7 +64,7 @@ .card .card-header %h3.card-title - Projects + = _('Projects') %span.badge.badge-pill #{@group.projects.count} %ul.content-list @@ -85,7 +82,7 @@ - if @group.shared_projects.any? .card .card-header - Projects shared with #{@group.name} + = _('Projects shared with %{group_name}') % { group_name: @group.name } %span.badge.badge-pill #{@group.shared_projects.count} %ul.content-list @@ -102,11 +99,11 @@ - if can?(current_user, :admin_group_member, @group) .card .card-header - Add user(s) to the group: + = _('Add user(s) to the group:') .card-body.form-holder %p.light - Read more about project permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + - link_to_help = link_to(_("here"), help_page_path("user/permissions"), class: "vlink") + = _('Read more about project permissions <strong>%{link_to_help}</strong>').html_safe % { link_to_help: link_to_help } = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div @@ -114,16 +111,15 @@ .prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr - = button_tag 'Add users to group', class: "btn btn-create" + = button_tag _('Add users to group'), class: "btn btn-create" = render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true .card .card-header - %strong= @group.name - group members + = _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name } %span.badge.badge-pill= @group.members.size .float-right - = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm" + = link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm" %ul.content-list.group-users-list.content-list.members-list = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } .card-footer diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index 37fb8fbab26..3ae9ce6c11f 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -5,7 +5,7 @@ %ol %li = _("Install a Runner compatible with GitLab CI") - = (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe + = (_("(check out the %{link} for information on how to install it).") % { link: link }).html_safe %li = _("Specify the following URL during the Runner setup:") %code#coordinator_address= root_url(only_path: false) diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index a676eba2aee..9c246e19faa 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,8 +1,8 @@ .nav-block.activities + = render 'shared/event_filter' .controls = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do %i.fa.fa-rss - = render 'shared/event_filter' .content_list = spinner diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 8b3974d97f8..d5a9cc646a6 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -30,33 +30,27 @@ .todos-filters .row-content-block.second-block - = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form d-sm-flex' do - .filter-categories.flex-fill - .filter-item.inline - - if params[:group_id].present? - = hidden_field_tag(:group_id, params[:group_id]) - = dropdown_tag(group_dropdown_label(params[:group_id], 'Group'), options: { toggle_class: 'js-group-search js-filter-submit', title: 'Filter by group', filter: true, filterInput: 'input#group-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-group js-filter-submit', - placeholder: 'Search groups', data: { data: todo_group_options, default_label: 'Group', display: 'static' } }) - .filter-item.inline - - if params[:project_id].present? - = hidden_field_tag(:project_id, params[:project_id]) - = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', - placeholder: 'Search projects', data: { data: todo_projects_options, default_label: 'Project', display: 'static' } }) - .filter-item.inline - - if params[:author_id].present? - = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', - placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } }) - .filter-item.inline - - if params[:type].present? - = hidden_field_tag(:type, params[:type]) - = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', - data: { data: todo_types_options, default_label: 'Type' } }) - .filter-item.inline.actions-filter - - if params[:action_id].present? - = hidden_field_tag(:action_id, params[:action_id]) - = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', - data: { data: todo_actions_options, default_label: 'Action' } }) + = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do + .filter-item.inline + - if params[:project_id].present? + = hidden_field_tag(:project_id, params[:project_id]) + = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', + placeholder: 'Search projects', data: { data: todo_projects_options, default_label: 'Project', display: 'static' } }) + .filter-item.inline + - if params[:author_id].present? + = hidden_field_tag(:author_id, params[:author_id]) + = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', + placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } }) + .filter-item.inline + - if params[:type].present? + = hidden_field_tag(:type, params[:type]) + = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', + data: { data: todo_types_options, default_label: 'Type' } }) + .filter-item.inline.actions-filter + - if params[:action_id].present? + = hidden_field_tag(:action_id, params[:action_id]) + = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', + data: { data: todo_actions_options, default_label: 'Action' } }) .filter-item.sort-filter .dropdown %button.dropdown-menu-toggle.dropdown-menu-toggle-sort{ type: 'button', 'data-toggle' => 'dropdown' } diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml index 84b4ce5b606..ac5cac50699 100644 --- a/app/views/doorkeeper/applications/_delete_form.html.haml +++ b/app/views/doorkeeper/applications/_delete_form.html.haml @@ -2,9 +2,9 @@ = form_tag oauth_application_path(application) do %input{ :name => "_method", :type => "hidden", :value => "delete" }/ - if defined? small - = button_tag type: "submit", class: "btn btn-transparent", data: { confirm: "Are you sure?" } do + = button_tag type: "submit", class: "btn btn-transparent", data: { confirm: _("Are you sure?") } do %span.sr-only - Destroy + = _('Destroy') = icon('trash') - else - = submit_tag 'Destroy', data: { confirm: "Are you sure?" }, class: submit_btn_css + = submit_tag _('Destroy'), data: { confirm: _("Are you sure?") }, class: submit_btn_css diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index be0935b8313..1ddd0df54cd 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -10,16 +10,14 @@ = f.text_area :redirect_uri, class: 'form-control', required: true %span.form-text.text-muted - Use one line per URI + = _('Use one line per URI') - if Doorkeeper.configuration.native_redirect_uri %span.form-text.text-muted - Use - %code= Doorkeeper.configuration.native_redirect_uri - for local tests + = _('Use <code>%{native_redirect_uri}</code> for local tests').html_safe % { native_redirect_uri: Doorkeeper.configuration.native_redirect_uri } .form-group = f.label :scopes, class: 'label-light' = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes .prepend-top-default - = f.submit 'Save application', class: "btn btn-create" + = f.submit _('Save application'), class: "btn btn-create" diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml index 49f90298a50..aad4200f240 100644 --- a/app/views/doorkeeper/applications/edit.html.haml +++ b/app/views/doorkeeper/applications/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", @application.name, "Applications" +- page_title _("Edit"), @application.name, _("Applications") - @content_class = "limit-container-width" unless fluid_layout -%h3.page-title Edit application +%h3.page-title= _('Edit application') = render 'form', application: @application diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index cdf3ff81bd9..ab3a1b100ce 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -1,4 +1,4 @@ -- page_title "Applications" +- page_title _("Applications") - @content_class = "limit-container-width" unless fluid_layout .row.prepend-top-default @@ -7,28 +7,27 @@ = page_title %p - if user_oauth_applications? - Manage applications that can use GitLab as an OAuth provider, - and applications that you've authorized to use your account. + = _("Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account.") - else - Manage applications that you've authorized to use your account. + = _("Manage applications that you've authorized to use your account.") .col-lg-8 - if user_oauth_applications? %h5.prepend-top-0 - Add new application + = _('Add new application') = render 'form', application: @application %hr - if user_oauth_applications? .oauth-applications %h5 - Your applications (#{@applications.size}) + = _("Your applications (%{size})") % { size: @applications.size } - if @applications.any? .table-responsive %table.table %thead %tr - %th Name - %th Callback URL - %th Clients + %th= _('Name') + %th= _('Callback URL') + %th= _('Clients') %th.last-heading %tbody - @applications.each do |application| @@ -41,25 +40,25 @@ %td = link_to edit_oauth_application_path(application), class: "btn btn-transparent append-right-5" do %span.sr-only - Edit + = _('Edit') = icon('pencil') = render 'delete_form', application: application, small: true - else .settings-message.text-center - You don't have any applications + = _("You don't have any applications") .oauth-authorized-applications.prepend-top-20.append-bottom-default - if user_oauth_applications? %h5 - Authorized applications (#{@authorized_tokens.size}) + = _("Authorized applications (%{size})") % { size: @authorized_tokens.size } - if @authorized_tokens.any? .table-responsive %table.table.table-striped %thead %tr - %th Name - %th Authorized At - %th Scope + %th= _('Name') + %th= _('Authorized At') + %th= _('Scope') %th %tbody - @authorized_apps.each do |app| @@ -72,12 +71,12 @@ - @authorized_anonymous_tokens.each do |token| %tr %td - Anonymous + = _('Anonymous') .form-text.text-muted - %em Authorization was granted by entering your username and password in the application. + %em= _("Authorization was granted by entering your username and password in the application.") %td= token.created_at %td= token.scopes %td= render 'doorkeeper/authorized_applications/delete_form', token: token - else .settings-message.text-center - You don't have any authorized applications + = _("You don't have any authorized applications") diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml index d3692d1f759..a66fab20d7c 100644 --- a/app/views/doorkeeper/applications/new.html.haml +++ b/app/views/doorkeeper/applications/new.html.haml @@ -1,6 +1,6 @@ -- page_title "New Application" +- page_title _("New Application") -%h3.page-title New Application +%h3.page-title= _("New Application") %hr diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml index 89ad626f73f..bb76ac6d5f6 100644 --- a/app/views/doorkeeper/applications/show.html.haml +++ b/app/views/doorkeeper/applications/show.html.haml @@ -1,27 +1,27 @@ -- add_to_breadcrumbs "Applications", oauth_applications_path +- add_to_breadcrumbs _("Applications"), oauth_applications_path - breadcrumb_title @application.name -- page_title @application.name, "Applications" +- page_title @application.name, _("Applications") - @content_class = "limit-container-width" unless fluid_layout %h3.page-title - Application: #{@application.name} + = _("Application: %{name}") % { name: @application.name } .table-holder.oauth-application-show %table.table %tr %td - Application Id + = _('Application Id') %td %code#application_id= @application.uid %tr %td - Secret: + = _('Secret:') %td %code#secret= @application.secret %tr %td - Callback url + = _('Callback url') %td - @application.redirect_uri.split.each do |uri| %div @@ -30,5 +30,5 @@ = render "shared/tokens/scopes_list", token: @application .form-actions - = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left' + = link_to _('Edit'), edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left' = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml index 6117b00149f..32b4ccb0fe6 100644 --- a/app/views/doorkeeper/authorizations/error.html.haml +++ b/app/views/doorkeeper/authorizations/error.html.haml @@ -1,3 +1,3 @@ -%h3.page-title An error has occurred +%h3.page-title= _("An error has occurred") %main{ :role => "main" } %pre= @pre_auth.error_response.body[:error_description] diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index 28cdc7607e0..ca62a59d909 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -3,34 +3,28 @@ .modal-content .modal-header %h3.page-title - Authorize - = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer' - to use your account? + - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer') + = _("Authorize %{link_to_client} to use your account?") .modal-body - if current_user.admin? .text-warning %p = icon("exclamation-triangle fw") - You are an admin, which means granting access to - %strong= @pre_auth.client.name - will allow them to interact with GitLab as an admin as well. Proceed with caution. + = _('You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution.').html_safe % { client_name: @pre_auth.client.name } %p - An application called - = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer' - is requesting access to your GitLab account. + - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer') + = _("An application called %{link_to_client} is requesting access to your GitLab account.").html_safe % { link_to_client: link_to_client } - auth_app_owner = @pre_auth.client.application.owner - if auth_app_owner - This application was created by - = succeed "." do - = link_to auth_app_owner.name, user_path(auth_app_owner) + - link_to_owner = link_to(auth_app_owner.name, user_path(auth_app_owner)) + = _("This application was created by %{link_to_owner}.").html_safe % { link_to_owner: link_to_owner } - Please note that this application is not provided by GitLab and you should verify its authenticity before - allowing access. + = _("Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access.") - if @pre_auth.scopes %p - This application will be able to: + = _("This application will be able to:") %ul - @pre_auth.scopes.each do |scope| %li @@ -44,7 +38,7 @@ = hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :nonce, @pre_auth.nonce - = submit_tag "Deny", class: "btn btn-danger" + = submit_tag _("Deny"), class: "btn btn-danger" = form_tag oauth_authorization_path, method: :post, class: 'inline' do = hidden_field_tag :client_id, @pre_auth.client.uid = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri @@ -52,4 +46,4 @@ = hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :nonce, @pre_auth.nonce - = submit_tag "Authorize", class: "btn btn-success prepend-left-10" + = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10" diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml index 44e868e6782..e4bfd69e7f8 100644 --- a/app/views/doorkeeper/authorizations/show.html.haml +++ b/app/views/doorkeeper/authorizations/show.html.haml @@ -1,3 +1,3 @@ -%h3.page-title Authorization code: +%h3.page-title= _("Authorization code:") %main{ :role => "main" } %code#authorization_code= params[:code] diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml index 11c1e67878e..08f2442f025 100644 --- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -6,4 +6,4 @@ = form_tag path do %input{ :name => "_method", :type => "hidden", :value => "delete" }/ - = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm' + = submit_tag _('Revoke'), onclick: "return confirm('#{_('Are you sure?')}')", class: 'btn btn-remove btn-sm' diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml index 30c9d02b72e..8e73298b250 100644 --- a/app/views/doorkeeper/authorized_applications/index.html.haml +++ b/app/views/doorkeeper/authorized_applications/index.html.haml @@ -1,12 +1,12 @@ %header - %h1 Your authorized applications + %h1= _("Your authorized applications") %main{ :role => "main" } .table-holder %table.table.table-striped %thead %tr - %th Application - %th Created At + %th= _('Application') + %th= _('Created At') %th %th %tbody diff --git a/app/views/explore/_head.html.haml b/app/views/explore/_head.html.haml index a3b0709e261..eefc797cf03 100644 --- a/app/views/explore/_head.html.haml +++ b/app/views/explore/_head.html.haml @@ -1,6 +1,6 @@ .explore-title.text-center %h2 - Explore GitLab + = _("Explore GitLab") %p.lead - Discover projects, groups and snippets. Share your projects with others + = _("Discover projects, groups and snippets. Share your projects with others") %br diff --git a/app/views/explore/groups/_nav.html.haml b/app/views/explore/groups/_nav.html.haml index ab4787c6d05..c337149a2f3 100644 --- a/app/views/explore/groups/_nav.html.haml +++ b/app/views/explore/groups/_nav.html.haml @@ -2,7 +2,7 @@ %ul.nav-links.nav.nav-tabs = nav_link(page: explore_groups_path) do = link_to explore_groups_path do - Explore Groups + = _("Explore Groups") .nav-controls = render 'shared/groups/search_form' = render 'shared/groups/dropdown' diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 0643b9cfbc5..387c37b7a91 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Groups" -- header_title "Groups", dashboard_groups_path +- page_title _("Groups") +- header_title _("Groups"), dashboard_groups_path - if current_user = render 'dashboard/groups_head' @@ -10,14 +10,14 @@ - if cookies[:explore_groups_landing_dismissed] != 'true' .explore-groups.landing.content-block.js-explore-groups-landing.hide - %button.dismiss-button{ type: 'button', 'aria-label' => 'Dismiss' }= icon('times') + %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= icon('times') .svg-container = custom_icon('icon_explore_groups_splash') .inner-content - %p Below you will find all the groups that are public. - %p You can easily contribute to them by requesting to join these groups. + %p= _("Below you will find all the groups that are public.") + %p= _("You can easily contribute to them by requesting to join these groups.") - if params[:filter].blank? && @groups.empty? - .nothing-here-block No public groups + .nothing-here-block= _("No public groups") - else = render 'groups' diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 6abb56ba6d2..b694103ccaf 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -2,16 +2,16 @@ .dropdown %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' } = icon('globe') - %span.light Visibility: + %span.light= _("Visibility:") - if params[:visibility_level].present? = visibility_level_label(params[:visibility_level].to_i) - else - Any + = _('Any') = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right %li = link_to filter_projects_path(visibility_level: nil) do - Any + = _('Any') - Gitlab::VisibilityLevel.values.each do |level| %li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' } = link_to filter_projects_path(visibility_level: level) do diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml index 558cd26f1e0..bf65c19b720 100644 --- a/app/views/explore/projects/_nav.html.haml +++ b/app/views/explore/projects/_nav.html.haml @@ -2,13 +2,13 @@ %ul.nav-links.nav.nav-tabs = nav_link(page: [trending_explore_projects_path, explore_root_path]) do = link_to trending_explore_projects_path do - Trending + = _('Trending') = nav_link(page: starred_explore_projects_path) do = link_to starred_explore_projects_path do - Most stars + = _('Most stars') = nav_link(page: explore_projects_path) do = link_to explore_projects_path do - All + = _('All') .nav-controls - unless current_user diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index f00802e0af7..452f390695c 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Projects" -- header_title "Projects", dashboard_projects_path +- page_title _("Projects") +- header_title _("Projects"), dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index f00802e0af7..452f390695c 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Projects" -- header_title "Projects", dashboard_projects_path +- page_title _("Projects") +- header_title _("Projects"), dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index f00802e0af7..452f390695c 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Projects" -- header_title "Projects", dashboard_projects_path +- page_title _("Projects") +- header_title _("Projects"), dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml index 577c63503a8..82a497289f3 100644 --- a/app/views/groups/_activities.html.haml +++ b/app/views/groups/_activities.html.haml @@ -1,8 +1,8 @@ .nav-block.activities + = render 'shared/event_filter' .controls = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do %i.fa.fa-rss - = render 'shared/event_filter' .content_list = spinner diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index 5e7be5cd37b..f0d1e837317 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -21,23 +21,13 @@ %th= _('Status') %tbody - @already_added_projects.each do |project| - %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } + %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) } %td = provider_project_link(provider, project.import_source) %td = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - - if project.import_status == 'finished' - %span - %i.fa.fa-check - = _('Done') - - elsif project.import_status == 'started' - %i.fa.fa-spinner.fa-spin - = _('Started') - - elsif project.import_status == 'failed' - = _('Failed') - - else - = project.human_import_status_name + = render 'import/project_status', project: project - @repos.each do |repo| %tr{ id: "repo_#{repo.id}", data: { qa: { repo_path: repo.full_name } } } @@ -61,6 +51,6 @@ = has_ci_cd_only_params? ? _('Connect') : _('Import') = icon("spinner spin", class: "loading-icon") -.js-importer-status{ data: { jobs_import_path: "#{url_for([:jobs, :import, provider])}", - import_path: "#{url_for([:import, provider])}", - ci_cd_only: "#{has_ci_cd_only_params?}" } } +.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]), + import_path: url_for([:import, provider]), + ci_cd_only: has_ci_cd_only_params?.to_s } } diff --git a/app/views/import/_project_status.html.haml b/app/views/import/_project_status.html.haml new file mode 100644 index 00000000000..280bcbc1e63 --- /dev/null +++ b/app/views/import/_project_status.html.haml @@ -0,0 +1,11 @@ +- case project.import_status +- when 'finished' + = icon('check') + = _('Done') +- when 'started' + = icon("spinner spin") + = _('Started') +- when 'failed' + = _('Failed') +- else + = project.human_import_status_name diff --git a/app/views/import/bitbucket/deploy_key.js.haml b/app/views/import/bitbucket/deploy_key.js.haml index 81b34ab5c9d..99e8ac1afa1 100644 --- a/app/views/import/bitbucket/deploy_key.js.haml +++ b/app/views/import/bitbucket/deploy_key.js.haml @@ -1,3 +1,3 @@ :plain job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>") + job.find(".import-actions").html("<p class='alert alert-danger'>#{_('Access denied! Please verify you can add deploy keys to this repository.')}</p>") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 4e8f715db4f..a75b7aa9dd2 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -1,22 +1,22 @@ -- page_title 'Bitbucket import' -- header_title 'Projects', root_path +- page_title _('Bitbucket import') +- header_title _('Projects'), root_path %h3.page-title %i.fa.fa-bitbucket - Import projects from Bitbucket + = _('Import projects from Bitbucket') - if @repos.any? %p.light - Select projects you want to import. + = _('Select projects you want to import.') %hr %p - if @incompatible_repos.any? = button_tag class: 'btn btn-import btn-success js-import-all' do - Import all compatible projects + = _('Import all compatible projects') = icon('spinner spin', class: 'loading-icon') - else = button_tag class: 'btn btn-import btn-success js-import-all' do - Import all projects + = _('Import all projects') = icon('spinner spin', class: 'loading-icon') .table-responsive @@ -26,9 +26,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From Bitbucket - %th To GitLab - %th Status + %th= _('From Bitbucket') + %th= _('To GitLab') + %th= _('Status') %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -40,10 +40,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _('done') - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _('started') - else = project.human_import_status_name @@ -66,7 +66,7 @@ = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: 'btn btn-import js-add-to-import' do - Import + = _('Import') = icon('spinner spin', class: 'loading-icon') - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } @@ -74,16 +74,13 @@ = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status - = label_tag 'Incompatible Project', nil, class: 'label badge-danger' + = label_tag _('Incompatible Project'), nil, class: 'label badge-danger' - if @incompatible_repos.any? %p - One or more of your Bitbucket projects cannot be imported into GitLab - directly because they use Subversion or Mercurial for version control, - rather than Git. Please convert - = link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview' - and go through the - = link_to 'import flow', status_import_bitbucket_path - again. + = _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.") + - link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview') + - link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path) + = _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } } diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml index 74d686b6703..b54b1af1e0c 100644 --- a/app/views/import/fogbugz/new.html.haml +++ b/app/views/import/fogbugz/new.html.haml @@ -1,26 +1,24 @@ -- page_title "FogBugz Import" -- header_title "Projects", root_path +- page_title _("FogBugz Import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-bug - Import projects from FogBugz + = _('Import projects from FogBugz') %hr = form_tag callback_import_fogbugz_path do %p - To get started you enter your FogBugz URL and login information below. - In the next steps, you'll be able to map users and select the projects - you want to import. + = _("To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import.") .form-group.row - = label_tag :uri, 'FogBugz URL', class: 'col-form-label col-md-2' + = label_tag :uri, _('FogBugz URL'), class: 'col-form-label col-md-2' .col-md-4 = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control' .form-group.row - = label_tag :email, 'FogBugz Email', class: 'col-form-label col-md-2' + = label_tag :email, _('FogBugz Email'), class: 'col-form-label col-md-2' .col-md-4 = text_field_tag :email, nil, class: 'form-control' .form-group.row - = label_tag :password, 'FogBugz Password', class: 'col-form-label col-md-2' + = label_tag :password, _('FogBugz Password'), class: 'col-form-label col-md-2' .col-md-4 = password_field_tag :password, nil, class: 'form-control' .form-actions - = submit_tag 'Continue to the next step', class: 'btn btn-create' + = submit_tag _('Continue to the next step'), class: 'btn btn-create' diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index d27c5d3c36d..ff2f989c509 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -1,39 +1,33 @@ -- page_title 'User map', 'FogBugz import' -- header_title "Projects", root_path +- page_title _('User map'), _('FogBugz import') +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-bug - Import projects from FogBugz + = _('Import projects from FogBugz') %hr = form_tag create_user_map_import_fogbugz_path do %p - Customize how FogBugz email addresses and usernames are imported into GitLab. - In the next step, you'll be able to select the projects you want to import. + = _("Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.") %p - The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below. + = _("The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.") %ul %li - %strong Default: Map a FogBugz account ID to a full name + %strong= _("Default: Map a FogBugz account ID to a full name") %p - An empty GitLab User field will add the FogBugz user's full name - (e.g. "By John Smith") in the description of all issues and comments. - It will also associate and/or assign these issues and comments with - the project creator. + = _("An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator.") %li - %strong Map a FogBugz account ID to a GitLab user + %strong= _("Map a FogBugz account ID to a GitLab user") %p - Selecting a GitLab user will add a link to the GitLab user in the descriptions - of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also - associate and/or assign these issues and comments with the selected user. + = _('Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also associate and/or assign these issues and comments with the selected user.').html_safe .table-holder %table.table %thead %tr - %th ID - %th Name - %th Email - %th GitLab User + %th= _("ID") + %th= _("Name") + %th= _("Email") + %th= _("GitLab User") %tbody - @user_map.each do |id, user| %tr @@ -45,4 +39,4 @@ scope: :all, email_user: true, selected: user[:gitlab_user]) .form-actions - = submit_tag 'Continue to the next step', class: 'btn btn-create' + = submit_tag _('Continue to the next step'), class: 'btn btn-create' diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index 7b832c6a23a..830d141ebea 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -1,20 +1,19 @@ -- page_title "FogBugz import" -- header_title "Projects", root_path +- page_title _("FogBugz import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-bug - Import projects from FogBugz + = _('Import projects from FogBugz') - if @repos.any? %p.light - Select projects you want to import. + = _('Select projects you want to import.') %p.light - Optionally, you can - = link_to 'customize', new_user_map_import_fogbugz_path - how FogBugz email addresses and usernames are imported into GitLab. + - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path) + = _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize } %hr %p = button_tag class: 'btn btn-import btn-success js-import-all' do - Import all projects + = _('Import all projects') = icon("spinner spin", class: "loading-icon") .table-responsive @@ -24,9 +23,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From FogBugz - %th To GitLab - %th Status + %th= _("From FogBugz") + %th= _("To GitLab") + %th= _("Status") %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -38,10 +37,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _("done") - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _("started") - else = project.human_import_status_name @@ -53,7 +52,7 @@ #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - Import + = _("Import") = icon("spinner spin", class: "loading-icon") .js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } } diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml index 581576a8a3d..2b3102f9af9 100644 --- a/app/views/import/gitea/new.html.haml +++ b/app/views/import/gitea/new.html.haml @@ -1,23 +1,22 @@ -- page_title "Gitea Import" -- header_title "Projects", root_path +- page_title _("Gitea Import") +- header_title _("Projects"), root_path %h3.page-title = custom_icon('go_logo') - Import Projects from Gitea + = _('Import Projects from Gitea') %p - To get started, please enter your Gitea Host URL and a - = succeed '.' do - = link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token' + - link_to_personal_token = link_to(_('Personal Access Token'), 'https://github.com/gogits/go-gogs-client/wiki#access-token') + = _('To get started, please enter your Gitea Host URL and a %{link_to_personal_token}.').html_safe % { link_to_personal_token: link_to_personal_token } = form_tag personal_access_token_import_gitea_path do .form-group.row - = label_tag :gitea_host_url, 'Gitea Host URL', class: 'col-form-label col-sm-2' + = label_tag :gitea_host_url, _('Gitea Host URL'), class: 'col-form-label col-sm-2' .col-sm-4 = text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control' .form-group.row - = label_tag :personal_access_token, 'Personal Access Token', class: 'col-form-label col-sm-2' + = label_tag :personal_access_token, _('Personal Access Token'), class: 'col-form-label col-sm-2' .col-sm-4 = text_field_tag :personal_access_token, nil, class: 'form-control' .form-actions - = submit_tag 'List Your Gitea Repositories', class: 'btn btn-create' + = submit_tag _('List Your Gitea Repositories'), class: 'btn btn-create' diff --git a/app/views/import/gitea/status.html.haml b/app/views/import/gitea/status.html.haml index 589ca27e45d..88244fde16b 100644 --- a/app/views/import/gitea/status.html.haml +++ b/app/views/import/gitea/status.html.haml @@ -1,7 +1,7 @@ -- page_title "Gitea Import" -- header_title "Projects", root_path +- page_title _("Gitea Import") +- header_title _("Projects"), root_path %h3.page-title = custom_icon('go_logo') - Import Projects from Gitea + = _('Import Projects from Gitea') = render 'import/githubish_status', provider: 'gitea' diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index b9ebb1a39d9..6ff25f2c842 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -1,7 +1,7 @@ - title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import') - page_title title - breadcrumb_title title -- header_title "Projects", root_path +- header_title _("Projects"), root_path %h3.page-title = icon 'github', text: import_github_title diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index b00b972d9c9..be057be6d1a 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -1,7 +1,7 @@ - title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import') - page_title title - breadcrumb_title title -- header_title "Projects", root_path +- header_title _("Projects"), root_path %h3.page-title = icon 'github', text: import_github_title diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 37734414835..b7bfbae5edf 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -1,15 +1,15 @@ -- page_title "GitLab.com import" -- header_title "Projects", root_path +- page_title _("GitLab.com import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-heart - Import projects from GitLab.com + = _('Import projects from GitLab.com') %p.light - Select projects you want to import. + = _('Select projects you want to import.') %hr %p = button_tag class: "btn btn-import btn-success js-import-all" do - Import all projects + = _('Import all projects') = icon("spinner spin", class: "loading-icon") .table-responsive @@ -19,9 +19,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From GitLab.com - %th To this GitLab instance - %th Status + %th= _('From GitLab.com') + %th= _('To this GitLab instance') + %th= _('Status') %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -33,10 +33,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _('done') - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _('started') - else = project.human_import_status_name @@ -48,7 +48,7 @@ = import_project_target(repo['namespace']['path'], repo['name']) %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - Import + = _('Import') = icon("spinner spin", class: "loading-icon") .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } } diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index cc672a5ea7c..a258fc64b1e 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -1,9 +1,9 @@ -- page_title "GitLab Import" -- header_title "Projects", root_path +- page_title _("GitLab Import") +- header_title _("Projects"), root_path %h3.page-title = icon('gitlab') - Import an exported GitLab project + = _('Import an exported GitLab project') %hr = form_tag import_gitlab_project_path, class: 'new_project', multipart: true do @@ -24,19 +24,19 @@ #{user_url(current_user.username)}/ = hidden_field_tag :namespace_id, value: current_user.namespace_id .form-group.col-12.col-sm-6.project-path - = label_tag :path, 'Project name', class: 'label-light' + = label_tag :path, _('Project name'), class: 'label-light' = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true .row .form-group.col-md-12 - To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. + = _("To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.") .row .form-group.col-sm-12 = hidden_field_tag :namespace_id, @namespace.id - = label_tag :file, 'GitLab project export', class: 'label-light' + = label_tag :file, _('GitLab project export'), class: 'label-light' .form-group = file_field_tag :file, class: '' .row .form-actions.col-sm-12 - = submit_tag 'Import project', class: 'btn btn-create' - = link_to 'Cancel', new_project_path, class: 'btn btn-cancel' + = submit_tag _('Import project'), class: 'btn btn-create' + = link_to _('Cancel'), new_project_path, class: 'btn btn-cancel' diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index 2f1fb8d9c56..fd6e4726fc5 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -1,62 +1,62 @@ -- page_title "Google Code import" -- header_title "Projects", root_path +- page_title _("Google Code import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-google - Import projects from Google Code + = _('Import projects from Google Code') %hr = form_tag callback_import_google_code_path, multipart: true do %p - Follow the steps below to export your Google Code project data. - In the next step, you'll be able to select the projects you want to import. + = _('Follow the steps below to export your Google Code project data.') + = _("In the next step, you'll be able to select the projects you want to import.") %ol %li %p - Go to - #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer'}. + - link_to_google_takeout = link_to(_("Google Takeout"), "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer') + = _("Go to %{link_to_google_takeout}.").html_safe % { link_to_google_takeout: link_to_google_takeout } %li %p - Make sure you're logged into the account that owns the projects you'd like to import. + = _("Make sure you're logged into the account that owns the projects you'd like to import.") %li %p - Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting". + = _('Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting".').html_safe %li %p - Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right. + = _('Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right.').html_safe %li %p - Choose <strong>Next</strong> at the bottom of the page. + = _('Choose <strong>Next</strong> at the bottom of the page.').html_safe %li %p - Leave the "File type" and "Delivery method" options on their default values. + = _('Leave the "File type" and "Delivery method" options on their default values.') %li %p - Choose <strong>Create archive</strong> and wait for archiving to complete. + = _('Choose <strong>Create archive</strong> and wait for archiving to complete.').html_safe %li %p - Click the <strong>Download</strong> button and wait for downloading to complete. + = _('Click the <strong>Download</strong> button and wait for downloading to complete.').html_safe %li %p - Find the downloaded ZIP file and decompress it. + = _('Find the downloaded ZIP file and decompress it.') %li %p - Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file. + = _('Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file.').html_safe %li %p - Upload <code>GoogleCodeProjectHosting.json</code> here: + = _('Upload <code>GoogleCodeProjectHosting.json</code> here:').html_safe %p %input{ type: "file", name: "dump_file", id: "dump_file" } %li %p - Do you want to customize how Google Code email addresses and usernames are imported into GitLab? + = _('Do you want to customize how Google Code email addresses and usernames are imported into GitLab?') %p = label_tag :create_user_map_0 do = radio_button_tag :create_user_map, 0, true - No, directly import the existing email addresses and usernames. + = _('No, directly import the existing email addresses and usernames.') %p = label_tag :create_user_map_1 do = radio_button_tag :create_user_map, 1, false - Yes, let me map Google Code users to full names or GitLab users. + = _('Yes, let me map Google Code users to full names or GitLab users.') %li %p - = submit_tag 'Continue to the next step', class: "btn btn-create" + = submit_tag _('Continue to the next step'), class: "btn btn-create" diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml index 91c774f575c..baaaf6bdc63 100644 --- a/app/views/import/google_code/new_user_map.html.haml +++ b/app/views/import/google_code/new_user_map.html.haml @@ -1,44 +1,36 @@ -- page_title "User map", "Google Code import" -- header_title "Projects", root_path +- page_title _("User map"), _("Google Code import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-google - Import projects from Google Code + = _('Import projects from Google Code') %hr = form_tag create_user_map_import_google_code_path do %p - Customize how Google Code email addresses and usernames are imported into GitLab. - In the next step, you'll be able to select the projects you want to import. + = _("Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.") %p - The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side. + = _("The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.").html_safe %ul %li - %strong Default: Directly import the Google Code email address or username + %strong= _("Default: Directly import the Google Code email address or username") %p - <code>"johnsmith@example.com": "johnsm...@example.com"</code> - will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. - The email address or username is masked to ensure the user's privacy. + = _('<code>"johnsmith@example.com": "johnsm...@example.com"</code> will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user\'s privacy.').html_safe %li - %strong Map a Google Code user to a GitLab user + %strong= _("Map a Google Code user to a GitLab user") %p - <code>"johnsmith@example.com": "@johnsmith"</code> - will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, - and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com. + = _('<code>"johnsmith@example.com": "@johnsmith"</code> will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.').html_safe %li - %strong Map a Google Code user to a full name + %strong= _("Map a Google Code user to a full name") %p - <code>"johnsmith@example.com": "John Smith"</code> - will add "By John Smith" to all issues and comments originally created by johnsmith@example.com. + = _('<code>"johnsmith@example.com": "John Smith"</code> will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.').html_safe %li - %strong Map a Google Code user to a full email address + %strong= _("Map a Google Code user to a full email address") %p - <code>"johnsmith@example.com": "johnsmith@example.com"</code> - will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. - By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address. + = _('<code>"johnsmith@example.com": "johnsmith@example.com"</code> will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user\'s privacy. Use this option if you want to show the full email address.').html_safe .form-group.row .col-sm-12 = text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15 .form-actions - = submit_tag 'Continue to the next step', class: "btn btn-create" + = submit_tag _('Continue to the next step'), class: "btn btn-create" diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index acf7a108cb0..347e2820f94 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -1,25 +1,24 @@ -- page_title "Google Code import" -- header_title "Projects", root_path +- page_title _("Google Code import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-google - Import projects from Google Code + = _('Import projects from Google Code') - if @repos.any? %p.light - Select projects you want to import. + = _('Select projects you want to import.') %p.light - Optionally, you can - = link_to "customize", new_user_map_import_google_code_path - how Google Code email addresses and usernames are imported into GitLab. + - link_to_customize = link_to(_("customize"), new_user_map_import_google_code_path) + = _("Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab.").html_safe % { link_to_customize: link_to_customize } %hr %p - if @incompatible_repos.any? = button_tag class: "btn btn-import btn-success js-import-all" do - Import all compatible projects + = _("Import all compatible projects") = icon("spinner spin", class: "loading-icon") - else = button_tag class: "btn btn-import btn-success js-import-all" do - Import all projects + = _("Import all projects") = icon("spinner spin", class: "loading-icon") .table-responsive @@ -29,9 +28,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From Google Code - %th To GitLab - %th Status + %th= _("From Google Code") + %th= _("To GitLab") + %th= _("Status") %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -43,10 +42,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _("done") - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _("started") - else = project.human_import_status_name @@ -58,7 +57,7 @@ #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - Import + = _("Import") = icon("spinner spin", class: "loading-icon") - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.id}" } @@ -66,15 +65,12 @@ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status - = label_tag "Incompatible Project", nil, class: "label badge-danger" + = label_tag _("Incompatible Project"), nil, class: "label badge-danger" - if @incompatible_repos.any? %p - One or more of your Google Code projects cannot be imported into GitLab - directly because they use Subversion or Mercurial for version control, - rather than Git. Please convert them to Git on Google Code, and go - through the - = link_to "import flow", new_import_google_code_path - again. + = _("One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.") + - link_to_import_flow = link_to(_("import flow"), new_import_google_code_path) + = _("Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again.").html_safe % { link_to_import_flow: link_to_import_flow } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } } diff --git a/app/views/import/manifest/_form.html.haml b/app/views/import/manifest/_form.html.haml new file mode 100644 index 00000000000..763beb5958f --- /dev/null +++ b/app/views/import/manifest/_form.html.haml @@ -0,0 +1,23 @@ += form_tag upload_import_manifest_path, multipart: true do + .form-group + = label_tag :group_id, nil, class: 'label-light' do + = _('Group') + .input-group + .input-group-prepend.has-tooltip{ title: root_url } + .input-group-text + = root_url + = select_tag :group_id, namespaces_options(nil, display_path: true, groups_only: true), { class: 'select2 js-select-namespace' } + .form-text.text-muted + = _('Choose the top-level group for your repository imports.') + + .form-group + = label_tag :manifest, class: 'label-light' do + = _('Manifest') + = file_field_tag :manifest, class: 'form-control-file', required: true + .form-text.text-muted + = _('Import multiple repositories by uploading a manifest file.') + = link_to icon('question-circle'), help_page_path('user/project/import/manifest') + + .append-bottom-10 + = submit_tag _('List available repositories'), class: 'btn btn-success' + = link_to _('Cancel'), new_project_path, class: 'btn btn-cancel' diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml new file mode 100644 index 00000000000..056e4922b9e --- /dev/null +++ b/app/views/import/manifest/new.html.haml @@ -0,0 +1,12 @@ +- page_title "Manifest file import" +- header_title "Projects", root_path + +%h3.page-title + = _('Manifest file import') + +- if @errors.present? + .alert.alert-danger + - @errors.each do |error| + = error + += render 'form' diff --git a/app/views/import/manifest/status.html.haml b/app/views/import/manifest/status.html.haml new file mode 100644 index 00000000000..5b2e1005398 --- /dev/null +++ b/app/views/import/manifest/status.html.haml @@ -0,0 +1,42 @@ +- page_title "Manifest import" +- header_title "Projects", root_path +- provider = 'manifest' + +%h3.page-title + = _('Manifest file import') + +%p + = button_tag class: "btn btn-import btn-success js-import-all" do + = import_all_githubish_repositories_button_label + = icon("spinner spin", class: "loading-icon") + +.table-responsive + %table.table.import-jobs + %thead + %tr + %th= _('Repository URL') + %th= _('To GitLab') + %th= _('Status') + %tbody + - @already_added_projects.each do |project| + %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) } + %td + = project.import_url + %td + = link_to_project project + %td.job-status + = render 'import/project_status', project: project + + - @pending_repositories.each do |repository| + %tr{ id: "repo_#{repository[:id]}" } + %td + = repository[:url] + %td.import-target + = import_project_target(@group.full_path, repository[:path]) + %td.import-actions.job-status + = button_tag class: "btn btn-import js-add-to-import" do + = _('Import') + = icon("spinner spin", class: "loading-icon") + +.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]), + import_path: url_for([:import, provider]) } } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index d8e32651b36..3aa8eb18bf3 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -61,7 +61,9 @@ - if header_link?(:sign_in) %li.nav-item %div - = link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in' + - sign_in_text = allow_signup? ? 'Sign in / Register' : 'Sign in' + = link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in' + %button.navbar-toggler.d-block.d-sm-none{ type: 'button' } %span.sr-only Toggle navigation diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index 62402c32e08..4c73da4c75b 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -6,14 +6,14 @@ = sprite_icon('admin', size: 24) .sidebar-context-title Admin Area %ul.sidebar-top-level-items - = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: {class: 'home'}) do + = nav_link(controller: %w(dashboard admin projects users groups jobs runners gitaly_servers cohorts conversational_development_index), html_options: {class: 'home'}) do = link_to admin_root_path, class: 'shortcuts-tree' do .nav-icon-container = sprite_icon('overview') %span.nav-item-name Overview %ul.sidebar-sub-level-items - = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts conversational_development_index), html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: %w(dashboard admin projects users groups jobs runners gitaly_servers cohorts conversational_development_index), html_options: { class: "fly-out-top-item" } ) do = link_to admin_root_path do %strong.fly-out-top-item-name #{ _('Overview') } @@ -42,6 +42,10 @@ = link_to admin_runners_path, title: 'Runners' do %span Runners + = nav_link(controller: :gitaly_servers) do + = link_to admin_gitaly_servers_path, title: 'Gitaly Servers' do + %span + Gitaly Servers = nav_link path: 'cohorts#index' do = link_to admin_cohorts_path, title: 'Cohorts' do %span diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 00d75b3399b..33de74dbaa2 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -122,7 +122,7 @@ = render_if_exists 'projects/sidebar/issues_service_desk' = nav_link(controller: :milestones) do - = link_to project_milestones_path(@project), title: 'Milestones' do + = link_to project_milestones_path(@project), title: 'Milestones', class: 'qa-milestones-link' do %span = _('Milestones') - if project_nav_tab? :external_issue_tracker diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 1f701f2aa1b..6bf21570d41 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,10 +1,9 @@ %div{ class: container_class } .nav-block.activity-filter-block.activities + = render 'shared/event_filter' .controls = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do = icon('rss') - = render 'shared/event_filter' - .content_list.project-activity{ :"data-href" => activity_project_path(@project) } = spinner diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 8f535b9d789..3da6db08580 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -1,49 +1,62 @@ - active_tab = local_assigns.fetch(:active_tab, 'blank') -- f = local_assigns.fetch(:f) .project-import .form-group.import-btn-container.clearfix - = f.label :visibility_level, class: 'label-light' do #the label here seems wrong + %h5 Import project from .import-buttons - if gitlab_project_import_enabled? .import_gitlab_project.has-tooltip{ data: { container: 'body' } } = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = icon('gitlab', text: 'GitLab export') - %div - - if github_import_enabled? + + - if github_import_enabled? + %div = link_to new_import_github_path, class: 'btn js-import-github' do = icon('github', text: 'GitHub') - %div - - if bitbucket_import_enabled? + + - if bitbucket_import_enabled? + %div = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do = icon('bitbucket', text: 'Bitbucket') - unless bitbucket_import_configured? = render 'bitbucket_import_modal' - %div - - if gitlab_import_enabled? + + - if gitlab_import_enabled? + %div = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do = icon('gitlab', text: 'GitLab.com') - unless gitlab_import_configured? = render 'gitlab_import_modal' - %div - - if google_code_import_enabled? + + - if google_code_import_enabled? + %div = link_to new_import_google_code_path, class: 'btn import_google_code' do = icon('google', text: 'Google Code') - %div - - if fogbugz_import_enabled? + + - if fogbugz_import_enabled? + %div = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do = icon('bug', text: 'Fogbugz') - %div - - if gitea_import_enabled? + + - if gitea_import_enabled? + %div = link_to new_import_gitea_path, class: 'btn import_gitea' do = custom_icon('go_logo') Gitea - %div - - if git_import_enabled? + + - if git_import_enabled? + %div %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } } = icon('git', text: 'Repo by URL') + + - if manifest_import_enabled? + %div + = link_to new_import_manifest_path, class: 'btn import_manifest' do + = icon('file-text-o', text: 'Manifest file') + .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } - %hr + = form_for @project, html: { class: 'new_project' } do |f| + %hr = render "shared/import_form", f: f = render 'new_project_fields', f: f, project_name_id: "import-url-name" diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index e0ecf56525a..f4c91377ecb 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -3,13 +3,12 @@ - if actions.present? .btn-group .dropdown - %button.dropdown.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' } - = custom_icon('icon_play') + %button.dropdown.dropdown-new.btn.btn-default.has-tooltip{ type: 'button', 'data-toggle' => 'dropdown', title: s_('Environments|Deploy to...') } + = sprite_icon('play') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-right - actions.each do |action| - next unless can?(current_user, :update_build, action) %li - = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do - = custom_icon('icon_play') + = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow', class: 'btn' do %span= action.name.humanize diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml index 95f950948ab..281e042c915 100644 --- a/app/views/projects/deployments/_rollback.haml +++ b/app/views/projects/deployments/_rollback.haml @@ -1,6 +1,7 @@ - if can?(current_user, :create_deployment, deployment) && deployment.deployable - = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do + - tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment') + = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do - if deployment.last? - = _("Re-deploy") + = sprite_icon('repeat') - else - = _("Rollback") + = sprite_icon('redo') diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml index a264252e095..4694bc39d54 100644 --- a/app/views/projects/environments/_external_url.html.haml +++ b/app/views/projects/environments/_external_url.html.haml @@ -1,4 +1,4 @@ - if environment.external_url && can?(current_user, :read_environment, environment) - = link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url' do + = link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url has-tooltip', title: s_('Environments|Open live environment') do = sprite_icon('external-link') View deployment diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml deleted file mode 100644 index c35f9af2873..00000000000 --- a/app/views/projects/environments/_stop.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -- if can?(current_user, :create_deployment, environment) && environment.stop_action? - .inline - = link_to stop_project_environment_path(@project, environment), method: :post, - class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do - = icon('stop', class: 'stop-env-icon') diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index add394a6356..c7890b37381 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -4,6 +4,33 @@ - page_title "Environments" %div{ class: container_class } + - if can?(current_user, :stop_environment, @environment) + #stop-environment-modal.modal.fade{ tabindex: -1 } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title.d-flex.mw-100 + Stopping + %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } + = @environment.name + ? + .modal-body + %p= s_('Environments|Are you sure you want to stop this environment?') + - unless @environment.stop_action_available? + .warning_message + %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, + emphasis_end: '</strong>'.html_safe, + ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, + ci_config_link_end: '</a>'.html_safe } + %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', + target: '_blank', + rel: 'noopener noreferrer' } + = s_('Environments|Learn more about stopping environments') + .modal-footer + = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } + = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do + = s_('Environments|Stop environment') + .row.top-area.adjust .col-md-7 %h3.page-title= @environment.name @@ -15,7 +42,10 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_project_environment_path(@project, @environment), class: 'btn' - if can?(current_user, :stop_environment, @environment) - = link_to 'Stop', stop_project_environment_path(@project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post + = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', + target: '#stop-environment-modal' } do + = sprite_icon('stop') + = s_('Environments|Stop') .environments-container - if @deployments.blank? diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml index 8a390cf8700..1a9ab288683 100644 --- a/app/views/projects/merge_requests/_mr_box.html.haml +++ b/app/views/projects/merge_requests/_mr_box.html.haml @@ -1,4 +1,4 @@ -.detail-page-description.content-block +.detail-page-description %h2.title = markdown_field(@merge_request, :title) diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index ace094a671a..28f0a167128 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -7,12 +7,12 @@ .form-group.row = f.label :title, "Title", class: "col-form-label col-sm-2" .col-sm-10 - = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true + = f.text_field :title, maxlength: 255, class: "qa-milestone-title form-control", required: true, autofocus: true .form-group.row.milestone-description = f.label :description, "Description", class: "col-form-label col-sm-2" .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project) } do - = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' + = render 'projects/zen', f: f, attr: :description, classes: 'qa-milestone-description note-textarea', placeholder: 'Write milestone description...' = render 'shared/notes/hints' .clearfix .error-alert @@ -20,7 +20,7 @@ .form-actions - if @milestone.new_record? - = f.submit 'Create milestone', class: "btn-create btn" + = f.submit 'Create milestone', class: "btn-create btn qa-milestone-create-button" = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel" - else = f.submit 'Save changes', class: "btn-save btn" diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 5b0197ed58c..26d2ea8447b 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -8,7 +8,7 @@ .nav-controls = render 'shared/milestones_sort_dropdown' - if can?(current_user, :admin_milestone, @project) - = link_to new_project_milestone_path(@project), class: "btn btn-new", title: 'New milestone' do + = link_to new_project_milestone_path(@project), class: "btn btn-new qa-new-project-milestone", title: 'New milestone' do New milestone .milestones diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index f7b04c436a6..2a9e20c2caa 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -60,7 +60,7 @@ = icon('angle-double-left') .detail-page-description.milestone-detail - %h2.title + %h2.title.qa-milestone-title = markdown_field(@milestone, :title) %div diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 5bb1bfb7059..6c363345e38 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -55,13 +55,12 @@ = render 'project_templates', f: f .tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' } - = form_for @project, html: { class: 'new_project' } do |f| - - if import_sources_enabled? - = render 'import_project_pane', f: f, active_tab: active_tab - - else - .nothing-here-block - %h4 No import options available - %p Contact an administrator to enable options for importing your project. + - if import_sources_enabled? + = render 'import_project_pane', active_tab: active_tab + - else + .nothing-here-block + %h4 No import options available + %p Contact an administrator to enable options for importing your project. .save-project-loader.d-none .center diff --git a/app/views/projects/protected_branches/_update_protected_branch.html.haml b/app/views/projects/protected_branches/_update_protected_branch.html.haml index f242459f69b..74bfaa9ff80 100644 --- a/app/views/projects/protected_branches/_update_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_update_protected_branch.html.haml @@ -6,5 +6,5 @@ %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level = dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') , - options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header', + options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header', data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }}) diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml index 82ef08272d3..05cee483c0e 100644 --- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -2,7 +2,7 @@ %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } %td - %span.ref-name.qa-protected-branch-name= protected_branch.name + %span.ref-name= protected_branch.name - if @project.root_ref?(protected_branch.name) %span.badge.badge-info.prepend-left-5 default diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index ecb5b1c6ebc..7afb7b3a93b 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,4 @@ -.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller +.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.flex-fill .fade-left= icon('angle-left') .fade-right= icon('angle-right') %ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index ac2ebb701a5..d38d161047b 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,7 +1,7 @@ - if any_projects?(@projects) .project-item-select-holder.btn-group - %a.btn.btn-new.new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } } + %a.btn.btn-new.new-project-item-link.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] } } = icon('spinner spin') = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled] - %button.btn.btn-new.new-project-item-select-button + %button.btn.btn-new.new-project-item-select-button.qa-new-project-item-select-button = icon('caret-down') diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml index 532712ee6d1..f3b56df0c96 100644 --- a/app/views/shared/hook_logs/_content.html.haml +++ b/app/views/shared/hook_logs/_content.html.haml @@ -30,7 +30,7 @@ %h5 Request body: %pre - :plain + :escaped #{JSON.pretty_generate(hook_log.request_data)} %h5 Response headers: %pre @@ -40,5 +40,5 @@ %h5 Response body: %pre - :plain + :escaped #{hook_log.response_body} diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 37625a4a163..c2da363b8c6 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -7,7 +7,7 @@ - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone") - if selected.present? || params[:milestone_title].present? = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id) -= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "qa-issuable-milestone-dropdown js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "qa-issuable-dropdown-menu-milestone dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index bd87bb38e77..3b017c62a80 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -18,7 +18,7 @@ = form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}" .col-sm-10{ class: ("col-md-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group.row - has_labels = @labels && @labels.any? = form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}" diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml index 6b2715b47a7..c360f1ffe2a 100644 --- a/app/views/shared/notes/_form.html.haml +++ b/app/views/shared/notes/_form.html.haml @@ -40,5 +40,5 @@ = yield(:note_actions) - %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Discard draft" } } + %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } Discard draft diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index f9f0efb302a..12706613ac2 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -15,14 +15,14 @@ class EmailReceiverWorker private - def handle_failure(raw, e) - Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + def handle_failure(raw, error) + Rails.logger.warn("Email can not be processed: #{error}\n\n#{raw}") return unless raw.present? can_retry = false reason = - case e + case error when Gitlab::Email::UnknownIncomingEmail "We couldn't figure out what the email is for. Please create your issue or comment through the web interface." when Gitlab::Email::SentNotificationNotFoundError @@ -42,7 +42,7 @@ class EmailReceiverWorker "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." when Gitlab::Email::InvalidRecordError can_retry = true - e.message + error.message end if reason diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index fd49bc18161..2d381c6fd6c 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -65,10 +65,10 @@ class GitGarbageCollectWorker client.repack_incremental end rescue GRPC::NotFound => e - Gitlab::GitLogger.error("#{method} failed:\nRepository not found") + Gitlab::GitLogger.error("#{__method__} failed:\nRepository not found") raise Gitlab::Git::Repository::NoRepository.new(e) rescue GRPC::BadStatus => e - Gitlab::GitLogger.error("#{method} failed:\n#{e}") + Gitlab::GitLogger.error("#{__method__} failed:\n#{e}") raise Gitlab::Git::CommandError.new(e) end diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb index a3ecfa8e711..01d03ec7888 100644 --- a/app/workers/object_storage/migrate_uploads_worker.rb +++ b/app/workers/object_storage/migrate_uploads_worker.rb @@ -1,6 +1,4 @@ # frozen_string_literal: true -# rubocop:disable Metrics/LineLength -# rubocop:disable Style/Documentation module ObjectStorage class MigrateUploadsWorker diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index ed39b4a1ea8..c9f6df9b56d 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -79,9 +79,10 @@ class ProcessCommitWorker # Avoid reprocessing commits that already exist in the upstream # when project is forked. This will also prevent duplicated system notes. def commit_exists_in_upstream?(project, commit_hash) - return false unless project.forked? + upstream_project = project.fork_source + + return false unless upstream_project - upstream_project = project.forked_from_project commit_id = commit_hash.with_indifferent_access[:id] upstream_project.commit(commit_id).present? end |