Commit f544b977 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce-upstream

parents 2a634dc0 b60de9c0
...@@ -21,7 +21,7 @@ gem 'rugged', '~> 0.24.0' ...@@ -21,7 +21,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries # Authentication libraries
gem 'devise', '~> 4.2' gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0' gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.1' gem 'omniauth', '~> 1.3.2'
gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
......
...@@ -473,7 +473,7 @@ GEM ...@@ -473,7 +473,7 @@ GEM
octokit (4.6.2) octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4) oj (2.17.4)
omniauth (1.3.1) omniauth (1.3.2)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1) omniauth-auth0 (1.4.1)
...@@ -958,7 +958,7 @@ DEPENDENCIES ...@@ -958,7 +958,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.6.2) octokit (~> 4.6.2)
oj (~> 2.17.4) oj (~> 2.17.4)
omniauth (~> 1.3.1) omniauth (~> 1.3.2)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.2.0) omniauth-authentiq (~> 0.2.0)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
......
...@@ -143,4 +143,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ...@@ -143,4 +143,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome? ## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
[These people](https://twitter.com/gitlab/favorites) seem to like it. [These people](https://twitter.com/gitlab/likes) seem to like it.
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
if (selected.tagName === 'LI') { if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) { if (selected.hasAttribute('data-value')) {
this.dismissDropdown(); this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else { } else {
const token = selected.querySelector('.js-filter-hint').innerText.trim(); const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim(); const tag = selected.querySelector('.js-filter-tag').innerText.trim();
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
} }
this.dismissDropdown(); this.dismissDropdown();
this.dispatchInputEvent();
} }
} }
...@@ -84,6 +85,12 @@ ...@@ -84,6 +85,12 @@
})); }));
} }
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
hideDropdown() { hideDropdown() {
this.getCurrentHook().list.hide(); this.getCurrentHook().list.hide();
} }
......
...@@ -66,11 +66,19 @@ ...@@ -66,11 +66,19 @@
const word = `${tokenName}:${tokenValue}`; const word = `${tokenName}:${tokenValue}`;
// Get the string to replace // Get the string to replace
const selectionStart = input.selectionStart; let newCaretPosition = input.selectionStart;
const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input); const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`; input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`;
gl.FilteredSearchDropdownManager.updateInputCaretPosition(selectionStart, input);
// If we have added a tokenValue at the end of the input,
// add a space and set selection to the end
if (right >= inputValue.length && tokenValue !== '') {
input.value += ' ';
newCaretPosition = input.value.length;
}
gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input);
} }
static updateInputCaretPosition(selectionStart, input) { static updateInputCaretPosition(selectionStart, input) {
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
} }
bindEvents() { bindEvents() {
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager); this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this); this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this); this.checkForEnterWrapper = this.checkForEnter.bind(this);
...@@ -32,6 +33,7 @@ ...@@ -32,6 +33,7 @@
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this); this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
this.tokenChange = this.tokenChange.bind(this); this.tokenChange = this.tokenChange.bind(this);
this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper); this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
...@@ -42,6 +44,7 @@ ...@@ -42,6 +44,7 @@
} }
unbindEvents() { unbindEvents() {
this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper); this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
...@@ -88,6 +91,11 @@ ...@@ -88,6 +91,11 @@
this.dropdownManager.resetDropdowns(); this.dropdownManager.resetDropdowns();
} }
handleFormSubmit(e) {
e.preventDefault();
this.search();
}
loadSearchParamsFromURL() { loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray(); const params = gl.utils.getUrlParamsArray();
const usernameParams = this.getUsernameParams(); const usernameParams = this.getUsernameParams();
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
} }
.ci-status-icon-pending, .ci-status-icon-pending,
.ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings { .ci-status-icon-success_with_warnings {
color: $gl-warning; color: $gl-warning;
......
...@@ -46,10 +46,6 @@ ...@@ -46,10 +46,6 @@
font-weight: bold; font-weight: bold;
} }
.fa-clipboard {
color: $dropdown-title-btn-color;
}
.commit-info { .commit-info {
&.branches { &.branches {
margin-left: 8px; margin-left: 8px;
......
...@@ -195,10 +195,10 @@ ul.notes { ...@@ -195,10 +195,10 @@ ul.notes {
} }
.note-body { .note-body {
overflow: auto; overflow-x: auto;
overflow-y: hidden;
.note-text { .note-text {
overflow: auto;
word-wrap: break-word; word-wrap: break-word;
@include md-typography; @include md-typography;
// Reset ul style types since we're nested inside a ul already // Reset ul style types since we're nested inside a ul already
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
.file-finder-input:hover, .file-finder-input:hover,
.issuable-search-form:hover, .issuable-search-form:hover,
.search-text-input:hover, .search-text-input:hover,
textarea:hover,
.form-control:hover { .form-control:hover {
border-color: lighten($dropdown-input-focus-border, 20%); border-color: lighten($dropdown-input-focus-border, 20%);
box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%); box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
......
...@@ -19,7 +19,8 @@ ...@@ -19,7 +19,8 @@
overflow: visible; overflow: visible;
} }
&.ci-failed { &.ci-failed,
&.ci-failed_with_warnings {
color: $gl-danger; color: $gl-danger;
border-color: $gl-danger; border-color: $gl-danger;
......
module ServicesHelper module ServicesHelper
def service_event_description(event) def service_event_description(event)
case event case event
when "push" when "push", "push_events"
"Event will be triggered by a push to the repository" "Event will be triggered by a push to the repository"
when "tag_push" when "tag_push", "tag_push_events"
"Event will be triggered when a new tag is pushed to the repository" "Event will be triggered when a new tag is pushed to the repository"
when "note" when "note", "note_events"
"Event will be triggered when someone adds a comment" "Event will be triggered when someone adds a comment"
when "issue" when "issue", "issue_events"
"Event will be triggered when an issue is created/updated/closed" "Event will be triggered when an issue is created/updated/closed"
when "confidential_issue" when "confidential_issue", "confidential_issue_events"
"Event will be triggered when a confidential issue is created/updated/closed" "Event will be triggered when a confidential issue is created/updated/closed"
when "merge_request" when "merge_request", "merge_request_events"
"Event will be triggered when a merge request is created/updated/merged" "Event will be triggered when a merge request is created/updated/merged"
when "build" when "build", "build_events"
"Event will be triggered when a build status changes" "Event will be triggered when a build status changes"
when "wiki_page" when "wiki_page", "wiki_page_events"
"Event will be triggered when a wiki page is created/updated" "Event will be triggered when a wiki page is created/updated"
when "commit" when "commit", "commit_events"
"Event will be triggered when a commit is created/updated" "Event will be triggered when a commit is created/updated"
end end
end end
...@@ -26,4 +26,6 @@ module ServicesHelper ...@@ -26,4 +26,6 @@ module ServicesHelper
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event) event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events" "#{event}_events"
end end
extend self
end end
...@@ -108,15 +108,11 @@ class Notify < BaseMailer ...@@ -108,15 +108,11 @@ class Notify < BaseMailer
def mail_thread(model, headers = {}) def mail_thread(model, headers = {})
add_project_headers add_project_headers
add_unsubscription_headers_and_links
headers["X-GitLab-#{model.class.name}-ID"] = model.id headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
end
if Gitlab::IncomingEmail.enabled? if Gitlab::IncomingEmail.enabled?
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key)) address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace address.display_name = @project.name_with_namespace
...@@ -172,4 +168,16 @@ class Notify < BaseMailer ...@@ -172,4 +168,16 @@ class Notify < BaseMailer
headers['X-GitLab-Project-Id'] = @project.id headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end end
def add_unsubscription_headers_and_links
return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable?
list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)]
if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard?
list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}"
end
headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
@sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
end
end end
...@@ -128,16 +128,21 @@ module Ci ...@@ -128,16 +128,21 @@ module Ci
end end
def stages def stages
# TODO, this needs refactoring, see gitlab-ce#26481.
stages_query = statuses
.group('stage').select(:stage).order('max(stage_idx)')
status_sql = statuses.latest.where('stage=sg.stage').status_sql status_sql = statuses.latest.where('stage=sg.stage').status_sql
stages_query = statuses.group('stage').select(:stage) warnings_sql = statuses.latest.select('COUNT(*) > 0')
.order('max(stage_idx)') .where('stage=sg.stage').failed_but_allowed.to_sql
stages_with_statuses = CommitStatus.from(stages_query, :sg). stages_with_statuses = CommitStatus.from(stages_query, :sg)
pluck('sg.stage', status_sql) .pluck('sg.stage', status_sql, "(#{warnings_sql})")
stages_with_statuses.map do |stage| stages_with_statuses.map do |stage|
Ci::Stage.new(self, name: stage.first, status: stage.last) Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
end end
end end
......
...@@ -8,10 +8,11 @@ module Ci ...@@ -8,10 +8,11 @@ module Ci
delegate :project, to: :pipeline delegate :project, to: :pipeline
def initialize(pipeline, name:, status: nil) def initialize(pipeline, name:, status: nil, warnings: nil)
@pipeline = pipeline @pipeline = pipeline
@name = name @name = name
@status = status @status = status
@warnings = warnings
end end
def to_param def to_param
...@@ -39,5 +40,17 @@ module Ci ...@@ -39,5 +40,17 @@ module Ci
def builds def builds
@builds ||= pipeline.builds.where(stage: name) @builds ||= pipeline.builds.where(stage: name)
end end
def success?
status.to_s == 'success'
end
def has_warnings?
if @warnings.nil?
statuses.latest.failed_but_allowed.any?
else
@warnings
end
end
end end
end end
module HasStatus module HasStatus
extend ActiveSupport::Concern extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped] STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running] ACTIVE_STATUSES = %w[pending running]
......
...@@ -133,6 +133,8 @@ class Namespace < ActiveRecord::Base ...@@ -133,6 +133,8 @@ class Namespace < ActiveRecord::Base
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
Gitlab::PagesTransfer.new.rename_namespace(path_was, path) Gitlab::PagesTransfer.new.rename_namespace(path_was, path)
remove_exports!
# If repositories moved successfully we need to # If repositories moved successfully we need to
# send update instructions to users. # send update instructions to users.
# However we cannot allow rollback since we moved namespace dir # However we cannot allow rollback since we moved namespace dir
...@@ -225,6 +227,8 @@ class Namespace < ActiveRecord::Base ...@@ -225,6 +227,8 @@ class Namespace < ActiveRecord::Base
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
end end
end end
remove_exports!
end end
def refresh_access_of_projects_invited_groups def refresh_access_of_projects_invited_groups
...@@ -237,4 +241,20 @@ class Namespace < ActiveRecord::Base ...@@ -237,4 +241,20 @@ class Namespace < ActiveRecord::Base
def full_path_changed? def full_path_changed?
path_changed? || parent_id_changed? path_changed? || parent_id_changed?
end end
def remove_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
end
def export_path
File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def full_path_was
if parent
parent.full_path + '/' + path_was
else
path_was
end
end
end end
...@@ -25,7 +25,7 @@ You can create a Personal Access Token here: ...@@ -25,7 +25,7 @@ You can create a Personal Access Token here:
http://app.asana.com/-/account_api' http://app.asana.com/-/account_api'
end end
def to_param def self.to_param
'asana' 'asana'
end end
...@@ -44,7 +44,7 @@ http://app.asana.com/-/account_api' ...@@ -44,7 +44,7 @@ http://app.asana.com/-/account_api'
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -12,7 +12,7 @@ class AssemblaService < Service ...@@ -12,7 +12,7 @@ class AssemblaService < Service
'Project Management Software (Source Commits Endpoint)' 'Project Management Software (Source Commits Endpoint)'
end end
def to_param def self.to_param
'assembla' 'assembla'
end end
...@@ -23,7 +23,7 @@ class AssemblaService < Service ...@@ -23,7 +23,7 @@ class AssemblaService < Service
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -40,7 +40,7 @@ class BambooService < CiService ...@@ -40,7 +40,7 @@ class BambooService < CiService
'You must set up automatic revision labeling and a repository trigger in Bamboo.' 'You must set up automatic revision labeling and a repository trigger in Bamboo.'
end end
def to_param def self.to_param
'bamboo' 'bamboo'
end end
...@@ -56,10 +56,6 @@ class BambooService < CiService ...@@ -56,10 +56,6 @@ class BambooService < CiService
] ]
end end
def supported_events
%w(push)
end
def build_page(sha, ref) def build_page(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:build_page] } with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end end
......
...@@ -19,7 +19,7 @@ class BugzillaService < IssueTrackerService ...@@ -19,7 +19,7 @@ class BugzillaService < IssueTrackerService
end end
end end
def to_param def self.to_param
'bugzilla' 'bugzilla'
end end
end end
...@@ -24,10 +24,6 @@ class BuildkiteService < CiService ...@@ -24,10 +24,6 @@ class BuildkiteService < CiService
hook.save hook.save
end end
def supported_events
%w(push)
end
def execute(data) def execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
...@@ -54,7 +50,7 @@ class BuildkiteService < CiService ...@@ -54,7 +50,7 @@ class BuildkiteService < CiService
'Continuous integration and deployments' 'Continuous integration and deployments'
end end
def to_param def self.to_param
'buildkite' 'buildkite'
end end
......
...@@ -19,11 +19,11 @@ class BuildsEmailService < Service ...@@ -19,11 +19,11 @@ class BuildsEmailService < Service
'Email the builds status to a list of recipients.' 'Email the builds status to a list of recipients.'
end end
def to_param def self.to_param
'builds_email' 'builds_email'
end end
def supported_events def self.supported_events
%w(build) %w(build)
end end
......
...@@ -12,7 +12,7 @@ class CampfireService < Service ...@@ -12,7 +12,7 @@ class CampfireService < Service
'Simple web-based real-time group chat' 'Simple web-based real-time group chat'
end end
def to_param def self.to_param
'campfire' 'campfire'
end end
...@@ -24,7 +24,7 @@ class CampfireService < Service ...@@ -24,7 +24,7 @@ class CampfireService < Service
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -25,7 +25,7 @@ class ChatNotificationService < Service ...@@ -25,7 +25,7 @@ class ChatNotificationService < Service
valid? valid?
end end
def supported_events def self.supported_events
%w[push issue confidential_issue merge_request note tag_push %w[push issue confidential_issue merge_request note tag_push
build pipeline wiki_page] build pipeline wiki_page]
end end
...@@ -82,19 +82,19 @@ class ChatNotificationService < Service ...@@ -82,19 +82,19 @@ class ChatNotificationService < Service
def get_message(object_kind, data) def get_message(object_kind, data)
case object_kind case object_kind
when "push", "tag_push" when "push", "tag_push"
PushMessage.new(data) ChatMessage::PushMessage.new(data)
when "issue" when "issue"
IssueMessage.new(data) unless is_update?(data) ChatMessage::IssueMessage.new(data) unless is_update?(data)
when "merge_request" when "merge_request"
MergeMessage.new(data) unless is_update?(data) ChatMessage::MergeMessage.new(data) unless is_update?(data)
when "note" when "note"
NoteMessage.new(data) ChatMessage::NoteMessage.new(data)
when "build" when "build"
BuildMessage.new(data) if should_build_be_notified?(data) ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline" when "pipeline"
PipelineMessage.new(data) if should_pipeline_be_notified?(data) ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page" when "wiki_page"
WikiPageMessage.new(data) ChatMessage::WikiPageMessage.new(data)
end end
end end
......
...@@ -13,8 +13,8 @@ class ChatSlashCommandsService < Service ...@@ -13,8 +13,8 @@ class ChatSlashCommandsService < Service
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end end
def supported_events def self.supported_events
[] %w()
end end
def can_test? def can_test?
......
...@@ -8,7 +8,7 @@ class CiService < Service ...@@ -8,7 +8,7 @@ class CiService < Service
self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -23,7 +23,7 @@ class CustomIssueTrackerService < IssueTrackerService ...@@ -23,7 +23,7 @@ class CustomIssueTrackerService < IssueTrackerService
end end
end end
def to_param def self.to_param
'custom_issue_tracker' 'custom_issue_tracker'
end end
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
class DeploymentService < Service class DeploymentService < Service
default_value_for :category, 'deployment' default_value_for :category, 'deployment'
def supported_events def self.supported_events
[] %w()
end end
def predefined_variables def predefined_variables
......
...@@ -32,7 +32,7 @@ class DroneCiService < CiService ...@@ -32,7 +32,7 @@ class DroneCiService < CiService
true true
end end
def supported_events def self.supported_events
%w(push merge_request tag_push) %w(push merge_request tag_push)
end end
...@@ -87,7 +87,7 @@ class DroneCiService < CiService ...@@ -87,7 +87,7 @@ class DroneCiService < CiService
'Drone is a Continuous Integration platform built on Docker, written in Go' 'Drone is a Continuous Integration platform built on Docker, written in Go'
end end
def to_param def self.to_param
'drone_ci' 'drone_ci'
end end
......
...@@ -12,11 +12,11 @@ class EmailsOnPushService < Service ...@@ -12,11 +12,11 @@ class EmailsOnPushService < Service
'Email the commits and diff of each push to a list of recipients.' 'Email the commits and diff of each push to a list of recipients.'
end end
def to_param def self.to_param
'emails_on_push' 'emails_on_push'
end end
def supported_events def self.supported_events
%w(push tag_push) %w(push tag_push)
end end
......
...@@ -13,7 +13,7 @@ class ExternalWikiService < Service ...@@ -13,7 +13,7 @@ class ExternalWikiService < Service
'Replaces the link to the internal wiki with a link to an external wiki.' 'Replaces the link to the internal wiki with a link to an external wiki.'
end end
def to_param def self.to_param
'external_wiki' 'external_wiki'
end end
...@@ -29,4 +29,8 @@ class ExternalWikiService < Service ...@@ -29,4 +29,8 @@ class ExternalWikiService < Service
nil nil
end end
end end
def self.supported_events
%w()
end
end end
...@@ -12,7 +12,7 @@ class FlowdockService < Service ...@@ -12,7 +12,7 @@ class FlowdockService < Service
'Flowdock is a collaboration web app for technical teams.' 'Flowdock is a collaboration web app for technical teams.'
end end
def to_param def self.to_param
'flowdock' 'flowdock'
end end
...@@ -22,7 +22,7 @@ class FlowdockService < Service ...@@ -22,7 +22,7 @@ class FlowdockService < Service
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -12,7 +12,7 @@ class GemnasiumService < Service ...@@ -12,7 +12,7 @@ class GemnasiumService < Service
'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.' 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
end end
def to_param def self.to_param
'gemnasium' 'gemnasium'
end end
...@@ -23,7 +23,7 @@ class GemnasiumService < Service ...@@ -23,7 +23,7 @@ class GemnasiumService < Service
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService ...@@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService
default_value_for :default, true default_value_for :default, true
def to_param def self.to_param
'gitlab' 'gitlab'
end end
......
...@@ -27,7 +27,7 @@ class HipchatService < Service ...@@ -27,7 +27,7 @@ class HipchatService < Service
'Private group chat and IM' 'Private group chat and IM'
end end
def to_param def self.to_param
'hipchat' 'hipchat'
end end
...@@ -45,7 +45,7 @@ class HipchatService < Service ...@@ -45,7 +45,7 @@ class HipchatService < Service
] ]
end end
def supported_events def self.supported_events
%w(push issue confidential_issue merge_request note tag_push build) %w(push issue confidential_issue merge_request note tag_push build)
end end
......
...@@ -17,11 +17,11 @@ class IrkerService < Service ...@@ -17,11 +17,11 @@ class IrkerService < Service
'gateway.' 'gateway.'
end end
def to_param def self.to_param
'irker' 'irker'
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -61,7 +61,7 @@ class IssueTrackerService < Service ...@@ -61,7 +61,7 @@ class IssueTrackerService < Service
end end
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -12,7 +12,7 @@ class JiraService < IssueTrackerService ...@@ -12,7 +12,7 @@ class JiraService < IssueTrackerService
# This is confusing, but JiraService does not really support these events. # This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service # The values here are required to display correct options in the service
# configuration screen. # configuration screen.
def supported_events def self.supported_events
%w(commit merge_request) %w(commit merge_request)
end end
...@@ -81,7 +81,7 @@ class JiraService < IssueTrackerService ...@@ -81,7 +81,7 @@ class JiraService < IssueTrackerService
end end
end end
def to_param def self.to_param
'jira' 'jira'
end end
......
...@@ -52,7 +52,7 @@ class KubernetesService < DeploymentService ...@@ -52,7 +52,7 @@ class KubernetesService < DeploymentService
'deployments with `app=$CI_ENVIRONMENT_SLUG`' 'deployments with `app=$CI_ENVIRONMENT_SLUG`'
end end
def to_param def self.to_param
'kubernetes' 'kubernetes'
end end
......
...@@ -7,7 +7,7 @@ class MattermostService < ChatNotificationService ...@@ -7,7 +7,7 @@ class MattermostService < ChatNotificationService
'Receive event notifications in Mattermost' 'Receive event notifications in Mattermost'
end end
def to_param def self.to_param
'mattermost' 'mattermost'
end end
...@@ -36,6 +36,6 @@ class MattermostService < ChatNotificationService ...@@ -36,6 +36,6 @@ class MattermostService < ChatNotificationService
end end
def default_channel_placeholder def default_channel_placeholder
"#town-square" "town-square"
end end
end end
...@@ -15,7 +15,7 @@ class MattermostSlashCommandsService < ChatSlashCommandsService ...@@ -15,7 +15,7 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
"Perform common operations on GitLab in Mattermost" "Perform common operations on GitLab in Mattermost"
end end
def to_param def self.to_param
'mattermost_slash_commands' 'mattermost_slash_commands'
end end
......
...@@ -15,11 +15,11 @@ class PipelinesEmailService < Service ...@@ -15,11 +15,11 @@ class PipelinesEmailService < Service
'Email the pipelines status to a list of recipients.' 'Email the pipelines status to a list of recipients.'
end end
def to_param def self.to_param
'pipelines_email' 'pipelines_email'
end end
def supported_events def self.supported_events
%w[pipeline] %w[pipeline]
end end
......
...@@ -14,7 +14,7 @@ class PivotaltrackerService < Service ...@@ -14,7 +14,7 @@ class PivotaltrackerService < Service
'Project Management Software (Source Commits Endpoint)' 'Project Management Software (Source Commits Endpoint)'
end end
def to_param def self.to_param
'pivotaltracker' 'pivotaltracker'
end end
...@@ -34,7 +34,7 @@ class PivotaltrackerService < Service ...@@ -34,7 +34,7 @@ class PivotaltrackerService < Service
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -13,7 +13,7 @@ class PushoverService < Service ...@@ -13,7 +13,7 @@ class PushoverService < Service
'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.' 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
end end
def to_param def self.to_param
'pushover' 'pushover'
end end
...@@ -61,7 +61,7 @@ class PushoverService < Service ...@@ -61,7 +61,7 @@ class PushoverService < Service
] ]
end end
def supported_events def self.supported_events
%w(push) %w(push)
end end
......
...@@ -19,7 +19,7 @@ class RedmineService < IssueTrackerService ...@@ -19,7 +19,7 @@ class RedmineService < IssueTrackerService
end end
end end
def to_param def self.to_param
'redmine' 'redmine'
end end
end end
...@@ -7,7 +7,7 @@ class SlackService < ChatNotificationService ...@@ -7,7 +7,7 @@ class SlackService < ChatNotificationService
'Receive event notifications in Slack' 'Receive event notifications in Slack'
end end
def to_param def self.to_param
'slack' 'slack'
end end
......
...@@ -9,7 +9,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService ...@@ -9,7 +9,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService
"Perform common operations on GitLab in Slack" "Perform common operations on GitLab in Slack"
end end
def to_param def self.to_param
'slack_slash_commands' 'slack_slash_commands'
end end
......
...@@ -43,14 +43,10 @@ class TeamcityService < CiService ...@@ -43,14 +43,10 @@ class TeamcityService < CiService
'requests build, that setting is in the vsc root advanced settings.' 'requests build, that setting is in the vsc root advanced settings.'
end end
def to_param def self.to_param
'teamcity' 'teamcity'
end end
def supported_events
%w(push)
end
def fields def fields
[ [
{ type: 'text', name: 'teamcity_url', { type: 'text', name: 'teamcity_url',
......
...@@ -76,6 +76,11 @@ class Service < ActiveRecord::Base ...@@ -76,6 +76,11 @@ class Service < ActiveRecord::Base
def to_param def to_param
# implement inside child # implement inside child
self.class.to_param
end
def self.to_param
raise NotImplementedError
end end
def fields def fields
...@@ -92,7 +97,11 @@ class Service < ActiveRecord::Base ...@@ -92,7 +97,11 @@ class Service < ActiveRecord::Base
end end
def event_names def event_names
supported_events.map { |event| "#{event}_events" } self.class.event_names
end
def self.event_names
self.supported_events.map { |event| "#{event}_events" }
end end
def event_field(event) def event_field(event)
...@@ -104,6 +113,10 @@ class Service < ActiveRecord::Base ...@@ -104,6 +113,10 @@ class Service < ActiveRecord::Base
end end
def supported_events def supported_events
self.class.supported_events
end
def self.supported_events
%w(push tag_push issue confidential_issue merge_request wiki_page) %w(push tag_push issue confidential_issue merge_request wiki_page)
end end
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
= icon('times') = icon('times')
#js-dropdown-hint.dropdown-menu.hint-dropdown #js-dropdown-hint.dropdown-menu.hint-dropdown
%ul{ 'data-dropdown' => true } %ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => '' } %li.filter-dropdown-item{ 'data-action' => 'submit' }
%button.btn.btn-link %button.btn.btn-link
= icon('search') = icon('search')
%span %span
...@@ -139,10 +139,6 @@ ...@@ -139,10 +139,6 @@
new MilestoneSelect(); new MilestoneSelect();
new IssueStatusSelect(); new IssueStatusSelect();
new SubscriptionSelect(); new SubscriptionSelect();
$('form.filter-form').on('submit', function (event) {
event.preventDefault();
Turbolinks.visit(this.action + '&' + $(this).serialize());
});
$(document).off('page:restore').on('page:restore', function (event) { $(document).off('page:restore').on('page:restore', function (event) {
if (gl.FilteredSearchManager) { if (gl.FilteredSearchManager) {
......
---
title: Handle unsubscribe from email notifications via replying to reply+%{key}+unsubscribe@ address
merge_request: 6597
author:
---
title: Allow creating protected branches when user can merge to such branch
merge_request: 8458
author:
---
title: Adds service trigger events to api
merge_request: 8324
author:
---
title: Remove rogue scrollbars for issue comments with inline elements
merge_request:
author:
---
title: Add hover style to copy icon on commit page header
merge_request:
author: Ryan Harris
---
title: Remove blue border from comment box hover
merge_request:
author:
---
title: Ensure export files are removed after a namespace is deleted
merge_request:
author:
---
title: Use warning icon in mini-graph if stage passed conditionally
merge_request: 8503
author:
---
title: Don't allow project guests to subscribe to merge requests through the API
merge_request:
author: Robert Schilling
---
title: Prevent users from creating notes on resources they can't access
merge_request:
author:
---
title: Prevent users from deleting system deploy keys via the project deploy key API
merge_request:
author:
---
title: allow issue filter bar to be operated with mouse only
merge_request: 8681
author:
---
title: Upgrade omniauth gem to 1.3.2
merge_request:
author:
...@@ -9,7 +9,7 @@ code is effective, understandable, and maintainable. ...@@ -9,7 +9,7 @@ code is effective, understandable, and maintainable.
Any developer can, and is encouraged to, perform code review on merge requests Any developer can, and is encouraged to, perform code review on merge requests
of colleagues and contributors. However, the final decision to accept a merge of colleagues and contributors. However, the final decision to accept a merge
request is up to one of our merge request "endbosses", denoted on the request is up to one the project's maintainers, denoted on the
[team page](https://about.gitlab.com/team). [team page](https://about.gitlab.com/team).
## Everyone ## Everyone
...@@ -81,15 +81,15 @@ balance in how deep the reviewer can interfere with the code created by a ...@@ -81,15 +81,15 @@ balance in how deep the reviewer can interfere with the code created by a
reviewee. reviewee.
- Learning how to find the right balance takes time; that is why we have - Learning how to find the right balance takes time; that is why we have
minibosses that become merge request endbosses after some time spent on reviewers that become maintainers after some time spent on reviewing merge
reviewing merge requests. requests.
- Finding bugs and improving code style is important, but thinking about good - Finding bugs and improving code style is important, but thinking about good
design is important as well. Building abstractions and good design is what design is important as well. Building abstractions and good design is what
makes it possible to hide complexity and makes future changes easier. makes it possible to hide complexity and makes future changes easier.
- Asking the reviewee to change the design sometimes means the complete rewrite - Asking the reviewee to change the design sometimes means the complete rewrite
of the contributed code. It's usually a good idea to ask another merge of the contributed code. It's usually a good idea to ask another maintainer or
request endboss before doing it, but have the courage to do it when you reviewer before doing it, but have the courage to do it when you believe it is
believe it is important. important.
- There is a difference in doing things right and doing things right now. - There is a difference in doing things right and doing things right now.
Ideally, we should do the former, but in the real world we need the latter as Ideally, we should do the former, but in the real world we need the latter as
well. A good example is a security fix which should be released as soon as well. A good example is a security fix which should be released as soon as
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
To ensure a merge request does not negatively impact performance of GitLab To ensure a merge request does not negatively impact performance of GitLab
_every_ merge request **must** adhere to the guidelines outlined in this _every_ merge request **must** adhere to the guidelines outlined in this
document. There are no exceptions to this rule unless specifically discussed document. There are no exceptions to this rule unless specifically discussed
with and agreed upon by merge request endbosses and performance specialists. with and agreed upon by backend maintainers and performance specialists.
To measure the impact of a merge request you can use To measure the impact of a merge request you can use
[Sherlock](profiling.md#sherlock). It's also highly recommended that you read [Sherlock](profiling.md#sherlock). It's also highly recommended that you read
...@@ -40,9 +40,9 @@ section below for more information. ...@@ -40,9 +40,9 @@ section below for more information.
about the impact. about the impact.
Sometimes it's hard to assess the impact of a merge request. In this case you Sometimes it's hard to assess the impact of a merge request. In this case you
should ask one of the merge request (mini) endbosses to review your changes. You should ask one of the merge request reviewers to review your changes. You can
can find a list of these endbosses at <https://about.gitlab.com/team/>. An find a list of these reviewers at <https://about.gitlab.com/team/>. A reviewer
endboss in turn can request a performance specialist to review the changes. in turn can request a performance specialist to review the changes.
## Query Counts ## Query Counts
......
...@@ -105,15 +105,19 @@ module API ...@@ -105,15 +105,19 @@ module API
present key.deploy_key, with: Entities::SSHKey present key.deploy_key, with: Entities::SSHKey
end end
desc 'Delete existing deploy key of currently authenticated user' do desc 'Delete deploy key for a project' do
success Key success Key
end end
params do params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key' requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end end
delete ":id/#{path}/:key_id" do delete ":id/#{path}/:key_id" do
key = user_project.deploy_keys.find(params[:key_id]) key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
key.destroy if key
key.destroy
else
not_found!('Deploy Key')
end
end end
end end
end end
......
...@@ -90,6 +90,12 @@ module API ...@@ -90,6 +90,12 @@ module API
MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id) MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
end end
def find_merge_request_with_access(id, access_level = :read_merge_request)
merge_request = user_project.merge_requests.find(id)
authorize! access_level, merge_request
merge_request
end
def authenticate! def authenticate!
unauthorized! unless current_user unauthorized! unless current_user
end end
......
...@@ -15,10 +15,8 @@ module API ...@@ -15,10 +15,8 @@ module API
end end
get ":id/merge_requests/:merge_request_id/versions" do get ":id/merge_requests/:merge_request_id/versions" do
merge_request = user_project.merge_requests. merge_request = find_merge_request_with_access(params[:merge_request_id])
find(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
end end
...@@ -34,10 +32,8 @@ module API ...@@ -34,10 +32,8 @@ module API
end end
get ":id/merge_requests/:merge_request_id/versions/:version_id" do get ":id/merge_requests/:merge_request_id/versions/:version_id" do
merge_request = user_project.merge_requests. merge_request = find_merge_request_with_access(params[:merge_request_id])
find(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end end
end end
......
...@@ -119,8 +119,8 @@ module API ...@@ -119,8 +119,8 @@ module API
success Entities::MergeRequest success Entities::MergeRequest
end end
get path do get path do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end end
...@@ -128,8 +128,8 @@ module API ...@@ -128,8 +128,8 @@ module API
success Entities::RepoCommit success Entities::RepoCommit
end end
get "#{path}/commits" do get "#{path}/commits" do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.commits, with: Entities::RepoCommit present merge_request.commits, with: Entities::RepoCommit
end end
...@@ -137,8 +137,8 @@ module API ...@@ -137,8 +137,8 @@ module API
success Entities::MergeRequestChanges success Entities::MergeRequestChanges
end end
get "#{path}/changes" do get "#{path}/changes" do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end end
...@@ -156,8 +156,7 @@ module API ...@@ -156,8 +156,7 @@ module API
:remove_source_branch :remove_source_branch
end end
put path do put path do
merge_request = find_project_merge_request(params.delete(:merge_request_id)) merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
authorize! :update_merge_request, merge_request
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
...@@ -236,10 +235,7 @@ module API ...@@ -236,10 +235,7 @@ module API
use :pagination use :pagination
end end
get "#{path}/comments" do get "#{path}/comments" do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present paginate(merge_request.notes.fresh), with: Entities::MRNote present paginate(merge_request.notes.fresh), with: Entities::MRNote
end end
...@@ -251,8 +247,7 @@ module API ...@@ -251,8 +247,7 @@ module API
requires :note, type: String, desc: 'The text of the comment' requires :note, type: String, desc: 'The text of the comment'
end end
post "#{path}/comments" do post "#{path}/comments" do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note)
authorize! :create_note, merge_request
opts = { opts = {
note: params[:note], note: params[:note],
...@@ -276,7 +271,7 @@ module API ...@@ -276,7 +271,7 @@ module API
use :pagination use :pagination
end end
get "#{path}/closes_issues" do get "#{path}/closes_issues" do
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_merge_request_with_access(params[:merge_request_id])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: issue_entity(user_project), current_user: current_user present paginate(issues), with: issue_entity(user_project), current_user: current_user
end end
......
...@@ -70,21 +70,27 @@ module API ...@@ -70,21 +70,27 @@ module API
end end
post ":id/#{noteables_str}/:noteable_id/notes" do post ":id/#{noteables_str}/:noteable_id/notes" do
opts = { opts = {
note: params[:body], note: params[:body],
noteable_type: noteables_str.classify, noteable_type: noteables_str.classify,
noteable_id: params[:noteable_id] noteable_id: params[:noteable_id]
} }
if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
opts[:created_at] = params[:created_at]
end if can?(current_user, noteable_read_ability_name(noteable), noteable)
if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
note = ::Notes::CreateService.new(user_project, current_user, opts).execute note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.valid? if note.valid?
present note, with: Entities::const_get(note.class.name) present note, with: Entities::const_get(note.class.name)
else
not_found!("Note #{note.errors.messages}")
end
else else
not_found!("Note #{note.errors.messages}") not_found!("Note")
end end
end end
......
...@@ -145,7 +145,7 @@ module API ...@@ -145,7 +145,7 @@ module API
name: :room, name: :room,
type: String, type: String,
desc: 'Campfire room' desc: 'Campfire room'
}, }
], ],
'custom-issue-tracker' => [ 'custom-issue-tracker' => [
{ {
...@@ -580,7 +580,36 @@ module API ...@@ -580,7 +580,36 @@ module API
desc: 'Should unstable builds be treated as passing?' desc: 'Should unstable builds be treated as passing?'
} }
] ]
}.freeze }
service_classes = [
AsanaService,
AssemblaService,
BambooService,
BugzillaService,
BuildkiteService,
BuildsEmailService,
CampfireService,
CustomIssueTrackerService,
DroneCiService,
EmailsOnPushService,
ExternalWikiService,
FlowdockService,
GemnasiumService,
HipchatService,
IrkerService,
JiraService,
KubernetesService,
MattermostSlashCommandsService,
SlackSlashCommandsService,
PipelinesEmailService,
PivotaltrackerService,
PushoverService,
RedmineService,
SlackService,
MattermostService,
TeamcityService,
].freeze
trigger_services = { trigger_services = {
'mattermost-slash-commands' => [ 'mattermost-slash-commands' => [
...@@ -614,6 +643,19 @@ module API ...@@ -614,6 +643,19 @@ module API
services.each do |service_slug, settings| services.each do |service_slug, settings|
desc "Set #{service_slug} service for project" desc "Set #{service_slug} service for project"
params do params do
service_classes.each do |service|
event_names = service.try(:event_names) || []
event_names.each do |event_name|
services[service.to_param.tr("_", "-")] << {
required: false,
name: event_name.to_sym,
type: String,
desc: ServicesHelper.service_event_description(event_name)
}
end
end
services.freeze
settings.each do |setting| settings.each do |setting|
if setting[:required] if setting[:required]
requires setting[:name], type: setting[:type], desc: setting[:desc] requires setting[:name], type: setting[:type], desc: setting[:desc]
...@@ -627,7 +669,7 @@ module API ...@@ -627,7 +669,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true) service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params) if service.update_attributes(service_params)
true present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
else else
render_api_error!('400 Bad Request', 400) render_api_error!('400 Bad Request', 400)
end end
......
...@@ -3,8 +3,8 @@ module API ...@@ -3,8 +3,8 @@ module API
before { authenticate! } before { authenticate! }
subscribable_types = { subscribable_types = {
'merge_request' => proc { |id| user_project.merge_requests.find(id) }, 'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
'merge_requests' => proc { |id| user_project.merge_requests.find(id) }, 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
'issues' => proc { |id| find_project_issue(id) }, 'issues' => proc { |id| find_project_issue(id) },
'labels' => proc { |id| find_project_label(id) }, 'labels' => proc { |id| find_project_label(id) },
} }
......
...@@ -5,7 +5,7 @@ module API ...@@ -5,7 +5,7 @@ module API
before { authenticate! } before { authenticate! }
ISSUABLE_TYPES = { ISSUABLE_TYPES = {
'merge_requests' => ->(id) { user_project.merge_requests.find(id) }, 'merge_requests' => ->(id) { find_merge_request_with_access(id) },
'issues' => ->(id) { find_project_issue(id) } 'issues' => ->(id) { find_project_issue(id) }
} }
......
...@@ -4,8 +4,11 @@ module Gitlab ...@@ -4,8 +4,11 @@ module Gitlab
module Build module Build
class Factory < Status::Factory class Factory < Status::Factory
def self.extended_statuses def self.extended_statuses
[Status::Build::Stop, Status::Build::Play, [[Status::Build::Cancelable,
Status::Build::Cancelable, Status::Build::Retryable] Status::Build::Retryable],
[Status::Build::FailedAllowed,
Status::Build::Play,
Status::Build::Stop]]
end end
def self.common_helpers def self.common_helpers
......
module Gitlab module Gitlab
module Ci module Ci
module Status module Status
module Pipeline module Build
class SuccessWithWarnings < SimpleDelegator class FailedAllowed < SimpleDelegator
include Status::Extended include Status::Extended
def text
'passed'
end
def label def label
'passed with warnings' 'failed (allowed to fail)'
end end
def icon def icon
...@@ -18,11 +14,11 @@ module Gitlab ...@@ -18,11 +14,11 @@ module Gitlab
end end
def group def group
'success_with_warnings' 'failed_with_warnings'
end end
def self.matches?(pipeline, user) def self.matches?(build, user)
pipeline.success? && pipeline.has_warnings? build.failed? && build.allow_failure?
end end
end end
end end
......
...@@ -5,41 +5,46 @@ module Gitlab ...@@ -5,41 +5,46 @@ module Gitlab
def initialize(subject, user) def initialize(subject, user)
@subject = subject @subject = subject
@user = user @user = user
@status = subject.status || HasStatus::DEFAULT_STATUS
end end
def fabricate! def fabricate!
if extended_status if extended_statuses.none?
extended_status.new(core_status)
else
core_status core_status
else
compound_extended_status
end end
end end
def self.extended_statuses def core_status
[] Gitlab::Ci::Status
.const_get(@status.capitalize)
.new(@subject, @user)
.extend(self.class.common_helpers)
end end
def self.common_helpers def compound_extended_status
Module.new extended_statuses.inject(core_status) do |status, extended|
extended.new(status)
end
end end
private def extended_statuses
return @extended_statuses if defined?(@extended_statuses)
def simple_status groups = self.class.extended_statuses.map do |group|
@simple_status ||= @subject.status || :created Array(group).find { |status| status.matches?(@subject, @user) }
end
@extended_statuses = groups.flatten.compact
end end
def core_status def self.extended_statuses
Gitlab::Ci::Status []
.const_get(simple_status.capitalize)
.new(@subject, @user)
.extend(self.class.common_helpers)
end end
def extended_status def self.common_helpers
@extended ||= self.class.extended_statuses.find do |status| Module.new
status.matches?(@subject, @user)
end
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Pipeline module Pipeline
class Factory < Status::Factory class Factory < Status::Factory
def self.extended_statuses def self.extended_statuses
[Pipeline::SuccessWithWarnings] [Status::SuccessWarning]
end end
def self.common_helpers def self.common_helpers
......
...@@ -3,6 +3,10 @@ module Gitlab ...@@ -3,6 +3,10 @@ module Gitlab
module Status module Status
module Stage module Stage
class Factory < Status::Factory class Factory < Status::Factory
def self.extended_statuses
[Status::SuccessWarning]
end
def self.common_helpers def self.common_helpers
Status::Stage::Common Status::Stage::Common
end end
......
module Gitlab
module Ci
module Status
##
# Extended status used when pipeline or stage passed conditionally.
# This means that failed jobs that are allowed to fail were present.
#
class SuccessWarning < SimpleDelegator
include Status::Extended
def text
'passed'
end
def label
'passed with warnings'
end
def icon
'icon_status_warning'
end
def group
'success_with_warnings'
end
def self.matches?(subject, user)
subject.success? && subject.has_warnings?
end
end
end
end
end
require 'gitlab/email/handler/create_note_handler' require 'gitlab/email/handler/create_note_handler'
require 'gitlab/email/handler/create_issue_handler' require 'gitlab/email/handler/create_issue_handler'
require 'gitlab/email/handler/unsubscribe_handler'
module Gitlab module Gitlab
module Email module Email
module Handler module Handler
HANDLERS = [CreateNoteHandler, CreateIssueHandler] HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler]
def self.for(mail, mail_key) def self.for(mail, mail_key)
HANDLERS.find do |klass| HANDLERS.find do |klass|
......
...@@ -9,52 +9,13 @@ module Gitlab ...@@ -9,52 +9,13 @@ module Gitlab
@mail_key = mail_key @mail_key = mail_key
end end
def message def can_execute?
@message ||= process_message
end
def author
raise NotImplementedError raise NotImplementedError
end end
def project def execute
raise NotImplementedError raise NotImplementedError
end end
private
def validate_permission!(permission)
raise UserNotFoundError unless author
raise UserBlockedError if author.blocked?
raise ProjectNotFound unless author.can?(:read_project, project)
raise UserNotAuthorizedError unless author.can?(permission, project)
end
def process_message
message = ReplyParser.new(mail).execute.strip
add_attachments(message)
end
def add_attachments(reply)
attachments = Email::AttachmentUploader.new(mail).execute(project)
reply + attachments.map do |link|
"\n\n#{link[:markdown]}"
end.join
end
def verify_record!(record:, invalid_exception:, record_name:)
return if record.persisted?
return if record.errors.key?(:commands_only)
error_title = "The #{record_name} could not be created for the following reasons:"
msg = error_title + record.errors.full_messages.map do |error|
"\n\n- #{error}"
end.join
raise invalid_exception, msg
end
end end
end end
end end
......
...@@ -5,6 +5,7 @@ module Gitlab ...@@ -5,6 +5,7 @@ module Gitlab
module Email module Email
module Handler module Handler
class CreateIssueHandler < BaseHandler class CreateIssueHandler < BaseHandler
include ReplyProcessing
attr_reader :project_path, :incoming_email_token attr_reader :project_path, :incoming_email_token
def initialize(mail, mail_key) def initialize(mail, mail_key)
......
require 'gitlab/email/handler/base_handler' require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
module Gitlab module Gitlab
module Email module Email
module Handler module Handler
class CreateNoteHandler < BaseHandler class CreateNoteHandler < BaseHandler
include ReplyProcessing
def can_handle? def can_handle?
mail_key =~ /\A\w+\z/ mail_key =~ /\A\w+\z/
end end
...@@ -24,6 +27,8 @@ module Gitlab ...@@ -24,6 +27,8 @@ module Gitlab
record_name: 'comment') record_name: 'comment')
end end
private
def author def author
sent_notification.recipient sent_notification.recipient
end end
...@@ -36,8 +41,6 @@ module Gitlab ...@@ -36,8 +41,6 @@ module Gitlab
@sent_notification ||= SentNotification.for(mail_key) @sent_notification ||= SentNotification.for(mail_key)
end end
private
def create_note def create_note
Notes::CreateService.new( Notes::CreateService.new(
project, project,
......
module Gitlab
module Email
module Handler
module ReplyProcessing
private
def author
raise NotImplementedError
end
def project
raise NotImplementedError
end
def message
@message ||= process_message
end
def process_message
message = ReplyParser.new(mail).execute.strip
add_attachments(message)
end
def add_attachments(reply)
attachments = Email::AttachmentUploader.new(mail).execute(project)
reply + attachments.map do |link|
"\n\n#{link[:markdown]}"
end.join
end
def validate_permission!(permission)
raise UserNotFoundError unless author
raise UserBlockedError if author.blocked?
raise ProjectNotFound unless author.can?(:read_project, project)
raise UserNotAuthorizedError unless author.can?(permission, project)
end
def verify_record!(record:, invalid_exception:, record_name:)
return if record.persisted?
return if record.errors.key?(:commands_only)
error_title = "The #{record_name} could not be created for the following reasons:"
msg = error_title + record.errors.full_messages.map do |error|
"\n\n- #{error}"
end.join
raise invalid_exception, msg
end
end
end
end
end
require 'gitlab/email/handler/base_handler'
module Gitlab
module Email
module Handler
class UnsubscribeHandler < BaseHandler
def can_handle?
mail_key =~ /\A\w+#{Regexp.escape(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)}\z/
end
def execute
raise SentNotificationNotFoundError unless sent_notification
return unless sent_notification.unsubscribable?
noteable = sent_notification.noteable
raise NoteableNotFoundError unless noteable
noteable.unsubscribe(sent_notification.recipient)
end
private
def sent_notification
@sent_notification ||= SentNotification.for(reply_key)
end
def reply_key
mail_key.sub(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX, '')
end
end
end
end
end
module Gitlab module Gitlab
module IncomingEmail module IncomingEmail
UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze
WILDCARD_PLACEHOLDER = '%{key}'.freeze WILDCARD_PLACEHOLDER = '%{key}'.freeze
class << self class << self
...@@ -18,7 +19,11 @@ module Gitlab ...@@ -18,7 +19,11 @@ module Gitlab
end end
def reply_address(key) def reply_address(key)
config.address.gsub(WILDCARD_PLACEHOLDER, key) config.address.sub(WILDCARD_PLACEHOLDER, key)
end
def unsubscribe_address(key)
config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}")
end end
def key_from_address(address) def key_from_address(address)
...@@ -49,7 +54,7 @@ module Gitlab ...@@ -49,7 +54,7 @@ module Gitlab
return nil unless wildcard_address return nil unless wildcard_address
regex = Regexp.escape(wildcard_address) regex = Regexp.escape(wildcard_address)
regex = regex.gsub(Regexp.escape('%{key}'), "(.+)") regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)')
Regexp.new(regex).freeze Regexp.new(regex).freeze
end end
end end
......
...@@ -35,7 +35,9 @@ module Gitlab ...@@ -35,7 +35,9 @@ module Gitlab
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user) return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
access_levels = project.protected_branches.matching(ref).map(&:push_access_levels).flatten access_levels = project.protected_branches.matching(ref).map(&:push_access_levels).flatten
access_levels.any? { |access_level| access_level.check_access(user) } has_access = access_levels.any? { |access_level| access_level.check_access(user) }
has_access || !project.repository.branch_exists?(ref) && can_merge_to_branch?(ref)
else else
user.can?(:push_code, project) user.can?(:push_code, project)
end end
......
...@@ -54,6 +54,7 @@ describe Projects::ServicesController do ...@@ -54,6 +54,7 @@ describe Projects::ServicesController do
context 'on successful update' do context 'on successful update' do
it 'sets the flash' do it 'sets the flash' do
expect(service).to receive(:to_param).and_return('hipchat') expect(service).to receive(:to_param).and_return('hipchat')
expect(service).to receive(:event_names).and_return(HipchatService.event_names)
put :update, put :update,
namespace_id: project.namespace.id, namespace_id: project.namespace.id,
......
...@@ -3,11 +3,12 @@ FactoryGirl.define do ...@@ -3,11 +3,12 @@ FactoryGirl.define do
transient do transient do
name 'test' name 'test'
status nil status nil
warnings nil
pipeline factory: :ci_empty_pipeline pipeline factory: :ci_empty_pipeline
end end
initialize_with do initialize_with do
Ci::Stage.new(pipeline, name: name, status: status) Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings)
end end
end end
end end
...@@ -10,7 +10,7 @@ describe "Admin::Projects", feature: true do ...@@ -10,7 +10,7 @@ describe "Admin::Projects", feature: true do
end end
describe "GET /admin/projects" do describe "GET /admin/projects" do
let!(:archived_project) { create :project, :public, archived: true } let!(:archived_project) { create :project, :public, :archived }
before do before do
visit admin_projects_path visit admin_projects_path
......
...@@ -7,7 +7,7 @@ feature 'Group merge requests page', feature: true do ...@@ -7,7 +7,7 @@ feature 'Group merge requests page', feature: true do
include_examples 'project features apply to issuables', MergeRequest include_examples 'project features apply to issuables', MergeRequest
context 'archived issuable' do context 'archived issuable' do
let(:project_archived) { create(:project, group: group, merge_requests_access_level: ProjectFeature::ENABLED, archived: true) } let(:project_archived) { create(:project, :archived, group: group, merge_requests_access_level: ProjectFeature::ENABLED) }
let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') } let(:issuable_archived) { create(:merge_request, source_project: project_archived, target_project: project_archived, title: 'issuable of an archived project') }
let(:access_level) { ProjectFeature::ENABLED } let(:access_level) { ProjectFeature::ENABLED }
let(:user) { user_in_group } let(:user) { user_in_group }
......
...@@ -134,14 +134,14 @@ describe 'Dropdown assignee', js: true, feature: true do ...@@ -134,14 +134,14 @@ describe 'Dropdown assignee', js: true, feature: true do
click_button 'Assigned to me' click_button 'Assigned to me'
end end
expect(filtered_search.value).to eq("assignee:#{user.to_reference}") expect(filtered_search.value).to eq("assignee:#{user.to_reference} ")
end end
it 'fills in the assignee username when the assignee has not been filtered' do it 'fills in the assignee username when the assignee has not been filtered' do
click_assignee(user_jacob.name) click_assignee(user_jacob.name)
expect(page).to have_css(js_dropdown_assignee, visible: false) expect(page).to have_css(js_dropdown_assignee, visible: false)
expect(filtered_search.value).to eq("assignee:@#{user_jacob.username}") expect(filtered_search.value).to eq("assignee:@#{user_jacob.username} ")
end end
it 'fills in the assignee username when the assignee has been filtered' do it 'fills in the assignee username when the assignee has been filtered' do
...@@ -149,14 +149,14 @@ describe 'Dropdown assignee', js: true, feature: true do ...@@ -149,14 +149,14 @@ describe 'Dropdown assignee', js: true, feature: true do
click_assignee(user.name) click_assignee(user.name)
expect(page).to have_css(js_dropdown_assignee, visible: false) expect(page).to have_css(js_dropdown_assignee, visible: false)
expect(filtered_search.value).to eq("assignee:@#{user.username}") expect(filtered_search.value).to eq("assignee:@#{user.username} ")
end end
it 'selects `no assignee`' do it 'selects `no assignee`' do
find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
expect(page).to have_css(js_dropdown_assignee, visible: false) expect(page).to have_css(js_dropdown_assignee, visible: false)
expect(filtered_search.value).to eq("assignee:none") expect(filtered_search.value).to eq("assignee:none ")
end end
end end
......
...@@ -121,14 +121,14 @@ describe 'Dropdown author', js: true, feature: true do ...@@ -121,14 +121,14 @@ describe 'Dropdown author', js: true, feature: true do
click_author(user_jacob.name) click_author(user_jacob.name)
expect(page).to have_css(js_dropdown_author, visible: false) expect(page).to have_css(js_dropdown_author, visible: false)
expect(filtered_search.value).to eq("author:@#{user_jacob.username}") expect(filtered_search.value).to eq("author:@#{user_jacob.username} ")
end end
it 'fills in the author username when the author has been filtered' do it 'fills in the author username when the author has been filtered' do
click_author(user.name) click_author(user.name)
expect(page).to have_css(js_dropdown_author, visible: false) expect(page).to have_css(js_dropdown_author, visible: false)
expect(filtered_search.value).to eq("author:@#{user.username}") expect(filtered_search.value).to eq("author:@#{user.username} ")
end end
end end
......
...@@ -159,7 +159,7 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -159,7 +159,7 @@ describe 'Dropdown label', js: true, feature: true do
click_label(bug_label.title) click_label(bug_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~#{bug_label.title}") expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
end end
it 'fills in the label name when the label is partially filled' do it 'fills in the label name when the label is partially filled' do
...@@ -167,49 +167,49 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -167,49 +167,49 @@ describe 'Dropdown label', js: true, feature: true do
click_label(bug_label.title) click_label(bug_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~#{bug_label.title}") expect(filtered_search.value).to eq("label:~#{bug_label.title} ")
end end
it 'fills in the label name that contains multiple words' do it 'fills in the label name that contains multiple words' do
click_label(two_words_label.title) click_label(two_words_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\"") expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\" ")
end end
it 'fills in the label name that contains multiple words and is very long' do it 'fills in the label name that contains multiple words and is very long' do
click_label(long_label.title) click_label(long_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~\"#{long_label.title}\"") expect(filtered_search.value).to eq("label:~\"#{long_label.title}\" ")
end end
it 'fills in the label name that contains double quotes' do it 'fills in the label name that contains double quotes' do
click_label(wont_fix_label.title) click_label(wont_fix_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}'") expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}' ")
end end
it 'fills in the label name with the correct capitalization' do it 'fills in the label name with the correct capitalization' do
click_label(uppercase_label.title) click_label(uppercase_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~#{uppercase_label.title}") expect(filtered_search.value).to eq("label:~#{uppercase_label.title} ")
end end
it 'fills in the label name with special characters' do it 'fills in the label name with special characters' do
click_label(special_label.title) click_label(special_label.title)
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:~#{special_label.title}") expect(filtered_search.value).to eq("label:~#{special_label.title} ")
end end
it 'selects `no label`' do it 'selects `no label`' do
find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click
expect(page).to have_css(js_dropdown_label, visible: false) expect(page).to have_css(js_dropdown_label, visible: false)
expect(filtered_search.value).to eq("label:none") expect(filtered_search.value).to eq("label:none ")
end end
end end
......
...@@ -127,7 +127,7 @@ describe 'Dropdown milestone', js: true, feature: true do ...@@ -127,7 +127,7 @@ describe 'Dropdown milestone', js: true, feature: true do
click_milestone(milestone.title) click_milestone(milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%#{milestone.title}") expect(filtered_search.value).to eq("milestone:%#{milestone.title} ")
end end
it 'fills in the milestone name when the milestone is partially filled' do it 'fills in the milestone name when the milestone is partially filled' do
...@@ -135,56 +135,56 @@ describe 'Dropdown milestone', js: true, feature: true do ...@@ -135,56 +135,56 @@ describe 'Dropdown milestone', js: true, feature: true do
click_milestone(milestone.title) click_milestone(milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%#{milestone.title}") expect(filtered_search.value).to eq("milestone:%#{milestone.title} ")
end end
it 'fills in the milestone name that contains multiple words' do it 'fills in the milestone name that contains multiple words' do
click_milestone(two_words_milestone.title) click_milestone(two_words_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\"") expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\" ")
end end
it 'fills in the milestone name that contains multiple words and is very long' do it 'fills in the milestone name that contains multiple words and is very long' do
click_milestone(long_milestone.title) click_milestone(long_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\"") expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\" ")
end end
it 'fills in the milestone name that contains double quotes' do it 'fills in the milestone name that contains double quotes' do
click_milestone(wont_fix_milestone.title) click_milestone(wont_fix_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}'") expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}' ")
end end
it 'fills in the milestone name with the correct capitalization' do it 'fills in the milestone name with the correct capitalization' do
click_milestone(uppercase_milestone.title) click_milestone(uppercase_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title}") expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title} ")
end end
it 'fills in the milestone name with special characters' do it 'fills in the milestone name with special characters' do
click_milestone(special_milestone.title) click_milestone(special_milestone.title)
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:%#{special_milestone.title}") expect(filtered_search.value).to eq("milestone:%#{special_milestone.title} ")
end end
it 'selects `no milestone`' do it 'selects `no milestone`' do
click_static_milestone('No Milestone') click_static_milestone('No Milestone')
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:none") expect(filtered_search.value).to eq("milestone:none ")
end end
it 'selects `upcoming milestone`' do it 'selects `upcoming milestone`' do
click_static_milestone('Upcoming') click_static_milestone('Upcoming')
expect(page).to have_css(js_dropdown_milestone, visible: false) expect(page).to have_css(js_dropdown_milestone, visible: false)
expect(filtered_search.value).to eq("milestone:upcoming") expect(filtered_search.value).to eq("milestone:upcoming ")
end end
end end
......
...@@ -539,7 +539,7 @@ describe 'Filter issues', js: true, feature: true do ...@@ -539,7 +539,7 @@ describe 'Filter issues', js: true, feature: true do
click_button user2.username click_button user2.username
end end
expect(filtered_search.value).to eq("author:@#{user2.username}") expect(filtered_search.value).to eq("author:@#{user2.username} ")
end end
it 'changes label' do it 'changes label' do
...@@ -551,7 +551,7 @@ describe 'Filter issues', js: true, feature: true do ...@@ -551,7 +551,7 @@ describe 'Filter issues', js: true, feature: true do
click_button label.name click_button label.name
end end
expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name}") expect(filtered_search.value).to eq("author:@#{user.username} label:~#{label.name} ")
end end
it 'changes label correctly space is in previous label' do it 'changes label correctly space is in previous label' do
...@@ -563,7 +563,7 @@ describe 'Filter issues', js: true, feature: true do ...@@ -563,7 +563,7 @@ describe 'Filter issues', js: true, feature: true do
click_button label.name click_button label.name
end end
expect(filtered_search.value).to eq("label:~#{label.name}") expect(filtered_search.value).to eq("label:~#{label.name} ")
end end
end end
......
require 'spec_helper'
feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do
let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
let(:project) { create(:empty_project) }
background do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
after do
FileUtils.rm_rf(export_path, secure: true)
end
context 'admin user' do
before do
login_as(:admin)
end
context 'moving the namespace' do
scenario 'removes the export file' do
setup_export_project
old_export_path = project.export_path.dup
expect(File).to exist(old_export_path)
project.namespace.update(path: 'new_path')
expect(File).not_to exist(old_export_path)
end
end
context 'deleting the namespace' do
scenario 'removes the export file' do
setup_export_project
old_export_path = project.export_path.dup
expect(File).to exist(old_export_path)
project.namespace.destroy
expect(File).not_to exist(old_export_path)
end
end
def setup_export_project
visit edit_namespace_project_path(project.namespace, project)
expect(page).to have_content('Export project')
click_link 'Export project'
visit edit_namespace_project_path(project.namespace, project)
expect(page).to have_content('Download export')
end
end
end
...@@ -6,7 +6,7 @@ describe MergeRequestsFinder do ...@@ -6,7 +6,7 @@ describe MergeRequestsFinder do
let(:project1) { create(:project) } let(:project1) { create(:project) }
let(:project2) { create(:project, forked_from_project: project1) } let(:project2) { create(:project, forked_from_project: project1) }
let(:project3) { create(:project, forked_from_project: project1, archived: true) } let(:project3) { create(:project, :archived, forked_from_project: project1) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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