summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--CHANGELOG3
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--app/assets/javascripts/sidebar.js.es612
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss1
-rw-r--r--app/assets/stylesheets/pages/groups.scss36
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/models/merge_request.rb24
-rw-r--r--app/services/merge_requests/base_service.rb9
-rw-r--r--app/services/merge_requests/update_service.rb15
-rw-r--r--app/services/slash_commands/interpret_service.rb12
-rw-r--r--app/views/dashboard/groups/_empty_state.html.haml7
-rw-r--r--app/views/dashboard/groups/index.html.haml13
-rw-r--r--app/views/layouts/_page.html.haml5
-rw-r--r--app/views/shared/icons/_icon_empty_groups.svg1
-rw-r--r--doc/update/8.12-to-8.13.md2
-rw-r--r--doc/user/project/slash_commands.md3
-rw-r--r--features/profile/ssh_keys.feature20
-rw-r--r--features/steps/profile/ssh_keys.rb46
-rw-r--r--lib/api/keys.rb7
-rwxr-xr-xscripts/prepare_build.sh15
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb14
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb10
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb55
-rw-r--r--spec/features/profiles/keys_spec.rb47
-rw-r--r--spec/models/merge_request_spec.rb40
-rw-r--r--spec/services/slash_commands/interpret_service_spec.rb27
27 files changed, 308 insertions, 126 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a11c4705e82..5d2fad03f19 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,9 +1,8 @@
-image: "ruby:2.3.1"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1"
cache:
key: "ruby-231"
paths:
- - vendor/apt
- vendor/ruby
variables:
@@ -141,14 +140,13 @@ spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.1
.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
only:
- master
cache:
key: "ruby21"
paths:
- - vendor/apt
- vendor/ruby
.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
diff --git a/CHANGELOG b/CHANGELOG
index 6cfaefd728e..7ffc74f4ec0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@ v 8.13.0 (unreleased)
- Fix centering of custom header logos (Ashley Dumaine)
- AbstractReferenceFilter caches project_refs on RequestStore when active
- 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
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Add more tests for calendar contribution (ClemMakesApps)
@@ -24,11 +25,13 @@ v 8.13.0 (unreleased)
- Preserve label filters when sorting !6136 (Joseph Frazier)
- Only update issuable labels if they have been changed
- 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.
- Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska)
- Fix Long commit messages overflow viewport in file tree
- Revert avoid touching file system on Build#artifacts?
- 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)
- Fix unnecessary escaping of reserved HTML characters in milestone title. !6533
- Add organization field to user profile
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index b72762837ea..4a788a01dad 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.6.2
+3.6.3
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
index ead3219bc31..755fac8107b 100644
--- a/app/assets/javascripts/sidebar.js.es6
+++ b/app/assets/javascripts/sidebar.js.es6
@@ -34,8 +34,8 @@
$(pageSelector).hasClass(expandedPageClass)
);
$(document)
- .on('click', sidebarToggleSelector, (e) => this.toggleSidebar(e))
- .on('click', pinnedToggleSelector, (e) => this.togglePinnedState(e))
+ .on('click', sidebarToggleSelector, () => this.toggleSidebar())
+ .on('click', pinnedToggleSelector, () => this.togglePinnedState())
.on('click', 'html, body', (e) => this.handleClickEvent(e))
.on('page:change', () => this.renderState());
this.renderState();
@@ -47,19 +47,17 @@
const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
- this.toggleSidebar(e);
+ this.toggleSidebar();
}
}
}
- toggleSidebar(e) {
- e.preventDefault();
+ toggleSidebar() {
this.isExpanded = !this.isExpanded;
this.renderState();
}
- togglePinnedState(e) {
- e.preventDefault();
+ togglePinnedState() {
this.isPinned = !this.isPinned;
if (!this.isPinned) {
this.isExpanded = false;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 3b7de4b57bb..557ef7291cf 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -142,6 +142,7 @@
transition-duration: .3s;
position: absolute;
top: 0;
+ cursor: pointer;
&:hover,
&:focus {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 732dc645c66..185ce970e71 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -57,7 +57,6 @@
}
.groups-header {
-
@media (min-width: $screen-sm-min) {
.nav-links {
width: 35%;
@@ -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;
+ }
+ }
+}
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 68bb4232f5b..8c8c56228ad 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -276,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
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),
notice: "The merge request can now be merged."
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index aec555dcec0..a431d46cc9e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base
where("merge_requests.id IN (#{union.to_sql})")
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)
reference = "#{self.class.reference_prefix}#{iid}"
@@ -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
end
- WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
-
def work_in_progress?
- !!(title =~ WIP_REGEX)
+ self.class.work_in_progress?(title)
end
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
def mergeable?(skip_ci_check: false)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index ba424b09463..d0d155b7ee1 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -5,16 +5,17 @@ module MergeRequests
end
def create_title_change_note(issuable, old_title)
- removed_wip = old_title =~ MergeRequest::WIP_REGEX && !issuable.work_in_progress?
- added_wip = old_title !~ MergeRequest::WIP_REGEX && issuable.work_in_progress?
+ removed_wip = MergeRequest.work_in_progress?(old_title) && !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
SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user)
elsif added_wip
SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user)
- else
- super
end
+
+ super if changed_title
end
def hook_data(merge_request, action, oldrev = nil)
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index f14f9e4b327..9dbec49d163 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -16,7 +16,7 @@ module MergeRequests
end
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
-
+ handle_wip_event(merge_request)
update(merge_request)
end
@@ -81,5 +81,18 @@ module MergeRequests
def after_update(issuable)
issuable.cache_merge_request_closes_issues!(current_user)
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
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index ffcad5b3a87..1725a30fae5 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -214,6 +214,18 @@ module SlashCommands
@updates[:due_date] = nil
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
desc 'CC'
params '@user'
diff --git a/app/views/dashboard/groups/_empty_state.html.haml b/app/views/dashboard/groups/_empty_state.html.haml
new file mode 100644
index 00000000000..f5222fe631e
--- /dev/null
+++ b/app/views/dashboard/groups/_empty_state.html.haml
@@ -0,0 +1,7 @@
+.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.
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index caca91af536..1a679c51774 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,9 +2,12 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-%ul.content-list
- - @group_members.each do |group_member|
- - group = group_member.group
- = render 'shared/groups/group', group: group, group_member: group_member
+- if @group_members.empty?
+ = render 'empty_state'
+- else
+ %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'
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 3950b4f53e7..8aefdcb3d9b 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,10 +1,11 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll
.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
= 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
= icon('fw thumb-tack')
diff --git a/app/views/shared/icons/_icon_empty_groups.svg b/app/views/shared/icons/_icon_empty_groups.svg
new file mode 100644
index 00000000000..9228be05f03
--- /dev/null
+++ b/app/views/shared/icons/_icon_empty_groups.svg
@@ -0,0 +1 @@
+<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
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index d61c76673f7..411e4837e20 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-13-stable-ee
```bash
cd /home/git/gitlab-shell
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
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 1792a0c501d..5f6a6c6503e 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -27,4 +27,5 @@ do.
| `/subscribe` | Subscribe |
| `/unsubscribe` | Unsubscribe |
| <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 |
diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature
deleted file mode 100644
index b0d5b748916..00000000000
--- a/features/profile/ssh_keys.feature
+++ /dev/null
@@ -1,20 +0,0 @@
-@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
diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb
deleted file mode 100644
index a400488a532..00000000000
--- a/features/steps/profile/ssh_keys.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-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
diff --git a/lib/api/keys.rb b/lib/api/keys.rb
index 2b723b79504..767f27ef334 100644
--- a/lib/api/keys.rb
+++ b/lib/api/keys.rb
@@ -4,10 +4,9 @@ module API
before { authenticate! }
resource :keys do
- # Get single ssh key by id. Only available to admin users.
- #
- # Example Request:
- # GET /keys/:id
+ desc 'Get single ssh key by id. Only available to admin users' do
+ success Entities::SSHKeyWithUser
+ end
get ":id" do
authenticated_as_admin!
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 6e987e7d9c9..1eaafdce389 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -16,21 +16,6 @@ retry() {
}
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
sed -i 's/username:.*/username: root/g' config/database.yml
sed -i 's/password:.*/password:/g' config/database.yml
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 94c9edc91fe..742edd8ba3d 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -644,6 +644,20 @@ describe Projects::MergeRequestsController do
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
let(:json_response) { JSON.parse(response.body) }
let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index bf2b93c92fb..3f2da1c380c 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -99,5 +99,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
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
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index 22d9d1b9fd5..cb3cea3fd51 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -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' } } }
end
- describe 'adding a due date from note' do
+ describe 'merge-request-only commands' do
before do
project.team << [user, :master]
login_with(user)
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
-
+
after do
wait_for_ajax
end
- it 'does not recognize the command nor create a note' do
- write_note("/due 2016-08-28")
+ describe 'toggling the WIP prefix in the title from note' do
+ 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
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index 3b20d38c520..eb1050d21c6 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -1,18 +1,57 @@
require 'rails_helper'
-describe 'Profile > SSH Keys', feature: true do
+feature 'Profile > SSH Keys', feature: true do
let(:user) { create(:user) }
before do
login_as(user)
- visit profile_keys_path
end
- describe 'User adds an SSH key' do
- it 'auto-populates the title', js: true do
+ describe 'User adds a key' 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))
expect(find_field('Title').value).to eq 'dummy@gitlab.com'
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
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 580a3235127..9d7be2429ed 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -287,6 +287,46 @@ describe MergeRequest, models: true do
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
let(:user) { create(:user) }
let(:user2) { create(:user) }
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index 5b1edba87a1..ae4d286d250 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -165,6 +165,23 @@ describe SlashCommands::InterpretService, services: true do
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
it 'populates {} if content contains an unsupported command' do
_, updates = service.execute(content, issuable)
@@ -376,6 +393,16 @@ describe SlashCommands::InterpretService, services: true do
let(:issuable) { issue }
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
let(:content) { '/remove_due_date' }
let(:issuable) { merge_request }