summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js15
-rw-r--r--app/assets/javascripts/compare_autocomplete.js5
-rw-r--r--app/assets/javascripts/single_file_diff.js13
-rw-r--r--app/assets/stylesheets/framework/blocks.scss6
-rw-r--r--app/assets/stylesheets/framework/files.scss9
-rw-r--r--app/assets/stylesheets/framework/flash.scss8
-rw-r--r--app/assets/stylesheets/framework/header.scss12
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss1
-rw-r--r--app/assets/stylesheets/pages/boards.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss13
-rw-r--r--app/assets/stylesheets/pages/editor.scss19
-rw-r--r--app/assets/stylesheets/pages/groups.scss36
-rw-r--r--app/assets/stylesheets/pages/milestone.scss1
-rw-r--r--app/assets/stylesheets/pages/projects.scss58
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb2
-rw-r--r--app/controllers/ci/lints_controller.rb1
-rw-r--r--app/controllers/groups/group_members_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb5
-rw-r--r--app/controllers/projects/boards/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb17
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/finders/access_requests_finder.rb27
-rw-r--r--app/finders/issuable_finder.rb15
-rw-r--r--app/helpers/application_helper.rb26
-rw-r--r--app/helpers/issuables_helper.rb36
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/concerns/access_requestable.rb5
-rw-r--r--app/models/group.rb36
-rw-r--r--app/models/member.rb79
-rw-r--r--app/models/members/group_member.rb16
-rw-r--r--app/models/members/project_member.rb44
-rw-r--r--app/models/merge_request.rb24
-rw-r--r--app/models/milestone.rb6
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/project_team.rb14
-rw-r--r--app/services/members/request_access_service.rb25
-rw-r--r--app/services/merge_requests/base_service.rb9
-rw-r--r--app/services/merge_requests/post_merge_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb15
-rw-r--r--app/services/notification_service.rb19
-rw-r--r--app/services/slash_commands/interpret_service.rb12
-rw-r--r--app/services/system_note_service.rb20
-rw-r--r--app/views/admin/background_jobs/_head.html.haml49
-rw-r--r--app/views/admin/dashboard/_head.html.haml57
-rw-r--r--app/views/ci/lints/_create.html.haml9
-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/explore/projects/_filter.html.haml4
-rw-r--r--app/views/layouts/_flash.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml6
-rw-r--r--app/views/projects/_activity.html.haml19
-rw-r--r--app/views/projects/_last_push.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml7
-rw-r--r--app/views/projects/builds/_sidebar.html.haml6
-rw-r--r--app/views/projects/commits/_head.html.haml45
-rw-r--r--app/views/projects/commits/show.html.haml3
-rw-r--r--app/views/projects/compare/_form.html.haml17
-rw-r--r--app/views/projects/compare/_ref_dropdown.html.haml1
-rw-r--r--app/views/projects/diffs/_content.html.haml4
-rw-r--r--app/views/projects/diffs/_file_header.html.haml1
-rw-r--r--app/views/projects/empty.html.haml2
-rw-r--r--app/views/projects/graphs/_head.html.haml35
-rw-r--r--app/views/projects/issues/_head.html.haml57
-rw-r--r--app/views/projects/issues/index.html.haml3
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/pipelines/_head.html.haml49
-rw-r--r--app/views/projects/tree/show.html.haml2
-rw-r--r--app/views/projects/wikis/_nav.html.haml25
-rw-r--r--app/views/shared/_sort_dropdown.html.haml22
-rw-r--r--app/views/shared/_visibility_level.html.haml2
-rw-r--r--app/views/shared/_visibility_radios.html.haml2
-rw-r--r--app/views/shared/icons/_icon_empty_groups.svg1
-rw-r--r--app/views/shared/icons/_icon_no_wrap.svg3
-rw-r--r--app/views/shared/icons/_icon_soft_wrap.svg3
-rw-r--r--app/views/shared/issuable/_nav.html.haml20
77 files changed, 763 insertions, 391 deletions
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index b846bab0424..de6cdd851be 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -22,6 +22,7 @@
// submitted textarea
})(this));
this.initModePanesAndLinks();
+ this.initSoftWrap();
new BlobLicenseSelectors({
editor: this.editor
});
@@ -50,6 +51,7 @@
this.$editModePanes.hide();
currentPane.fadeIn(200);
if (paneId === "#preview") {
+ this.$toggleButton.hide();
return $.post(currentLink.data("preview-url"), {
content: this.editor.getValue()
}, function(response) {
@@ -57,10 +59,23 @@
return currentPane.syntaxHighlight();
});
} else {
+ this.$toggleButton.show();
return this.editor.focus();
}
};
+ EditBlob.prototype.initSoftWrap = function() {
+ this.isSoftWrapped = false;
+ this.$toggleButton = $('.soft-wrap-toggle');
+ this.$toggleButton.on('click', this.toggleSoftWrap.bind(this));
+ };
+
+ EditBlob.prototype.toggleSoftWrap = function(e) {
+ this.isSoftWrapped = !this.isSoftWrapped;
+ this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped);
+ this.editor.getSession().setUseWrapMode(this.isSoftWrapped);
+ };
+
return EditBlob;
})();
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 4e3a28cd163..294d2c9052c 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -23,8 +23,9 @@
selectable: true,
filterable: true,
filterByText: true,
- fieldName: $dropdown.attr('name'),
- filterInput: 'input[type="text"]',
+ toggleLabel: true,
+ fieldName: $dropdown.data('field-name'),
+ filterInput: 'input[type="search"]',
renderRow: function(ref) {
var link;
if (ref.header != null) {
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 156b9b8abec..ee6af123268 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -10,12 +10,13 @@
ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
- COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>';
+ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
function SingleFileDiff(file) {
this.file = file;
this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file);
+ this.$toggleIcon = $('.diff-toggle-caret', this.file);
this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
this.isOpen = !this.diffForPath;
if (this.diffForPath) {
@@ -23,18 +24,22 @@
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
this.content = null;
this.collapsedContent.after(this.loadingContent);
+ this.$toggleIcon.addClass('fa-caret-right');
} else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
this.content.after(this.collapsedContent);
+ this.$toggleIcon.addClass('fa-caret-down');
}
- this.collapsedContent.on('click', this.toggleDiff);
- $('.file-title > a', this.file).on('click', this.toggleDiff);
+ $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
}
SingleFileDiff.prototype.toggleDiff = function(e) {
+ var $target = $(e.target);
+ if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) {
this.content.hide();
+ this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
this.collapsedContent.show();
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
@@ -42,10 +47,12 @@
} else if (this.content) {
this.collapsedContent.hide();
this.content.show();
+ this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
if (typeof DiffNotesApp !== 'undefined') {
DiffNotesApp.compileComponents();
}
} else {
+ this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
return this.getContentHTML();
}
};
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 2432ddb72f4..d315db4cb32 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -19,10 +19,8 @@
&.diff-collapsed {
padding: 5px;
- cursor: pointer;
-
- &:hover {
- background-color: $row-hover;
+ .click-to-expand {
+ cursor: pointer;
}
}
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 76a3c083697..81520500594 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -26,6 +26,15 @@
padding: 10px $gl-padding;
word-wrap: break-word;
border-radius: 3px 3px 0 0;
+ cursor: pointer;
+
+ &:hover {
+ background-color: $dark-background-color;
+ }
+
+ .diff-toggle-caret {
+ padding-right: 6px;
+ }
&.file-title-clear {
padding-left: 0;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 7ae309ba103..3ac1678dd05 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -3,6 +3,8 @@
margin: 0;
margin-bottom: $gl-padding;
font-size: 14px;
+ position: relative;
+ z-index: 1;
.flash-notice {
@extend .alert;
@@ -33,6 +35,12 @@
}
}
+.content-wrapper {
+ .flash-notice .container-fluid {
+ background-color: transparent;
+ }
+}
+
@media (max-width: $screen-md-min) {
ul.notes {
.flash-container.timeline-content {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index d4a030f7f7a..c748f856501 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -112,11 +112,15 @@ header {
.header-logo {
position: absolute;
left: 50%;
- margin-left: -18px;
top: 7px;
transition-duration: .3s;
z-index: 999;
+ #logo {
+ position: relative;
+ left: -50%;
+ }
+
svg, img {
height: 36px;
}
@@ -126,8 +130,12 @@ header {
}
@media (max-width: $screen-xs-max) {
- right: 25px;
+ right: 20px;
left: auto;
+
+ #logo {
+ left: auto;
+ }
}
}
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/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 9c84dceed05..ecc5b24e360 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -197,6 +197,7 @@ lex
a {
color: inherit;
+ word-wrap: break-word;
}
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index a5a260d4c8f..194a39a8377 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -107,10 +107,14 @@
.block {
width: 100%;
- }
- .block-first {
- padding: 5px 16px 11px;
+ &.coverage {
+ padding: 0 16px 11px;
+ }
+
+ .btn-group-justified {
+ margin-top: 5px;
+ }
}
.js-build-variable {
@@ -214,6 +218,9 @@
.build-detail-row {
margin-bottom: 5px;
+ &:last-of-type {
+ margin-bottom: 0;
+ }
}
.build-light-text {
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 1aa4e06d975..e1304335271 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -59,6 +59,7 @@
}
.encoding-selector,
+ .soft-wrap-toggle,
.license-selector,
.gitignore-selector,
.gitlab-ci-yml-selector {
@@ -67,6 +68,24 @@
font-family: $regular_font;
}
+ .soft-wrap-toggle {
+ margin: 0 $btn-side-margin;
+ .soft-wrap {
+ display: block;
+ }
+ .no-wrap {
+ display: none;
+ }
+ &.soft-wrap-active {
+ .soft-wrap {
+ display: none;
+ }
+ .no-wrap {
+ display: block;
+ }
+ }
+ }
+
.gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown {
line-height: 21px;
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/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 6b865730487..8c2ba3ed58c 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -33,6 +33,7 @@
// Issue title
span a {
color: $gl-text-color;
+ word-wrap: break-word;
}
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 8c8c403244e..78bc4b79e86 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -743,6 +743,62 @@ pre.light-well {
.dropdown-menu {
width: 300px;
}
+
+ &.from .compare-dropdown-toggle {
+ width: 237px;
+ }
+
+ &.to .compare-dropdown-toggle {
+ width: 254px;
+ }
+
+ .dropdown-toggle-text {
+ display: block;
+ height: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
+ }
+}
+
+.compare-ellipsis {
+ display: inline;
+}
+
+@media (max-width: $screen-xs-max) {
+ .compare-form-group {
+ .input-group {
+ width: 100%;
+
+ & > .compare-dropdown-toggle {
+ width: 100%;
+ }
+ }
+
+ .dropdown-menu {
+ width: 100%;
+ }
+ }
+
+ .compare-switch-container {
+ text-align: center;
+ padding: 0 0 $gl-padding;
+
+ .commits-compare-switch {
+ float: none;
+ }
+ }
+
+ .compare-ellipsis {
+ display: block;
+ text-align: center;
+ padding: 0 0 $gl-padding;
+ }
+
+ .commits-compare-btn {
+ width: 100%;
+ }
}
.clearable-input {
@@ -779,4 +835,4 @@ pre.light-well {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
-} \ No newline at end of file
+}
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index aed77d0358a..aa7570cd896 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController
def show
@members = @group.members.order("access_level DESC").page(params[:members_page])
- @requesters = @group.requesters
+ @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@projects = @group.projects.page(params[:projects_page])
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 0d2f4f6eb38..1d963bdf7d5 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController
end
@project_members = @project.members.page(params[:project_members_page])
- @requesters = @project.requesters
+ @requesters = AccessRequestsFinder.new(@project).execute(current_user)
end
def transfer
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index e06d12cfce1..78012960252 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -14,6 +14,7 @@ module Ci
@config_processor = Ci::GitlabCiYamlProcessor.new(@content)
@stages = @config_processor.stages
@builds = @config_processor.builds
+ @jobs = @config_processor.jobs
end
rescue
@error = 'Undefined error'
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 272164cd0cc..9c323d7705a 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
@members = @members.order('access_level DESC').page(params[:page]).per(50)
- @requesters = @group.requesters if can?(current_user, :admin_group, @group)
+ @requesters = AccessRequestsFinder.new(@group).execute(current_user)
@group_member = @group.group_members.new
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 7d0eff37635..3ec173abcdb 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -1,6 +1,5 @@
class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled
- before_action :authenticate_admin!
def new
@namespace_id = project_params[:namespace_id]
@@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file
)
end
-
- def authenticate_admin!
- render_404 unless current_user.is_admin?
- end
end
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 9404612a993..4aa7982eab4 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -33,7 +33,7 @@ module Projects
def issue
@issue ||=
- IssuesFinder.new(current_user, project_id: project.id, state: 'all')
+ IssuesFinder.new(current_user, project_id: project.id)
.execute
.where(iid: params[:id])
.first!
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 935417d4ae8..8c8c56228ad 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :define_commit_vars, only: [:diffs]
before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines]
+ before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
# Allow read any merge_request
before_action :authorize_read_merge_request!
@@ -275,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."
@@ -308,8 +309,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return
end
- TodoService.new.merge_merge_request(merge_request, current_user)
-
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present?
@@ -418,10 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def validates_merge_request
- # If source project was removed and merge request for some reason
- # wasn't close (Ex. mr from fork to origin)
- return invalid_mr if !@merge_request.source_project && @merge_request.open?
-
# Show git not found page
# if there is no saved commits between source & target branch
if @merge_request.commits.blank?
@@ -496,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def invalid_mr
- # Render special view for MR with removed source or target branch
+ # Render special view for MR with removed target branch
render 'invalid'
end
@@ -538,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diff_notes_disabled = !@merge_request_diff.latest?
@diffs = @merge_request_diff.diffs(diff_options)
end
+
+ def close_merge_request_without_source_project
+ if !@merge_request.source_project && @merge_request.open?
+ @merge_request.close
+ end
+ end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 42a7e5a2c30..2343c7d20ec 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_members = @group_members.order('access_level DESC')
end
- @requesters = @project.requesters if can?(current_user, :admin_project, @project)
+ @requesters = AccessRequestsFinder.new(@project).execute(current_user)
@project_member = @project.project_members.new
@project_group_links = @project.project_group_links
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index c99aeadb5e4..62916270172 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController
noteable =
case params[:type]
when 'Issue'
- IssuesFinder.new(current_user, project_id: @project.id, state: 'all').
+ IssuesFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id])
when 'MergeRequest'
- MergeRequestsFinder.new(current_user, project_id: @project.id, state: 'all').
+ MergeRequestsFinder.new(current_user, project_id: @project.id).
execute.find_by(iid: params[:type_id])
when 'Commit'
@project.commit(params[:type_id])
diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb
new file mode 100644
index 00000000000..b6ee49df99b
--- /dev/null
+++ b/app/finders/access_requests_finder.rb
@@ -0,0 +1,27 @@
+class AccessRequestsFinder
+ attr_accessor :source
+
+ # Arguments:
+ # source - a Group or Project
+ def initialize(source)
+ @source = source
+ end
+
+ def execute(*args)
+ execute!(*args)
+ rescue Gitlab::Access::AccessDeniedError
+ []
+ end
+
+ def execute!(current_user)
+ raise Gitlab::Access::AccessDeniedError unless can_see_access_requests?(current_user)
+
+ source.requesters
+ end
+
+ private
+
+ def can_see_access_requests?(current_user)
+ source && Ability.allowed?(current_user, :"admin_#{source.class.to_s.underscore}", source)
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 8f9ef8f725c..9f170428100 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -183,17 +183,12 @@ class IssuableFinder
end
def by_state(items)
- case params[:state]
- when 'closed'
- items.closed
- when 'merged'
- items.respond_to?(:merged) ? items.merged : items.closed
- when 'all'
- items
- when 'opened'
- items.opened
+ params[:state] ||= 'all'
+
+ if items.respond_to?(params[:state])
+ items.public_send(params[:state])
else
- raise 'You must specify default state'
+ items
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 1df430e6279..ebd78bf9888 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -280,32 +280,6 @@ module ApplicationHelper
end
end
- def state_filters_text_for(entity, project)
- titles = {
- opened: "Open"
- }
-
- entity_title = titles[entity] || entity.to_s.humanize
-
- count =
- if project.nil?
- nil
- elsif current_controller?(:issues)
- project.issues.visible_to_user(current_user).send(entity).count
- elsif current_controller?(:merge_requests)
- project.merge_requests.send(entity).count
- end
-
- html = content_tag :span, entity_title
-
- if count.present?
- html += " "
- html += content_tag :span, number_with_delimiter(count), class: 'badge'
- end
-
- html.html_safe
- end
-
def truncate_first_line(message, length = 50)
truncate(message.each_line.first.chomp, length: length) if message
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 5c04bba323f..8c04200fab9 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -94,6 +94,24 @@ module IssuablesHelper
label_names.join(', ')
end
+ def issuables_state_counter_text(issuable_type, state)
+ titles = {
+ opened: "Open"
+ }
+
+ state_title = titles[state] || state.to_s.humanize
+
+ count =
+ Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
+ issuables_count_for_state(issuable_type, state)
+ end
+
+ html = content_tag(:span, state_title)
+ html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
+
+ html.html_safe
+ end
+
private
def sidebar_gutter_collapsed?
@@ -111,4 +129,22 @@ module IssuablesHelper
issuable.open? ? :opened : :closed
end
end
+
+ def issuables_count_for_state(issuable_type, state)
+ issuables_finder = public_send("#{issuable_type}_finder")
+ issuables_finder.params[:state] = state
+
+ issuables_finder.execute.page(1).total_count
+ end
+
+ IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
+ private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
+
+ def issuables_state_counter_cache_key(issuable_type, state)
+ opts = params.with_indifferent_access
+ opts[:state] = state
+ opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
+
+ hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
+ end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 522e2264bb8..5dbf66173de 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -373,7 +373,7 @@ module Ci
end
def artifacts?
- !artifacts_expired? && self[:artifacts_file].present?
+ !artifacts_expired? && artifacts_file.exists?
end
def artifacts_metadata?
diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb
index eedd32a729f..62bc6b809f4 100644
--- a/app/models/concerns/access_requestable.rb
+++ b/app/models/concerns/access_requestable.rb
@@ -8,9 +8,6 @@ module AccessRequestable
extend ActiveSupport::Concern
def request_access(user)
- members.create(
- access_level: Gitlab::Access::DEVELOPER,
- user: user,
- requested_at: Time.now.utc)
+ Members::RequestAccessService.new(self, user).execute
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index aefb94b2ada..a2f88cca828 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -102,40 +102,44 @@ class Group < Namespace
self[:lfs_enabled]
end
- def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
- user_ids.each do |user_id|
- Member.add_user(
- self.group_members,
- user_id,
- access_level,
- current_user: current_user,
- expires_at: expires_at
- )
- end
+ def add_users(users, access_level, current_user: nil, expires_at: nil)
+ GroupMember.add_users_to_group(
+ self,
+ users,
+ access_level,
+ current_user: current_user,
+ expires_at: expires_at
+ )
end
def add_user(user, access_level, current_user: nil, expires_at: nil)
- add_users([user], access_level, current_user: current_user, expires_at: expires_at)
+ GroupMember.add_user(
+ self,
+ user,
+ access_level,
+ current_user: current_user,
+ expires_at: expires_at
+ )
end
def add_guest(user, current_user = nil)
- add_user(user, Gitlab::Access::GUEST, current_user: current_user)
+ add_user(user, :guest, current_user: current_user)
end
def add_reporter(user, current_user = nil)
- add_user(user, Gitlab::Access::REPORTER, current_user: current_user)
+ add_user(user, :reporter, current_user: current_user)
end
def add_developer(user, current_user = nil)
- add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user)
+ add_user(user, :developer, current_user: current_user)
end
def add_master(user, current_user = nil)
- add_user(user, Gitlab::Access::MASTER, current_user: current_user)
+ add_user(user, :master, current_user: current_user)
end
def add_owner(user, current_user = nil)
- add_user(user, Gitlab::Access::OWNER, current_user: current_user)
+ add_user(user, :owner, current_user: current_user)
end
def has_owner?(user)
diff --git a/app/models/member.rb b/app/models/member.rb
index 69406379948..38a278ea559 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -80,49 +80,70 @@ class Member < ActiveRecord::Base
find_by(invite_token: invite_token)
end
- # This method is used to find users that have been entered into the "Add members" field.
- # These can be the User objects directly, their IDs, their emails, or new emails to be invited.
- def user_for_id(user_id)
- return user_id if user_id.is_a?(User)
-
- user = User.find_by(id: user_id)
- user ||= User.find_by(email: user_id)
- user ||= user_id
- user
- end
-
- def add_user(members, user_id, access_level, current_user: nil, expires_at: nil)
- user = user_for_id(user_id)
+ def add_user(source, user, access_level, current_user: nil, expires_at: nil)
+ user = retrieve_user(user)
+ access_level = retrieve_access_level(access_level)
# `user` can be either a User object or an email to be invited
- if user.is_a?(User)
- member = members.find_or_initialize_by(user_id: user.id)
+ member =
+ if user.is_a?(User)
+ source.members.find_by(user_id: user.id) ||
+ source.requesters.find_by(user_id: user.id) ||
+ source.members.build(user_id: user.id)
+ else
+ source.members.build(invite_email: user)
+ end
+
+ return member unless can_update_member?(current_user, member)
+
+ member.attributes = {
+ created_by: member.created_by || current_user,
+ access_level: access_level,
+ expires_at: expires_at
+ }
+
+ if member.request?
+ ::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute
else
- member = members.build
- member.invite_email = user
+ member.save
end
- if can_update_member?(current_user, member) || project_creator?(member, access_level)
- member.created_by ||= current_user
- member.access_level = access_level
- member.expires_at = expires_at
+ member
+ end
- member.save
- end
+ def access_levels
+ Gitlab::Access.sym_options
end
private
+ # This method is used to find users that have been entered into the "Add members" field.
+ # These can be the User objects directly, their IDs, their emails, or new emails to be invited.
+ def retrieve_user(user)
+ return user if user.is_a?(User)
+
+ User.find_by(id: user) || User.find_by(email: user) || user
+ end
+
+ def retrieve_access_level(access_level)
+ access_levels.fetch(access_level) { access_level.to_i }
+ end
+
def can_update_member?(current_user, member)
# There is no current user for bulk actions, in which case anything is allowed
- !current_user ||
- current_user.can?(:update_group_member, member) ||
- current_user.can?(:update_project_member, member)
+ !current_user || current_user.can?(:"update_#{member.type.underscore}", member)
end
- def project_creator?(member, access_level)
- member.new_record? && member.owner? &&
- access_level.to_i == ProjectMember::MASTER
+ def add_users_to_source(source, users, access_level, current_user: nil, expires_at: nil)
+ users.each do |user|
+ add_user(
+ source,
+ user,
+ access_level,
+ current_user: current_user,
+ expires_at: expires_at
+ )
+ end
end
end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 2f13d339c89..1b54a85d064 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -12,6 +12,22 @@ class GroupMember < Member
Gitlab::Access.options_with_owner
end
+ def self.access_levels
+ Gitlab::Access.sym_options_with_owner
+ end
+
+ def self.add_users_to_group(group, users, access_level, current_user: nil, expires_at: nil)
+ self.transaction do
+ add_users_to_source(
+ group,
+ users,
+ access_level,
+ current_user: current_user,
+ expires_at: expires_at
+ )
+ end
+ end
+
def group
source
end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index ec2d40eb11c..125f26369d7 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -34,36 +34,20 @@ class ProjectMember < Member
# :master
# )
#
- def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil)
- access_level = if roles_hash.has_key?(access)
- roles_hash[access]
- elsif roles_hash.values.include?(access.to_i)
- access
- else
- raise "Non valid access"
- end
-
- users = user_ids.map { |user_id| Member.user_for_id(user_id) }
-
- ProjectMember.transaction do
+ def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil)
+ self.transaction do
project_ids.each do |project_id|
project = Project.find(project_id)
- users.each do |user|
- Member.add_user(
- project.project_members,
- user,
- access_level,
- current_user: current_user,
- expires_at: expires_at
- )
- end
+ add_users_to_source(
+ project,
+ users,
+ access_level,
+ current_user: current_user,
+ expires_at: expires_at
+ )
end
end
-
- true
- rescue
- false
end
def truncate_teams(project_ids)
@@ -84,13 +68,15 @@ class ProjectMember < Member
truncate_teams [project.id]
end
- def roles_hash
- Gitlab::Access.sym_options
- end
-
def access_level_roles
Gitlab::Access.options
end
+
+ private
+
+ def can_update_member?(current_user, member)
+ super || (member.owner? && member.new_record?)
+ end
end
def access_field
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/models/milestone.rb b/app/models/milestone.rb
index 2bd7f198030..44c3cbb2c73 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -158,7 +158,7 @@ class Milestone < ActiveRecord::Base
end
def title=(value)
- write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
+ write_attribute(:title, sanitize_title(value)) if value.present?
end
# Sorts the issues for the given IDs.
@@ -204,4 +204,8 @@ class Milestone < ActiveRecord::Base
iid
end
end
+
+ def sanitize_title(value)
+ CGI.unescape_html(Sanitize.clean(value.to_s))
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 7265cb55594..507228606df 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -146,6 +146,7 @@ class Project < ActiveRecord::Base
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
+ delegate :add_user, to: :team
# Validations
validates :creator, presence: true, on: :create
@@ -1016,10 +1017,6 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user)
end
- def add_user(user, access_level, current_user: nil, expires_at: nil)
- team.add_user(user, access_level, current_user: current_user, expires_at: expires_at)
- end
-
def default_branch
@default_branch ||= repository.root_ref if repository.exists?
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index d9ce5088903..79d041d2775 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -33,18 +33,24 @@ class ProjectTeam
member
end
- def add_users(users, access, current_user: nil, expires_at: nil)
+ def add_users(users, access_level, current_user: nil, expires_at: nil)
ProjectMember.add_users_to_projects(
[project.id],
users,
- access,
+ access_level,
current_user: current_user,
expires_at: expires_at
)
end
- def add_user(user, access, current_user: nil, expires_at: nil)
- add_users([user], access, current_user: current_user, expires_at: expires_at)
+ def add_user(user, access_level, current_user: nil, expires_at: nil)
+ ProjectMember.add_user(
+ project,
+ user,
+ access_level,
+ current_user: current_user,
+ expires_at: expires_at
+ )
end
# Remove all users from project team
diff --git a/app/services/members/request_access_service.rb b/app/services/members/request_access_service.rb
new file mode 100644
index 00000000000..2614153d900
--- /dev/null
+++ b/app/services/members/request_access_service.rb
@@ -0,0 +1,25 @@
+module Members
+ class RequestAccessService < BaseService
+ attr_accessor :source
+
+ def initialize(source, current_user)
+ @source = source
+ @current_user = current_user
+ end
+
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can_request_access?(source)
+
+ source.members.create(
+ access_level: Gitlab::Access::DEVELOPER,
+ user: current_user,
+ requested_at: Time.now.utc)
+ end
+
+ private
+
+ def can_request_access?(source)
+ source && can?(current_user, :request_access, source)
+ end
+ end
+end
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/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index 8437d9b8b43..e8fb1b59752 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -7,6 +7,7 @@ module MergeRequests
class PostMergeService < MergeRequests::BaseService
def execute(merge_request)
close_issues(merge_request)
+ todo_service.merge_merge_request(merge_request, current_user)
merge_request.mark_as_merged
create_merge_event(merge_request, current_user)
create_note(merge_request)
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/notification_service.rb b/app/services/notification_service.rb
index 6139ed56e25..de8049b8e2e 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -134,7 +134,8 @@ class NotificationService
merge_request,
merge_request.target_project,
current_user,
- :merged_merge_request_email
+ :merged_merge_request_email,
+ skip_current_user: !merge_request.merge_when_build_succeeds?
)
end
@@ -514,9 +515,16 @@ class NotificationService
end
end
- def close_resource_email(target, project, current_user, method)
+ def close_resource_email(target, project, current_user, method, skip_current_user: true)
action = method == :merged_merge_request_email ? "merge" : "close"
- recipients = build_recipients(target, project, current_user, action: action)
+
+ recipients = build_recipients(
+ target,
+ project,
+ current_user,
+ action: action,
+ skip_current_user: skip_current_user
+ )
recipients.each do |recipient|
mailer.send(method, recipient.id, target.id, current_user.id).deliver_later
@@ -557,7 +565,7 @@ class NotificationService
end
end
- def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
+ def build_recipients(target, project, current_user, action: nil, previous_assignee: nil, skip_current_user: true)
custom_action = build_custom_key(action, target)
recipients = target.participants(current_user)
@@ -586,7 +594,8 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target)
recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user)
+ recipients.delete(current_user) if skip_current_user
+
recipients.uniq
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/services/system_note_service.rb b/app/services/system_note_service.rb
index 0c8446e7c3d..5ccaa5275b7 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -24,6 +24,7 @@ module SystemNoteService
body = "Added #{commits_text}:\n\n"
body << existing_commit_summary(noteable, existing_commits, oldrev)
body << new_commit_summary(new_commits).join("\n")
+ body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(noteable: noteable, project: project, author: author, note: body)
end
@@ -254,8 +255,7 @@ module SystemNoteService
#
# "Started branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch)
- h = Gitlab::Routing.url_helpers
- link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
+ link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
body = "Started branch [`#{branch}`](#{link})"
create_note(noteable: issue, project: project, author: author, note: body)
@@ -466,4 +466,20 @@ module SystemNoteService
def escape_html(text)
Rack::Utils.escape_html(text)
end
+
+ def url_helpers
+ @url_helpers ||= Gitlab::Routing.url_helpers
+ end
+
+ def diff_comparison_url(merge_request, project, oldrev)
+ diff_id = merge_request.merge_request_diff.id
+
+ url_helpers.diffs_namespace_project_merge_request_url(
+ project.namespace,
+ project,
+ merge_request.iid,
+ diff_id: diff_id,
+ start_sha: oldrev
+ )
+ end
end
diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml
index 107fc25244a..b3530915068 100644
--- a/app/views/admin/background_jobs/_head.html.haml
+++ b/app/views/admin/background_jobs/_head.html.haml
@@ -1,24 +1,25 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: :system_info) do
- = link_to admin_system_info_path, title: 'System Info' do
- %span
- System Info
- = nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs' do
- %span
- Background Jobs
- = nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs' do
- %span
- Logs
- = nav_link(controller: :health_check) do
- = link_to admin_health_check_path, title: 'Health Check' do
- %span
- Health Check
- = nav_link(controller: :requests_profiles) do
- = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
- %span
- Requests Profiles
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(controller: :system_info) do
+ = link_to admin_system_info_path, title: 'System Info' do
+ %span
+ System Info
+ = nav_link(controller: :background_jobs) do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %span
+ Background Jobs
+ = nav_link(controller: :logs) do
+ = link_to admin_logs_path, title: 'Logs' do
+ %span
+ Logs
+ = nav_link(controller: :health_check) do
+ = link_to admin_health_check_path, title: 'Health Check' do
+ %span
+ Health Check
+ = nav_link(controller: :requests_profiles) do
+ = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+ %span
+ Requests Profiles
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index c91ab4cb946..ec40391a3e3 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -1,28 +1,29 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview' do
- %span
- Overview
- = nav_link(controller: [:admin, :projects]) do
- = link_to admin_namespaces_projects_path, title: 'Projects' do
- %span
- Projects
- = nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users' do
- %span
- Users
- = nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link path: 'builds#index' do
- = link_to admin_builds_path, title: 'Builds' do
- %span
- Builds
- = nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path, title: 'Runners' do
- %span
- Runners
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview' do
+ %span
+ Overview
+ = nav_link(controller: [:admin, :projects]) do
+ = link_to admin_namespaces_projects_path, title: 'Projects' do
+ %span
+ Projects
+ = nav_link(controller: :users) do
+ = link_to admin_users_path, title: 'Users' do
+ %span
+ Users
+ = nav_link(controller: :groups) do
+ = link_to admin_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link path: 'builds#index' do
+ = link_to admin_builds_path, title: 'Builds' do
+ %span
+ Builds
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to admin_runners_path, title: 'Runners' do
+ %span
+ Runners
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml
index f7875e68b7e..d5c21c6dffe 100644
--- a/app/views/ci/lints/_create.html.haml
+++ b/app/views/ci/lints/_create.html.haml
@@ -21,13 +21,16 @@
%br
%b Tag list:
- = build[:tags]
+ = build[:tag_list].to_a.join(", ")
%br
%b Refs only:
- = build[:only] && build[:only].join(", ")
+ = @jobs[build[:name].to_sym][:only].to_a.join(", ")
%br
%b Refs except:
- = build[:except] && build[:except].join(", ")
+ = @jobs[build[:name].to_sym][:except].to_a.join(", ")
+ %br
+ %b Environment:
+ = build[:environment]
%br
%b When:
= build[:when]
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/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index cd485da5104..132bbe26fe0 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -8,7 +8,7 @@
- else
Any
%b.caret
- %ul.dropdown-menu
+ %ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_projects_path(visibility_level: nil) do
Any
@@ -28,7 +28,7 @@
- else
Any
%b.caret
- %ul.dropdown-menu
+ %ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to filter_projects_path(tag: nil) do
Any
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index 3612f1ce5c6..baa8036de10 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -1,8 +1,10 @@
.flash-container.flash-container-page
- if alert
.flash-alert
- = alert
+ %div{ class: (container_class) }
+ %span= alert
- elsif notice
.flash-notice
- = notice
+ %div{ class: (container_class) }
+ %span= notice
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 4f7839a881f..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')
@@ -20,6 +21,7 @@
.container-fluid
= render "layouts/nav/#{nav}"
.content-wrapper{ class: "#{layout_nav_class}" }
+ = yield :sub_nav
= render "layouts/broadcast"
= render "layouts/flash"
= yield :flash_message
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index ac50ce83f6a..d011e51e696 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,13 +1,16 @@
-.nav-block.activity-filter-block
- - if current_user
- .controls
- = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
- %i.fa.fa-rss
+- @no_container = true
- = render 'shared/event_filter'
+%div{ class: container_class }
+ .nav-block.activity-filter-block
+ - if current_user
+ .controls
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
+ = icon('rss')
-.content_list.project-activity{:"data-href" => activity_project_path(@project)}
-= spinner
+ = render 'shared/event_filter'
+
+ .content_list.project-activity{:"data-href" => activity_project_path(@project)}
+ = spinner
:javascript
var activity = new Activities();
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 3c6b931f41a..1c3bccccb5c 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,6 +1,6 @@
- if event = last_push_event
- if show_last_push_widget?(event)
- .row-content-block.top-block.clear-block.hidden-xs
+ .row-content-block.top-block.hidden-xs.white
%div{ class: container_class }
.event-last-push
.event-last-push-text
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 0237e152b54..d4f59764a70 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -21,6 +21,13 @@
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
= dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ = button_tag class: 'soft-wrap-toggle btn', type: 'button' do
+ %span.no-wrap
+ = custom_icon('icon_no_wrap')
+ No wrap
+ %span.soft-wrap
+ = custom_icon('icon_soft_wrap')
+ Soft wrap
.encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 0aa3092baa2..f5344091cae 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -8,7 +8,7 @@
%a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
= icon('angle-double-right')
- if @build.coverage
- .block.block-first
+ .block.coverage
.title
Test coverage
%p.build-detail-row
@@ -95,7 +95,7 @@
- @build.trigger_request.variables.each do |key, value|
.hide.js-build
- .js-build-variable= key
+ .js-build-variable= key
.js-build-value= value
.block
@@ -128,7 +128,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do
- = icon('check')
+ = icon('right-arrow')
= ci_icon_for_status(build.status)
%span
- if build.name
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 4d1ee1c5318..80763ce67ca 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,27 +1,28 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project) do
- Files
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+ = link_to project_files_path(@project) do
+ Files
- = nav_link(controller: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
- Commits
+ = nav_link(controller: [:commit, :commits]) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ Commits
- = nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
- Network
+ = nav_link(controller: %w(network)) do
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ Network
- = nav_link(controller: :compare) do
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
- Compare
+ = nav_link(controller: :compare) do
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+ Compare
- = nav_link(html_options: {class: branches_tab_class}) do
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- Branches
+ = nav_link(html_options: {class: branches_tab_class}) do
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ Branches
- = nav_link(controller: [:tags, :releases]) do
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- Tags
+ = nav_link(controller: [:tags, :releases]) do
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ Tags
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 9a44ba94970..876c8002627 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -5,7 +5,8 @@
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
-= render "head"
+= content_for :sub_nav do
+ = render "head"
%div{ class: container_class }
.row-content-block.second-block.content-component-block
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index d79336f5a60..76b68c544aa 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,17 +1,22 @@
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix
- if params[:to] && params[:from]
- = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
- .form-group.dropdown.compare-form-group.js-compare-from-dropdown
+ .compare-switch-container
+ = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
+ .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon from
- = text_field_tag :from, params[:from], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from].presence }
+ = hidden_field_tag :from, params[:from]
+ = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+ .dropdown-toggle-text= params[:from] || 'Select branch/tag'
= render "ref_dropdown"
- = "..."
- .form-group.dropdown.compare-form-group.js-compare-to-dropdown
+ .compare-ellipsis ...
+ .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group
%span.input-group-addon to
- = text_field_tag :to, params[:to], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to].presence }
+ = hidden_field_tag :to, params[:to]
+ = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+ .dropdown-toggle-text= params[:to] || 'Select branch/tag'
= render "ref_dropdown"
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml
index c604c6d0135..27d928c87a0 100644
--- a/app/views/projects/compare/_ref_dropdown.html.haml
+++ b/app/views/projects/compare/_ref_dropdown.html.haml
@@ -1,4 +1,5 @@
.dropdown-menu.dropdown-menu-selectable
= dropdown_title "Select branch/tag"
+ = dropdown_filter "Filter by branch/tag"
= dropdown_content
= dropdown_loading
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index d37961c4e40..779c8ea0104 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -11,7 +11,9 @@
- elsif diff_file.collapsed?
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path))
.nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
- This diff is collapsed. Click to expand it.
+ This diff is collapsed.
+ %a.click-to-expand
+ Click to expand it.
- elsif diff_file.diff_lines.length > 0
- if diff_view == :parallel
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 95a2772fd0b..a6a2e5690b5 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -1,3 +1,4 @@
+%i.fa.diff-toggle-caret
- if defined?(blob) && blob && diff_file.submodule?
%span
= icon('archive fw')
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 636beb73ec2..7a39064adc5 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -23,6 +23,8 @@
or a
= link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link'
to this project.
+ %p
+ You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected.
- if can?(current_user, :push_code, @project)
%div{ class: container_class }
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 082e2cb4d8c..1a62a6a809c 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,18 +1,19 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
- - content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/chart.js')
- = page_specific_javascript_tag('graphs/graphs_bundle.js')
- = nav_link(action: :show) do
- = link_to 'Contributors', namespace_project_graph_path
- = nav_link(action: :commits) do
- = link_to 'Commits', commits_namespace_project_graph_path
- = nav_link(action: :languages) do
- = link_to 'Languages', languages_namespace_project_graph_path
- - if @project.feature_available?(:builds, current_user)
- = nav_link(action: :ci) do
- = link_to ci_namespace_project_graph_path do
- Continuous Integration
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/chart.js')
+ = page_specific_javascript_tag('graphs/graphs_bundle.js')
+ = nav_link(action: :show) do
+ = link_to 'Contributors', namespace_project_graph_path
+ = nav_link(action: :commits) do
+ = link_to 'Commits', commits_namespace_project_graph_path
+ = nav_link(action: :languages) do
+ = link_to 'Languages', languages_namespace_project_graph_path
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(action: :ci) do
+ = link_to ci_namespace_project_graph_path do
+ Continuous Integration
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index f88b33018d0..509b01c548a 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -1,32 +1,33 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
- = nav_link(controller: :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
- %span
- Issues
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
+ = nav_link(controller: :issues) do
+ = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+ %span
+ Issues
- = nav_link(controller: :boards) do
- = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
- %span
- Board
+ = nav_link(controller: :boards) do
+ = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do
+ %span
+ Board
- - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
- = nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
- %span
- Merge Requests
+ - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
+ = nav_link(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ %span
+ Merge Requests
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
- %span
- Labels
+ - if project_nav_tab? :labels
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ %span
+ Labels
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
- %span
- Milestones \ No newline at end of file
+ - if project_nav_tab? :milestones
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ %span
+ Milestones
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 8da9f2100e9..cc57cfdb342 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -3,7 +3,8 @@
- page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user)
-= render "projects/issues/head"
+= content_for :sub_nav do
+ = render "projects/issues/head"
= content_for :meta_tags do
- if current_user
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index fda0592dd41..cc8cb134fb8 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -72,7 +72,7 @@
= link_to "#", class: 'btn js-toggle-button import_git' do
= icon('git', text: 'Repo by URL')
%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
= icon('gitlab', text: 'GitLab export')
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index 5f571499e80..7d421c0e740 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -1,27 +1,28 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- - if project_nav_tab? :pipelines
- = nav_link(controller: :pipelines) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipelines) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
- - if project_nav_tab? :builds
- = nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
- %span
- Builds
+ - if project_nav_tab? :builds
+ = nav_link(controller: %w(builds)) do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+ %span
+ Builds
- - if project_nav_tab? :environments
- = nav_link(controller: %w(environments)) do
- = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
- %span
- Environments
+ - if project_nav_tab? :environments
+ = nav_link(controller: %w(environments)) do
+ = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ %span
+ Environments
- - if can?(current_user, :read_cycle_analytics, @project)
- = nav_link(controller: %w(cycle_analytics)) do
- = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
- %span
- Cycle Analytics
+ - if can?(current_user, :read_cycle_analytics, @project)
+ = nav_link(controller: %w(cycle_analytics)) do
+ = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do
+ %span
+ Cycle Analytics
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 37d341212af..9864be3562a 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -4,8 +4,8 @@
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
-= render 'projects/last_push'
= render "projects/commits/head"
+= render 'projects/last_push'
%div{ class: container_class }
.tree-controls
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 551a20c1044..09c4411d67e 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,15 +1,16 @@
-.scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
+= content_for :sub_nav do
+ .scrolling-tabs-container.sub-nav-scroll
+ = render 'shared/nav_scroll'
+ .nav-links.sub-nav.scrolling-tabs
+ %ul{ class: (container_class) }
+ = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
+ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
- = nav_link(path: 'wikis#pages') do
- = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
+ = nav_link(path: 'wikis#pages') do
+ = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
- = nav_link(path: 'wikis#git_access') do
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
- Git Access
+ = nav_link(path: 'wikis#git_access') do
+ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
+ Git Access
- = render 'projects/wikis/new'
+ = render 'projects/wikis/new'
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 249bce926ce..36bbac6fbf5 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -8,26 +8,26 @@
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
%li
- = link_to page_filter_path(sort: sort_value_priority) do
+ = link_to page_filter_path(sort: sort_value_priority, label: true) do
= sort_title_priority
- = link_to page_filter_path(sort: sort_value_recently_created) do
+ = link_to page_filter_path(sort: sort_value_recently_created, label: true) do
= sort_title_recently_created
- = link_to page_filter_path(sort: sort_value_oldest_created) do
+ = link_to page_filter_path(sort: sort_value_oldest_created, label: true) do
= sort_title_oldest_created
- = link_to page_filter_path(sort: sort_value_recently_updated) do
+ = link_to page_filter_path(sort: sort_value_recently_updated, label: true) do
= sort_title_recently_updated
- = link_to page_filter_path(sort: sort_value_oldest_updated) do
+ = link_to page_filter_path(sort: sort_value_oldest_updated, label: true) do
= sort_title_oldest_updated
- = link_to page_filter_path(sort: sort_value_milestone_soon) do
+ = link_to page_filter_path(sort: sort_value_milestone_soon, label: true) do
= sort_title_milestone_soon
- = link_to page_filter_path(sort: sort_value_milestone_later) do
+ = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do
= sort_title_milestone_later
- if controller.controller_name == 'issues' || controller.action_name == 'issues'
- = link_to page_filter_path(sort: sort_value_due_date_soon) do
+ = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
= sort_title_due_date_soon
- = link_to page_filter_path(sort: sort_value_due_date_later) do
+ = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
= sort_title_due_date_later
- = link_to page_filter_path(sort: sort_value_upvotes) do
+ = link_to page_filter_path(sort: sort_value_upvotes, label: true) do
= sort_title_upvotes
- = link_to page_filter_path(sort: sort_value_downvotes) do
+ = link_to page_filter_path(sort: sort_value_downvotes, label: true) do
= sort_title_downvotes
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index add4536a0a2..b11257ee0e6 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -6,7 +6,7 @@
- if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else
- .col-sm-10
+ %div
%span.info
= visibility_level_icon(visibility_level)
%strong
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index ebe2eb0433d..182c4eebd50 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -10,6 +10,6 @@
.option-descr
= visibility_level_description(level, form_model)
- unless restricted_visibility_levels.empty?
- .col-sm-10
+ %div
%span.info
Some visibility level settings have been restricted by the administrator.
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/app/views/shared/icons/_icon_no_wrap.svg b/app/views/shared/icons/_icon_no_wrap.svg
new file mode 100644
index 00000000000..fe34cada002
--- /dev/null
+++ b/app/views/shared/icons/_icon_no_wrap.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="m6 11h-4.509c-.263 0-.491.226-.491.505v.991c0 .291.22.505.491.505h4.509v.679c0 .301.194.413.454.236l2.355-1.607c.251-.171.259-.442 0-.619l-2.355-1.607c-.251-.171-.454-.07-.454.236v.681m-5-7.495c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991m10 8c0-.279.215-.505.49-.505h3.02c.271 0 .49.214.49.505v.991c0 .279-.215.505-.49.505h-3.02c-.271 0-.49-.214-.49-.505v-.991m-10-4c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991"/>
+</svg>
diff --git a/app/views/shared/icons/_icon_soft_wrap.svg b/app/views/shared/icons/_icon_soft_wrap.svg
new file mode 100644
index 00000000000..ea27a2024b1
--- /dev/null
+++ b/app/views/shared/icons/_icon_soft_wrap.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <path fill-rule="evenodd" d="m12 11h-2v-.681c0-.307-.203-.407-.454-.236l-2.355 1.607c-.259.177-.251.448 0 .619l2.355 1.607c.259.177.454.065.454-.236v-.679h2c0 0 0 0 0 0 1.657 0 3-1.343 3-3 0-.828-.336-1.578-.879-2.121-.543-.543-1.293-.879-2.121-.879-.001 0-.002 0-.002 0h-10.497c-.271 0-.5.226-.5.505v.991c0 .291.224.505.5.505h10.497c.001 0 .002 0 .002 0 .552 0 1 .448 1 1 0 .276-.112.526-.293.707-.181.181-.431.293-.707.293m-11-7.495c0-.279.22-.505.498-.505h13c.275 0 .498.214.498.505v.991c0 .279-.22.505-.498.505h-13c-.275 0-.498-.214-.498-.505v-.991m0 8c0-.279.215-.505.49-.505h3.02c.271 0 .49.214.49.505v.991c0 .279-.215.505-.49.505h-3.02c-.271 0-.49-.214-.49-.505v-.991"/>
+</svg>
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 1d9b09a5ef1..5527a2f889a 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -1,25 +1,25 @@
+- type = local_assigns.fetch(:type, :issues)
+- page_context_word = type.to_s.humanize(capitalize: false)
+- issuables = @issues || @merge_requests
+
%ul.nav-links.issues-state-filters
- - if defined?(type) && type == :merge_requests
- - page_context_word = 'merge requests'
- - else
- - page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do
- #{state_filters_text_for(:opened, @project)}
+ #{issuables_state_counter_text(type, :opened)}
- - if defined?(type) && type == :merge_requests
+ - if type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do
- #{state_filters_text_for(:merged, @project)}
+ #{issuables_state_counter_text(type, :merged)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do
- #{state_filters_text_for(:closed, @project)}
+ #{issuables_state_counter_text(type, :closed)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do
- #{state_filters_text_for(:closed, @project)}
+ #{issuables_state_counter_text(type, :closed)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do
- #{state_filters_text_for(:all, @project)}
+ #{issuables_state_counter_text(type, :all)}