Commit d5549025 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-upstream' into 'master'

CE upstream

Closes gitlab-ce#26813 and gitlab-ce#25989

See merge request !1102
parents 6fb89ee2 6c404fa9
......@@ -21,7 +21,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth', '~> 1.3.2'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2'
......
......@@ -473,7 +473,7 @@ GEM
octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4)
omniauth (1.3.1)
omniauth (1.3.2)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
......@@ -958,7 +958,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.3.1)
omniauth (~> 1.3.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.2.0)
omniauth-azure-oauth2 (~> 0.0.6)
......
......@@ -143,4 +143,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome?
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 @@
if (selected.tagName === 'LI') {
if (selected.hasAttribute('data-value')) {
this.dismissDropdown();
} else if (selected.getAttribute('data-action') === 'submit') {
this.dismissDropdown();
this.dispatchFormSubmitEvent();
} else {
const token = selected.querySelector('.js-filter-hint').innerText.trim();
const tag = selected.querySelector('.js-filter-tag').innerText.trim();
......
......@@ -39,6 +39,7 @@
}
this.dismissDropdown();
this.dispatchInputEvent();
}
}
......@@ -84,6 +85,12 @@
}));
}
dispatchFormSubmitEvent() {
// dispatchEvent() is necessary as form.submit() does not
// trigger event handlers
this.input.form.dispatchEvent(new Event('submit'));
}
hideDropdown() {
this.getCurrentHook().list.hide();
}
......
......@@ -66,11 +66,19 @@
const word = `${tokenName}:${tokenValue}`;
// Get the string to replace
const selectionStart = input.selectionStart;
let newCaretPosition = input.selectionStart;
const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
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) {
......
......@@ -25,6 +25,7 @@
}
bindEvents() {
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
this.checkForEnterWrapper = this.checkForEnter.bind(this);
......@@ -32,6 +33,7 @@
this.checkForBackspaceWrapper = this.checkForBackspace.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.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
......@@ -42,6 +44,7 @@
}
unbindEvents() {
this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
......@@ -88,6 +91,11 @@
this.dropdownManager.resetDropdowns();
}
handleFormSubmit(e) {
e.preventDefault();
this.search();
}
loadSearchParamsFromURL() {
const params = gl.utils.getUrlParamsArray();
const usernameParams = this.getUsernameParams();
......
......@@ -15,6 +15,7 @@
}
.ci-status-icon-pending,
.ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings {
color: $gl-warning;
......
......@@ -46,10 +46,6 @@
font-weight: bold;
}
.fa-clipboard {
color: $dropdown-title-btn-color;
}
.commit-info {
&.branches {
margin-left: 8px;
......
......@@ -195,10 +195,10 @@ ul.notes {
}
.note-body {
overflow: auto;
overflow-x: auto;
overflow-y: hidden;
.note-text {
overflow: auto;
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
......
......@@ -18,7 +18,6 @@
.file-finder-input:hover,
.issuable-search-form:hover,
.search-text-input:hover,
textarea:hover,
.form-control:hover {
border-color: lighten($dropdown-input-focus-border, 20%);
box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
......
......@@ -19,7 +19,8 @@
overflow: visible;
}
&.ci-failed {
&.ci-failed,
&.ci-failed_with_warnings {
color: $gl-danger;
border-color: $gl-danger;
......
module ServicesHelper
def service_event_description(event)
case event
when "push"
when "push", "push_events"
"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"
when "note"
when "note", "note_events"
"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"
when "confidential_issue"
when "confidential_issue", "confidential_issue_events"
"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"
when "build"
when "build", "build_events"
"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"
when "commit"
when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
end
end
......@@ -26,4 +26,6 @@ module ServicesHelper
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events"
end
extend self
end
......@@ -108,15 +108,11 @@ class Notify < BaseMailer
def mail_thread(model, headers = {})
add_project_headers
add_unsubscription_headers_and_links
headers["X-GitLab-#{model.class.name}-ID"] = model.id
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?
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace
......@@ -172,4 +168,16 @@ class Notify < BaseMailer
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
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
......@@ -128,16 +128,21 @@ module Ci
end
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
stages_query = statuses.group('stage').select(:stage)
.order('max(stage_idx)')
warnings_sql = statuses.latest.select('COUNT(*) > 0')
.where('stage=sg.stage').failed_but_allowed.to_sql
stages_with_statuses = CommitStatus.from(stages_query, :sg).
pluck('sg.stage', status_sql)
stages_with_statuses = CommitStatus.from(stages_query, :sg)
.pluck('sg.stage', status_sql, "(#{warnings_sql})")
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
......
......@@ -8,10 +8,11 @@ module Ci
delegate :project, to: :pipeline
def initialize(pipeline, name:, status: nil)
def initialize(pipeline, name:, status: nil, warnings: nil)
@pipeline = pipeline
@name = name
@status = status
@warnings = warnings
end
def to_param
......@@ -39,5 +40,17 @@ module Ci
def builds
@builds ||= pipeline.builds.where(stage: name)
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
module HasStatus
extend ActiveSupport::Concern
DEFAULT_STATUS = 'created'
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
......
......@@ -133,6 +133,8 @@ class Namespace < ActiveRecord::Base
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
Gitlab::PagesTransfer.new.rename_namespace(path_was, path)
remove_exports!
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
......@@ -225,6 +227,8 @@ class Namespace < ActiveRecord::Base
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
end
end
remove_exports!
end
def refresh_access_of_projects_invited_groups
......@@ -237,4 +241,20 @@ class Namespace < ActiveRecord::Base
def full_path_changed?
path_changed? || parent_id_changed?
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
......@@ -25,7 +25,7 @@ You can create a Personal Access Token here:
http://app.asana.com/-/account_api'
end
def to_param
def self.to_param
'asana'
end
......@@ -44,7 +44,7 @@ http://app.asana.com/-/account_api'
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -12,7 +12,7 @@ class AssemblaService < Service
'Project Management Software (Source Commits Endpoint)'
end
def to_param
def self.to_param
'assembla'
end
......@@ -23,7 +23,7 @@ class AssemblaService < Service
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -40,7 +40,7 @@ class BambooService < CiService
'You must set up automatic revision labeling and a repository trigger in Bamboo.'
end
def to_param
def self.to_param
'bamboo'
end
......@@ -56,10 +56,6 @@ class BambooService < CiService
]
end
def supported_events
%w(push)
end
def build_page(sha, ref)
with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end
......
......@@ -19,7 +19,7 @@ class BugzillaService < IssueTrackerService
end
end
def to_param
def self.to_param
'bugzilla'
end
end
......@@ -24,10 +24,6 @@ class BuildkiteService < CiService
hook.save
end
def supported_events
%w(push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
......@@ -54,7 +50,7 @@ class BuildkiteService < CiService
'Continuous integration and deployments'
end
def to_param
def self.to_param
'buildkite'
end
......
......@@ -19,11 +19,11 @@ class BuildsEmailService < Service
'Email the builds status to a list of recipients.'
end
def to_param
def self.to_param
'builds_email'
end
def supported_events
def self.supported_events
%w(build)
end
......
......@@ -12,7 +12,7 @@ class CampfireService < Service
'Simple web-based real-time group chat'
end
def to_param
def self.to_param
'campfire'
end
......@@ -24,7 +24,7 @@ class CampfireService < Service
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -25,7 +25,7 @@ class ChatNotificationService < Service
valid?
end
def supported_events
def self.supported_events
%w[push issue confidential_issue merge_request note tag_push
build pipeline wiki_page]
end
......@@ -82,19 +82,19 @@ class ChatNotificationService < Service
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
PushMessage.new(data)
ChatMessage::PushMessage.new(data)
when "issue"
IssueMessage.new(data) unless is_update?(data)
ChatMessage::IssueMessage.new(data) unless is_update?(data)
when "merge_request"
MergeMessage.new(data) unless is_update?(data)
ChatMessage::MergeMessage.new(data) unless is_update?(data)
when "note"
NoteMessage.new(data)
ChatMessage::NoteMessage.new(data)
when "build"
BuildMessage.new(data) if should_build_be_notified?(data)
ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
PipelineMessage.new(data) if should_pipeline_be_notified?(data)
ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
WikiPageMessage.new(data)
ChatMessage::WikiPageMessage.new(data)
end
end
......
......@@ -13,8 +13,8 @@ class ChatSlashCommandsService < Service
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end
def supported_events
[]
def self.supported_events
%w()
end
def can_test?
......
......@@ -8,7 +8,7 @@ class CiService < Service
self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -23,7 +23,7 @@ class CustomIssueTrackerService < IssueTrackerService
end
end
def to_param
def self.to_param
'custom_issue_tracker'
end
......
......@@ -5,8 +5,8 @@
class DeploymentService < Service
default_value_for :category, 'deployment'
def supported_events
[]
def self.supported_events
%w()
end
def predefined_variables
......
......@@ -32,7 +32,7 @@ class DroneCiService < CiService
true
end
def supported_events
def self.supported_events
%w(push merge_request tag_push)
end
......@@ -87,7 +87,7 @@ class DroneCiService < CiService
'Drone is a Continuous Integration platform built on Docker, written in Go'
end
def to_param
def self.to_param
'drone_ci'
end
......
......@@ -12,11 +12,11 @@ class EmailsOnPushService < Service
'Email the commits and diff of each push to a list of recipients.'
end
def to_param
def self.to_param
'emails_on_push'
end
def supported_events
def self.supported_events
%w(push tag_push)
end
......
......@@ -13,7 +13,7 @@ class ExternalWikiService < Service
'Replaces the link to the internal wiki with a link to an external wiki.'
end
def to_param
def self.to_param
'external_wiki'
end
......@@ -29,4 +29,8 @@ class ExternalWikiService < Service
nil
end
end
def self.supported_events
%w()
end
end
......@@ -12,7 +12,7 @@ class FlowdockService < Service
'Flowdock is a collaboration web app for technical teams.'
end
def to_param
def self.to_param
'flowdock'
end
......@@ -22,7 +22,7 @@ class FlowdockService < Service
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -12,7 +12,7 @@ class GemnasiumService < Service
'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
end
def to_param
def self.to_param
'gemnasium'
end
......@@ -23,7 +23,7 @@ class GemnasiumService < Service
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService
default_value_for :default, true
def to_param
def self.to_param
'gitlab'
end
......
......@@ -27,7 +27,7 @@ class HipchatService < Service
'Private group chat and IM'
end
def to_param
def self.to_param
'hipchat'
end
......@@ -45,7 +45,7 @@ class HipchatService < Service
]
end
def supported_events
def self.supported_events
%w(push issue confidential_issue merge_request note tag_push build)
end
......
......@@ -17,11 +17,11 @@ class IrkerService < Service
'gateway.'
end
def to_param
def self.to_param
'irker'
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -61,7 +61,7 @@ class IssueTrackerService < Service
end
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -33,7 +33,7 @@ class JenkinsDeprecatedService < CiService
'is deprecated. Use "Jenkins CI" service instead.'
end
def to_param
def self.to_param
'jenkins_deprecated'
end
......
......@@ -52,7 +52,7 @@ class JenkinsService < CiService
File.join(jenkins_url, "project/#{project_name}").to_s
end
def supported_events
def self.supported_events
%w(push merge_request tag_push)
end
......@@ -68,7 +68,7 @@ class JenkinsService < CiService
'You must have installed the Git Plugin and GitLab Plugin in Jenkins'
end
def to_param
def self.to_param
'jenkins'
end
......
......@@ -12,7 +12,7 @@ class JiraService < IssueTrackerService
# This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service
# configuration screen.
def supported_events
def self.supported_events
%w(commit merge_request)
end
......@@ -81,7 +81,7 @@ class JiraService < IssueTrackerService
end
end
def to_param
def self.to_param
'jira'
end
......
......@@ -52,7 +52,7 @@ class KubernetesService < DeploymentService
'deployments with `app=$CI_ENVIRONMENT_SLUG`'
end
def to_param
def self.to_param
'kubernetes'
end
......
......@@ -7,7 +7,7 @@ class MattermostService < ChatNotificationService
'Receive event notifications in Mattermost'
end
def to_param
def self.to_param
'mattermost'
end
......@@ -36,6 +36,6 @@ class MattermostService < ChatNotificationService
end
def default_channel_placeholder
"#town-square"
"town-square"
end
end
......@@ -15,7 +15,7 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
"Perform common operations on GitLab in Mattermost"
end
def to_param
def self.to_param
'mattermost_slash_commands'
end
......
......@@ -15,11 +15,11 @@ class PipelinesEmailService < Service
'Email the pipelines status to a list of recipients.'
end
def to_param
def self.to_param
'pipelines_email'
end
def supported_events
def self.supported_events
%w[pipeline]
end
......
......@@ -14,7 +14,7 @@ class PivotaltrackerService < Service
'Project Management Software (Source Commits Endpoint)'
end
def to_param
def self.to_param
'pivotaltracker'
end
......@@ -34,7 +34,7 @@ class PivotaltrackerService < Service
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -13,7 +13,7 @@ class PushoverService < Service
'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
end
def to_param
def self.to_param
'pushover'
end
......@@ -61,7 +61,7 @@ class PushoverService < Service
]
end
def supported_events
def self.supported_events
%w(push)
end
......
......@@ -19,7 +19,7 @@ class RedmineService < IssueTrackerService
end
end
def to_param
def self.to_param
'redmine'
end
end
......@@ -7,7 +7,7 @@ class SlackService < ChatNotificationService
'Receive event notifications in Slack'
end
def to_param
def self.to_param
'slack'
end
......
......@@ -9,7 +9,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService
"Perform common operations on GitLab in Slack"
end
def to_param
def self.to_param
'slack_slash_commands'
end
......
......@@ -43,14 +43,10 @@ class TeamcityService < CiService
'requests build, that setting is in the vsc root advanced settings.'
end
def to_param
def self.to_param
'teamcity'
end
def supported_events
%w(push)
end
def fields
[
{ type: 'text', name: 'teamcity_url',
......
......@@ -76,6 +76,11 @@ class Service < ActiveRecord::Base
def to_param
# implement inside child
self.class.to_param
end
def self.to_param
raise NotImplementedError
end
def fields
......@@ -92,7 +97,11 @@ class Service < ActiveRecord::Base
end
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
def event_field(event)
......@@ -104,6 +113,10 @@ class Service < ActiveRecord::Base
end
def supported_events
self.class.supported_events
end
def self.supported_events
%w(push tag_push issue confidential_issue merge_request wiki_page)
end
......
......@@ -17,7 +17,7 @@
= icon('times')
#js-dropdown-hint.dropdown-menu.hint-dropdown
%ul{ 'data-dropdown' => true }
%li.filter-dropdown-item{ 'data-value' => '' }
%li.filter-dropdown-item{ 'data-action' => 'submit' }
%button.btn.btn-link
= icon('search')
%span
......@@ -139,10 +139,6 @@
new MilestoneSelect();
new IssueStatusSelect();
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) {
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.
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
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).
## Everyone
......@@ -81,15 +81,15 @@ balance in how deep the reviewer can interfere with the code created by a
reviewee.
- 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
reviewing merge requests.
reviewers that become maintainers after some time spent on reviewing merge
requests.
- Finding bugs and improving code style is important, but thinking about good
design is important as well. Building abstractions and good design is what
makes it possible to hide complexity and makes future changes easier.
- 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
request endboss before doing it, but have the courage to do it when you
believe it is important.
of the contributed code. It's usually a good idea to ask another maintainer or
reviewer before doing it, but have the courage to do it when you believe it is
important.
- 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
well. A good example is a security fix which should be released as soon as
......
......@@ -3,7 +3,7 @@
To ensure a merge request does not negatively impact performance of GitLab
_every_ merge request **must** adhere to the guidelines outlined in this
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
[Sherlock](profiling.md#sherlock). It's also highly recommended that you read
......@@ -40,9 +40,9 @@ section below for more information.
about the impact.
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
can find a list of these endbosses at <https://about.gitlab.com/team/>. An
endboss in turn can request a performance specialist to review the changes.
should ask one of the merge request reviewers to review your changes. You can
find a list of these reviewers at <https://about.gitlab.com/team/>. A reviewer
in turn can request a performance specialist to review the changes.
## Query Counts
......
......@@ -105,15 +105,19 @@ module API
present key.deploy_key, with: Entities::SSHKey
end
desc 'Delete existing deploy key of currently authenticated user' do
desc 'Delete deploy key for a project' do
success Key
end
params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end
delete ":id/#{path}/:key_id" do
key = user_project.deploy_keys.find(params[:key_id])
key.destroy
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
if key
key.destroy
else
not_found!('Deploy Key')
end
end
end
end
......
......@@ -90,6 +90,12 @@ module API
MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
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!
unauthorized! unless current_user
end
......
......@@ -15,10 +15,8 @@ module API
end
get ":id/merge_requests/:merge_request_id/versions" do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs, with: Entities::MergeRequestDiff
end
......@@ -34,10 +32,8 @@ module API
end
get ":id/merge_requests/:merge_request_id/versions/:version_id" do
merge_request = user_project.merge_requests.
find(params[:merge_request_id])
merge_request = find_merge_request_with_access(params[:merge_request_id])
authorize! :read_merge_request, merge_request
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end
end
......
......@@ -119,8 +119,8 @@ module API
success Entities::MergeRequest
end
get path do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
......@@ -128,8 +128,8 @@ module API
success Entities::RepoCommit
end
get "#{path}/commits" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request.commits, with: Entities::RepoCommit
end
......@@ -137,8 +137,8 @@ module API
success Entities::MergeRequestChanges
end
get "#{path}/changes" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end
......@@ -156,8 +156,7 @@ module API
:remove_source_branch
end
put path do
merge_request = find_project_merge_request(params.delete(:merge_request_id))
authorize! :update_merge_request, merge_request
merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
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?
......@@ -236,10 +235,7 @@ module API
use :pagination
end
get "#{path}/comments" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :read_merge_request, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id])
present paginate(merge_request.notes.fresh), with: Entities::MRNote
end
......@@ -251,8 +247,7 @@ module API
requires :note, type: String, desc: 'The text of the comment'
end
post "#{path}/comments" do
merge_request = find_project_merge_request(params[:merge_request_id])
authorize! :create_note, merge_request
merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note)
opts = {
note: params[:note],
......@@ -276,7 +271,7 @@ module API
use :pagination
end
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))
present paginate(issues), with: issue_entity(user_project), current_user: current_user
end
......
......@@ -70,21 +70,27 @@ module API
end
post ":id/#{noteables_str}/:noteable_id/notes" do
opts = {
note: params[:body],
noteable_type: noteables_str.classify,
noteable_id: params[:noteable_id]
note: params[:body],
noteable_type: noteables_str.classify,
noteable_id: params[:noteable_id]
}
if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user)
opts[:created_at] = params[:created_at]
end
noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id])
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?
present note, with: Entities::const_get(note.class.name)
if note.valid?
present note, with: Entities::const_get(note.class.name)
else
not_found!("Note #{note.errors.messages}")
end
else
not_found!("Note #{note.errors.messages}")
not_found!("Note")
end
end
......
......@@ -145,7 +145,7 @@ module API
name: :room,
type: String,
desc: 'Campfire room'
},
}
],
'custom-issue-tracker' => [
{
......@@ -580,7 +580,38 @@ module API
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,
JenkinsService,
JenkinsDeprecatedService
].freeze
trigger_services = {
'mattermost-slash-commands' => [
......@@ -614,6 +645,19 @@ module API
services.each do |service_slug, settings|
desc "Set #{service_slug} service for project"
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|
if setting[:required]
requires setting[:name], type: setting[:type], desc: setting[:desc]
......@@ -627,7 +671,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params)
true
present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
else
render_api_error!('400 Bad Request', 400)
end
......
......@@ -3,8 +3,8 @@ module API
before { authenticate! }
subscribable_types = {
'merge_request' => proc { |id| user_project.merge_requests.find(id) },
'merge_requests' => 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| find_merge_request_with_access(id, :update_merge_request) },
'issues' => proc { |id| find_project_issue(id) },
'labels' => proc { |id| find_project_label(id) },
}
......
......@@ -5,7 +5,7 @@ module API
before { authenticate! }
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) }
}
......
......@@ -4,8 +4,11 @@ module Gitlab
module Build
class Factory < Status::Factory
def self.extended_statuses
[Status::Build::Stop, Status::Build::Play,
Status::Build::Cancelable, Status::Build::Retryable]
[[Status::Build::Cancelable,
Status::Build::Retryable],
[Status::Build::FailedAllowed,
Status::Build::Play,
Status::Build::Stop]]
end
def self.common_helpers
......
module Gitlab
module Ci
module Status
module Pipeline
class SuccessWithWarnings < SimpleDelegator
module Build
class FailedAllowed < SimpleDelegator
include Status::Extended
def text
'passed'
end
def label
'passed with warnings'
'failed (allowed to fail)'
end
def icon
......@@ -18,11 +14,11 @@ module Gitlab
end
def group
'success_with_warnings'
'failed_with_warnings'
end
def self.matches?(pipeline, user)
pipeline.success? && pipeline.has_warnings?
def self.matches?(build, user)
build.failed? && build.allow_failure?
end
end
end
......
......@@ -5,41 +5,46 @@ module Gitlab
def initialize(subject, user)
@subject = subject
@user = user
@status = subject.status || HasStatus::DEFAULT_STATUS
end
def fabricate!
if extended_status
extended_status.new(core_status)
else
if extended_statuses.none?
core_status
else
compound_extended_status
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
def self.common_helpers
Module.new
def compound_extended_status
extended_statuses.inject(core_status) do |status, extended|
extended.new(status)
end
end
private
def extended_statuses
return @extended_statuses if defined?(@extended_statuses)
def simple_status
@simple_status ||= @subject.status || :created
groups = self.class.extended_statuses.map do |group|
Array(group).find { |status| status.matches?(@subject, @user) }
end
@extended_statuses = groups.flatten.compact
end
def core_status
Gitlab::Ci::Status
.const_get(simple_status.capitalize)
.new(@subject, @user)
.extend(self.class.common_helpers)
def self.extended_statuses
[]
end
def extended_status
@extended ||= self.class.extended_statuses.find do |status|
status.matches?(@subject, @user)
end
def self.common_helpers
Module.new
end
end
end
......
......@@ -4,7 +4,7 @@ module Gitlab
module Pipeline
class Factory < Status::Factory
def self.extended_statuses
[Pipeline::SuccessWithWarnings]
[Status::SuccessWarning]
end
def self.common_helpers
......
......@@ -3,6 +3,10 @@ module Gitlab
module Status
module Stage
class Factory < Status::Factory
def self.extended_statuses
[Status::SuccessWarning]
end
def self.common_helpers
Status::Stage::Common
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_issue_handler'
require 'gitlab/email/handler/unsubscribe_handler'
module Gitlab
module Email
module Handler
HANDLERS = [CreateNoteHandler, CreateIssueHandler]
HANDLERS = [UnsubscribeHandler, CreateNoteHandler, CreateIssueHandler]
def self.for(mail, mail_key)
HANDLERS.find do |klass|
......
......@@ -9,52 +9,13 @@ module Gitlab
@mail_key = mail_key
end
def message
@message ||= process_message
end
def author
def can_execute?
raise NotImplementedError
end
def project
def execute
raise NotImplementedError
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
......
......@@ -5,6 +5,7 @@ module Gitlab
module Email
module Handler
class CreateIssueHandler < BaseHandler
include ReplyProcessing
attr_reader :project_path, :incoming_email_token
def initialize(mail, mail_key)
......
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
module Gitlab
module Email
module Handler
class CreateNoteHandler < BaseHandler
include ReplyProcessing
def can_handle?
mail_key =~ /\A\w+\z/
end
......@@ -24,6 +27,8 @@ module Gitlab
record_name: 'comment')
end
private
def author
sent_notification.recipient
end
......@@ -36,8 +41,6 @@ module Gitlab
@sent_notification ||= SentNotification.for(mail_key)
end
private
def create_note
Notes::CreateService.new(
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 IncomingEmail
UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze
WILDCARD_PLACEHOLDER = '%{key}'.freeze
class << self
......@@ -18,7 +19,11 @@ module Gitlab
end
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
def key_from_address(address)
......@@ -49,7 +54,7 @@ module Gitlab
return nil unless wildcard_address
regex = Regexp.escape(wildcard_address)
regex = regex.gsub(Regexp.escape('%{key}'), "(.+)")
regex = regex.sub(Regexp.escape(WILDCARD_PLACEHOLDER), '(.+)')
Regexp.new(regex).freeze
end
end
......
......@@ -35,7 +35,9 @@ module Gitlab
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.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
user.can?(:push_code, project)
end
......
......@@ -54,6 +54,7 @@ describe Projects::ServicesController do
context 'on successful update' do
it 'sets the flash' do
expect(service).to receive(:to_param).and_return('hipchat')
expect(service).to receive(:event_names).and_return(HipchatService.event_names)
put :update,
namespace_id: project.namespace.id,
......
......@@ -3,11 +3,12 @@ FactoryGirl.define do
transient do
name 'test'
status nil
warnings nil
pipeline factory: :ci_empty_pipeline
end
initialize_with do
Ci::Stage.new(pipeline, name: name, status: status)
Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings)
end
end
end
......@@ -10,7 +10,7 @@ describe "Admin::Projects", feature: true do
end
describe "GET /admin/projects" do
let!(:archived_project) { create :project, :public, archived: true }
let!(:archived_project) { create :project, :public, :archived }
before do
visit admin_projects_path
......
......@@ -7,7 +7,7 @@ feature 'Group merge requests page', feature: true do
include_examples 'project features apply to issuables', MergeRequest
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(:access_level) { ProjectFeature::ENABLED }
let(:user) { user_in_group }
......
......@@ -134,14 +134,14 @@ describe 'Dropdown assignee', js: true, feature: true do
click_button 'Assigned to me'
end
expect(filtered_search.value).to eq("assignee:#{user.to_reference}")
expect(filtered_search.value).to eq("assignee:#{user.to_reference} ")
end
it 'fills in the assignee username when the assignee has not been filtered' do
click_assignee(user_jacob.name)
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
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
click_assignee(user.name)
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
it 'selects `no assignee`' do
find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
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
......
......@@ -121,14 +121,14 @@ describe 'Dropdown author', js: true, feature: true do
click_author(user_jacob.name)
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
it 'fills in the author username when the author has been filtered' do
click_author(user.name)
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
......
......@@ -159,7 +159,7 @@ describe 'Dropdown label', js: true, feature: true do
click_label(bug_label.title)
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
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
click_label(bug_label.title)
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
it 'fills in the label name that contains multiple words' do
click_label(two_words_label.title)
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
it 'fills in the label name that contains multiple words and is very long' do
click_label(long_label.title)
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
it 'fills in the label name that contains double quotes' do
click_label(wont_fix_label.title)
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
it 'fills in the label name with the correct capitalization' do
click_label(uppercase_label.title)
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
it 'fills in the label name with special characters' do
click_label(special_label.title)
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
it 'selects `no label`' do
find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click
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
......
......@@ -127,7 +127,7 @@ describe 'Dropdown milestone', js: true, feature: true do
click_milestone(milestone.title)
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
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
click_milestone(milestone.title)
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
it 'fills in the milestone name that contains multiple words' do
click_milestone(two_words_milestone.title)
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
it 'fills in the milestone name that contains multiple words and is very long' do
click_milestone(long_milestone.title)
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
it 'fills in the milestone name that contains double quotes' do
click_milestone(wont_fix_milestone.title)
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
it 'fills in the milestone name with the correct capitalization' do
click_milestone(uppercase_milestone.title)
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
it 'fills in the milestone name with special characters' do
click_milestone(special_milestone.title)
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
it 'selects `no milestone`' do
click_static_milestone('No Milestone')
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
it 'selects `upcoming milestone`' do
click_static_milestone('Upcoming')
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
......
......@@ -62,28 +62,28 @@ describe 'Dropdown weight', js: true, feature: true do
click_weight(1)
expect(page).to have_css(js_dropdown_weight, visible: false)
expect(filtered_search.value).to eq("weight:1")
expect(filtered_search.value).to eq("weight:1 ")
end
it 'fills in weight 2' do
click_weight(2)
expect(page).to have_css(js_dropdown_weight, visible: false)
expect(filtered_search.value).to eq("weight:2")
expect(filtered_search.value).to eq("weight:2 ")
end
it 'fills in weight 3' do
click_weight(3)
expect(page).to have_css(js_dropdown_weight, visible: false)
expect(filtered_search.value).to eq("weight:3")
expect(filtered_search.value).to eq("weight:3 ")
end
it 'fills in `no weight`' do
click_static_weight('No Weight')
expect(page).to have_css(js_dropdown_weight, visible: false)
expect(filtered_search.value).to eq("weight:none")
expect(filtered_search.value).to eq("weight:none ")
end
end
......
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.
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