BigW Consortium Gitlab

Commit 88857a2c by Phil Hughes

Merge branch 'master' into revert-c676283b

parents 735731a5 4be63f5b
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
"always-semicolon": true, "always-semicolon": true,
"color-case": "lower", "color-case": "lower",
"block-indent": " ", "block-indent": " ",
"color-shorthand": true, "color-shorthand": false,
"element-case": "lower", "element-case": "lower",
"space-before-colon": "", "space-before-colon": "",
"space-after-colon": " ", "space-after-colon": " ",
......
image: "ruby:2.3.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1"
cache: cache:
key: "ruby-231" key: "ruby-231"
paths: paths:
- vendor/apt
- vendor/ruby - vendor/ruby
variables: variables:
...@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack ...@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.1 # Execute all testing suites against Ruby 2.1
.ruby-21: &ruby-21 .ruby-21: &ruby-21
image: "ruby:2.1" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1"
<<: *use-db <<: *use-db
only: only:
- master - master
cache: cache:
key: "ruby21" key: "ruby21"
paths: paths:
- vendor/apt
- vendor/ruby - vendor/ruby
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21 .rspec-knapsack-ruby21: &rspec-knapsack-ruby21
......
...@@ -639,6 +639,10 @@ Lint/RescueException: ...@@ -639,6 +639,10 @@ Lint/RescueException:
Lint/ShadowedException: Lint/ShadowedException:
Enabled: false Enabled: false
# Checks for Object#to_s usage in string interpolation.
Lint/StringConversionInInterpolation:
Enabled: true
# Do not use prefix `_` for a variable that is used. # Do not use prefix `_` for a variable that is used.
Lint/UnderscorePrefixedVariableName: Lint/UnderscorePrefixedVariableName:
Enabled: true Enabled: true
......
...@@ -27,11 +27,6 @@ Lint/Loop: ...@@ -27,11 +27,6 @@ Lint/Loop:
Lint/ShadowingOuterLocalVariable: Lint/ShadowingOuterLocalVariable:
Enabled: false Enabled: false
# Offense count: 6
# Cop supports --auto-correct.
Lint/StringConversionInInterpolation:
Enabled: false
# Offense count: 49 # Offense count: 49
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
......
...@@ -79,7 +79,7 @@ linters: ...@@ -79,7 +79,7 @@ linters:
# HEX colors should use three-character values where possible. # HEX colors should use three-character values where possible.
HexLength: HexLength:
enabled: true enabled: false
# HEX color values should use lower-case colors to differentiate between # HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`. # letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
......
...@@ -6,6 +6,7 @@ v 8.13.0 (unreleased) ...@@ -6,6 +6,7 @@ v 8.13.0 (unreleased)
- Fix centering of custom header logos (Ashley Dumaine) - Fix centering of custom header logos (Ashley Dumaine)
- AbstractReferenceFilter caches project_refs on RequestStore when active - AbstractReferenceFilter caches project_refs on RequestStore when active
- Replaced the check sign to arrow in the show build view. !6501 - Replaced the check sign to arrow in the show build view. !6501
- Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar)
- Speed-up group milestones show page - Speed-up group milestones show page
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Add more tests for calendar contribution (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps)
...@@ -18,16 +19,20 @@ v 8.13.0 (unreleased) ...@@ -18,16 +19,20 @@ v 8.13.0 (unreleased)
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Add configurable email subject suffix (Fu Xu)
- Use a ConnectionPool for Rails.cache on Sidekiq servers - Use a ConnectionPool for Rails.cache on Sidekiq servers
- Replace `alias_method_chain` with `Module#prepend` - Replace `alias_method_chain` with `Module#prepend`
- Enable GitLab Import/Export for non-admin users.
- Preserve label filters when sorting !6136 (Joseph Frazier) - Preserve label filters when sorting !6136 (Joseph Frazier)
- Only update issuable labels if they have been changed - Only update issuable labels if they have been changed
- Take filters in account in issuable counters. !6496 - Take filters in account in issuable counters. !6496
- Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*)
- Revoke button in Applications Settings underlines on hover. - Revoke button in Applications Settings underlines on hover.
- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
- Fix Long commit messages overflow viewport in file tree - Fix Long commit messages overflow viewport in file tree
- Revert avoid touching file system on Build#artifacts? - Revert avoid touching file system on Build#artifacts?
- Add broadcast messages and alerts below sub-nav - Add broadcast messages and alerts below sub-nav
- Better empty state for Groups view
- Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe)
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile - Add organization field to user profile
...@@ -37,8 +42,10 @@ v 8.13.0 (unreleased) ...@@ -37,8 +42,10 @@ v 8.13.0 (unreleased)
- Notify the Merger about merge after successful build (Dimitris Karakasilis) - Notify the Merger about merge after successful build (Dimitris Karakasilis)
- Fix broken repository 500 errors in project list - Fix broken repository 500 errors in project list
- Close todos when accepting merge requests via the API !6486 (tonygambone) - Close todos when accepting merge requests via the API !6486 (tonygambone)
- Changed Slack service user referencing from full name to username (Sebastian Poxhofer)
v 8.12.4 (unreleased) v 8.12.4 (unreleased)
- Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell)
v 8.12.3 v 8.12.3
- Update Gitlab Shell to support low IO priority for storage moves - Update Gitlab Shell to support low IO priority for storage moves
...@@ -113,6 +120,7 @@ v 8.12.0 ...@@ -113,6 +120,7 @@ v 8.12.0
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Show all pipelines for merge requests even from discarded commits !6414 - Show all pipelines for merge requests even from discarded commits !6414
- Replace contributions calendar timezone payload with dates (ClemMakesApps) - Replace contributions calendar timezone payload with dates (ClemMakesApps)
- Changed MR widget build status to pipeline status !6335
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Enable pipeline events by default !6278 - Enable pipeline events by default !6278
- Move parsing of sidekiq ps into helper !6245 (pascalbetz) - Move parsing of sidekiq ps into helper !6245 (pascalbetz)
......
...@@ -99,17 +99,17 @@ gem 'unf', '~> 0.1.4' ...@@ -99,17 +99,17 @@ gem 'unf', '~> 0.1.4'
gem 'seed-fu', '~> 2.3.5' gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie'
gem 'github-markup', '~> 1.4' gem 'github-markup', '~> 1.4'
gem 'redcarpet', '~> 3.3.3' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0' gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1' gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2' gem 'asciidoctor', '~> 1.5.2'
gem 'rouge', '~> 2.0' gem 'rouge', '~> 2.0'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
......
...@@ -157,6 +157,10 @@ GEM ...@@ -157,6 +157,10 @@ GEM
database_cleaner (1.5.3) database_cleaner (1.5.3)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
deckar01-task_list (1.0.5)
activesupport (~> 4.0)
html-pipeline
rack (~> 1.0)
default_value_for (3.0.2) default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.1) activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
...@@ -725,8 +729,6 @@ GEM ...@@ -725,8 +729,6 @@ GEM
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
systemu (2.6.5) systemu (2.6.5)
task_list (1.0.2)
html-pipeline
teaspoon (1.1.5) teaspoon (1.1.5)
railties (>= 3.2.5, < 6) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
...@@ -831,6 +833,7 @@ DEPENDENCIES ...@@ -831,6 +833,7 @@ DEPENDENCIES
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 1.0.5)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
...@@ -963,7 +966,6 @@ DEPENDENCIES ...@@ -963,7 +966,6 @@ DEPENDENCIES
sprockets-es6 (~> 0.9.2) sprockets-es6 (~> 0.9.2)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2)
teaspoon (~> 1.1.0) teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
......
...@@ -26,15 +26,15 @@ ...@@ -26,15 +26,15 @@
}; };
showTooltip = function(target, title) { showTooltip = function(target, title) {
return $(target).tooltip({ var $target = $(target);
container: 'body', var originalTitle = $target.data('original-title');
html: 'true',
placement: 'auto bottom', $target
title: title, .attr('title', 'Copied!')
trigger: 'manual' .tooltip('fixTitle')
}).tooltip('show').one('mouseleave', function() { .tooltip('show')
return $(this).tooltip('hide'); .attr('title', originalTitle)
}); .tooltip('fixTitle');
}; };
$(function() { $(function() {
......
...@@ -34,8 +34,8 @@ ...@@ -34,8 +34,8 @@
$(pageSelector).hasClass(expandedPageClass) $(pageSelector).hasClass(expandedPageClass)
); );
$(document) $(document)
.on('click', sidebarToggleSelector, (e) => this.toggleSidebar(e)) .on('click', sidebarToggleSelector, () => this.toggleSidebar())
.on('click', pinnedToggleSelector, (e) => this.togglePinnedState(e)) .on('click', pinnedToggleSelector, () => this.togglePinnedState())
.on('click', 'html, body', (e) => this.handleClickEvent(e)) .on('click', 'html, body', (e) => this.handleClickEvent(e))
.on('page:change', () => this.renderState()); .on('page:change', () => this.renderState());
this.renderState(); this.renderState();
...@@ -47,19 +47,17 @@ ...@@ -47,19 +47,17 @@
const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0; const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0; const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) { if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
this.toggleSidebar(e); this.toggleSidebar();
} }
} }
} }
toggleSidebar(e) { toggleSidebar() {
e.preventDefault();
this.isExpanded = !this.isExpanded; this.isExpanded = !this.isExpanded;
this.renderState(); this.renderState();
} }
togglePinnedState(e) { togglePinnedState() {
e.preventDefault();
this.isPinned = !this.isPinned; this.isPinned = !this.isPinned;
if (!this.isPinned) { if (!this.isPinned) {
this.isExpanded = false; this.isExpanded = false;
......
...@@ -142,6 +142,7 @@ ...@@ -142,6 +142,7 @@
transition-duration: .3s; transition-duration: .3s;
position: absolute; position: absolute;
top: 0; top: 0;
cursor: pointer;
&:hover, &:hover,
&:focus { &:focus {
......
...@@ -57,7 +57,6 @@ ...@@ -57,7 +57,6 @@
} }
.groups-header { .groups-header {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.nav-links { .nav-links {
width: 35%; width: 35%;
...@@ -68,3 +67,38 @@ ...@@ -68,3 +67,38 @@
} }
} }
} }
.groups-empty-state {
padding: 50px 100px;
overflow: hidden;
@media (max-width: $screen-md-min) {
padding: 50px 0;
}
svg {
float: right;
@media (max-width: $screen-md-min) {
float: none;
display: block;
width: 250px;
position: relative;
left: 50%;
margin-left: -125px;
}
}
.text-content {
float: left;
width: 460px;
margin-top: 120px;
@media (max-width: $screen-md-min) {
float: none;
margin-top: 60px;
width: auto;
text-align: center;
}
}
}
...@@ -70,7 +70,8 @@ ...@@ -70,7 +70,8 @@
&.ci-success { &.ci-success {
color: $gl-success; color: $gl-success;
a.environment { a.environment,
a.pipeline {
color: inherit; color: inherit;
} }
} }
......
...@@ -7,7 +7,7 @@ module SpammableActions ...@@ -7,7 +7,7 @@ module SpammableActions
def mark_as_spam def mark_as_spam
if SpamService.new(spammable).mark_as_spam! if SpamService.new(spammable).mark_as_spam!
redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully." redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully."
else else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end end
......
class Import::GitlabProjectsController < Import::BaseController class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
before_action :authenticate_admin!
def new def new
@namespace_id = project_params[:namespace_id] @namespace_id = project_params[:namespace_id]
...@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file :path, :namespace_id, :file
) )
end end
def authenticate_admin!
render_404 unless current_user.is_admin?
end
end end
...@@ -276,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -276,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def remove_wip def remove_wip
MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request) MergeRequests::UpdateService.new(project, current_user, wip_event: 'unwip').execute(@merge_request)
redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request),
notice: "The merge request can now be merged." notice: "The merge request can now be merged."
......
...@@ -139,7 +139,7 @@ module ProjectsHelper ...@@ -139,7 +139,7 @@ module ProjectsHelper
end end
options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe
end end
private private
......
...@@ -3,4 +3,12 @@ class DeviseMailer < Devise::Mailer ...@@ -3,4 +3,12 @@ class DeviseMailer < Devise::Mailer
default reply_to: Gitlab.config.gitlab.email_reply_to default reply_to: Gitlab.config.gitlab.email_reply_to
layout 'devise_mailer' layout 'devise_mailer'
protected
def subject_for(key)
subject = super
subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
subject
end
end end
...@@ -45,7 +45,7 @@ module Emails ...@@ -45,7 +45,7 @@ module Emails
@token = token @token = token
mail(to: member.invite_email, mail(to: member.invite_email,
subject: "Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}") subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end end
def member_invite_accepted_email(member_source_type, member_id) def member_invite_accepted_email(member_source_type, member_id)
......
...@@ -92,6 +92,7 @@ class Notify < BaseMailer ...@@ -92,6 +92,7 @@ class Notify < BaseMailer
subject = "" subject = ""
subject << "#{@project.name} | " if @project subject << "#{@project.name} | " if @project
subject << extra.join(' | ') if extra.present? subject << extra.join(' | ') if extra.present?
subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
subject subject
end end
......
...@@ -80,7 +80,7 @@ class CommitRange ...@@ -80,7 +80,7 @@ class CommitRange
end end
def inspect def inspect
%(#<#{self.class}:#{object_id} #{to_s}>) %(#<#{self.class}:#{object_id} #{self}>)
end end
def to_s def to_s
......
...@@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base ...@@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base
where("merge_requests.id IN (#{union.to_sql})") where("merge_requests.id IN (#{union.to_sql})")
end end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def self.work_in_progress?(title)
!!(title =~ WIP_REGEX)
end
def self.wipless_title(title)
title.sub(WIP_REGEX, "")
end
def self.wip_title(title)
work_in_progress?(title) ? title : "WIP: #{title}"
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
...@@ -389,14 +403,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -389,14 +403,16 @@ class MergeRequest < ActiveRecord::Base
@closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def work_in_progress? def work_in_progress?
!!(title =~ WIP_REGEX) self.class.work_in_progress?(title)
end end
def wipless_title def wipless_title
self.title.sub(WIP_REGEX, "") self.class.wipless_title(self.title)
end
def wip_title
self.class.wip_title(self.title)
end end
def mergeable?(skip_ci_check: false) def mergeable?(skip_ci_check: false)
......
...@@ -11,7 +11,7 @@ class SlackService ...@@ -11,7 +11,7 @@ class SlackService
attr_reader :description attr_reader :description
def initialize(params) def initialize(params)
@user_name = params[:user][:name] @user_name = params[:user][:username]
@project_name = params[:project_name] @project_name = params[:project_name]
@project_url = params[:project_url] @project_url = params[:project_url]
......
...@@ -10,7 +10,7 @@ class SlackService ...@@ -10,7 +10,7 @@ class SlackService
attr_reader :title attr_reader :title
def initialize(params) def initialize(params)
@user_name = params[:user][:name] @user_name = params[:user][:username]
@project_name = params[:project_name] @project_name = params[:project_name]
@project_url = params[:project_url] @project_url = params[:project_url]
......
...@@ -10,7 +10,7 @@ class SlackService ...@@ -10,7 +10,7 @@ class SlackService
def initialize(params) def initialize(params)
params = HashWithIndifferentAccess.new(params) params = HashWithIndifferentAccess.new(params)
@user_name = params[:user][:name] @user_name = params[:user][:username]
@project_name = params[:project_name] @project_name = params[:project_name]
@project_url = params[:project_url] @project_url = params[:project_url]
......
...@@ -9,7 +9,7 @@ class SlackService ...@@ -9,7 +9,7 @@ class SlackService
attr_reader :description attr_reader :description
def initialize(params) def initialize(params)
@user_name = params[:user][:name] @user_name = params[:user][:username]
@project_name = params[:project_name] @project_name = params[:project_name]
@project_url = params[:project_url] @project_url = params[:project_url]
......
...@@ -5,16 +5,17 @@ module MergeRequests ...@@ -5,16 +5,17 @@ module MergeRequests
end end
def create_title_change_note(issuable, old_title) def create_title_change_note(issuable, old_title)
removed_wip = old_title =~ MergeRequest::WIP_REGEX && !issuable.work_in_progress? removed_wip = MergeRequest.work_in_progress?(old_title) && !issuable.work_in_progress?
added_wip = old_title !~ MergeRequest::WIP_REGEX && issuable.work_in_progress? added_wip = !MergeRequest.work_in_progress?(old_title) && issuable.work_in_progress?
changed_title = MergeRequest.wipless_title(old_title) != issuable.wipless_title
if removed_wip if removed_wip
SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user) SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user)
elsif added_wip elsif added_wip
SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user) SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user)
else
super
end end
super if changed_title
end end
def hook_data(merge_request, action, oldrev = nil) def hook_data(merge_request, action, oldrev = nil)
......
...@@ -16,7 +16,7 @@ module MergeRequests ...@@ -16,7 +16,7 @@ module MergeRequests
end end
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
handle_wip_event(merge_request)
update(merge_request) update(merge_request)
end end
...@@ -81,5 +81,18 @@ module MergeRequests ...@@ -81,5 +81,18 @@ module MergeRequests
def after_update(issuable) def after_update(issuable)
issuable.cache_merge_request_closes_issues!(current_user) issuable.cache_merge_request_closes_issues!(current_user)
end end
private
def handle_wip_event(merge_request)
if wip_event = params.delete(:wip_event)
# We update the title that is provided in the params or we use the mr title
title = params[:title] || merge_request.title
params[:title] = case wip_event
when 'wip' then MergeRequest.wip_title(title)
when 'unwip' then MergeRequest.wipless_title(title)
end
end
end
end end
end end
...@@ -214,6 +214,18 @@ module SlashCommands ...@@ -214,6 +214,18 @@ module SlashCommands
@updates[:due_date] = nil @updates[:due_date] = nil
end end
desc do
"Toggle the Work In Progress status"
end
condition do
issuable.persisted? &&
issuable.respond_to?(:work_in_progress?) &&
current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
end
command :wip do
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
end
# This is a dummy command, so that it appears in the autocomplete commands # This is a dummy command, so that it appears in the autocomplete commands
desc 'CC' desc 'CC'
params '@user' params '@user'
......
...@@ -72,7 +72,7 @@ class SystemHooksService ...@@ -72,7 +72,7 @@ class SystemHooksService
return 'user_add_to_group' if event == :create return 'user_add_to_group' if event == :create
return 'user_remove_from_group' if event == :destroy return 'user_remove_from_group' if event == :destroy
else else
"#{model.class.name.downcase}_#{event.to_s}" "#{model.class.name.downcase}_#{event}"
end end
end end
......
...@@ -246,7 +246,7 @@ module SystemNoteService ...@@ -246,7 +246,7 @@ module SystemNoteService
'deleted' 'deleted'
end end
body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize body = "#{verb} #{branch_type} branch `#{branch}`".capitalize
create_note(noteable: noteable, project: project, author: author, note: body) create_note(noteable: noteable, project: project, author: author, note: body)
end end
......
.groups-empty-state
= custom_icon("icon_empty_groups")
.text-content
%h4 A group is a collection of several projects.
%p If you organize your projects under a group, it works like a folder.
%p You can manage your group member’s permissions and access to each project in the group.
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
- header_title "Groups", dashboard_groups_path - header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head' = render 'dashboard/groups_head'
%ul.content-list - if @group_members.empty?
- @group_members.each do |group_member| = render 'empty_state'
- group = group_member.group - else
= render 'shared/groups/group', group: group, group_member: group_member %ul.content-list
- @group_members.each do |group_member|
- group = group_member.group
= render 'shared/groups/group', group: group, group_member: group_member
= paginate @group_members, theme: 'gitlab' = paginate @group_members, theme: 'gitlab'
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll .sidebar-wrapper.nicescroll
.sidebar-action-buttons .sidebar-action-buttons
= link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" }
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
= icon('bars') = icon('bars')
= link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
%div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } }
%span.sr-only Toggle navigation pinning %span.sr-only Toggle navigation pinning
= icon('fw thumb-tack') = icon('fw thumb-tack')
......
...@@ -4,14 +4,15 @@ ...@@ -4,14 +4,15 @@
.ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) } .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) }
= ci_icon_for_status(status) = ci_icon_for_status(status)
%span %span
CI build Pipeline
= link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
= ci_label_for_status(status) = ci_label_for_status(status)
for for
- commit = @merge_request.diff_head_commit - commit = @merge_request.diff_head_commit
= succeed "." do = succeed "." do
= link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
%span.ci-coverage %span.ci-coverage
= link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'} = link_to "View details", pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'pipelines'}
- elsif @merge_request.has_ci? - elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
= link_to "#", class: 'btn js-toggle-button import_git' do = link_to "#", class: 'btn js-toggle-button import_git' do
= icon('git', text: 'Repo by URL') = icon('git', text: 'Repo by URL')
%div{ class: 'import_gitlab_project' } %div{ class: 'import_gitlab_project' }
- if gitlab_project_import_enabled? && current_user.is_admin? - if gitlab_project_import_enabled?
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export') = icon('gitlab', text: 'GitLab export')
......
<svg width="249" height="368" viewBox="891 156 249 368" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><rect id="a" width="131" height="162" rx="10"/><mask id="e" x="0" y="0" width="131" height="162" fill="#fff"><use xlink:href="#a"/></mask><path d="M223.616 127.958V108.96c0-4.416-3.584-8-8.005-8h-23.985c-2.778 0-5.98 2.014-7.18 4.5l-5.07 10.5h-49.763c-5.527 0-9.996 4.475-9.996 9.997v53.005c0 5.513 4.475 9.997 9.996 9.997h84.01c5.525 0 9.994-4.477 9.994-9.998v-51.004z" id="b"/><mask id="f" x="0" y="0" width="104" height="88" fill="#fff"><use xlink:href="#b"/></mask><path d="M47 25h.996C53.52 25 58 29.472 58 34.99v20.02C58 60.526 53.52 65 47.996 65H10.004C4.48 65 0 60.528 0 55.01V34.99C0 29.474 4.48 25 10.004 25H11v-7c0-9.94 8.06-18 18-18s18 8.06 18 18v7zm-6 0H17v-7c0-6.627 5.373-12 12-12s12 5.373 12 12v7z" id="c"/><mask id="g" x="0" y="0" width="58" height="65" fill="#fff"><use xlink:href="#c"/></mask><path d="M0 10.008C0 4.48 4.476 0 10 0h218c5.523 0 10 4.473 10 10.008v140.94c0 5.53-4.062 11.882-9.08 14.196l-100.84 46.5c-5.015 2.31-13.142 2.312-18.16 0l-100.84-46.5C4.064 162.832 0 156.484 0 150.95V10.007z" id="d"/><mask id="h" x="0" y="0" width="238" height="213.417" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(891 156)"><g transform="rotate(8 -266.528 490.3)"><use stroke="#E5E5E5" mask="url(#e)" stroke-width="8" fill="#FFF" xlink:href="#a"/><rect fill="#FC8A51" x="20" y="31" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="60" y="31" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="36" y="31" width="20" height="4" rx="2"/><rect fill="#6B4FBB" x="20" y="65" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="44" y="65" width="20" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="80" width="20" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="80" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="20" y="48" width="12" height="4" rx="2"/><rect fill="#FC8A51" x="36" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="60" y="80" width="12" height="4" rx="2"/><rect fill="#6B4FBB" x="52" y="48" width="12" height="4" rx="2"/><rect fill="#FDE5D8" x="68" y="48" width="12" height="4" rx="2"/></g><use stroke="#B5A7DD" mask="url(#f)" stroke-width="8" fill="#FFF" transform="rotate(5 171.616 144.96)" xlink:href="#b"/><path d="M58 132c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#C1E7D0"/><path d="M90.143 132c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M74.686 133.875l-3.18-3.18c-.29-.29-.77-.296-1.06-.005l-1.55 1.55c-.287.287-.29.766.004 1.06l4.92 4.92c.504.504 1.32.504 1.823 0l.654-.653 7.804-7.804c.3-.3.29-.77-.005-1.067l-1.578-1.58c-.302-.3-.775-.298-1.068-.004l-6.764 6.763z" fill="#31AF64"/><path d="M4 66c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18S4 75.94 4 66z" fill="#D5ECF7"/><path d="M36.143 66c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M22 55.714c5.68 0 10.286 4.605 10.286 10.286 0 5.68-4.605 10.286-10.286 10.286-3.45 0-6.505-1.7-8.37-4.307L22 66V55.714z" fill="#2D9FD8"/><g transform="rotate(-8 748.533 18.147)"><use stroke="#FDE5D8" mask="url(#g)" stroke-width="8" fill="#FFF" xlink:href="#c"/><path d="M31 46.584c1.766-.772 3-2.534 3-4.584 0-2.76-2.24-5-5-5s-5 2.24-5 5c0 2.05 1.234 3.812 3 4.584v3.42c0 1.1.895 1.996 2 1.996 1.112 0 2-.894 2-1.997v-3.42z" fill="#FC8A51"/></g><g transform="translate(0 154)"><use stroke="#E5E5E5" mask="url(#h)" stroke-width="8" fill="#FFF" xlink:href="#d"/><g opacity=".3"><path d="M141.837 104.53l-2.56-7.993-5.074-15.843c-.26-.815-1.398-.815-1.66 0l-5.074 15.843h-16.85l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33 22.16-16.33c.61-.452.866-1.25.632-1.98" fill="#A1A1A1"/><path fill="#5C5C5C" d="M119.044 122.84l8.425-26.303h-16.85l8.424 26.304"/><path fill="#787878" d="M119.044 122.84l-8.425-26.303H98.81l20.232 26.304"/><path fill="#787878" d="M119.044 122.84l8.425-26.303h11.807l-20.233 26.304"/><path d="M98.812 96.537l-2.56 7.993c-.234.73.022 1.528.633 1.98l22.16 16.33L98.81 96.538z" fill="#A1A1A1"/><path d="M98.812 96.537h11.807l-5.075-15.843c-.26-.815-1.398-.815-1.66 0l-5.073 15.843z" fill="#5C5C5C"/><path d="M139.277 96.537l2.56 7.993c.234.73-.022 1.528-.634 1.98l-22.16 16.33 20.234-26.303z" fill="#A1A1A1"/><path d="M139.277 96.537H127.47l5.074-15.843c.26-.815 1.398-.815 1.66 0l5.073 15.843z" fill="#5C5C5C"/></g><path d="M57 18.29c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H41c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83H77c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm36 0c1.105 0 2-.818 2-1.828 0-1.01-.895-1.83-2-1.83h-16c-1.105 0-2 .82-2 1.83 0 1.01.895 1.83 2 1.83h16zm17 24.693c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V28.35c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.633zm202 32.923c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V61.274c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm202 32.923c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm-202 0c0 1.01.895 1.828 2 1.828s2-.82 2-1.83V94.2c0-1.012-.895-1.83-2-1.83s-2 .818-2 1.83v14.63zm202 32.922c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm-202 0c0 1.01.895 1.83 2 1.83s2-.82 2-1.83V127.12c0-1.01-.895-1.83-2-1.83s-2 .82-2 1.83v14.632zm179.023 19.555c-.988.452-1.388 1.55-.894 2.454.493.904 1.694 1.27 2.682.82l14.31-6.545c.99-.452 1.39-1.55.896-2.454-.494-.902-1.696-1.27-2.684-.817l-14.31 6.544zm-32.2 14.723c-.987.452-1.388 1.55-.894 2.454.493.904 1.695 1.27 2.683.818l14.31-6.544c.99-.45 1.39-1.55.895-2.454-.494-.903-1.695-1.27-2.683-.818l-14.31 6.544zm-32.2 14.724c-.987.45-1.387 1.55-.893 2.454.494.903 1.695 1.27 2.683.818l14.31-6.544c.99-.452 1.39-1.55.896-2.454-.495-.904-1.697-1.27-2.685-.818l-14.31 6.544zm-23.67-2.023l-12.186-5.57c-.987-.452-2.19-.086-2.683.817-.494.904-.093 2.003.895 2.454l12.185 5.573c.754.345 1.57.645 2.438.898 1.052.307 2.177-.224 2.513-1.187.335-.962-.246-1.99-1.298-2.298-.677-.197-1.302-.426-1.864-.684zM62.57 168.437c-.988-.452-2.19-.086-2.683.818-.494.903-.094 2.002.894 2.454l14.31 6.544c.988.45 2.19.085 2.683-.818.494-.904.094-2.003-.894-2.454l-14.312-6.544zm-32.2-14.723c-.988-.452-2.19-.086-2.683.818-.494.904-.093 2.003.895 2.454l14.31 6.544c.988.452 2.19.086 2.684-.818.494-.903.093-2.002-.895-2.454l-14.312-6.543z" fill="#EEE"/></g><g><path d="M104 18c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#FADFD9"/><path d="M136.143 18c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M119.43 8.994c0-.707.57-1.28 1.283-1.28h2.574c.71 0 1.284.57 1.284 1.28v10.298c0 .706-.57 1.28-1.283 1.28h-2.574c-.71 0-1.284-.57-1.284-1.28V8.994zm0 15.433c0-.71.57-1.284 1.283-1.284h2.574c.71 0 1.284.57 1.284 1.284V27c0 .71-.57 1.286-1.283 1.286h-2.574c-.71 0-1.284-.57-1.284-1.285v-2.573z" fill="#E75E40"/></g><g><path d="M213 89c0-9.94 8.06-18 18-18s18 8.06 18 18-8.06 18-18 18-18-8.06-18-18z" fill="#F6D4DC"/><path d="M245.143 89c0-7.81-6.332-14.143-14.143-14.143-7.81 0-14.143 6.332-14.143 14.143 0 7.81 6.332 14.143 14.143 14.143 7.81 0 14.143-6.332 14.143-14.143z" fill="#FFF"/><path d="M231 86.348l-3.603-3.602c-.288-.29-.766-.286-1.063.01l-1.578 1.578c-.3.302-.3.773-.01 1.063L228.348 89l-3.602 3.603c-.29.288-.286.766.01 1.063l1.578 1.578c.302.3.773.3 1.063.01L231 91.652l3.603 3.602c.288.29.766.286 1.063-.01l1.578-1.578c.3-.302.3-.773.01-1.063L233.652 89l3.602-3.603c.29-.288.286-.766-.01-1.063l-1.578-1.578c-.302-.3-.773-.3-1.063-.01L231 86.348z" fill="#D22852"/></g></g></svg>
\ No newline at end of file
...@@ -70,6 +70,7 @@ production: &base ...@@ -70,6 +70,7 @@ production: &base
email_from: example@example.com email_from: example@example.com
email_display_name: GitLab email_display_name: GitLab
email_reply_to: noreply@example.com email_reply_to: noreply@example.com
email_subject_suffix: ''
# Email server smtp settings are in config/initializers/smtp_settings.rb.sample # Email server smtp settings are in config/initializers/smtp_settings.rb.sample
......
...@@ -186,6 +186,7 @@ Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].ni ...@@ -186,6 +186,7 @@ Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].ni
Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab' Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}" Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || ""
Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url) Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url)
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git' Settings.gitlab['user'] ||= 'git'
......
...@@ -13,15 +13,17 @@ override certain values. ...@@ -13,15 +13,17 @@ override certain values.
Variable | Type | Description Variable | Type | Description
-------- | ---- | ----------- -------- | ---- | -----------
`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`) `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test` `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development` `DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab `GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab `GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab `GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_EMAIL_SUBJECT_SUFFIX` | string | The e-mail subject suffix used in e-mails sent by GitLab
`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## Complete database variables ## Complete database variables
......
...@@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-13-stable-ee ...@@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-13-stable-ee
```bash ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v3.6.2 sudo -u git -H git checkout v3.6.3
``` ```
### 6. Update gitlab-workhorse ### 6. Update gitlab-workhorse
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
> that of the exporter. > that of the exporter.
> - For existing installations, the project import option has to be enabled in > - For existing installations, the project import option has to be enabled in
> application settings (`/admin/application_settings`) under 'Import sources'. > application settings (`/admin/application_settings`) under 'Import sources'.
> You will have to be an administrator to enable and use the import functionality. > Ask your administrator if you don't see the **GitLab export** button when
> creating a new project.
> - You can find some useful raketasks if you are an administrator in the > - You can find some useful raketasks if you are an administrator in the
> [import_export](../../../administration/raketasks/project_import_export.md) > [import_export](../../../administration/raketasks/project_import_export.md)
> raketask. > raketask.
......
...@@ -27,4 +27,5 @@ do. ...@@ -27,4 +27,5 @@ do.
| `/subscribe` | Subscribe | | `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe | | `/unsubscribe` | Unsubscribe |
| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date | | <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code> | Set due date |
| `/remove_due_date` | Remove due date | | `/remove_due_date` | Remove due date |
| `/wip` | Toggle the Work In Progress status |
@profile
Feature: Profile SSH Keys
Background:
Given I sign in as a user
And I have ssh key "ssh-rsa Work"
And I visit profile keys page
Scenario: I should see ssh keys
Then I should see my ssh keys
Scenario: Add new ssh key
Given I should see new ssh key form
And I submit new ssh key "Laptop"
Then I should see new ssh key "Laptop"
Scenario: Remove ssh key
Given I click link "Work"
And I click link "Remove"
Then I visit profile keys page
And I should not see "Work" ssh key
...@@ -20,6 +20,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps ...@@ -20,6 +20,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
expect(page).to have_link('GitLab.com') expect(page).to have_link('GitLab.com')
expect(page).to have_link('Google Code') expect(page).to have_link('Google Code')
expect(page).to have_link('Repo by URL') expect(page).to have_link('Repo by URL')
expect(page).to have_link('GitLab export')
end end
step 'I click on "Import project from GitHub"' do step 'I click on "Import project from GitHub"' do
......
class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
include SharedAuthentication
step 'I should see my ssh keys' do
@user.keys.each do |key|
expect(page).to have_content(key.title)
end
end
step 'I should see new ssh key form' do
expect(page).to have_content("Add an SSH key")
end
step 'I submit new ssh key "Laptop"' do
fill_in "key_title", with: "Laptop"
fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop"
click_button "Add key"
end
step 'I should see new ssh key "Laptop"' do
key = Key.find_by(title: "Laptop")
expect(page).to have_content(key.title)
expect(page).to have_content(key.key)
expect(current_path).to eq profile_key_path(key)
end
step 'I click link "Work"' do
click_link "Work"
end
step 'I click link "Remove"' do
click_link "Remove"
end
step 'I visit profile keys page' do
visit profile_keys_path
end
step 'I should not see "Work" ssh key' do
expect(page).not_to have_content "Work"
end
step 'I have ssh key "ssh-rsa Work"' do
create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work")
end
end
...@@ -8,16 +8,19 @@ module API ...@@ -8,16 +8,19 @@ module API
awardable_string = awardable_type.pluralize awardable_string = awardable_type.pluralize
awardable_id_string = "#{awardable_type}_id" awardable_id_string = "#{awardable_type}_id"
params do
requires :id, type: String, desc: 'The ID of a project'
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
[ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji", [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji" ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
].each do |endpoint| ].each do |endpoint|
# Get a list of project +awardable+ award emoji
# desc 'Get a list of project +awardable+ award emoji' do
# Parameters: detail 'This feature was introduced in 8.9'
# id (required) - The ID of a project success Entities::AwardEmoji
# awardable_id (required) - The ID of an issue or MR end
# Example Request:
# GET /projects/:id/issues/:awardable_id/award_emoji
get endpoint do get endpoint do
if can_read_awardable? if can_read_awardable?
awards = paginate(awardable.award_emoji) awards = paginate(awardable.award_emoji)
...@@ -27,14 +30,13 @@ module API ...@@ -27,14 +30,13 @@ module API
end end
end end
# Get a specific award emoji desc 'Get a specific award emoji' do
# detail 'This feature was introduced in 8.9'
# Parameters: success Entities::AwardEmoji
# id (required) - The ID of a project end
# awardable_id (required) - The ID of an issue or MR params do
# award_id (required) - The ID of the award requires :award_id, type: Integer, desc: 'The ID of the award'
# Example Request: end
# GET /projects/:id/issues/:awardable_id/award_emoji/:award_id
get "#{endpoint}/:award_id" do get "#{endpoint}/:award_id" do
if can_read_awardable? if can_read_awardable?
present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
...@@ -43,17 +45,14 @@ module API ...@@ -43,17 +45,14 @@ module API
end end
end end
# Award a new Emoji desc 'Award a new Emoji' do
# detail 'This feature was introduced in 8.9'
# Parameters: success Entities::AwardEmoji
# id (required) - The ID of a project end
# awardable_id (required) - The ID of an issue or mr params do
# name (required) - The name of a award_emoji (without colons) requires :name, type: String, desc: 'The name of a award_emoji (without colons)'
# Example Request: end
# POST /projects/:id/issues/:awardable_id/award_emoji
post endpoint do post endpoint do
required_attributes! [:name]
not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable? not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
award = awardable.create_award_emoji(params[:name], current_user) award = awardable.create_award_emoji(params[:name], current_user)
...@@ -65,14 +64,13 @@ module API ...@@ -65,14 +64,13 @@ module API
end end
end end
# Delete a +awardables+ award emoji desc 'Delete a +awardables+ award emoji' do
# detail 'This feature was introduced in 8.9'
# Parameters: success Entities::AwardEmoji
# id (required) - The ID of a project end
# awardable_id (required) - The ID of an issue or MR params do
# award_emoji_id (required) - The ID of an award emoji requires :award_id, type: Integer, desc: 'The ID of an award emoji'
# Example Request: end
# DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id
delete "#{endpoint}/:award_id" do delete "#{endpoint}/:award_id" do
award = awardable.award_emoji.find(params[:award_id]) award = awardable.award_emoji.find(params[:award_id])
......
...@@ -4,10 +4,9 @@ module API ...@@ -4,10 +4,9 @@ module API
before { authenticate! } before { authenticate! }
resource :keys do resource :keys do
# Get single ssh key by id. Only available to admin users. desc 'Get single ssh key by id. Only available to admin users' do
# success Entities::SSHKeyWithUser
# Example Request: end
# GET /keys/:id
get ":id" do get ":id" do
authenticated_as_admin! authenticated_as_admin!
......
...@@ -2,29 +2,7 @@ require 'task_list/filter' ...@@ -2,29 +2,7 @@ require 'task_list/filter'
module Banzai module Banzai
module Filter module Filter
# Work around a bug in the default TaskList::Filter that adds a `task-list`
# class to every list element, regardless of whether or not it contains a
# task list.
#
# This is a (hopefully) temporary fix, pending a new release of the
# task_list gem.
#
# See https://github.com/github/task_list/pull/60
module ClassNamesFilter
def add_css_class(node, *new_class_names)
if new_class_names.include?('task-list')
# Don't add class to all lists
return
elsif new_class_names.include?('task-list-item')
super(node.parent, 'task-list')
end
super(node, *new_class_names)
end
end
class TaskListFilter < TaskList::Filter class TaskListFilter < TaskList::Filter
prepend ClassNamesFilter
end end
end end
end end
...@@ -2,7 +2,7 @@ module Gitlab ...@@ -2,7 +2,7 @@ module Gitlab
module SidekiqMiddleware module SidekiqMiddleware
class ArgumentsLogger class ArgumentsLogger
def call(worker, job, queue) def call(worker, job, queue)
Sidekiq.logger.info "arguments: #{job['args']}" Sidekiq.logger.info "arguments: #{JSON.dump(job['args'])}"
yield yield
end end
end end
......
...@@ -16,21 +16,6 @@ retry() { ...@@ -16,21 +16,6 @@ retry() {
} }
if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
mkdir -p vendor/apt
# Install phantomjs package
pushd vendor/apt
PHANTOMJS_FILE="phantomjs-$PHANTOMJS_VERSION-linux-x86_64"
if [ ! -d "$PHANTOMJS_FILE" ]; then
curl -q -L "https://s3.amazonaws.com/gitlab-build-helpers/$PHANTOMJS_FILE.tar.bz2" | tar jx
fi
cp "$PHANTOMJS_FILE/bin/phantomjs" "/usr/bin/"
popd
# Try to install packages
retry 'apt-get update -yqqq; apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \
libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip'
cp config/database.yml.mysql config/database.yml cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml sed -i 's/username:.*/username: root/g' config/database.yml
sed -i 's/password:.*/password:/g' config/database.yml sed -i 's/password:.*/password:/g' config/database.yml
......
...@@ -644,6 +644,20 @@ describe Projects::MergeRequestsController do ...@@ -644,6 +644,20 @@ describe Projects::MergeRequestsController do
end end
end end
context 'POST remove_wip' do
it 'removes the wip status' do
merge_request.title = merge_request.wip_title
merge_request.save
post :remove_wip,
namespace_id: merge_request.project.namespace.to_param,
project_id: merge_request.project.to_param,
id: merge_request.iid
expect(merge_request.reload.title).to eq(merge_request.wipless_title)
end
end
context 'POST resolve_conflicts' do context 'POST resolve_conflicts' do
let(:json_response) { JSON.parse(response.body) } let(:json_response) { JSON.parse(response.body) }
let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
......
...@@ -99,5 +99,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do ...@@ -99,5 +99,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
end end
end end
end end
describe 'toggling the WIP prefix from the title from note' do
let(:issue) { create(:issue, project: project) }
it 'does not recognize the command nor create a note' do
write_note("/wip")
expect(page).not_to have_content '/wip'
end
end
end end
end end
...@@ -14,21 +14,66 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do ...@@ -14,21 +14,66 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
end end
describe 'adding a due date from note' do describe 'merge-request-only commands' do
before do before do
project.team << [user, :master] project.team << [user, :master]
login_with(user) login_with(user)
visit namespace_project_merge_request_path(project.namespace, project, merge_request) visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end end
after do after do
wait_for_ajax wait_for_ajax
end end
it 'does not recognize the command nor create a note' do describe 'toggling the WIP prefix in the title from note' do
write_note("/due 2016-08-28") context 'when the current user can toggle the WIP prefix' do
it 'adds the WIP: prefix to the title' do
write_note("/wip")
expect(page).not_to have_content '/wip'
expect(page).to have_content 'Your commands have been executed!'
expect(merge_request.reload.work_in_progress?).to eq true
end
it 'removes the WIP: prefix from the title' do
merge_request.title = merge_request.wip_title
merge_request.save
write_note("/wip")
expect(page).not_to have_content '/wip'
expect(page).to have_content 'Your commands have been executed!'
expect(merge_request.reload.work_in_progress?).to eq false
end
end
context 'when the current user cannot toggle the WIP prefix' do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
logout
login_with(guest)
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
it 'does not change the WIP prefix' do
write_note("/wip")
expect(page).not_to have_content '/wip'
expect(page).not_to have_content 'Your commands have been executed!'
expect(merge_request.reload.work_in_progress?).to eq false
end
end
end
describe 'adding a due date from note' do
it 'does not recognize the command nor create a note' do
write_note('/due 2016-08-28')
expect(page).not_to have_content '/due 2016-08-28' expect(page).not_to have_content '/due 2016-08-28'
end
end end
end end
end end
require 'rails_helper' require 'rails_helper'
describe 'Profile > SSH Keys', feature: true do feature 'Profile > SSH Keys', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
login_as(user) login_as(user)
visit profile_keys_path
end end
describe 'User adds an SSH key' do describe 'User adds a key' do
it 'auto-populates the title', js: true do before do
visit profile_keys_path
end
scenario 'auto-populates the title', js: true do
fill_in('Key', with: attributes_for(:key).fetch(:key)) fill_in('Key', with: attributes_for(:key).fetch(:key))
expect(find_field('Title').value).to eq 'dummy@gitlab.com' expect(find_field('Title').value).to eq 'dummy@gitlab.com'
end end
scenario 'saves the new key' do
attrs = attributes_for(:key)
fill_in('Key', with: attrs[:key])
fill_in('Title', with: attrs[:title])
click_button('Add key')
expect(page).to have_content("Title: #{attrs[:title]}")
expect(page).to have_content(attrs[:key])
end
end
scenario 'User sees their keys' do
key = create(:key, user: user)
visit profile_keys_path
expect(page).to have_content(key.title)
end
scenario 'User removes a key via the key index' do
create(:key, user: user)
visit profile_keys_path
click_link('Remove')
expect(page).to have_content('Your SSH keys (0)')
end
scenario 'User removes a key via its details page' do
key = create(:key, user: user)
visit profile_key_path(key)
click_link('Remove')
expect(page).to have_content('Your SSH keys (0)')
end end
end end
...@@ -86,14 +86,14 @@ feature 'Import/Export - project import integration test', feature: true, js: tr ...@@ -86,14 +86,14 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
login_as(normal_user) login_as(normal_user)
end end
scenario 'non-admin user is not allowed to import a project' do scenario 'non-admin user is allowed to import a project' do
expect(Project.all.count).to be_zero expect(Project.all.count).to be_zero
visit new_project_path visit new_project_path
fill_in :project_path, with: 'test-project-path', visible: true fill_in :project_path, with: 'test-project-path', visible: true
expect(page).not_to have_content('GitLab export') expect(page).to have_content('GitLab export')
end end
end end
......
...@@ -3,8 +3,8 @@ require 'spec_helper' ...@@ -3,8 +3,8 @@ require 'spec_helper'
describe AccessRequestsFinder, services: true do describe AccessRequestsFinder, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:access_requester) { create(:user) } let(:access_requester) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let(:group) { create(:group) } let(:group) { create(:group, :public) }
before do before do
project.request_access(access_requester) project.request_access(access_requester)
...@@ -16,7 +16,7 @@ describe AccessRequestsFinder, services: true do ...@@ -16,7 +16,7 @@ describe AccessRequestsFinder, services: true do
access_requesters = described_class.new(source).public_send(method_name, user) access_requesters = described_class.new(source).public_send(method_name, user)
expect(access_requesters.size).to eq(1) expect(access_requesters.size).to eq(1)
expect(access_requesters.first).to be_a "#{source.class.to_s}Member".constantize expect(access_requesters.first).to be_a "#{source.class}Member".constantize
expect(access_requesters.first.user).to eq(access_requester) expect(access_requesters.first.user).to eq(access_requester)
end end
end end
......
require 'spec_helper'
describe Banzai::Filter::TaskListFilter, lib: true do
include FilterSpecHelper
it 'does not apply `task-list` class to non-task lists' do
exp = act = %(<ul><li>Item</li></ul>)
expect(filter(act).to_html).to eq exp
end
it 'applies `task-list` to single-item task lists' do
act = filter('<ul><li>[ ] Task 1</li></ul>')
expect(act.to_html).to start_with '<ul class="task-list">'
end
end
...@@ -27,7 +27,7 @@ describe 'Import/Export attribute configuration', lib: true do ...@@ -27,7 +27,7 @@ describe 'Import/Export attribute configuration', lib: true do
relation_names.each do |relation_name| relation_names.each do |relation_name|
relation_class = relation_class_for_name(relation_name) relation_class = relation_class_for_name(relation_name)
expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class.to_s} to exist in safe_model_attributes" expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes"
current_attributes = parsed_attributes(relation_name, relation_class.attribute_names) current_attributes = parsed_attributes(relation_name, relation_class.attribute_names)
safe_attributes = safe_model_attributes[relation_class.to_s] safe_attributes = safe_model_attributes[relation_class.to_s]
......
...@@ -831,6 +831,7 @@ describe Notify do ...@@ -831,6 +831,7 @@ describe Notify do
let(:user) { create(:user, email: 'old-email@mail.com') } let(:user) { create(:user, email: 'old-email@mail.com') }
before do before do
stub_config_setting(email_subject_suffix: 'A Nice Suffix')
perform_enqueued_jobs do perform_enqueued_jobs do
user.email = "new-email@mail.com" user.email = "new-email@mail.com"
user.save user.save
...@@ -847,7 +848,7 @@ describe Notify do ...@@ -847,7 +848,7 @@ describe Notify do
end end
it 'has the correct subject' do it 'has the correct subject' do
is_expected.to have_subject "Confirmation instructions" is_expected.to have_subject /^Confirmation instructions/
end end
it 'includes a link to the site' do it 'includes a link to the site' do
......
...@@ -37,6 +37,16 @@ shared_examples 'an email sent from GitLab' do ...@@ -37,6 +37,16 @@ shared_examples 'an email sent from GitLab' do
reply_to = subject.header[:reply_to].addresses reply_to = subject.header[:reply_to].addresses
expect(reply_to).to eq([gitlab_sender_reply_to]) expect(reply_to).to eq([gitlab_sender_reply_to])
end end
context 'when custom suffix for email subject is set' do
before do
stub_config_setting(email_subject_suffix: 'A Nice Suffix')
end
it 'ends the subject with the suffix' do
is_expected.to have_subject /\ \| A Nice Suffix$/
end
end
end end
shared_examples 'an email that contains a header with author username' do shared_examples 'an email that contains a header with author username' do
......
...@@ -174,7 +174,7 @@ describe Member, models: true do ...@@ -174,7 +174,7 @@ describe Member, models: true do
describe '.add_user' do describe '.add_user' do
%w[project group].each do |source_type| %w[project group].each do |source_type|
context "when source is a #{source_type}" do context "when source is a #{source_type}" do
let!(:source) { create(source_type) } let!(:source) { create(source_type, :public) }
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:admin) { create(:admin) } let!(:admin) { create(:admin) }
......
...@@ -287,6 +287,46 @@ describe MergeRequest, models: true do ...@@ -287,6 +287,46 @@ describe MergeRequest, models: true do
end end
end end
describe "#wipless_title" do
['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix|
it "removes the '#{wip_prefix}' prefix" do
wipless_title = subject.title
subject.title = "#{wip_prefix}#{subject.title}"
expect(subject.wipless_title).to eq wipless_title
end
it "is satisfies the #work_in_progress? method" do
subject.title = "#{wip_prefix}#{subject.title}"
subject.title = subject.wipless_title
expect(subject.work_in_progress?).to eq false
end
end
end
describe "#wip_title" do
it "adds the WIP: prefix to the title" do
wip_title = "WIP: #{subject.title}"
expect(subject.wip_title).to eq wip_title
end
it "does not add the WIP: prefix multiple times" do
wip_title = "WIP: #{subject.title}"
subject.title = subject.wip_title
subject.title = subject.wip_title
expect(subject.wip_title).to eq wip_title
end
it "is satisfies the #work_in_progress? method" do
subject.title = subject.wip_title
expect(subject.work_in_progress?).to eq true
end
end
describe '#can_remove_source_branch?' do describe '#can_remove_source_branch?' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
......
...@@ -7,7 +7,7 @@ describe SlackService::IssueMessage, models: true do ...@@ -7,7 +7,7 @@ describe SlackService::IssueMessage, models: true do
{ {
user: { user: {
name: 'Test User', name: 'Test User',
username: 'Test User' username: 'test.user'
}, },
project_name: 'project_name', project_name: 'project_name',
project_url: 'somewhere.com', project_url: 'somewhere.com',
...@@ -40,7 +40,7 @@ describe SlackService::IssueMessage, models: true do ...@@ -40,7 +40,7 @@ describe SlackService::IssueMessage, models: true do
context 'open' do context 'open' do
it 'returns a message regarding opening of issues' do it 'returns a message regarding opening of issues' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'<somewhere.com|[project_name>] Issue opened by Test User') '<somewhere.com|[project_name>] Issue opened by test.user')
expect(subject.attachments).to eq([ expect(subject.attachments).to eq([
{ {
title: "#100 Issue title", title: "#100 Issue title",
...@@ -60,7 +60,7 @@ describe SlackService::IssueMessage, models: true do ...@@ -60,7 +60,7 @@ describe SlackService::IssueMessage, models: true do
it 'returns a message regarding closing of issues' do it 'returns a message regarding closing of issues' do
expect(subject.pretext). to eq( expect(subject.pretext). to eq(
'<somewhere.com|[project_name>] Issue <url|#100 Issue title> closed by Test User') '<somewhere.com|[project_name>] Issue <url|#100 Issue title> closed by test.user')
expect(subject.attachments).to be_empty expect(subject.attachments).to be_empty
end end
end end
......
...@@ -7,7 +7,7 @@ describe SlackService::MergeMessage, models: true do ...@@ -7,7 +7,7 @@ describe SlackService::MergeMessage, models: true do
{ {
user: { user: {
name: 'Test User', name: 'Test User',
username: 'Test User' username: 'test.user'
}, },
project_name: 'project_name', project_name: 'project_name',
project_url: 'somewhere.com', project_url: 'somewhere.com',
...@@ -31,7 +31,7 @@ describe SlackService::MergeMessage, models: true do ...@@ -31,7 +31,7 @@ describe SlackService::MergeMessage, models: true do
context 'open' do context 'open' do
it 'returns a message regarding opening of merge requests' do it 'returns a message regarding opening of merge requests' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'Test User opened <somewhere.com/merge_requests/100|merge request !100> '\ 'test.user opened <somewhere.com/merge_requests/100|merge request !100> '\
'in <somewhere.com|project_name>: *Issue title*') 'in <somewhere.com|project_name>: *Issue title*')
expect(subject.attachments).to be_empty expect(subject.attachments).to be_empty
end end
...@@ -43,7 +43,7 @@ describe SlackService::MergeMessage, models: true do ...@@ -43,7 +43,7 @@ describe SlackService::MergeMessage, models: true do
end end
it 'returns a message regarding closing of merge requests' do it 'returns a message regarding closing of merge requests' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'Test User closed <somewhere.com/merge_requests/100|merge request !100> '\ 'test.user closed <somewhere.com/merge_requests/100|merge request !100> '\
'in <somewhere.com|project_name>: *Issue title*') 'in <somewhere.com|project_name>: *Issue title*')
expect(subject.attachments).to be_empty expect(subject.attachments).to be_empty
end end
......
...@@ -7,7 +7,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -7,7 +7,7 @@ describe SlackService::NoteMessage, models: true do
@args = { @args = {
user: { user: {
name: 'Test User', name: 'Test User',
username: 'username', username: 'test.user',
avatar_url: 'http://fakeavatar' avatar_url: 'http://fakeavatar'
}, },
project_name: 'project_name', project_name: 'project_name',
...@@ -37,7 +37,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -37,7 +37,7 @@ describe SlackService::NoteMessage, models: true do
it 'returns a message regarding notes on commits' do it 'returns a message regarding notes on commits' do
message = SlackService::NoteMessage.new(@args) message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq("Test User commented on " \ expect(message.pretext).to eq("test.user commented on " \
"<url|commit 5f163b2b> in <somewhere.com|project_name>: " \ "<url|commit 5f163b2b> in <somewhere.com|project_name>: " \
"*Added a commit message*") "*Added a commit message*")
expected_attachments = [ expected_attachments = [
...@@ -63,7 +63,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -63,7 +63,7 @@ describe SlackService::NoteMessage, models: true do
it 'returns a message regarding notes on a merge request' do it 'returns a message regarding notes on a merge request' do
message = SlackService::NoteMessage.new(@args) message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq("Test User commented on " \ expect(message.pretext).to eq("test.user commented on " \
"<url|merge request !30> in <somewhere.com|project_name>: " \ "<url|merge request !30> in <somewhere.com|project_name>: " \
"*merge request title*") "*merge request title*")
expected_attachments = [ expected_attachments = [
...@@ -90,7 +90,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -90,7 +90,7 @@ describe SlackService::NoteMessage, models: true do
it 'returns a message regarding notes on an issue' do it 'returns a message regarding notes on an issue' do
message = SlackService::NoteMessage.new(@args) message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq( expect(message.pretext).to eq(
"Test User commented on " \ "test.user commented on " \
"<url|issue #20> in <somewhere.com|project_name>: " \ "<url|issue #20> in <somewhere.com|project_name>: " \
"*issue title*") "*issue title*")
expected_attachments = [ expected_attachments = [
...@@ -115,7 +115,7 @@ describe SlackService::NoteMessage, models: true do ...@@ -115,7 +115,7 @@ describe SlackService::NoteMessage, models: true do
it 'returns a message regarding notes on a project snippet' do it 'returns a message regarding notes on a project snippet' do
message = SlackService::NoteMessage.new(@args) message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq("Test User commented on " \ expect(message.pretext).to eq("test.user commented on " \
"<url|snippet #5> in <somewhere.com|project_name>: " \ "<url|snippet #5> in <somewhere.com|project_name>: " \
"*snippet title*") "*snippet title*")
expected_attachments = [ expected_attachments = [
......
...@@ -9,7 +9,7 @@ describe SlackService::PushMessage, models: true do ...@@ -9,7 +9,7 @@ describe SlackService::PushMessage, models: true do
before: 'before', before: 'before',
project_name: 'project_name', project_name: 'project_name',
ref: 'refs/heads/master', ref: 'refs/heads/master',
user_name: 'user_name', user_name: 'test.user',
project_url: 'url' project_url: 'url'
} }
end end
...@@ -26,7 +26,7 @@ describe SlackService::PushMessage, models: true do ...@@ -26,7 +26,7 @@ describe SlackService::PushMessage, models: true do
it 'returns a message regarding pushes' do it 'returns a message regarding pushes' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'user_name pushed to branch <url/commits/master|master> of '\ 'test.user pushed to branch <url/commits/master|master> of '\
'<url|project_name> (<url/compare/before...after|Compare changes>)' '<url|project_name> (<url/compare/before...after|Compare changes>)'
) )
expect(subject.attachments).to eq([ expect(subject.attachments).to eq([
...@@ -46,13 +46,13 @@ describe SlackService::PushMessage, models: true do ...@@ -46,13 +46,13 @@ describe SlackService::PushMessage, models: true do
before: Gitlab::Git::BLANK_SHA, before: Gitlab::Git::BLANK_SHA,
project_name: 'project_name', project_name: 'project_name',
ref: 'refs/tags/new_tag', ref: 'refs/tags/new_tag',
user_name: 'user_name', user_name: 'test.user',
project_url: 'url' project_url: 'url'
} }
end end
it 'returns a message regarding pushes' do it 'returns a message regarding pushes' do
expect(subject.pretext).to eq('user_name pushed new tag ' \ expect(subject.pretext).to eq('test.user pushed new tag ' \
'<url/commits/new_tag|new_tag> to ' \ '<url/commits/new_tag|new_tag> to ' \
'<url|project_name>') '<url|project_name>')
expect(subject.attachments).to be_empty expect(subject.attachments).to be_empty
...@@ -66,7 +66,7 @@ describe SlackService::PushMessage, models: true do ...@@ -66,7 +66,7 @@ describe SlackService::PushMessage, models: true do
it 'returns a message regarding a new branch' do it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'user_name pushed new branch <url/commits/master|master> to '\ 'test.user pushed new branch <url/commits/master|master> to '\
'<url|project_name>' '<url|project_name>'
) )
expect(subject.attachments).to be_empty expect(subject.attachments).to be_empty
...@@ -80,7 +80,7 @@ describe SlackService::PushMessage, models: true do ...@@ -80,7 +80,7 @@ describe SlackService::PushMessage, models: true do
it 'returns a message regarding a removed branch' do it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'user_name removed branch master from <url|project_name>' 'test.user removed branch master from <url|project_name>'
) )
expect(subject.attachments).to be_empty expect(subject.attachments).to be_empty
end end
......
...@@ -7,7 +7,7 @@ describe SlackService::WikiPageMessage, models: true do ...@@ -7,7 +7,7 @@ describe SlackService::WikiPageMessage, models: true do
{ {
user: { user: {
name: 'Test User', name: 'Test User',
username: 'Test User' username: 'test.user'
}, },
project_name: 'project_name', project_name: 'project_name',
project_url: 'somewhere.com', project_url: 'somewhere.com',
...@@ -25,7 +25,7 @@ describe SlackService::WikiPageMessage, models: true do ...@@ -25,7 +25,7 @@ describe SlackService::WikiPageMessage, models: true do
it 'returns a message that a new wiki page was created' do it 'returns a message that a new wiki page was created' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'Test User created <url|wiki page> in <somewhere.com|project_name>: '\ 'test.user created <url|wiki page> in <somewhere.com|project_name>: '\
'*Wiki page title*') '*Wiki page title*')
end end
end end
...@@ -35,7 +35,7 @@ describe SlackService::WikiPageMessage, models: true do ...@@ -35,7 +35,7 @@ describe SlackService::WikiPageMessage, models: true do
it 'returns a message that a wiki page was updated' do it 'returns a message that a wiki page was updated' do
expect(subject.pretext).to eq( expect(subject.pretext).to eq(
'Test User edited <url|wiki page> in <somewhere.com|project_name>: '\ 'test.user edited <url|wiki page> in <somewhere.com|project_name>: '\
'*Wiki page title*') '*Wiki page title*')
end end
end end
......
...@@ -26,7 +26,7 @@ describe Members::ApproveAccessRequestService, services: true do ...@@ -26,7 +26,7 @@ describe Members::ApproveAccessRequestService, services: true do
it 'returns a <Source>Member' do it 'returns a <Source>Member' do
member = described_class.new(source, user, params).execute member = described_class.new(source, user, params).execute
expect(member).to be_a "#{source.class.to_s}Member".constantize expect(member).to be_a "#{source.class}Member".constantize
expect(member.requested_at).to be_nil expect(member.requested_at).to be_nil
end end
......
...@@ -19,7 +19,7 @@ describe Members::RequestAccessService, services: true do ...@@ -19,7 +19,7 @@ describe Members::RequestAccessService, services: true do
it 'returns a <Source>Member' do it 'returns a <Source>Member' do
member = described_class.new(source, user).execute member = described_class.new(source, user).execute
expect(member).to be_a "#{source.class.to_s}Member".constantize expect(member).to be_a "#{source.class}Member".constantize
expect(member.requested_at).to be_present expect(member.requested_at).to be_present
end end
end end
......
...@@ -165,6 +165,23 @@ describe SlashCommands::InterpretService, services: true do ...@@ -165,6 +165,23 @@ describe SlashCommands::InterpretService, services: true do
end end
end end
shared_examples 'wip command' do
it 'returns wip_event: "wip" if content contains /wip' do
_, updates = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'wip')
end
end
shared_examples 'unwip command' do
it 'returns wip_event: "unwip" if content contains /wip' do
issuable.update(title: issuable.wip_title)
_, updates = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'unwip')
end
end
shared_examples 'empty command' do shared_examples 'empty command' do
it 'populates {} if content contains an unsupported command' do it 'populates {} if content contains an unsupported command' do
_, updates = service.execute(content, issuable) _, updates = service.execute(content, issuable)
...@@ -376,6 +393,16 @@ describe SlashCommands::InterpretService, services: true do ...@@ -376,6 +393,16 @@ describe SlashCommands::InterpretService, services: true do
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'wip command' do
let(:content) { '/wip' }
let(:issuable) { merge_request }
end
it_behaves_like 'unwip command' do
let(:content) { '/wip' }
let(:issuable) { merge_request }
end
it_behaves_like 'empty command' do it_behaves_like 'empty command' do
let(:content) { '/remove_due_date' } let(:content) { '/remove_due_date' }
let(:issuable) { merge_request } let(:issuable) { merge_request }
......
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