diff options
Diffstat (limited to 'app')
19 files changed, 92 insertions, 534 deletions
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 12d68256598..faf722f61af 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -42,19 +42,12 @@ export default { return { showDetail: false, detailIssue: boardsStore.detail, - multiSelect: boardsStore.multiSelect, }; }, computed: { issueDetailVisible() { return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id; }, - multiSelectVisible() { - return this.multiSelect.list.findIndex(issue => issue.id === this.issue.id) > -1; - }, - canMultiSelect() { - return gon.features && gon.features.multiSelectBoard; - }, }, methods: { mouseDown() { @@ -65,20 +58,14 @@ export default { }, showIssue(e) { if (e.target.classList.contains('js-no-trigger')) return; + if (this.showDetail) { this.showDetail = false; - // If CMD or CTRL is clicked - const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey); - if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) { - eventHub.$emit('clearDetailIssue', isMultiSelect); - - if (isMultiSelect) { - eventHub.$emit('newDetailIssue', this.issue, isMultiSelect); - } + eventHub.$emit('clearDetailIssue'); } else { - eventHub.$emit('newDetailIssue', this.issue, isMultiSelect); + eventHub.$emit('newDetailIssue', this.issue); boardsStore.setListDetail(this.list); } } @@ -90,7 +77,6 @@ export default { <template> <li :class="{ - 'multi-select': multiSelectVisible, 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible, diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 1273fcc6a91..de41698ca04 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -1,22 +1,12 @@ <script> -import { Sortable, MultiDrag } from 'sortablejs'; +/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ +import Sortable from 'sortablejs'; import { GlLoadingIcon } from '@gitlab/ui'; -import _ from 'underscore'; import boardNewIssue from './board_new_issue.vue'; import boardCard from './board_card.vue'; import eventHub from '../eventhub'; import boardsStore from '../stores/boards_store'; -import { sprintf, __ } from '~/locale'; -import createFlash from '~/flash'; -import { - getBoardSortableDefaultOptions, - sortableStart, - sortableEnd, -} from '../mixins/sortable_default_options'; - -if (gon.features && gon.features.multiSelectBoard) { - Sortable.mount(new MultiDrag()); -} +import { getBoardSortableDefaultOptions, sortableStart } from '../mixins/sortable_default_options'; export default { name: 'BoardList', @@ -64,14 +54,6 @@ export default { showIssueForm: false, }; }, - computed: { - paginatedIssueText() { - return sprintf(__('Showing %{pageSize} of %{total} issues'), { - pageSize: this.list.issues.length, - total: this.list.issuesSize, - }); - }, - }, watch: { filters: { handler() { @@ -105,20 +87,11 @@ export default { eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop); }, mounted() { - const multiSelectOpts = {}; - if (gon.features && gon.features.multiSelectBoard) { - multiSelectOpts.multiDrag = true; - multiSelectOpts.selectedClass = 'js-multi-select'; - multiSelectOpts.animation = 500; - } - const options = getBoardSortableDefaultOptions({ scroll: true, disabled: this.disabled, filter: '.board-list-count, .is-disabled', dataIdAttr: 'data-issue-id', - removeCloneOnHide: false, - ...multiSelectOpts, group: { name: 'issues', /** @@ -172,66 +145,25 @@ export default { card.showDetail = false; const { list } = card; - const issue = list.findIssue(Number(e.item.dataset.issueId)); - boardsStore.startMoving(list, issue); sortableStart(); }, onAdd: e => { - const { items = [], newIndicies = [] } = e; - if (items.length) { - // Not using e.newIndex here instead taking a min of all - // the newIndicies. Basically we have to find that during - // a drop what is the index we're going to start putting - // all the dropped elements from. - const newIndex = Math.min(...newIndicies.map(obj => obj.index).filter(i => i !== -1)); - const issues = items.map(item => - boardsStore.moving.list.findIssue(Number(item.dataset.issueId)), - ); + boardsStore.moveIssueToList( + boardsStore.moving.list, + this.list, + boardsStore.moving.issue, + e.newIndex, + ); - boardsStore.moveMultipleIssuesToList({ - listFrom: boardsStore.moving.list, - listTo: this.list, - issues, - newIndex, - }); - } else { - boardsStore.moveIssueToList( - boardsStore.moving.list, - this.list, - boardsStore.moving.issue, - e.newIndex, - ); - this.$nextTick(() => { - e.item.remove(); - }); - } + this.$nextTick(() => { + e.item.remove(); + }); }, onUpdate: e => { const sortedArray = this.sortable.toArray().filter(id => id !== '-1'); - - const { items = [], newIndicies = [], oldIndicies = [] } = e; - if (items.length) { - const newIndex = Math.min(...newIndicies.map(obj => obj.index)); - const issues = items.map(item => - boardsStore.moving.list.findIssue(Number(item.dataset.issueId)), - ); - boardsStore.moveMultipleIssuesInList({ - list: this.list, - issues, - oldIndicies: oldIndicies.map(obj => obj.index), - newIndex, - idArray: sortedArray, - }); - e.items.forEach(el => { - Sortable.utils.deselect(el); - }); - boardsStore.clearMultiSelect(); - return; - } - boardsStore.moveIssueInList( this.list, boardsStore.moving.issue, @@ -240,133 +172,9 @@ export default { sortedArray, ); }, - onEnd: e => { - const { items = [], clones = [], to } = e; - - // This is not a multi select operation - if (!items.length && !clones.length) { - sortableEnd(); - return; - } - - let toList; - if (to) { - const containerEl = to.closest('.js-board-list'); - toList = boardsStore.findList('id', Number(containerEl.dataset.board)); - } - - /** - * onEnd is called irrespective if the cards were moved in the - * same list or the other list. Don't remove items if it's same list. - */ - const isSameList = toList && toList.id === this.list.id; - - if (toList && !isSameList && boardsStore.shouldRemoveIssue(this.list, toList)) { - const issues = items.map(item => this.list.findIssue(Number(item.dataset.issueId))); - - if (_.compact(issues).length && !boardsStore.issuesAreContiguous(this.list, issues)) { - const indexes = []; - const ids = this.list.issues.map(i => i.id); - issues.forEach(issue => { - const index = ids.indexOf(issue.id); - if (index > -1) { - indexes.push(index); - } - }); - - // Descending sort because splice would cause index discrepancy otherwise - const sortedIndexes = indexes.sort((a, b) => (a < b ? 1 : -1)); - - sortedIndexes.forEach(i => { - /** - * **setTimeout and splice each element one-by-one in a loop - * is intended.** - * - * The problem here is all the indexes are in the list but are - * non-contiguous. Due to that, when we splice all the indexes, - * at once, Vue -- during a re-render -- is unable to find reference - * nodes and the entire app crashes. - * - * If the indexes are contiguous, this piece of code is not - * executed. If it is, this is a possible regression. Only when - * issue indexes are far apart, this logic should ever kick in. - */ - setTimeout(() => { - this.list.issues.splice(i, 1); - }, 0); - }); - } - } - - if (!toList) { - createFlash(__('Something went wrong while performing the action.')); - } - - if (!isSameList) { - boardsStore.clearMultiSelect(); - - // Since Vue's list does not re-render the same keyed item, we'll - // remove `multi-select` class to express it's unselected - if (clones && clones.length) { - clones.forEach(el => el.classList.remove('multi-select')); - } - - // Due to some bug which I am unable to figure out - // Sortable does not deselect some pending items from the - // source list. - // We'll just do it forcefully here. - Array.from(document.querySelectorAll('.js-multi-select') || []).forEach(item => { - Sortable.utils.deselect(item); - }); - - /** - * SortableJS leaves all the moving items "as is" on the DOM. - * Vue picks up and rehydrates the DOM, but we need to explicity - * remove the "trash" items from the DOM. - * - * This is in parity to the logic on single item move from a list/in - * a list. For reference, look at the implementation of onAdd method. - */ - this.$nextTick(() => { - if (items && items.length) { - items.forEach(item => { - item.remove(); - }); - } - }); - } - sortableEnd(); - }, onMove(e) { return !e.related.classList.contains('board-list-count'); }, - onSelect(e) { - const { - item: { classList }, - } = e; - - if ( - classList && - classList.contains('js-multi-select') && - !classList.contains('multi-select') - ) { - Sortable.utils.deselect(e.item); - } - }, - onDeselect: e => { - const { - item: { dataset, classList }, - } = e; - - if ( - classList && - classList.contains('multi-select') && - !classList.contains('js-multi-select') - ) { - const issue = this.list.findIssue(Number(dataset.issueId)); - boardsStore.toggleMultiSelect(issue); - } - }, }); this.sortable = Sortable.create(this.$refs.list, options); @@ -452,7 +260,7 @@ export default { <li v-if="showCount" class="board-list-count text-center" data-issue-id="-1"> <gl-loading-icon v-show="list.loadingMore" label="Loading more issues" /> <span v-if="list.issues.length === list.issuesSize">{{ __('Showing all issues') }}</span> - <span v-else>{{ paginatedIssueText }}</span> + <span v-else> Showing {{ list.issues.length }} of {{ list.issuesSize }} issues </span> </li> </ul> </div> diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js deleted file mode 100644 index 3c66c7a0660..00000000000 --- a/app/assets/javascripts/boards/constants.js +++ /dev/null @@ -1,11 +0,0 @@ -export const ListType = { - assignee: 'assignee', - milestone: 'milestone', - backlog: 'backlog', - closed: 'closed', - label: 'label', -}; - -export default { - ListType, -}; diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index befca70eeae..da2669e7cde 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -146,7 +146,7 @@ export default () => { updateTokens() { this.filterManager.updateTokens(); }, - updateDetailIssue(newIssue, multiSelect = false) { + updateDetailIssue(newIssue) { const { sidebarInfoEndpoint } = newIssue; if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { newIssue.setFetchingState('subscriptions', true); @@ -185,23 +185,9 @@ export default () => { }); } - if (multiSelect) { - boardsStore.toggleMultiSelect(newIssue); - - if (boardsStore.detail.issue) { - boardsStore.clearDetailIssue(); - return; - } - - return; - } - boardsStore.setIssueDetail(newIssue); }, - clearDetailIssue(multiSelect = false) { - if (multiSelect) { - boardsStore.clearMultiSelect(); - } + clearDetailIssue() { boardsStore.clearDetailIssue(); }, toggleSubscription(id) { diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 1e213c324eb..b3e56a34c28 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -5,7 +5,6 @@ import ListLabel from './label'; import ListAssignee from './assignee'; import ListIssue from 'ee_else_ce/boards/models/issue'; import { urlParamsToObject } from '~/lib/utils/common_utils'; -import flash from '~/flash'; import boardsStore from '../stores/boards_store'; import ListMilestone from './milestone'; @@ -177,53 +176,6 @@ class List { }); } - addMultipleIssues(issues, listFrom, newIndex) { - let moveBeforeId = null; - let moveAfterId = null; - - const listHasIssues = issues.every(issue => this.findIssue(issue.id)); - - if (!listHasIssues) { - if (newIndex !== undefined) { - if (this.issues[newIndex - 1]) { - moveBeforeId = this.issues[newIndex - 1].id; - } - - if (this.issues[newIndex]) { - moveAfterId = this.issues[newIndex].id; - } - - this.issues.splice(newIndex, 0, ...issues); - } else { - this.issues.push(...issues); - } - - if (this.label) { - issues.forEach(issue => issue.addLabel(this.label)); - } - - if (this.assignee) { - if (listFrom && listFrom.type === 'assignee') { - issues.forEach(issue => issue.removeAssignee(listFrom.assignee)); - } - issues.forEach(issue => issue.addAssignee(this.assignee)); - } - - if (IS_EE && this.milestone) { - if (listFrom && listFrom.type === 'milestone') { - issues.forEach(issue => issue.removeMilestone(listFrom.milestone)); - } - issues.forEach(issue => issue.addMilestone(this.milestone)); - } - - if (listFrom) { - this.issuesSize += issues.length; - - this.updateMultipleIssues(issues, listFrom, moveBeforeId, moveAfterId); - } - } - } - addIssue(issue, listFrom, newIndex) { let moveBeforeId = null; let moveAfterId = null; @@ -278,23 +230,6 @@ class List { }); } - moveMultipleIssues({ issues, oldIndicies, newIndex, moveBeforeId, moveAfterId }) { - oldIndicies.reverse().forEach(index => { - this.issues.splice(index, 1); - }); - this.issues.splice(newIndex, 0, ...issues); - - gl.boardService - .moveMultipleIssues({ - ids: issues.map(issue => issue.id), - fromListId: null, - toListId: null, - moveBeforeId, - moveAfterId, - }) - .catch(() => flash(__('Something went wrong while moving issues.'))); - } - updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) { gl.boardService .moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId) @@ -303,37 +238,10 @@ class List { }); } - updateMultipleIssues(issues, listFrom, moveBeforeId, moveAfterId) { - gl.boardService - .moveMultipleIssues({ - ids: issues.map(issue => issue.id), - fromListId: listFrom.id, - toListId: this.id, - moveBeforeId, - moveAfterId, - }) - .catch(() => flash(__('Something went wrong while moving issues.'))); - } - findIssue(id) { return this.issues.find(issue => issue.id === id); } - removeMultipleIssues(removeIssues) { - const ids = removeIssues.map(issue => issue.id); - - this.issues = this.issues.filter(issue => { - const matchesRemove = ids.includes(issue.id); - - if (matchesRemove) { - this.issuesSize -= 1; - issue.removeLabel(this.label); - } - - return !matchesRemove; - }); - } - removeIssue(removeIssue) { this.issues = this.issues.filter(issue => { const matchesRemove = removeIssue.id === issue.id; diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index 03369febb4a..0d11db89511 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -48,16 +48,6 @@ export default class BoardService { return boardsStore.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId); } - moveMultipleIssues({ - ids, - fromListId = null, - toListId = null, - moveBeforeId = null, - moveAfterId = null, - }) { - return boardsStore.moveMultipleIssues({ ids, fromListId, toListId, moveBeforeId, moveAfterId }); - } - newIssue(id, issue) { return boardsStore.newIssue(id, issue); } diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 8b737d1dab0..6da1cca9628 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -11,7 +11,6 @@ import { __ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import eventHub from '../eventhub'; -import { ListType } from '../constants'; const boardsStore = { disabled: false, @@ -40,7 +39,6 @@ const boardsStore = { issue: {}, list: {}, }, - multiSelect: { list: [] }, setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) { const listsEndpointGenerate = `${listsEndpoint}/generate.json`; @@ -53,6 +51,7 @@ const boardsStore = { recentBoardsEndpoint: `${recentBoardsEndpoint}.json`, }; }, + create() { this.state.lists = []; this.filter.path = getUrlParamsArray().join('&'); @@ -135,107 +134,6 @@ const boardsStore = { Object.assign(this.moving, { list, issue }); }, - moveMultipleIssuesToList({ listFrom, listTo, issues, newIndex }) { - const issueTo = issues.map(issue => listTo.findIssue(issue.id)); - const issueLists = _.flatten(issues.map(issue => issue.getLists())); - const listLabels = issueLists.map(list => list.label); - - const hasMoveableIssues = _.compact(issueTo).length > 0; - - if (!hasMoveableIssues) { - // Check if target list assignee is already present in this issue - if ( - listTo.type === ListType.assignee && - listFrom.type === ListType.assignee && - issues.some(issue => issue.findAssignee(listTo.assignee)) - ) { - const targetIssues = issues.map(issue => listTo.findIssue(issue.id)); - targetIssues.forEach(targetIssue => targetIssue.removeAssignee(listFrom.assignee)); - } else if (listTo.type === 'milestone') { - const currentMilestones = issues.map(issue => issue.milestone); - const currentLists = this.state.lists - .filter(list => list.type === 'milestone' && list.id !== listTo.id) - .filter(list => - list.issues.some(listIssue => issues.some(issue => listIssue.id === issue.id)), - ); - - issues.forEach(issue => { - currentMilestones.forEach(milestone => { - issue.removeMilestone(milestone); - }); - }); - - issues.forEach(issue => { - issue.addMilestone(listTo.milestone); - }); - - currentLists.forEach(currentList => { - issues.forEach(issue => { - currentList.removeIssue(issue); - }); - }); - - listTo.addMultipleIssues(issues, listFrom, newIndex); - } else { - // Add to new lists issues if it doesn't already exist - listTo.addMultipleIssues(issues, listFrom, newIndex); - } - } else { - listTo.updateMultipleIssues(issues, listFrom); - issues.forEach(issue => { - issue.removeLabel(listFrom.label); - }); - } - - if (listTo.type === ListType.closed && listFrom.type !== ListType.backlog) { - issueLists.forEach(list => { - issues.forEach(issue => { - list.removeIssue(issue); - }); - }); - - issues.forEach(issue => { - issue.removeLabels(listLabels); - }); - } else if (listTo.type === ListType.backlog && listFrom.type === ListType.assignee) { - issues.forEach(issue => { - issue.removeAssignee(listFrom.assignee); - }); - issueLists.forEach(list => { - issues.forEach(issue => { - list.removeIssue(issue); - }); - }); - } else if (listTo.type === ListType.backlog && listFrom.type === ListType.milestone) { - issues.forEach(issue => { - issue.removeMilestone(listFrom.milestone); - }); - issueLists.forEach(list => { - issues.forEach(issue => { - list.removeIssue(issue); - }); - }); - } else if ( - this.shouldRemoveIssue(listFrom, listTo) && - this.issuesAreContiguous(listFrom, issues) - ) { - listFrom.removeMultipleIssues(issues); - } - }, - - issuesAreContiguous(list, issues) { - // When there's only 1 issue selected, we can return early. - if (issues.length === 1) return true; - - // Create list of ids for issues involved. - const listIssueIds = list.issues.map(issue => issue.id); - const movedIssueIds = issues.map(issue => issue.id); - - // Check if moved issue IDs is sub-array - // of source list issue IDs (i.e. contiguous selection). - return listIssueIds.join('|').includes(movedIssueIds.join('|')); - }, - moveIssueToList(listFrom, listTo, issue, newIndex) { const issueTo = listTo.findIssue(issue.id); const issueLists = issue.getLists(); @@ -297,17 +195,6 @@ const boardsStore = { list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId); }, - moveMultipleIssuesInList({ list, issues, oldIndicies, newIndex, idArray }) { - const beforeId = parseInt(idArray[newIndex - 1], 10) || null; - const afterId = parseInt(idArray[newIndex + issues.length], 10) || null; - list.moveMultipleIssues({ - issues, - oldIndicies, - newIndex, - moveBeforeId: beforeId, - moveAfterId: afterId, - }); - }, findList(key, val, type = 'label') { const filteredList = this.state.lists.filter(list => { const byType = type @@ -373,10 +260,6 @@ const boardsStore = { }`; }, - generateMultiDragPath(boardId) { - return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues/bulk_move`; - }, - all() { return axios.get(this.state.endpoints.listsEndpoint); }, @@ -426,16 +309,6 @@ const boardsStore = { }); }, - moveMultipleIssues({ ids, fromListId, toListId, moveBeforeId, moveAfterId }) { - return axios.put(this.generateMultiDragPath(this.state.endpoints.boardId), { - from_list_id: fromListId, - to_list_id: toListId, - move_before_id: moveBeforeId, - move_after_id: moveAfterId, - ids, - }); - }, - newIssue(id, issue) { return axios.post(this.generateIssuesPath(id), { issue, @@ -506,25 +379,6 @@ const boardsStore = { setCurrentBoard(board) { this.state.currentBoard = board; }, - - toggleMultiSelect(issue) { - const selectedIssueIds = this.multiSelect.list.map(issue => issue.id); - const index = selectedIssueIds.indexOf(issue.id); - - if (index === -1) { - this.multiSelect.list.push(issue); - return; - } - - this.multiSelect.list = [ - ...this.multiSelect.list.slice(0, index), - ...this.multiSelect.list.slice(index + 1), - ]; - }, - - clearMultiSelect() { - this.multiSelect.list = []; - }, }; BoardsStoreEE.initEESpecific(boardsStore); diff --git a/app/assets/javascripts/test_utils/index.js b/app/assets/javascripts/test_utils/index.js index 1a1f3e8d0a8..1e75ee60671 100644 --- a/app/assets/javascripts/test_utils/index.js +++ b/app/assets/javascripts/test_utils/index.js @@ -1,10 +1,8 @@ import 'core-js/es/map'; import 'core-js/es/set'; -import { Sortable } from 'sortablejs'; import simulateDrag from './simulate_drag'; import simulateInput from './simulate_input'; // Export to global space for rspec to use window.simulateDrag = simulateDrag; window.simulateInput = simulateInput; -window.Sortable = Sortable; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 2a7a53d8bd7..d540a347dde 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -245,7 +245,6 @@ box-shadow: 0 1px 2px $issue-boards-card-shadow; line-height: $gl-padding; list-style: none; - position: relative; &:not(:last-child) { margin-bottom: $gl-padding-8; @@ -256,11 +255,6 @@ background-color: $blue-50; } - &.multi-select { - border-color: $blue-200; - background-color: $blue-50; - } - .badge { border: 0; outline: 0; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index ef872e693e0..ab281bc7f23 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -37,6 +37,7 @@ .documentation { padding: 7px; + font-size: $gl-font-size-large; } .card.links-card { diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 3c86f3108ab..40b8d5ed72c 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -5,9 +5,6 @@ class Groups::BoardsController < Groups::ApplicationController include RecordUserLastActivity before_action :assign_endpoint_vars - before_action do - push_frontend_feature_flag(:multi_select_board) - end private diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 3b335fa4af4..14b02993e6e 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -7,9 +7,6 @@ class Projects::BoardsController < Projects::ApplicationController before_action :check_issues_available! before_action :authorize_read_board!, only: [:index, :show] before_action :assign_endpoint_vars - before_action do - push_frontend_feature_flag(:multi_select_board) - end private diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index b510129b35d..7a7e485a95a 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -60,12 +60,11 @@ module AtomicInternalId iid_always_track = Feature.enabled?(:iid_always_track, default_enabled: true) return unless @internal_id_needs_tracking || iid_always_track - @internal_id_needs_tracking = false - scope_value = internal_id_read_scope(scope) - value = read_attribute(column) return unless scope_value + value = read_attribute(column) + if value.present? # The value was set externally, e.g. by the user # We update the InternalId record to keep track of the greatest value. @@ -75,6 +74,8 @@ module AtomicInternalId internal_id_scope_usage, value, init) + + @internal_id_needs_tracking = false end end diff --git a/app/services/git/process_ref_changes_service.rb b/app/services/git/process_ref_changes_service.rb new file mode 100644 index 00000000000..33925147750 --- /dev/null +++ b/app/services/git/process_ref_changes_service.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Git + class ProcessRefChangesService < BaseService + PIPELINE_PROCESS_LIMIT = 4 + + def execute + changes = params[:changes] + + process_changes_by_action(:branch, changes.branch_changes) + process_changes_by_action(:tag, changes.tag_changes) + end + + private + + def process_changes_by_action(ref_type, changes) + changes_by_action = group_changes_by_action(changes) + + changes_by_action.each do |_, changes| + process_changes(ref_type, changes) if changes.any? + end + end + + def group_changes_by_action(changes) + changes.group_by do |change| + change_action(change) + end + end + + def change_action(change) + return :created if Gitlab::Git.blank_ref?(change[:oldrev]) + return :removed if Gitlab::Git.blank_ref?(change[:newrev]) + + :pushed + end + + def process_changes(ref_type, changes) + push_service_class = push_service_class_for(ref_type) + + changes.each do |change| + push_service_class.new( + project, + current_user, + change: change, + push_options: params[:push_options], + create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project) + ).execute + end + end + + def push_service_class_for(ref_type) + return Git::TagPushService if ref_type == :tag + + Git::BranchPushService + end + end +end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index dc3c363f650..528b1ea61b3 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -56,7 +56,7 @@ module Issues handle_milestone_change(issue) - added_mentions = issue.mentioned_users - old_mentioned_users + added_mentions = issue.mentioned_users(current_user) - old_mentioned_users if added_mentions.present? notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 4acc3f1981a..ae678d4c036 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -69,7 +69,8 @@ module MergeRequests ) end - added_mentions = merge_request.mentioned_users - old_mentioned_users + added_mentions = merge_request.mentioned_users(current_user) - old_mentioned_users + if added_mentions.present? notification_service.async.new_mentions_in_merge_request( merge_request, diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 853faed9d85..573be8fbe8b 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -5,7 +5,7 @@ module Notes def execute(note) return note unless note.editable? - old_mentioned_users = note.mentioned_users.to_a + old_mentioned_users = note.mentioned_users(current_user).to_a note.update(params.merge(updated_by: current_user)) diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index dce27dee9be..dace8a77736 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,5 @@ - page_title @path.split("/").reverse.map(&:humanize) +- @content_class = "limit-container-width" unless fluid_layout + .documentation.md.prepend-top-default = markdown @markdown diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index a4b9ef18a3b..4f193e95faa 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -3,8 +3,6 @@ class PostReceive include ApplicationWorker - PIPELINE_PROCESS_LIMIT = 4 - def perform(gl_repository, identifier, changes, push_options = {}) project, repo_type = Gitlab::GlRepository.parse(gl_repository) @@ -49,8 +47,7 @@ class PostReceive expire_caches(post_received, post_received.project.repository) enqueue_repository_cache_update(post_received) - process_changes(Git::BranchPushService, project, user, push_options, changes.branch_changes) - process_changes(Git::TagPushService, project, user, push_options, changes.tag_changes) + process_ref_changes(project, user, push_options: push_options, changes: changes) update_remote_mirrors(post_received) after_project_changes_hooks(project, user, changes.refs, changes.repository_data) end @@ -75,18 +72,10 @@ class PostReceive ) end - def process_changes(service_class, project, user, push_options, changes) - return if changes.empty? - - changes.each do |change| - service_class.new( - project, - user, - change: change, - push_options: push_options, - create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project) - ).execute - end + def process_ref_changes(project, user, params = {}) + return unless params[:changes].any? + + Git::ProcessRefChangesService.new(project, user, params).execute end def update_remote_mirrors(post_received) |