Commit a4a1812c authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-08-24

# Conflicts:
#	app/assets/stylesheets/framework/variables.scss
#	app/assets/stylesheets/pages/environments.scss

[ci skip]
parents 191f7ba0 5846c02d
...@@ -109,8 +109,6 @@ export class AwardsHandler { ...@@ -109,8 +109,6 @@ export class AwardsHandler {
} }
const $menu = $(`.${this.menuClass}`); const $menu = $(`.${this.menuClass}`);
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) { if ($menu.length) {
if ($menu.is('.is-visible')) { if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active'); $addBtn.removeClass('is-active');
...@@ -134,9 +132,6 @@ export class AwardsHandler { ...@@ -134,9 +132,6 @@ export class AwardsHandler {
}, 200); }, 200);
}); });
} }
$thumbsBtn.toggleClass('disabled', $userAuthored);
$thumbsBtn.prop('disabled', $userAuthored);
} }
// Create the emoji menu with the first category of emojis. // Create the emoji menu with the first category of emojis.
...@@ -364,10 +359,6 @@ export class AwardsHandler { ...@@ -364,10 +359,6 @@ export class AwardsHandler {
return $emojiButton.hasClass('active'); return $emojiButton.hasClass('active');
} }
isUserAuthored($button) {
return $button.hasClass('js-user-authored');
}
decrementCounter($emojiButton, emoji) { decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton); const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10); const counterNumber = parseInt(counter.text(), 10);
...@@ -474,20 +465,16 @@ export class AwardsHandler { ...@@ -474,20 +465,16 @@ export class AwardsHandler {
} }
postEmoji($emojiButton, awardUrl, emoji, callback) { postEmoji($emojiButton, awardUrl, emoji, callback) {
if (this.isUserAuthored($emojiButton)) { axios
this.userAuthored($emojiButton); .post(awardUrl, {
} else { name: emoji,
axios })
.post(awardUrl, { .then(({ data }) => {
name: emoji, if (data.ok) {
}) callback();
.then(({ data }) => { }
if (data.ok) { })
callback(); .catch(() => flash(__('Something went wrong on our end.')));
}
})
.catch(() => flash(__('Something went wrong on our end.')));
}
} }
findEmojiIcon(votesBlock, emoji) { findEmojiIcon(votesBlock, emoji) {
......
...@@ -82,29 +82,17 @@ export default { ...@@ -82,29 +82,17 @@ export default {
getAwardHTML(name) { getAwardHTML(name) {
return glEmojiTag(name); return glEmojiTag(name);
}, },
getAwardClassBindings(awardList, awardName) { getAwardClassBindings(awardList) {
return { return {
active: this.hasReactionByCurrentUser(awardList), active: this.hasReactionByCurrentUser(awardList),
disabled: !this.canInteractWithEmoji(awardList, awardName), disabled: !this.canInteractWithEmoji(),
}; };
}, },
canInteractWithEmoji(awardList, awardName) { canInteractWithEmoji() {
let isAllowed = true; return this.getUserData.id;
const restrictedEmojis = ['thumbsup', 'thumbsdown'];
// Users can not add :+1: and :-1: to their own notes
if (
this.getUserData.id === this.noteAuthorId &&
restrictedEmojis.indexOf(awardName) > -1
) {
isAllowed = false;
}
return this.getUserData.id && isAllowed;
}, },
hasReactionByCurrentUser(awardList) { hasReactionByCurrentUser(awardList) {
return awardList.filter(award => award.user.id === this.getUserData.id) return awardList.filter(award => award.user.id === this.getUserData.id).length;
.length;
}, },
awardTitle(awardsList) { awardTitle(awardsList) {
const hasReactionByCurrentUser = this.hasReactionByCurrentUser( const hasReactionByCurrentUser = this.hasReactionByCurrentUser(
...@@ -197,7 +185,7 @@ export default { ...@@ -197,7 +185,7 @@ export default {
v-tooltip v-tooltip
v-for="(awardList, awardName, index) in groupedAwards" v-for="(awardList, awardName, index) in groupedAwards"
:key="index" :key="index"
:class="getAwardClassBindings(awardList, awardName)" :class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)" :title="awardTitle(awardList)"
class="btn award-control" class="btn award-control"
data-boundary="viewport" data-boundary="viewport"
......
...@@ -671,6 +671,7 @@ Animation Functions ...@@ -671,6 +671,7 @@ Animation Functions
$dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1); $dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1);
/* /*
<<<<<<< HEAD
GitLab Plans GitLab Plans
*/ */
...@@ -685,6 +686,8 @@ Cross-project Pipelines ...@@ -685,6 +686,8 @@ Cross-project Pipelines
$linked-project-column-margin: 60px; $linked-project-column-margin: 60px;
/* /*
=======
>>>>>>> upstream/master
Performance Bar Performance Bar
*/ */
$perf-bar-text: #999; $perf-bar-text: #999;
......
...@@ -662,6 +662,7 @@ ...@@ -662,6 +662,7 @@
.prometheus-table-row-highlight { .prometheus-table-row-highlight {
background-color: $theme-gray-100; background-color: $theme-gray-100;
<<<<<<< HEAD
} }
// EE-only // EE-only
...@@ -739,4 +740,6 @@ ...@@ -739,4 +740,6 @@
.action-group .btn + .btn { .action-group .btn + .btn {
margin-left: $gl-padding-8; margin-left: $gl-padding-8;
} }
=======
>>>>>>> upstream/master
} }
...@@ -5,7 +5,7 @@ module ToggleAwardEmoji ...@@ -5,7 +5,7 @@ module ToggleAwardEmoji
authenticate_user! authenticate_user!
name = params.require(:name) name = params.require(:name)
if awardable.user_can_award?(current_user, name) if awardable.user_can_award?(current_user)
awardable.toggle_award_emoji(name, current_user) awardable.toggle_award_emoji(name, current_user)
todoable = to_todoable(awardable) todoable = to_todoable(awardable)
......
...@@ -117,7 +117,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -117,7 +117,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def referenced_merge_requests def referenced_merge_requests
@merge_requests, @closed_by_merge_requests = ::Issues::FetchReferencedMergeRequestsService.new(project, current_user).execute(issue) @merge_requests, @closed_by_merge_requests = ::Issues::ReferencedMergeRequestsService.new(project, current_user).execute(issue)
respond_to do |format| respond_to do |format|
format.json do format.json do
......
...@@ -94,14 +94,6 @@ module IssuesHelper ...@@ -94,14 +94,6 @@ module IssuesHelper
end end
end end
def award_user_authored_class(award)
if award == 'thumbsdown' || award == 'thumbsup'
'user-authored js-user-authored'
else
''
end
end
def awards_sort(awards) def awards_sort(awards)
awards.sort_by do |award, award_emojis| awards.sort_by do |award, award_emojis|
if award == "thumbsup" if award == "thumbsup"
......
...@@ -76,12 +76,8 @@ module Awardable ...@@ -76,12 +76,8 @@ module Awardable
true true
end end
def awardable_votes?(name) def user_can_award?(current_user)
AwardEmoji::UPVOTE_NAME == name || AwardEmoji::DOWNVOTE_NAME == name Ability.allowed?(current_user, :award_emoji, self)
end
def user_can_award?(current_user, name)
awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
end end
def user_authored?(current_user) def user_authored?(current_user)
...@@ -117,12 +113,4 @@ module Awardable ...@@ -117,12 +113,4 @@ module Awardable
def normalize_name(name) def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name) Gitlab::Emoji.normalize_emoji_name(name)
end end
def awardable_by_user?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
end
end end
...@@ -177,27 +177,6 @@ class Issue < ActiveRecord::Base ...@@ -177,27 +177,6 @@ class Issue < ActiveRecord::Base
"#{project.to_reference(from, full: full)}#{reference}" "#{project.to_reference(from, full: full)}#{reference}"
end end
def referenced_merge_requests(current_user = nil)
ext = all_references(current_user)
notes_with_associations.each do |object|
object.all_references(current_user, extractor: ext)
end
merge_requests = ext.merge_requests.sort_by(&:iid)
cross_project_filter = -> (merge_requests) do
merge_requests.select { |mr| mr.target_project == project }
end
Ability.merge_requests_readable_by_user(
merge_requests, current_user,
filters: {
read_cross_project: cross_project_filter
}
)
end
# All branches containing the current issue's ID, except for # All branches containing the current issue's ID, except for
# those with a merge request open referencing the current issue. # those with a merge request open referencing the current issue.
def related_branches(current_user) def related_branches(current_user)
...@@ -205,7 +184,11 @@ class Issue < ActiveRecord::Base ...@@ -205,7 +184,11 @@ class Issue < ActiveRecord::Base
branch =~ /\A#{iid}-(?!\d+-stable)/i branch =~ /\A#{iid}-(?!\d+-stable)/i
end end
branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch) branches_with_merge_request =
Issues::ReferencedMergeRequestsService
.new(project, current_user)
.referenced_merge_requests(self)
.map(&:source_branch)
branches_with_iid - branches_with_merge_request branches_with_iid - branches_with_merge_request
end end
...@@ -249,26 +232,6 @@ class Issue < ActiveRecord::Base ...@@ -249,26 +232,6 @@ class Issue < ActiveRecord::Base
project project
end end
# From all notes on this issue, we'll select the system notes about linked
# merge requests. Of those, the MRs closing `self` are returned.
def closed_by_merge_requests(current_user = nil)
return [] unless open?
ext = all_references(current_user)
notes.system.each do |note|
note.all_references(current_user, extractor: ext)
end
merge_requests = ext.merge_requests.select(&:open?)
if merge_requests.any?
ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: id).pluck(:merge_request_id)
merge_requests.select { |mr| mr.id.in?(ids) }
else
[]
end
end
def moved? def moved?
!moved_to.nil? !moved_to.nil?
end end
......
...@@ -152,6 +152,15 @@ class RemoteMirror < ActiveRecord::Base ...@@ -152,6 +152,15 @@ class RemoteMirror < ActiveRecord::Base
result.to_s result.to_s
end end
def ensure_remote!
return unless project
return unless remote_name && url
# If this fails or the remote already exists, we won't know due to
# https://gitlab.com/gitlab-org/gitaly/issues/1317
project.repository.add_remote(remote_name, url)
end
private private
def raw def raw
......
# frozen_string_literal: true
module Issues
class FetchReferencedMergeRequestsService < Issues::BaseService
def execute(issue)
referenced_merge_requests = issue.referenced_merge_requests(current_user)
referenced_merge_requests = Gitlab::IssuableSorter.sort(project, referenced_merge_requests) { |i| i.iid.to_s }
closed_by_merge_requests = issue.closed_by_merge_requests(current_user)
closed_by_merge_requests = Gitlab::IssuableSorter.sort(project, closed_by_merge_requests) { |i| i.iid.to_s }
[referenced_merge_requests, closed_by_merge_requests]
end
end
end
# frozen_string_literal: true
module Issues
class ReferencedMergeRequestsService < Issues::BaseService
def execute(issue)
referenced = referenced_merge_requests(issue)
closed_by = closed_by_merge_requests(issue)
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(referenced + closed_by,
head_pipeline: { project: [:route, { namespace: :route }] })
[sort_by_iid(referenced), sort_by_iid(closed_by)]
end
def referenced_merge_requests(issue)
merge_requests = extract_merge_requests(issue)
cross_project_filter = -> (merge_requests) do
merge_requests.select { |mr| mr.target_project == project }
end
Ability.merge_requests_readable_by_user(
merge_requests,
current_user,
filters: {
read_cross_project: cross_project_filter
}
)
end
def closed_by_merge_requests(issue)
return [] unless issue.open?
merge_requests = extract_merge_requests(issue, filter: :system).select(&:open?)
return [] if merge_requests.empty?
ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: issue.id).pluck(:merge_request_id)
merge_requests.select { |mr| mr.id.in?(ids) }
end
private
def extract_merge_requests(issue, filter: nil)
ext = issue.all_references(current_user)
notes = issue_notes(issue)
notes = notes.select(&filter) if filter
notes.each do |note|
note.all_references(current_user, extractor: ext)
end
ext.merge_requests
end
def issue_notes(issue)
@issue_notes ||= {}
@issue_notes[issue] ||= issue.notes.includes(:author)
end
def sort_by_iid(merge_requests)
Gitlab::IssuableSorter.sort(project, merge_requests) { |mr| mr.iid.to_s }
end
end
end
...@@ -10,6 +10,7 @@ module Projects ...@@ -10,6 +10,7 @@ module Projects
return success unless remote_mirror.enabled? return success unless remote_mirror.enabled?
begin begin
remote_mirror.ensure_remote!
repository.fetch_remote(remote_mirror.remote_name, no_tags: true) repository.fetch_remote(remote_mirror.remote_name, no_tags: true)
opts = {} opts = {}
......
...@@ -403,7 +403,7 @@ module QuickActions ...@@ -403,7 +403,7 @@ module QuickActions
match[1] if match match[1] if match
end end
command :award do |name| command :award do |name|
if name && issuable.user_can_award?(current_user, name) if name && issuable.user_can_award?(current_user)
@updates[:emoji_award] = name @updates[:emoji_award] = name
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards| - awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)], class: [(award_state_class(awardable, awards, current_user))],
data: { placement: "bottom", title: award_user_list(awards, current_user) } } data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji) = emoji_icon(emoji)
%span.award-control-text.js-counter %span.award-control-text.js-counter
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button', %button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'), 'aria-label': _('Add reaction'),
class: ("js-user-authored" if user_authored),
data: { title: _('Add reaction'), placement: "bottom" } } data: { title: _('Add reaction'), placement: "bottom" } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley') %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
- if note.emoji_awardable? - if note.emoji_awardable?
- user_authored = note.user_authored?(current_user) - user_authored = note.user_authored?(current_user)
.note-actions-item .note-actions-item
= button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if note.emoji_awardable? - if note.emoji_awardable?
- user_authored = note.user_authored?(current_user) - user_authored = note.user_authored?(current_user)
.note-actions-item .note-actions-item
= link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
......
---
title: Improve performance when fetching related merge requests for an issue
merge_request: 21237
author:
type: performance
---
title: Allow author to vote on their own issue and MRs
merge_request: 21203
author:
type: changed
---
title: Fix remote mirrors failing if Git remotes have not been added
merge_request: 21351
author:
type: fixed
...@@ -56,6 +56,7 @@ description: 'Learn how to contribute to GitLab.' ...@@ -56,6 +56,7 @@ description: 'Learn how to contribute to GitLab.'
- [Performance guidelines](performance.md) - [Performance guidelines](performance.md)
- [Merge request performance guidelines](merge_request_performance_guidelines.md) - [Merge request performance guidelines](merge_request_performance_guidelines.md)
for ensuring merge requests do not negatively impact GitLab performance for ensuring merge requests do not negatively impact GitLab performance
- [Profiling](profiling.md) for profiling a URL
## Database guides ## Database guides
......
...@@ -70,7 +70,7 @@ The add-on component gitlab-shell serves repositories over SSH. It manages the S ...@@ -70,7 +70,7 @@ The add-on component gitlab-shell serves repositories over SSH. It manages the S
Gitaly executes git operations from gitlab-shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files). Gitaly executes git operations from gitlab-shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files).
You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/infrastructure/production-architecture/). You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/).
### Installation Folder Summary ### Installation Folder Summary
......
...@@ -457,11 +457,35 @@ GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is o ...@@ -457,11 +457,35 @@ GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is o
sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION) sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
sudo -u git -H make sudo -u git -H make
### Install Gitaly
# Fetch Gitaly source with Git and compile with Go
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly]" RAILS_ENV=production
You can specify a different Git repository by providing it as an extra parameter:
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,https://example.com/gitaly.git]" RAILS_ENV=production
Next, make sure gitaly configured:
# Restrict Gitaly socket access
sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
sudo chown git /home/git/gitlab/tmp/sockets/private
# If you are using non-default settings you need to update config.toml
cd /home/git/gitaly
sudo -u git -H editor config.toml
For more information about configuring Gitaly see
[doc/administration/gitaly](../administration/gitaly).
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
# Type 'yes' to create the database tables. # Type 'yes' to create the database tables.
# or you can skip the question by adding force=yes
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
# When done you see 'Administrator account created:' # When done you see 'Administrator account created:'
...@@ -491,28 +515,6 @@ Make GitLab start on boot: ...@@ -491,28 +515,6 @@ Make GitLab start on boot:
sudo update-rc.d gitlab defaults 21 sudo update-rc.d gitlab defaults 21
### Install Gitaly
# Fetch Gitaly source with Git and compile with Go
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
You can specify a different Git repository by providing it as an extra parameter:
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
Next, make sure gitaly configured:
# Restrict Gitaly socket access
sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
sudo chown git /home/git/gitlab/tmp/sockets/private
# If you are using non-default settings you need to update config.toml
cd /home/git/gitaly
sudo -u git -H editor config.toml
For more information about configuring Gitaly see
[doc/administration/gitaly](../administration/gitaly).
### Setup Logrotate ### Setup Logrotate
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
......
...@@ -100,7 +100,7 @@ module API ...@@ -100,7 +100,7 @@ module API
end end
def can_award_awardable? def can_award_awardable?
awardable.user_can_award?(current_user, params[:name]) awardable.user_can_award?(current_user)
end end
def awardable def awardable
......
...@@ -14,11 +14,12 @@ module Banzai ...@@ -14,11 +14,12 @@ module Banzai
# Eager loading these ensures we don't end up running dozens of # Eager loading these ensures we don't end up running dozens of
# queries in this process. # queries in this process.
target_project: [ target_project: [
{ namespace: :owner }, { namespace: [:owner, :route] },
{ group: [:owners, :group_members] }, { group: [:owners, :group_members] },
:invited_groups, :invited_groups,
:project_members, :project_members,
:project_feature :project_feature,
:route
] ]
}), }),
self.class.data_attribute self.class.data_attribute
......
import _ from 'underscore';
import { PROJECT_BADGE } from '~/badges/constants'; import { PROJECT_BADGE } from '~/badges/constants';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants'; import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
export const createDummyBadge = () => { export const createDummyBadge = () => {
const id = Math.floor(1000 * Math.random()); const id = _.uniqueId();
return { return {
id, id,
imageUrl: `${TEST_HOST}/badges/${id}/image/url`, imageUrl: `${TEST_HOST}/badges/${id}/image/url`,
......
...@@ -53,21 +53,14 @@ describe Awardable do ...@@ -53,21 +53,14 @@ describe Awardable do
issue.project.add_guest(user) issue.project.add_guest(user)
end end
it 'does not allow upvoting or downvoting your own issue' do
issue.update!(author: user)
expect(issue.user_can_award?(user, AwardEmoji::DOWNVOTE_NAME)).to be_falsy
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
end
it 'is truthy when the user is allowed to award emoji' do it 'is truthy when the user is allowed to award emoji' do
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_truthy expect(issue.user_can_award?(user)).to be_truthy
end end
it 'is falsy when the project is archived' do it 'is falsy when the project is archived' do
issue.project.update!(archived: true) issue.project.update!(archived: true)
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy expect(issue.user_can_award?(user)).to be_falsy
end end
end end
......
...@@ -188,98 +188,6 @@ describe Issue do ...@@ -188,98 +188,6 @@ describe Issue do
end end
end end
describe '#closed_by_merge_requests' do
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project)}
let(:closed_issue) { build(:issue, :closed, project: project)}
let(:mr) do
opts = {
title: 'Awesome merge_request',
description: "Fixes #{issue.to_reference}",
source_branch: 'feature',
target_branch: 'master'
}
MergeRequests::CreateService.new(project, project.owner, opts).execute
end
let(:closed_mr) do
opts = {
title: 'Awesome merge_request 2',
description: "Fixes #{issue.to_reference}",
source_branch: 'feature',
target_branch: 'master',
state: 'closed'
}
MergeRequests::CreateService.new(project, project.owner, opts).execute
end
it 'returns the merge request to close this issue' do
expect(issue.closed_by_merge_requests(mr.author)).to eq([mr])
end
it "returns an empty array when the merge request is closed already" do
expect(issue.closed_by_merge_requests(closed_mr.author)).to eq([])
end
it "returns an empty array when the current issue is closed already" do
expect(closed_issue.closed_by_merge_requests(closed_issue.author)).to eq([])
end
end
describe '#referenced_merge_requests' do
let(:project) { create(:project, :public) }
let(:issue) do
create(:issue, description: merge_request.to_reference, project: project)
end
let!(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: 'master',
target_branch: 'feature')
end
it 'returns the referenced merge requests' do
mr2 = create(:merge_request,
source_project: project,
source_branch: 'feature',
target_branch: 'master')
create(:note_on_issue,
noteable: issue,
note: mr2.to_reference,
project_id: project.id)
expect(issue.referenced_merge_requests).to eq([merge_request, mr2])
end
it 'returns cross project referenced merge requests' do
other_project = create(:project, :public)
cross_project_merge_request = create(:merge_request, source_project: other_project)
create(:note_on_issue,
noteable: issue,
note: cross_project_merge_request.to_reference(issue.project),
project_id: issue.project.id)
expect(issue.referenced_merge_requests).to eq([merge_request, cross_project_merge_request])
end
it 'excludes cross project references if the user cannot read cross project' do
user = create(:user)
allow(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
other_project = create(:project, :public)
cross_project_merge_request = create(:merge_request, source_project: other_project)
create(:note_on_issue,
noteable: issue,
note: cross_project_merge_request.to_reference(issue.project),
project_id: issue.project.id)
expect(issue.referenced_merge_requests(user)).to eq([merge_request])
end
end
describe '#can_move?' do describe '#can_move?' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:issue) { create(:issue) } let(:issue) { create(:issue) }
...@@ -365,7 +273,12 @@ describe Issue do ...@@ -365,7 +273,12 @@ describe Issue do
source_project: subject.project, source_project: subject.project,
source_branch: "#{subject.iid}-branch" }) source_branch: "#{subject.iid}-branch" })
merge_request.create_cross_references!(user) merge_request.create_cross_references!(user)
expect(subject.referenced_merge_requests(user)).not_to be_empty
referenced_merge_requests = Issues::ReferencedMergeRequestsService
.new(subject.project, user)
.referenced_merge_requests(subject)
expect(referenced_merge_requests).not_to be_empty
expect(subject.related_branches(user)).to eq([subject.to_branch_name]) expect(subject.related_branches(user)).to eq([subject.to_branch_name])
end end
......
...@@ -220,6 +220,18 @@ describe RemoteMirror do ...@@ -220,6 +220,18 @@ describe RemoteMirror do
end end
end end
context '#ensure_remote!' do
let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
it 'adds a remote multiple times with no errors' do
expect(remote_mirror.project.repository).to receive(:add_remote).with(remote_mirror.remote_name, remote_mirror.url).twice.and_call_original
2.times do
remote_mirror.ensure_remote!
end
end
end
context '#updated_since?' do context '#updated_since?' do
let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first } let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
let(:timestamp) { Time.now - 5.minutes } let(:timestamp) { Time.now - 5.minutes }
......
...@@ -167,12 +167,6 @@ describe API::AwardEmoji do ...@@ -167,12 +167,6 @@ describe API::AwardEmoji do
expect(response).to have_gitlab_http_status(401) expect(response).to have_gitlab_http_status(401)
end end
it "returns a 404 error if the user authored issue" do
post api("/projects/#{project.id}/issues/#{issue2.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_gitlab_http_status(404)
end
it "normalizes +1 as thumbsup award" do it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: '+1' post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: '+1'
...@@ -215,12 +209,6 @@ describe API::AwardEmoji do ...@@ -215,12 +209,6 @@ describe API::AwardEmoji do
expect(json_response['user']['username']).to eq(user.username) expect(json_response['user']['username']).to eq(user.username)
end end
it "it returns 404 error when user authored note" do
post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note2.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_gitlab_http_status(404)
end
it "normalizes +1 as thumbsup award" do it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: '+1' post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: '+1'
......
require 'spec_helper.rb'
describe Issues::FetchReferencedMergeRequestsService do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:other_project) { create(:project) }
let(:mr) { create(:merge_request, source_project: project, target_project: project, id: 2)}
let(:other_mr) { create(:merge_request, source_project: other_project, target_project: other_project, id: 1)}
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
context 'with mentioned merge requests' do
it 'returns a list of sorted merge requests' do
allow(issue).to receive(:referenced_merge_requests).with(user).and_return([other_mr, mr])
mrs, closed_by_mrs = service.execute(issue)
expect(mrs).to match_array([mr, other_mr])
expect(closed_by_mrs).to match_array([])
end
end
context 'with closed-by merge requests' do
it 'returns a list of sorted merge requests' do
allow(issue).to receive(:closed_by_merge_requests).with(user).and_return([other_mr, mr])
mrs, closed_by_mrs = service.execute(issue)
expect(mrs).to match_array([])
expect(closed_by_mrs).to match_array([mr, other_mr])
end
end
end
# frozen_string_literal: true
require 'spec_helper.rb'
describe Issues::ReferencedMergeRequestsService do
def create_referencing_mr(attributes = {})
create(:merge_request, attributes).tap do |merge_request|
create(:note, :system, project: project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
end
end
def create_closing_mr(attributes = {})
create_referencing_mr(attributes).tap do |merge_request|
create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request)
end
end
set(:user) { create(:user) }
set(:project) { create(:project, :public, :repository) }
set(:other_project) { create(:project, :public, :repository) }
set(:issue) { create(:issue, author: user, project: project) }
set(:closing_mr) { create_closing_mr(source_project: project) }
set(:closing_mr_other_project) { create_closing_mr(source_project: other_project) }
set(:referencing_mr) { create_referencing_mr(source_project: project, source_branch: 'csv') }
set(:referencing_mr_other_project) { create_referencing_mr(source_project: other_project, source_branch: 'csv') }
let(:service) { described_class.new(project, user) }
describe '#execute' do
it 'returns a list of sorted merge requests' do
mrs, closed_by_mrs = service.execute(issue)
expect(mrs).to eq([closing_mr, referencing_mr, closing_mr_other_project, referencing_mr_other_project])
expect(closed_by_mrs).to eq([closing_mr, closing_mr_other_project])
end
context 'performance' do
it 'does not run extra queries when extra namespaces are included', :use_clean_rails_memory_store_caching do
service.execute(issue) # warm cache
control_count = ActiveRecord::QueryRecorder.new { service.execute(issue) }.count
third_project = create(:project, :public)
create_closing_mr(source_project: third_project)
service.execute(issue) # warm cache
expect { service.execute(issue) }.not_to exceed_query_limit(control_count)
end
it 'preloads the head pipeline for each merge request, and its routes' do
# Hack to ensure no data is preserved on issue before starting the spec,
# to avoid false negatives
reloaded_issue = Issue.find(issue.id)
pipeline_routes = lambda do |merge_requests|
merge_requests.map { |mr| mr.head_pipeline&.project&.full_path }
end
closing_mr_other_project.update!(head_pipeline: create(:ci_pipeline))
control_count = ActiveRecord::QueryRecorder.new { service.execute(reloaded_issue).each(&pipeline_routes) }
closing_mr.update!(head_pipeline: create(:ci_pipeline))
expect { service.execute(issue).each(&pipeline_routes) }
.not_to exceed_query_limit(control_count)
end
it 'only loads issue notes once' do
expect(issue).to receive(:notes).once.and_call_original
service.execute(issue)
end
end
end
describe '#referenced_merge_requests' do
it 'returns the referenced merge requests' do
expect(service.referenced_merge_requests(issue)).to match_array([
closing_mr,
closing_mr_other_project,
referencing_mr,
referencing_mr_other_project
])
end
it 'excludes cross project references if the user cannot read cross project' do
allow(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(user, :read_cross_project).at_least(:once).and_return(false)
expect(service.referenced_merge_requests(issue)).not_to include(closing_mr_other_project)
expect(service.referenced_merge_requests(issue)).not_to include(referencing_mr_other_project)
end
context 'performance' do
it 'does not run a query for each note author', :use_clean_rails_memory_store_caching do
service.referenced_merge_requests(issue) # warm cache
control_count = ActiveRecord::QueryRecorder.new { service.referenced_merge_requests(issue) }.count
create(:note, project: project, noteable: issue, author: create(:user))
service.referenced_merge_requests(issue) # warm cache
expect { service.referenced_merge_requests(issue) }.not_to exceed_query_limit(control_count)
end
end
end
describe '#closed_by_merge_requests' do
let(:closed_issue) { build(:issue, :closed, project: project)}
it 'returns the open merge requests that close this issue' do
create_closing_mr(source_project: project, state: 'closed')
expect(service.closed_by_merge_requests(issue)).to match_array([closing_mr, closing_mr_other_project])
end
it 'returns an empty array when the current issue is closed already' do
expect(service.closed_by_merge_requests(closed_issue)).to eq([])
end
context 'performance' do
it 'does not run a query for each note author', :use_clean_rails_memory_store_caching do
service.closed_by_merge_requests(issue) # warm cache
control_count = ActiveRecord::QueryRecorder.new { service.closed_by_merge_requests(issue) }.count
create(:note, :system, project: project, noteable: issue, author: create(:user))
service.closed_by_merge_requests(issue) # warm cache
expect { service.closed_by_merge_requests(issue) }.not_to exceed_query_limit(control_count)
end
end
end
end
...@@ -18,6 +18,7 @@ describe Projects::UpdateRemoteMirrorService do ...@@ -18,6 +18,7 @@ describe Projects::UpdateRemoteMirrorService do
end end
it "fetches the remote repository" do it "fetches the remote repository" do
expect(remote_mirror).to receive(:ensure_remote!).and_call_original
expect(repository).to receive(:fetch_remote).with(remote_mirror.remote_name, no_tags: true) do expect(repository).to receive(:fetch_remote).with(remote_mirror.remote_name, no_tags: true) do
sync_remote(repository, remote_mirror.remote_name, local_branch_names) sync_remote(repository, remote_mirror.remote_name, local_branch_names)
end end
......
...@@ -65,7 +65,9 @@ module CycleAnalyticsHelpers ...@@ -65,7 +65,9 @@ module CycleAnalyticsHelpers
end end
def merge_merge_requests_closing_issue(user, project, issue) def merge_merge_requests_closing_issue(user, project, issue)
merge_requests = issue.closed_by_merge_requests(user) merge_requests = Issues::ReferencedMergeRequestsService
.new(project, user)
.closed_by_merge_requests(issue)
merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user).execute(merge_request) } merge_requests.each { |merge_request| MergeRequests::MergeService.new(project, user).execute(merge_request) }
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment