summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2016-10-20 17:14:31 +0800
committerLin Jen-Shin <godfat@godfat.org>2016-10-20 17:14:31 +0800
commite5de4c77acbc128d1e8b8a0c3e469542ead8202f (patch)
treeb78b8a994814f5edb82011ab6641837b6d516eb8 /app
parentcefc7eb15bd7526a7c40fc0a1ca3534c2a12a0ef (diff)
parentb5cee66612fca4f1fb9791f1c8615039d1703df3 (diff)
downloadgitlab-ce-e5de4c77acbc128d1e8b8a0c3e469542ead8202f.tar.gz
Merge remote-tracking branch 'upstream/master' into pipeline-notifications
* upstream/master: (221 commits) Differentiate the expire from leave event Use LabelsFinder on Fogbuz importer Use LabelsFinder on Google Code importer Change the order of tested methods in project_members_controller_spec Remove show_menu_above attribute from issuable dropdowns. Spaces before `}`! Avoid touch label links that does not belongs to project when moving it Remove order by label type on LabelsFinder Rename Labels::CreateService to Labels::FindOrCreateService Add self.project_foreign_key on both Issue and MergeRequest Only show label type for projects that belong to a group Disable subscribing to group-level labels Remove unused method Project#all_labels Update specs to cope with new label types and priorities Fix max number of permitted priorities per project label Fix GitHub importer spec Update CHANGELOG Add support to group labels to SlashCommands::InterpretService Use join instead of subquery on Label.unprioritized scope Warn user deleting a group label affect all projects within the group ...
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/cycle_analytics.js.es62
-rw-r--r--app/assets/javascripts/dispatcher.js.es62
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es611
-rw-r--r--app/assets/javascripts/project_new.js38
-rw-r--r--app/assets/stylesheets/framework/files.scss1
-rw-r--r--app/assets/stylesheets/framework/layout.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/boards.scss3
-rw-r--r--app/assets/stylesheets/pages/builds.scss13
-rw-r--r--app/assets/stylesheets/pages/diff.scss1
-rw-r--r--app/assets/stylesheets/pages/environments.scss8
-rw-r--r--app/assets/stylesheets/pages/labels.scss23
-rw-r--r--app/assets/stylesheets/pages/login.scss9
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss9
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss49
-rw-r--r--app/assets/stylesheets/pages/projects.scss83
-rw-r--r--app/controllers/concerns/issuable_actions.rb5
-rw-r--r--app/controllers/dashboard/labels_controller.rb4
-rw-r--r--app/controllers/groups/labels_controller.rb92
-rw-r--r--app/controllers/projects/boards/issues_controller.rb4
-rw-r--r--app/controllers/projects/boards/lists_controller.rb5
-rw-r--r--app/controllers/projects/environments_controller.rb25
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/labels_controller.rb38
-rw-r--r--app/controllers/projects/merge_requests_controller.rb32
-rw-r--r--app/controllers/projects_controller.rb35
-rw-r--r--app/finders/issuable_finder.rb17
-rw-r--r--app/finders/labels_finder.rb92
-rw-r--r--app/helpers/award_emoji_helper.rb9
-rw-r--r--app/helpers/builds_helper.rb8
-rw-r--r--app/helpers/events_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb82
-rw-r--r--app/helpers/preferences_helper.rb16
-rw-r--r--app/helpers/projects_helper.rb33
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/concerns/expirable.rb6
-rw-r--r--app/models/concerns/has_status.rb1
-rw-r--r--app/models/concerns/issuable.rb22
-rw-r--r--app/models/concerns/sortable.rb10
-rw-r--r--app/models/deployment.rb13
-rw-r--r--app/models/environment.rb28
-rw-r--r--app/models/event.rb9
-rw-r--r--app/models/external_issue.rb5
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/group_label.rb11
-rw-r--r--app/models/issue.rb16
-rw-r--r--app/models/label.rb129
-rw-r--r--app/models/label_priority.rb8
-rw-r--r--app/models/list.rb11
-rw-r--r--app/models/members/project_member.rb6
-rw-r--r--app/models/merge_request.rb26
-rw-r--r--app/models/project.rb18
-rw-r--r--app/models/project_feature.rb19
-rw-r--r--app/models/project_label.rb34
-rw-r--r--app/models/project_services/issue_tracker_service.rb6
-rw-r--r--app/models/project_services/jira_service.rb5
-rw-r--r--app/models/todo.rb8
-rw-r--r--app/policies/group_label_policy.rb5
-rw-r--r--app/policies/group_policy.rb1
-rw-r--r--app/policies/project_label_policy.rb5
-rw-r--r--app/policies/project_policy.rb14
-rw-r--r--app/services/boards/lists/create_service.rb6
-rw-r--r--app/services/boards/lists/generate_service.rb3
-rw-r--r--app/services/create_deployment_service.rb21
-rw-r--r--app/services/event_create_service.rb4
-rw-r--r--app/services/issuable_base_service.rb15
-rw-r--r--app/services/issues/move_service.rb8
-rw-r--r--app/services/labels/find_or_create_service.rb33
-rw-r--r--app/services/labels/transfer_service.rb78
-rw-r--r--app/services/merge_requests/merge_service.rb20
-rw-r--r--app/services/projects/autocomplete_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb4
-rw-r--r--app/services/slash_commands/interpret_service.rb6
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml4
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml4
-rw-r--r--app/views/devise/shared/_signin_box.html.haml29
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml6
-rw-r--r--app/views/groups/labels/destroy.js.haml2
-rw-r--r--app/views/groups/labels/edit.html.haml7
-rw-r--r--app/views/groups/labels/index.html.haml20
-rw-r--r--app/views/groups/labels/new.html.haml8
-rw-r--r--app/views/layouts/nav/_group.html.haml4
-rw-r--r--app/views/notify/pipeline_failed_email.html.haml4
-rw-r--r--app/views/notify/pipeline_success_email.html.haml4
-rw-r--r--app/views/projects/_customize_workflow.html.haml8
-rw-r--r--app/views/projects/_home_panel.html.haml5
-rw-r--r--app/views/projects/_wiki.html.haml19
-rw-r--r--app/views/projects/builds/_sidebar.html.haml17
-rw-r--r--app/views/projects/builds/_user.html.haml7
-rw-r--r--app/views/projects/ci/builds/_build.html.haml9
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml47
-rw-r--r--app/views/projects/commit/_pipelines_list.haml1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml4
-rw-r--r--app/views/projects/deployments/_actions.haml41
-rw-r--r--app/views/projects/deployments/_commit.html.haml2
-rw-r--r--app/views/projects/deployments/_deployment.html.haml4
-rw-r--r--app/views/projects/deployments/_rollback.haml6
-rw-r--r--app/views/projects/edit.html.haml56
-rw-r--r--app/views/projects/environments/_environment.html.haml6
-rw-r--r--app/views/projects/environments/_external_url.html.haml3
-rw-r--r--app/views/projects/environments/_stop.html.haml5
-rw-r--r--app/views/projects/environments/index.html.haml19
-rw-r--r--app/views/projects/environments/show.html.haml6
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/_issues.html.haml2
-rw-r--r--app/views/projects/issues/edit.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/labels/_label.html.haml50
-rw-r--r--app/views/projects/labels/destroy.js.haml2
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml13
-rw-r--r--app/views/projects/labels/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml12
-rw-r--r--app/views/projects/merge_requests/_show.html.haml4
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/pipelines/index.html.haml11
-rw-r--r--app/views/projects/pipelines_settings/show.html.haml6
-rw-r--r--app/views/projects/show.html.haml130
-rw-r--r--app/views/shared/_label.html.haml53
-rw-r--r--app/views/shared/_label_row.html.haml7
-rw-r--r--app/views/shared/_labels_row.html.haml2
-rw-r--r--app/views/shared/issuable/_filter.html.haml14
-rw-r--r--app/views/shared/issuable/_form.html.haml8
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/labels/_form.html.haml (renamed from app/views/projects/labels/_form.html.haml)4
-rw-r--r--app/workers/expire_build_instance_artifacts_worker.rb10
129 files changed, 1514 insertions, 582 deletions
diff --git a/app/assets/javascripts/cycle_analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6
index cd9886ba58d..bd9accacb8c 100644
--- a/app/assets/javascripts/cycle_analytics.js.es6
+++ b/app/assets/javascripts/cycle_analytics.js.es6
@@ -1,3 +1,5 @@
+//= require vue
+
((global) => {
const COOKIE_NAME = 'cycle_analytics_help_dismissed';
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 73691f40c74..afc0d6f8c62 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -168,6 +168,8 @@
shortcut_handler = new ShortcutsNavigation();
new ShortcutsBlob(true);
break;
+ case 'groups:labels:new':
+ case 'groups:labels:edit':
case 'projects:labels:new':
case 'projects:labels:edit':
new Labels();
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index f1e719937c7..b4f6e70f694 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -266,7 +266,7 @@
},
fieldName: $dropdown.data('field-name'),
id: function(label) {
- if (label.id <= 0) return;
+ if (label.id <= 0) return label.title;
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
return label.id;
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index fcadc4bc515..3ff6851d59b 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -17,6 +17,12 @@
View on <%- external_url_formatted %>
</a>
</span>
+ <span class="stop-env-container js-stop-env-link">
+ <a href="<%- stop_url %>" class="close-evn-link" data-method="post" rel="nofollow" data-confirm="Are you sure you want to stop this environment?">
+ <i class="fa fa-stop-circle-o"/>
+ Stop environment
+ </a>
+ </span>
</div>
</div>`;
@@ -205,6 +211,11 @@
if ($(`.mr-state-widget #${ environment.id }`).length) return;
const $template = $(DEPLOYMENT_TEMPLATE);
if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
+
+ if (!environment.stop_url) {
+ $('.js-stop-env-link', $template).remove();
+ }
+
if (environment.deployed_at && environment.deployed_at_formatted) {
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
} else {
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index 3cf41505814..478e82aa14d 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -4,9 +4,8 @@
this.ProjectNew = (function() {
function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this);
- this.$selects = $('.features select').filter(function () {
- return $(this).data('field');
- });
+ this.$selects = $('.features select');
+ this.$repoSelects = this.$selects.filter('.js-repo-select');
$('.project-edit-container').on('ajax:before', (function(_this) {
return function() {
@@ -16,6 +15,7 @@
})(this));
this.toggleSettings();
this.toggleSettingsOnclick();
+ this.toggleRepoVisibility();
}
ProjectNew.prototype.toggleSettings = function() {
@@ -43,6 +43,38 @@
}
};
+ ProjectNew.prototype.toggleRepoVisibility = function () {
+ var $repoAccessLevel = $('.js-repo-access-level select');
+
+ this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
+ .nextAll()
+ .hide();
+
+ $repoAccessLevel.off('change')
+ .on('change', function () {
+ var selectedVal = parseInt($repoAccessLevel.val());
+
+ this.$repoSelects.each(function () {
+ var $this = $(this),
+ repoSelectVal = parseInt($this.val());
+
+ $this.find('option').show();
+
+ if (selectedVal < repoSelectVal) {
+ $this.val(selectedVal);
+ }
+
+ $this.find("option[value='" + selectedVal + "']").nextAll().hide();
+ });
+
+ if (selectedVal) {
+ this.$repoSelects.removeClass('disabled');
+ } else {
+ this.$repoSelects.addClass('disabled');
+ }
+ }.bind(this));
+ };
+
return ProjectNew;
})();
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 13c1bbf0359..f49d7b92a00 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -167,7 +167,6 @@
*/
&.code {
padding: 0;
- -webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
}
}
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 8bb047db2dd..7baa4296abf 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -27,3 +27,15 @@ body {
.container-limited {
max-width: $fixed-layout-width;
}
+
+
+/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch,
+which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side
+effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children
+of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */
+
+.navbar,
+.page-gutter,
+.page-with-sidebar {
+ -webkit-overflow-scrolling: auto;
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7690d65de8e..eafe84570a8 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -16,6 +16,7 @@ $white-light: #fff;
$white-normal: #ededed;
$white-dark: #ececec;
+$gray-lightest: #fdfdfd;
$gray-light: #fafafa;
$gray-lighter: #f9f9f9;
$gray-normal: #f5f5f5;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 6e81c12aa55..d8fabbdcebe 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -1,4 +1,3 @@
-lex
[v-cloak] {
display: none;
}
@@ -132,7 +131,7 @@ lex
}
.board-blank-state {
- height: 100%;
+ height: calc(100% - 49px);
padding: $gl-padding;
background-color: #fff;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 2fbf0cf34bf..d6a55fbd464 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -195,7 +195,7 @@
.build-job {
position: relative;
- .fa {
+ .fa-arrow-right {
position: absolute;
left: 15px;
top: 20px;
@@ -205,14 +205,23 @@
&.active {
font-weight: bold;
- .fa {
+ .fa-arrow-right {
display: block;
}
}
+ &.retried {
+ background-color: $gray-lightest;
+ }
+
&:hover {
background-color: $row-hover;
}
+
+ .fa-refresh {
+ font-size: 13px;
+ margin-left: 3px;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index bdc82a8f0f5..fe6421f8b3f 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -52,7 +52,6 @@
background: #fff;
color: #333;
border-radius: 0 0 3px 3px;
- -webkit-overflow-scrolling: auto;
.unfold {
cursor: pointer;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 820cc0fc991..12ee0a5dc3d 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -38,6 +38,14 @@
color: $gl-dark-link-color;
}
+ .stop-env-link {
+ color: $table-text-gray;
+
+ .stop-env-icon {
+ font-size: 14px;
+ }
+ }
+
.deployment {
.build-column {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 9bac6d46355..397f89f501a 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -66,7 +66,21 @@
text-overflow: ellipsis;
vertical-align: middle;
max-width: 100%;
- }
+ }
+ }
+
+ .label-type {
+ display: block;
+ margin-bottom: 10px;
+ margin-left: 50px;
+
+ @media (min-width: $screen-sm-min) {
+ display: inline-block;
+ width: 100px;
+ margin-left: 10px;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
}
.label-description {
@@ -209,6 +223,13 @@
}
.label-subscribe-button {
+ .label-subscribe-button-icon {
+ &[disabled] {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ }
+
.label-subscribe-button-loading {
display: none;
}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 4c0c0839fe2..e6d9be5185d 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -124,15 +124,20 @@
display: -webkit-flex;
display: flex;
box-shadow: 0 0 0 1px $border-color;
- border-top-right-radius: 2px;
- border-top-left-radius: 2px;
+ border-top-right-radius: $border-radius-default;
+ border-top-left-radius: $border-radius-default;
li {
flex: 1;
text-align: center;
+ &:first-of-type {
+ border-top-left-radius: $border-radius-default;
+ }
+
&:last-of-type {
border-left: 1px solid $border-color;
+ border-top-right-radius: $border-radius-default;
}
&:not(.active) {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 101472278e2..35a1877df95 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -183,6 +183,15 @@
.ci-coverage {
float: right;
}
+
+ .stop-env-container {
+ color: $gl-text-color;
+ float: right;
+
+ a {
+ color: $gl-text-color;
+ }
+ }
}
.mr_source_commit,
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 7b71876b822..dfaf1ab732d 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -23,10 +23,17 @@
.table.ci-table {
min-width: 1200px;
- .branch-commit {
- width: 33%;
+ .pipeline-id {
+ color: $black;
}
+ .branch-commit {
+ width: 30%;
+
+ .branch-name {
+ max-width: 195px;
+ }
+ }
}
}
@@ -73,6 +80,10 @@
border-top-width: 1px;
}
+ .build.retried {
+ background-color: $gray-lightest;
+ }
+
.commit-link {
.ci-status {
@@ -88,6 +99,15 @@
}
}
+ .avatar {
+ margin-left: 0;
+ float: none;
+ }
+
+ .api {
+ color: $code-color;
+ }
+
.branch-commit {
.branch-name {
@@ -109,12 +129,11 @@
.fa {
font-size: 12px;
- color: $table-text-gray;
+ color: $gl-text-color;
}
.commit-id {
color: $gl-link-color;
- margin-right: 8px;
}
.commit-title {
@@ -125,10 +144,6 @@
text-overflow: ellipsis;
}
- .avatar {
- margin-left: 0;
- }
-
.label {
margin-right: 4px;
}
@@ -144,17 +159,11 @@
.icon-container {
display: inline-block;
- text-align: right;
- width: 15px;
+ width: 10px;
- .fa {
- position: relative;
- right: 3px;
- }
-
- svg {
- position: relative;
- right: 1px;
+ &.commit-icon {
+ width: 15px;
+ text-align: center;
}
}
@@ -634,6 +643,10 @@
&.pipelines {
+ .ci-table {
+ min-width: 900px;
+ }
+
.content-list.pipelines {
overflow: auto;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index d30f02340b9..1062d7effb0 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -761,62 +761,6 @@ 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 {
@@ -855,3 +799,30 @@ pre.light-well {
border-bottom-right-radius: 0;
}
}
+
+.project-home-empty {
+ border-top: 0;
+
+ .container-fluid {
+ background: none;
+ }
+
+ p {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 650px;
+ }
+}
+
+.project-feature-nested {
+ @media (min-width: $screen-sm-min) {
+ padding-left: 45px;
+ }
+}
+
+.project-repo-select {
+ &.disabled {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+}
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index bb32bc502e6..be86fa106f8 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -2,6 +2,7 @@ module IssuableActions
extend ActiveSupport::Concern
included do
+ before_action :labels, only: [:show, :new, :edit]
before_action :authorize_destroy_issuable!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
end
@@ -25,6 +26,10 @@ module IssuableActions
private
+ def labels
+ @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+ end
+
def authorize_destroy_issuable!
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb
index 2a88350a4ca..d5031da867a 100644
--- a/app/controllers/dashboard/labels_controller.rb
+++ b/app/controllers/dashboard/labels_controller.rb
@@ -1,9 +1,9 @@
class Dashboard::LabelsController < Dashboard::ApplicationController
def index
- labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title)
+ labels = LabelsFinder.new(current_user).execute
respond_to do |format|
- format.json { render json: labels }
+ format.json { render json: labels.as_json(only: [:id, :title, :color]) }
end
end
end
diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb
new file mode 100644
index 00000000000..29528b2cfaa
--- /dev/null
+++ b/app/controllers/groups/labels_controller.rb
@@ -0,0 +1,92 @@
+class Groups::LabelsController < Groups::ApplicationController
+ before_action :label, only: [:edit, :update, :destroy]
+ before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy]
+ before_action :save_previous_label_path, only: [:edit]
+
+ respond_to :html
+
+ def index
+ respond_to do |format|
+ format.html do
+ @labels = @group.labels.page(params[:page])
+ end
+
+ format.json do
+ available_labels = LabelsFinder.new(current_user, group_id: @group.id).execute
+ render json: available_labels.as_json(only: [:id, :title, :color])
+ end
+ end
+ end
+
+ def new
+ @label = @group.labels.new
+ @previous_labels_path = previous_labels_path
+ end
+
+ def create
+ @label = @group.labels.create(label_params)
+
+ if @label.valid?
+ redirect_to group_labels_path(@group)
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @previous_labels_path = previous_labels_path
+ end
+
+ def update
+ if @label.update_attributes(label_params)
+ redirect_back_or_group_labels_path
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @label.destroy
+
+ respond_to do |format|
+ format.html do
+ redirect_to group_labels_path(@group), notice: 'Label was removed'
+ end
+ format.js
+ end
+ end
+
+ protected
+
+ def authorize_admin_labels!
+ return render_404 unless can?(current_user, :admin_label, @group)
+ end
+
+ def authorize_read_labels!
+ return render_404 unless can?(current_user, :read_label, @group)
+ end
+
+ def label
+ @label ||= @group.labels.find(params[:id])
+ end
+
+ def label_params
+ params.require(:label).permit(:title, :description, :color)
+ end
+
+ def redirect_back_or_group_labels_path(options = {})
+ redirect_to previous_labels_path, options
+ end
+
+ def previous_labels_path
+ session.fetch(:previous_labels_path, fallback_path)
+ end
+
+ def fallback_path
+ group_labels_path(@group)
+ end
+
+ def save_previous_label_path
+ session[:previous_labels_path] = URI(request.referer || '').path
+ end
+end
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 71eb56aed0b..a2b01ff43dc 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -72,10 +72,10 @@ module Projects
def serialize_as_json(resource)
resource.as_json(
+ labels: true,
only: [:iid, :title, :confidential],
include: {
- assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
- labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
+ assignee: { only: [:id, :name, :username], methods: [:avatar_url] }
})
end
end
diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb
index 76ae41319c4..67e3c9add81 100644
--- a/app/controllers/projects/boards/lists_controller.rb
+++ b/app/controllers/projects/boards/lists_controller.rb
@@ -76,9 +76,8 @@ module Projects
resource.as_json(
only: [:id, :list_type, :position],
methods: [:title],
- include: {
- label: { only: [:id, :title, :description, :color, :priority] }
- })
+ label: true
+ )
end
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 58678f96879..ea22b2dcc15 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -2,11 +2,19 @@ class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create]
- before_action :authorize_update_environment!, only: [:edit, :update, :destroy]
- before_action :environment, only: [:show, :edit, :update, :destroy]
+ before_action :authorize_create_deployment!, only: [:stop]
+ before_action :authorize_update_environment!, only: [:edit, :update]
+ before_action :environment, only: [:show, :edit, :update, :stop]
def index
- @environments = project.environments
+ @scope = params[:scope]
+ @all_environments = project.environments
+ @environments =
+ if @scope == 'stopped'
+ @all_environments.stopped
+ else
+ @all_environments.available
+ end
end
def show
@@ -38,14 +46,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
- def destroy
- if @environment.destroy
- flash[:notice] = 'Environment was successfully removed.'
- else
- flash[:alert] = 'Failed to remove environment.'
- end
+ def stop
+ return render_404 unless @environment.stoppable?
- redirect_to namespace_project_environments_path(project.namespace, project)
+ new_action = @environment.stop!(current_user)
+ redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, new_action])
end
private
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 96041b07647..cb649264146 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -26,7 +26,9 @@ class Projects::IssuesController < Projects::ApplicationController
@issues = issues_collection
@issues = @issues.page(params[:page])
- @labels = @project.labels.where(title: params[:label_name])
+ if params[:label_name].present?
+ @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
+ end
respond_to do |format|
format.html
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index a6626df4826..4f855134368 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -3,21 +3,22 @@ class Projects::LabelsController < Projects::ApplicationController
before_action :module_enabled
before_action :label, only: [:edit, :update, :destroy]
+ before_action :find_labels, only: [:index, :set_priorities, :remove_priority]
before_action :authorize_read_label!
- before_action :authorize_admin_labels!, only: [
- :new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities
- ]
+ before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
+ :generate, :destroy, :remove_priority,
+ :set_priorities]
respond_to :js, :html
def index
- @labels = @project.labels.unprioritized.page(params[:page])
- @prioritized_labels = @project.labels.prioritized
+ @prioritized_labels = @available_labels.prioritized(@project)
+ @labels = @available_labels.unprioritized(@project).page(params[:page])
respond_to do |format|
format.html
format.json do
- render json: @project.labels
+ render json: @available_labels.as_json(only: [:id, :title, :color])
end
end
end
@@ -36,7 +37,7 @@ class Projects::LabelsController < Projects::ApplicationController
end
else
respond_to do |format|
- format.html { render 'new' }
+ format.html { render :new }
format.json { render json: { message: @label.errors.messages }, status: 400 }
end
end
@@ -49,7 +50,7 @@ class Projects::LabelsController < Projects::ApplicationController
if @label.update_attributes(label_params)
redirect_to namespace_project_labels_path(@project.namespace, @project)
else
- render 'edit'
+ render :edit
end
end
@@ -68,6 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController
def destroy
@label.destroy
+ @labels = find_labels
respond_to do |format|
format.html do
@@ -80,20 +82,24 @@ class Projects::LabelsController < Projects::ApplicationController
def remove_priority
respond_to do |format|
- if label.update_attribute(:priority, nil)
+ label = @available_labels.find(params[:id])
+
+ if label.unprioritize!(project)
format.json { render json: label }
else
- message = label.errors.full_messages.uniq.join('. ')
- format.json { render json: { message: message }, status: :unprocessable_entity }
+ format.json { head :unprocessable_entity }
end
end
end
def set_priorities
Label.transaction do
- params[:label_ids].each_with_index do |label_id, index|
- label = @project.labels.find_by_id(label_id)
- label.update_attribute(:priority, index) if label
+ available_labels_ids = @available_labels.where(id: params[:label_ids]).pluck(:id)
+ label_ids = params[:label_ids].select { |id| available_labels_ids.include?(id.to_i) }
+
+ label_ids.each_with_index do |label_id, index|
+ label = @available_labels.find(label_id)
+ label.prioritize!(project, index)
end
end
@@ -119,6 +125,10 @@ class Projects::LabelsController < Projects::ApplicationController
end
alias_method :subscribable_resource, :label
+ def find_labels
+ @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities)
+ end
+
def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index a39b47b6d95..0f593d1a936 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -40,7 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
- @labels = @project.labels.where(title: params[:label_name])
+ if params[:label_name].present?
+ labels_params = { project_id: @project.id, title: params[:label_name] }
+ @labels = LabelsFinder.new(current_user, labels_params).execute
+ end
respond_to do |format|
format.html
@@ -422,10 +425,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
project = environment.project
deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
+ stop_url =
+ if environment.stoppable? && can?(current_user, :create_deployment, environment)
+ stop_namespace_project_environment_path(project.namespace, project, environment)
+ end
+
{
id: environment.id,
name: environment.name,
url: namespace_project_environment_path(project.namespace, project, environment),
+ stop_url: stop_url,
external_url: environment.external_url,
external_url_formatted: environment.formatted_external_url,
deployed_at: deployment.try(:created_at),
@@ -483,13 +492,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@noteable = @merge_request
@commits_count = @merge_request.commits.count
- @pipeline = @merge_request.pipeline
- @statuses = @pipeline.statuses.relevant if @pipeline
-
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
+
+ define_pipelines_vars
end
# Discussion tab data is rendered on html responses of actions
@@ -517,7 +525,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@pipeline = @merge_request.pipeline
- @pipelines = [@pipeline].compact
end
def define_commit_vars
@@ -544,6 +551,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
)
end
+ def define_pipelines_vars
+ @pipelines = @merge_request.all_pipelines
+
+ if @pipelines.any?
+ @pipeline = @pipelines.first
+ @statuses = @pipeline.statuses.relevant
+ end
+ end
+
def define_new_vars
@noteable = @merge_request
@@ -559,10 +575,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
- @pipeline = @merge_request.pipeline
- @statuses = @pipeline.statuses.relevant if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
+
+ @labels = LabelsFinder.new(current_user, project_id: @project.id).execute
+
+ define_pipelines_vars
end
def invalid_mr
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 62916270172..76b730198d4 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,4 +1,5 @@
class ProjectsController < Projects::ApplicationController
+ include IssuableCollections
include ExtractsPath
before_action :authenticate_user!, except: [:show, :activity, :refs]
@@ -103,16 +104,7 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
format.html do
@notification_setting = current_user.notification_settings_for(@project) if current_user
-
- if @project.repository_exists?
- if @project.empty_repo?
- render 'projects/empty'
- else
- render :show
- end
- else
- render 'projects/no_repo'
- end
+ render_landing_page
end
format.atom do
@@ -285,6 +277,26 @@ class ProjectsController < Projects::ApplicationController
private
+ # Render project landing depending of which features are available
+ # So if page is not availble in the list it renders the next page
+ #
+ # pages list order: repository readme, wiki home, issues list, customize workflow
+ def render_landing_page
+ if @project.feature_available?(:repository, current_user)
+ return render 'projects/no_repo' unless @project.repository_exists?
+ render 'projects/empty' if @project.empty_repo?
+ else
+ if @project.wiki_enabled?
+ @wiki_home = @project.wiki.find_page('home', params[:version_id])
+ elsif @project.feature_available?(:issues, current_user)
+ @issues = issues_collection
+ @issues = @issues.page(params[:page])
+ end
+
+ render :show
+ end
+ end
+
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'application'
@@ -308,7 +320,8 @@ class ProjectsController < Projects::ApplicationController
project_feature_attributes:
[
:issues_access_level, :builds_access_level,
- :wiki_access_level, :merge_requests_access_level, :snippets_access_level
+ :wiki_access_level, :merge_requests_access_level,
+ :snippets_access_level, :repository_access_level
]
}
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9f170428100..e27986ef95b 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -124,15 +124,12 @@ class IssuableFinder
def labels
return @labels if defined?(@labels)
- if labels? && !filter_by_no_label?
- @labels = Label.where(title: label_names)
-
- if projects
- @labels = @labels.where(project: projects)
+ @labels =
+ if labels? && !filter_by_no_label?
+ LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute
+ else
+ Label.none
end
- else
- @labels = Label.none
- end
end
def assignee?
@@ -274,8 +271,10 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names, params[:sort])
+
if projects
- items = items.where(labels: { project_id: projects })
+ label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id)
+ items = items.where(labels: { id: label_ids })
end
end
end
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
new file mode 100644
index 00000000000..6ace14a4bb5
--- /dev/null
+++ b/app/finders/labels_finder.rb
@@ -0,0 +1,92 @@
+class LabelsFinder < UnionFinder
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute(authorized_only: true)
+ @authorized_only = authorized_only
+
+ items = find_union(label_ids, Label)
+ items = with_title(items)
+ sort(items)
+ end
+
+ private
+
+ attr_reader :current_user, :params, :authorized_only
+
+ def label_ids
+ label_ids = []
+
+ if project
+ label_ids << project.group.labels if project.group.present?
+ label_ids << project.labels
+ else
+ label_ids << Label.where(group_id: projects.group_ids)
+ label_ids << Label.where(project_id: projects.select(:id))
+ end
+
+ label_ids
+ end
+
+ def sort(items)
+ items.reorder(title: :asc)
+ end
+
+ def with_title(items)
+ items = items.where(title: title) if title
+ items
+ end
+
+ def group_id
+ params[:group_id].presence
+ end
+
+ def project_id
+ params[:project_id].presence
+ end
+
+ def projects_ids
+ params[:project_ids].presence
+ end
+
+ def title
+ params[:title].presence || params[:name].presence
+ end
+
+ def project
+ return @project if defined?(@project)
+
+ if project_id
+ @project = find_project
+ else
+ @project = nil
+ end
+
+ @project
+ end
+
+ def find_project
+ if authorized_only
+ available_projects.find_by(id: project_id)
+ else
+ Project.find_by(id: project_id)
+ end
+ end
+
+ def projects
+ return @projects if defined?(@projects)
+
+ @projects = authorized_only ? available_projects : Project.all
+ @projects = @projects.in_namespace(group_id) if group_id
+ @projects = @projects.where(id: projects_ids) if projects_ids
+ @projects = @projects.reorder(nil)
+
+ @projects
+ end
+
+ def available_projects
+ @available_projects ||= ProjectsFinder.new.execute(current_user)
+ end
+end
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index aa134cea31c..592ffe7b89f 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -1,9 +1,12 @@
module AwardEmojiHelper
def toggle_award_url(awardable)
- if @project
- url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
+ return url_for([:toggle_award_emoji, awardable]) unless @project
+
+ if awardable.is_a?(Note)
+ # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (6.5x)
+ toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace, project_id: @project, id: awardable.id)
else
- url_for([:toggle_award_emoji, awardable])
+ url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
end
end
end
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
new file mode 100644
index 00000000000..f3aaff9140d
--- /dev/null
+++ b/app/helpers/builds_helper.rb
@@ -0,0 +1,8 @@
+module BuildsHelper
+ def sidebar_build_class(build, current_build)
+ build_class = ''
+ build_class += ' active' if build == current_build
+ build_class += ' retried' if build.retried?
+ build_class
+ end
+end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index bfedcb1c42b..f8ded05c31a 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -154,7 +154,7 @@ module EventsHelper
end
def event_commit_title(message)
- escape_once(truncate(message.split("\n").first, length: 70))
+ (message.split("\n").first || "").truncate(70)
rescue
"--broken encoding"
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 692fadd505f..03b2db1bc91 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -124,6 +124,10 @@ module IssuablesHelper
end
end
+ def issuable_filters_present
+ params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name]
+ end
+
def issuables_count_for_state(issuable_type, state)
issuables_finder = public_send("#{issuable_type}_finder")
issuables_finder.params[:state] = state
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index b9f3d6c75c2..221a84b042f 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -4,9 +4,8 @@ module LabelsHelper
# Link to a Label
#
# label - Label object to link to
- # project - Project object which will be used as the context for the label's
- # link. If omitted, defaults to `@project`, or the label's own
- # project.
+ # subject - Project/Group object which will be used as the context for the
+ # label's link. If omitted, defaults to the label's own group/project.
# type - The type of item the link will point to (:issue or
# :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the
@@ -15,15 +14,14 @@ module LabelsHelper
#
# Examples:
#
- # # Allow the generated link to use the label's own project
+ # # Allow the generated link to use the label's own subject
# link_to_label(label)
#
- # # Force the generated link to use @project
- # @project = Project.first
- # link_to_label(label)
+ # # Force the generated link to use a provided group
+ # link_to_label(label, subject: Group.last)
#
# # Force the generated link to use a provided project
- # link_to_label(label, project: Project.last)
+ # link_to_label(label, subject: Project.last)
#
# # Force the generated link to point to merge requests instead of issues
# link_to_label(label, type: :merge_request)
@@ -32,9 +30,8 @@ module LabelsHelper
# link_to_label(label) { "My Custom Label Text" }
#
# Returns a String
- def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block)
- project ||= @project || label.project
- link = label_filter_path(project, label, type: type)
+ def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block)
+ link = label_filter_path(subject || label.subject, label, type: type)
if block_given?
link_to link, class: css_class, &block
@@ -43,15 +40,40 @@ module LabelsHelper
end
end
- def label_filter_path(project, label, type: issue)
- send("namespace_project_#{type.to_s.pluralize}_path",
- project.namespace,
- project,
- label_name: [label.name])
+ def label_filter_path(subject, label, type: :issue)
+ case subject
+ when Group
+ send("#{type.to_s.pluralize}_group_path",
+ subject,
+ label_name: [label.name])
+ when Project
+ send("namespace_project_#{type.to_s.pluralize}_path",
+ subject.namespace,
+ subject,
+ label_name: [label.name])
+ end
+ end
+
+ def edit_label_path(label)
+ case label
+ when GroupLabel then edit_group_label_path(label.group, label)
+ when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label)
+ end
+ end
+
+ def destroy_label_path(label)
+ case label
+ when GroupLabel then group_label_path(label.group, label)
+ when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label)
+ end
end
- def project_label_names
- @project.labels.pluck(:title)
+ def toggle_subscription_data(label)
+ return unless label.is_a?(ProjectLabel)
+
+ {
+ url: toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label)
+ }
end
def render_colored_label(label, label_suffix = '', tooltip: true)
@@ -68,8 +90,8 @@ module LabelsHelper
span.html_safe
end
- def render_colored_cross_project_label(label, tooltip: true)
- label_suffix = label.project.name_with_namespace
+ def render_colored_cross_project_label(label, source_project = nil, tooltip: true)
+ label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
render_colored_label(label, label_suffix, tooltip: tooltip)
end
@@ -115,7 +137,10 @@ module LabelsHelper
end
def labels_filter_path
+ return group_labels_path(@group, :json) if @group
+
project = @target_project || @project
+
if project
namespace_project_labels_path(project.namespace, project, :json)
else
@@ -124,11 +149,24 @@ module LabelsHelper
end
def label_subscription_status(label)
- label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed'
+ case label
+ when GroupLabel then 'Subscribing to group labels is currently not supported.'
+ when ProjectLabel then label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed'
+ end
end
def label_subscription_toggle_button_text(label)
- label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe'
+ case label
+ when GroupLabel then 'Subscribing to group labels is currently not supported.'
+ when ProjectLabel then label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe'
+ end
+ end
+
+ def label_deletion_confirm_text(label)
+ case label
+ when GroupLabel then 'Remove this label? This will affect all projects within the group. Are you sure?'
+ when ProjectLabel then 'Remove this label? Are you sure?'
+ end
end
# Required for Banzai::Filter::LabelReferenceFilter
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index c3832cf5d65..a46f2c6e17d 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -50,6 +50,20 @@ module PreferencesHelper
end
def default_project_view
- current_user ? current_user.project_view : 'readme'
+ return 'readme' unless current_user
+
+ user_view = current_user.project_view
+
+ if @project.feature_available?(:repository, current_user)
+ user_view
+ elsif user_view == "activity"
+ "activity"
+ elsif @project.wiki_enabled?
+ "wiki"
+ elsif @project.feature_available?(:issues, current_user)
+ "projects/issues/issues"
+ else
+ "customize_workflow"
+ end
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index e667c9e4e2e..d26b4018be6 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -134,16 +134,35 @@ module ProjectsHelper
options = project_feature_options
if @project.private?
+ level = @project.project_feature.send(field)
options.delete('Everyone with access')
- highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED
+ highest_available_option = options.values.max if level == ProjectFeature::ENABLED
end
options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
- content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe
+
+ content_tag(
+ :select,
+ options,
+ name: "project[project_feature_attributes][#{field}]",
+ id: "project_project_feature_attributes_#{field}",
+ class: "pull-right form-control #{repo_children_classes(field)}",
+ data: { field: field }
+ ).html_safe
end
private
+ def repo_children_classes(field)
+ needs_repo_check = [:merge_requests_access_level, :builds_access_level]
+ return unless needs_repo_check.include?(field)
+
+ classes = "project-repo-select js-repo-select"
+ classes << " disabled" unless @project.feature_available?(:repository, current_user)
+
+ classes
+ end
+
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
@@ -155,12 +174,8 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
- if can?(current_user, :read_pipeline, project)
- nav_tabs << :pipelines
- end
-
if can?(current_user, :read_build, project)
- nav_tabs << :builds
+ nav_tabs << :pipelines
end
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
@@ -435,4 +450,8 @@ module ProjectsHelper
'Everyone with access' => ProjectFeature::ENABLED
}
end
+
+ def project_child_container_class(view_path)
+ view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
+ end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index d1c68300067..66bab722110 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -19,7 +19,7 @@ module Ci
validates_presence_of :status, unless: :importing?
validate :valid_commit_sha, unless: :importing?
- after_save :keep_around_commits, unless: :importing?
+ after_create :keep_around_commits, unless: :importing?
delegate :stages, to: :statuses
@@ -165,7 +165,7 @@ module Ci
def retryable?
builds.latest.any? do |build|
- build.failed? && build.retryable?
+ (build.failed? || build.canceled?) && build.retryable?
end
end
diff --git a/app/models/concerns/expirable.rb b/app/models/concerns/expirable.rb
index be93435453b..b66ba08dc59 100644
--- a/app/models/concerns/expirable.rb
+++ b/app/models/concerns/expirable.rb
@@ -5,11 +5,15 @@ module Expirable
scope :expired, -> { where('expires_at <= ?', Time.current) }
end
+ def expired?
+ expires? && expires_at <= Time.current
+ end
+
def expires?
expires_at.present?
end
def expires_soon?
- expires_at < 7.days.from_now
+ expires? && expires_at < 7.days.from_now
end
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 9f64f76721d..ef3e73a4072 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -5,6 +5,7 @@ module HasStatus
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
COMPLETED_STATUSES = %w[success failed canceled]
+ ORDERED_STATUSES = %w[failed pending running canceled success skipped]
class_methods do
def status_sql
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index c4b42ad82c7..17c3b526c97 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -145,8 +145,14 @@ module Issuable
end
def order_labels_priority(excluded_labels: [])
- condition_field = "#{table_name}.id"
- highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql
+ params = {
+ target_type: name,
+ target_column: "#{table_name}.id",
+ project_column: "#{table_name}.#{project_foreign_key}",
+ excluded_labels: excluded_labels
+ }
+
+ highest_priority = highest_label_priority(params).to_sql
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
group(arel_table[:id]).
@@ -230,18 +236,6 @@ module Issuable
labels.order('title ASC').pluck(:title)
end
- def remove_labels
- labels.delete_all
- end
-
- def add_labels_by_names(label_names)
- label_names.each do |label_name|
- label = project.labels.create_with(color: Label::DEFAULT_COLOR).
- find_or_create_by(title: label_name.strip)
- self.labels << label
- end
- end
-
# Convert this Issuable class name to a format usable by Ability definitions
#
# Examples:
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 1ebecd86af9..12b23f00769 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -38,11 +38,13 @@ module Sortable
private
- def highest_label_priority(object_types, condition_field, excluded_labels: [])
- query = Label.select(Label.arel_table[:priority].minimum).
+ def highest_label_priority(target_type:, target_column:, project_column:, excluded_labels: [])
+ query = Label.select(LabelPriority.arel_table[:priority].minimum).
+ left_join_priorities.
joins(:label_links).
- where(label_links: { target_type: object_types }).
- where("label_links.target_id = #{condition_field}").
+ where("label_priorities.project_id = #{project_column}").
+ where(label_links: { target_type: target_type }).
+ where("label_links.target_id = #{target_column}").
reorder(nil)
query.where.not(title: excluded_labels) if excluded_labels.present?
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 3d9902d496e..1f8c5fb3d85 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -34,7 +34,7 @@ class Deployment < ActiveRecord::Base
end
def manual_actions
- deployable.try(:other_actions)
+ @manual_actions ||= deployable.try(:other_actions)
end
def includes_commit?(commit)
@@ -84,6 +84,17 @@ class Deployment < ActiveRecord::Base
take
end
+ def stop_action
+ return nil unless on_stop.present?
+ return nil unless manual_actions
+
+ @stop_action ||= manual_actions.find_by(name: on_stop)
+ end
+
+ def stoppable?
+ stop_action.present?
+ end
+
def formatted_deployment_time
created_at.to_time.in_time_zone.to_s(:medium)
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index d970bc0a005..d575f1dc73a 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -19,6 +19,24 @@ class Environment < ActiveRecord::Base
allow_nil: true,
addressable_url: true
+ delegate :stop_action, to: :last_deployment, allow_nil: true
+
+ scope :available, -> { with_state(:available) }
+ scope :stopped, -> { with_state(:stopped) }
+
+ state_machine :state, initial: :available do
+ event :start do
+ transition stopped: :available
+ end
+
+ event :stop do
+ transition available: :stopped
+ end
+
+ state :available
+ state :stopped
+ end
+
def last_deployment
deployments.last
end
@@ -66,4 +84,14 @@ class Environment < ActiveRecord::Base
external_url.gsub(/\A.*?:\/\//, '')
end
+
+ def stoppable?
+ available? && stop_action.present?
+ end
+
+ def stop!(current_user)
+ return unless stoppable?
+
+ stop_action.play(current_user)
+ end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 0764cb8cabd..3993b35f96d 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -12,6 +12,7 @@ class Event < ActiveRecord::Base
JOINED = 8 # User joined project
LEFT = 9 # User left project
DESTROYED = 10
+ EXPIRED = 11 # User left project due to expiry
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
@@ -115,6 +116,10 @@ class Event < ActiveRecord::Base
action == LEFT
end
+ def expired?
+ action == EXPIRED
+ end
+
def destroyed?
action == DESTROYED
end
@@ -124,7 +129,7 @@ class Event < ActiveRecord::Base
end
def membership_changed?
- joined? || left?
+ joined? || left? || expired?
end
def created_project?
@@ -184,6 +189,8 @@ class Event < ActiveRecord::Base
'joined'
elsif left?
'left'
+ elsif expired?
+ 'removed due to membership expiration from'
elsif destroyed?
'destroyed'
elsif commented?
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index b7894c99846..fd9a8c1b8b7 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -29,11 +29,6 @@ class ExternalIssue
@project
end
- # Pattern used to extract `JIRA-123` issue references from text
- def self.reference_pattern
- @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
- end
-
def to_reference(_from_project = nil)
id
end
diff --git a/app/models/group.rb b/app/models/group.rb
index a2f88cca828..00a595d2705 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -19,6 +19,7 @@ class Group < Namespace
has_many :project_group_links, dependent: :destroy
has_many :shared_projects, through: :project_group_links, source: :project
has_many :notification_settings, dependent: :destroy, as: :source
+ has_many :labels, class_name: 'GroupLabel'
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects
diff --git a/app/models/group_label.rb b/app/models/group_label.rb
new file mode 100644
index 00000000000..a698b532d19
--- /dev/null
+++ b/app/models/group_label.rb
@@ -0,0 +1,11 @@
+class GroupLabel < Label
+ belongs_to :group
+
+ validates :group, presence: true
+
+ alias_attribute :subject, :group
+
+ def to_reference(source_project = nil, target_project = nil, format: :id)
+ super(source_project, target_project, format: format)
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index abd58e0454a..133a5993815 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -138,6 +138,10 @@ class Issue < ActiveRecord::Base
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
end
+ def self.project_foreign_key
+ 'project_id'
+ end
+
def self.sort(method, excluded_labels: [])
case method.to_s
when 'due_date_asc' then order_due_date_asc
@@ -274,4 +278,16 @@ class Issue < ActiveRecord::Base
def check_for_spam?
project.public?
end
+
+ def as_json(options = {})
+ super(options).tap do |json|
+ if options.has_key?(:labels)
+ json[:labels] = labels.as_json(
+ project: project,
+ only: [:id, :title, :description, :color],
+ methods: [:text_color]
+ )
+ end
+ end
+ end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index e8e12e2904e..149fd98ecb3 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -15,34 +15,49 @@ class Label < ActiveRecord::Base
default_value_for :color, DEFAULT_COLOR
- belongs_to :project
-
has_many :lists, dependent: :destroy
+ has_many :priorities, class_name: 'LabelPriority'
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
validates :color, color: true, allow_blank: false
- validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow ',' for label titles
- validates :title,
- presence: true,
- format: { with: /\A[^,]+\z/ },
- uniqueness: { scope: :project_id }
-
- before_save :nullify_priority
+ validates :title, presence: true, format: { with: /\A[^,]+\z/ }
+ validates :title, uniqueness: { scope: [:group_id, :project_id] }
default_scope { order(title: :asc) }
- scope :templates, -> { where(template: true) }
+ scope :templates, -> { where(template: true) }
+ scope :with_title, ->(title) { where(title: title) }
+
+ def self.prioritized(project)
+ joins(:priorities)
+ .where(label_priorities: { project_id: project })
+ .reorder('label_priorities.priority ASC, labels.title ASC')
+ end
+
+ def self.unprioritized(project)
+ labels = Label.arel_table
+ priorities = LabelPriority.arel_table
+
+ label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin).
+ on(labels[:id].eq(priorities[:label_id]).and(priorities[:project_id].eq(project.id))).
+ join_sources
- def self.prioritized
- where.not(priority: nil).reorder(:priority, :title)
+ joins(label_priorities).where(priorities[:priority].eq(nil))
end
- def self.unprioritized
- where(priority: nil)
+ def self.left_join_priorities
+ labels = Label.arel_table
+ priorities = LabelPriority.arel_table
+
+ label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin).
+ on(labels[:id].eq(priorities[:label_id])).
+ join_sources
+
+ joins(label_priorities)
end
alias_attribute :name, :title
@@ -77,6 +92,44 @@ class Label < ActiveRecord::Base
nil
end
+ def open_issues_count(user = nil, project = nil)
+ issues_count(user, project_id: project.try(:id) || project_id, state: 'opened')
+ end
+
+ def closed_issues_count(user = nil, project = nil)
+ issues_count(user, project_id: project.try(:id) || project_id, state: 'closed')
+ end
+
+ def open_merge_requests_count(user = nil, project = nil)
+ merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened')
+ end
+
+ def prioritize!(project, value)
+ label_priority = priorities.find_or_initialize_by(project_id: project.id)
+ label_priority.priority = value
+ label_priority.save!
+ end
+
+ def unprioritize!(project)
+ priorities.where(project: project).delete_all
+ end
+
+ def priority(project)
+ priorities.find_by(project: project).try(:priority)
+ end
+
+ def template?
+ template
+ end
+
+ def text_color
+ LabelsHelper.text_color_for_bg(self.color)
+ end
+
+ def title=(value)
+ write_attribute(:title, sanitize_title(value)) if value.present?
+ end
+
##
# Returns the String necessary to reference this Label in Markdown
#
@@ -84,49 +137,47 @@ class Label < ActiveRecord::Base
#
# Examples:
#
- # Label.first.to_reference # => "~1"
- # Label.first.to_reference(format: :name) # => "~\"bug\""
- # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
+ # Label.first.to_reference # => "~1"
+ # Label.first.to_reference(format: :name) # => "~\"bug\""
+ # Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1"
#
# Returns a String
#
- def to_reference(from_project = nil, format: :id)
+ def to_reference(source_project = nil, target_project = nil, format: :id)
format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
- if cross_project_reference?(from_project)
- project.to_reference + reference
+ if cross_project_reference?(source_project, target_project)
+ source_project.to_reference + reference
else
reference
end
end
- def open_issues_count(user = nil)
- issues.visible_to_user(user).opened.count
- end
-
- def closed_issues_count(user = nil)
- issues.visible_to_user(user).closed.count
+ def as_json(options = {})
+ super(options).tap do |json|
+ json[:priority] = priority(options[:project]) if options.has_key?(:project)
+ end
end
- def open_merge_requests_count
- merge_requests.opened.count
- end
+ private
- def template?
- template
+ def cross_project_reference?(source_project, target_project)
+ source_project && target_project && source_project != target_project
end
- def text_color
- LabelsHelper::text_color_for_bg(self.color)
+ def issues_count(user, params = {})
+ IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
+ .execute
+ .count
end
- def title=(value)
- write_attribute(:title, sanitize_title(value)) if value.present?
+ def merge_requests_count(user, params = {})
+ MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
+ .execute
+ .count
end
- private
-
def label_format_reference(format = :id)
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
@@ -137,10 +188,6 @@ class Label < ActiveRecord::Base
end
end
- def nullify_priority
- self.priority = nil if priority.blank?
- end
-
def sanitize_title(value)
CGI.unescapeHTML(Sanitize.clean(value.to_s))
end
diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb
new file mode 100644
index 00000000000..5b85e0b6533
--- /dev/null
+++ b/app/models/label_priority.rb
@@ -0,0 +1,8 @@
+class LabelPriority < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :label
+
+ validates :project, :label, :priority, presence: true
+ validates :label_id, uniqueness: { scope: :project_id }
+ validates :priority, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+end
diff --git a/app/models/list.rb b/app/models/list.rb
index eb87decdbc8..065d75bd1dc 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -26,6 +26,17 @@ class List < ActiveRecord::Base
label? ? label.name : list_type.humanize
end
+ def as_json(options = {})
+ super(options).tap do |json|
+ if options.has_key?(:label)
+ json[:label] = label.as_json(
+ project: board.project,
+ only: [:id, :title, :description, :color]
+ )
+ end
+ end
+ end
+
private
def can_be_destroyed
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 125f26369d7..e4880973117 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -121,7 +121,11 @@ class ProjectMember < Member
end
def post_destroy_hook
- event_service.leave_project(self.project, self.user)
+ if expired?
+ event_service.expired_leave_project(self.project, self.user)
+ else
+ event_service.leave_project(self.project, self.user)
+ end
super
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 8c6905a442d..0cc0b3c2a0e 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -137,6 +137,10 @@ class MergeRequest < ActiveRecord::Base
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
end
+ def self.project_foreign_key
+ 'target_project_id'
+ end
+
# Returns all the merge requests from an ActiveRecord:Relation.
#
# This method uses a UNION as it usually operates on the result of
@@ -787,21 +791,21 @@ class MergeRequest < ActiveRecord::Base
def all_pipelines
return unless source_project
- @all_pipelines ||= begin
- sha = if persisted?
- all_commits_sha
- else
- diff_head_sha
- end
-
- source_project.pipelines.order(id: :desc).
- where(sha: sha, ref: source_branch)
- end
+ @all_pipelines ||= source_project.pipelines
+ .where(sha: all_commits_sha, ref: source_branch)
+ .order(id: :desc)
end
# Note that this could also return SHA from now dangling commits
+ #
def all_commits_sha
- merge_request_diffs.flat_map(&:commits_sha).uniq
+ if persisted?
+ merge_request_diffs.flat_map(&:commits_sha).uniq
+ elsif compare_commits
+ compare_commits.to_a.reverse.map(&:id)
+ else
+ [diff_head_sha]
+ end
end
def merge_commit
diff --git a/app/models/project.rb b/app/models/project.rb
index aee74c3dba1..653c38322c5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -107,7 +107,7 @@ class Project < ActiveRecord::Base
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
has_many :issues, dependent: :destroy
- has_many :labels, dependent: :destroy
+ has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy
@@ -388,6 +388,10 @@ class Project < ActiveRecord::Base
Project.count
end
end
+
+ def group_ids
+ joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id)
+ end
end
def lfs_enabled?
@@ -664,6 +668,10 @@ class Project < ActiveRecord::Base
end
end
+ def issue_reference_pattern
+ issues_tracker.reference_pattern
+ end
+
def default_issues_tracker?
!external_issue_tracker
end
@@ -729,10 +737,8 @@ class Project < ActiveRecord::Base
def create_labels
Label.templates.each do |label|
- label = label.dup
- label.template = nil
- label.project_id = self.id
- label.save
+ params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
+ Labels::FindOrCreateService.new(owner, self, params).execute
end
end
@@ -1293,7 +1299,7 @@ class Project < ActiveRecord::Base
environment_ids.where(ref: ref)
end
- environments.where(id: environment_ids).select do |environment|
+ environments.available.where(id: environment_ids).select do |environment|
environment.includes_commit?(commit)
end
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 530f7d5a30e..b37ce1d3cf6 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -13,23 +13,26 @@ class ProjectFeature < ActiveRecord::Base
# Enabled: enabled for everyone able to access the project
#
- # Permision levels
+ # Permission levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
- FEATURES = %i(issues merge_requests wiki snippets builds)
+ FEATURES = %i(issues merge_requests wiki snippets builds repository)
# Default scopes force us to unscope here since a service may need to check
# permissions for a project in pending_delete
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) }
+ validate :repository_children_level
+
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
+ default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
@@ -57,6 +60,18 @@ class ProjectFeature < ActiveRecord::Base
private
+ # Validates builds and merge requests access level
+ # which cannot be higher than repository access level
+ def repository_children_level
+ validator = lambda do |field|
+ level = public_send(field) || ProjectFeature::ENABLED
+ not_allowed = level > repository_access_level
+ self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed
+ end
+
+ %i(merge_requests_access_level builds_access_level).each(&validator)
+ end
+
def get_permission(user, level)
case level
when DISABLED
diff --git a/app/models/project_label.rb b/app/models/project_label.rb
new file mode 100644
index 00000000000..33c2b617715
--- /dev/null
+++ b/app/models/project_label.rb
@@ -0,0 +1,34 @@
+class ProjectLabel < Label
+ MAX_NUMBER_OF_PRIORITIES = 1
+
+ belongs_to :project
+
+ validates :project, presence: true
+
+ validate :permitted_numbers_of_priorities
+ validate :title_must_not_exist_at_group_level
+
+ delegate :group, to: :project, allow_nil: true
+
+ alias_attribute :subject, :project
+
+ def to_reference(target_project = nil, format: :id)
+ super(project, target_project, format: format)
+ end
+
+ private
+
+ def title_must_not_exist_at_group_level
+ return unless group.present? && title_changed?
+
+ if group.labels.with_title(self.title).exists?
+ errors.add(:title, :label_already_exists_at_group_level, group: group.name)
+ end
+ end
+
+ def permitted_numbers_of_priorities
+ if priorities && priorities.size > MAX_NUMBER_OF_PRIORITIES
+ errors.add(:priorities, 'Number of permitted priorities exceeded')
+ end
+ end
+end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index d1df6d0292f..b26ddd518d7 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -3,6 +3,12 @@ class IssueTrackerService < Service
default_value_for :category, 'issue_tracker'
+ # Pattern used to extract links from comments
+ # Override this method on services that uses different patterns
+ def reference_pattern
+ @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
+ end
+
def default?
default
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 97bcbacf2b9..f81b66fd219 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -13,6 +13,11 @@ class JiraService < IssueTrackerService
before_update :reset_password
+ # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
+ def reference_pattern
+ @reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
+ end
+
def reset_password
# don't reset the password if a new one is provided
if api_url_changed? && !password_touched?
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 6ae9956ade5..11c072dd000 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -52,7 +52,13 @@ class Todo < ActiveRecord::Base
# Todos with highest priority first then oldest todos
# Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue"
def order_by_labels_priority
- highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql
+ params = {
+ target_type: ['Issue', 'MergeRequest'],
+ target_column: "todos.target_id",
+ project_column: "todos.project_id"
+ }
+
+ highest_priority = highest_label_priority(params).to_sql
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb
new file mode 100644
index 00000000000..7b34aa182eb
--- /dev/null
+++ b/app/policies/group_label_policy.rb
@@ -0,0 +1,5 @@
+class GroupLabelPolicy < BasePolicy
+ def rules
+ delegate! @subject.group
+ end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 97ff6233968..b65fb68cd88 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -19,6 +19,7 @@ class GroupPolicy < BasePolicy
if master
can! :create_projects
can! :admin_milestones
+ can! :admin_label
end
# Only group owner and administrators can admin group
diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb
new file mode 100644
index 00000000000..b12b4c5166b
--- /dev/null
+++ b/app/policies/project_label_policy.rb
@@ -0,0 +1,5 @@
+class ProjectLabelPolicy < BasePolicy
+ def rules
+ delegate! @subject.project
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index be4721d7a51..fbb3d4507d6 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -162,11 +162,13 @@ class ProjectPolicy < BasePolicy
end
def disabled_features!
+ repository_enabled = project.feature_available?(:repository, user)
+
unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue))
end
- unless project.feature_available?(:merge_requests, user)
+ unless project.feature_available?(:merge_requests, user) && repository_enabled
cannot!(*named_abilities(:merge_request))
end
@@ -183,13 +185,21 @@ class ProjectPolicy < BasePolicy
cannot!(*named_abilities(:wiki))
end
- unless project.feature_available?(:builds, user)
+ unless project.feature_available?(:builds, user) && repository_enabled
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment))
end
+ unless repository_enabled
+ cannot! :push_code
+ cannot! :push_code_to_protected_branches
+ cannot! :download_code
+ cannot! :fork_project
+ cannot! :read_commit_status
+ end
+
unless project.container_registry_enabled
cannot!(*named_abilities(:container_image))
end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index abc7aeece39..fe0d762ccd2 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -3,7 +3,7 @@ module Boards
class CreateService < BaseService
def execute(board)
List.transaction do
- label = project.labels.find(params[:label_id])
+ label = available_labels.find(params[:label_id])
position = next_position(board)
create_list(board, label, position)
@@ -12,6 +12,10 @@ module Boards
private
+ def available_labels
+ LabelsFinder.new(current_user, project_id: project.id).execute
+ end
+
def next_position(board)
max_position = board.lists.movable.maximum(:position)
max_position.nil? ? 0 : max_position.succ
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
index d8048f1c67e..939f9bfd068 100644
--- a/app/services/boards/lists/generate_service.rb
+++ b/app/services/boards/lists/generate_service.rb
@@ -19,8 +19,7 @@ module Boards
end
def find_or_create_label(params)
- project.labels.create_with(color: params[:color])
- .find_or_create_by(name: params[:name])
+ ::Labels::FindOrCreateService.new(current_user, project, params).execute
end
def label_params
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index ff9a8310a8c..8ae15ad32f4 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -6,7 +6,13 @@ class CreateDeploymentService < BaseService
ActiveRecord::Base.transaction do
@deployable = deployable
- @environment = prepare_environment
+
+ @environment = environment
+ @environment.external_url = expanded_url if expanded_url
+ @environment.fire_state_event(action)
+
+ return unless @environment.save
+ return if @environment.stopped?
deploy.tap do |deployment|
deployment.update_merge_request_metrics!
@@ -27,13 +33,12 @@ class CreateDeploymentService < BaseService
tag: params[:tag],
sha: params[:sha],
user: current_user,
- deployable: @deployable)
+ deployable: @deployable,
+ on_stop: options[:on_stop])
end
- def prepare_environment
- project.environments.find_or_create_by(name: expanded_name) do |environment|
- environment.external_url = expanded_url
- end
+ def environment
+ @environment ||= project.environments.find_or_create_by(name: expanded_name)
end
def expanded_name
@@ -61,4 +66,8 @@ class CreateDeploymentService < BaseService
def variables
params[:variables] || []
end
+
+ def action
+ options[:action] || 'start'
+ end
end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 07fc77001a5..e24cc66e0fe 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -62,6 +62,10 @@ class EventCreateService
create_event(project, current_user, Event::LEFT)
end
+ def expired_leave_project(project, current_user)
+ create_event(project, current_user, Event::EXPIRED)
+ end
+
def create_project(project, current_user)
create_event(project, current_user, Event::CREATED)
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 57d521f2fea..bb92cd80cc9 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -80,17 +80,18 @@ class IssuableBaseService < BaseService
def filter_labels_in_param(key)
return if params[key].to_a.empty?
- params[key] = project.labels.where(id: params[key]).pluck(:id)
+ params[key] = available_labels.where(id: params[key]).pluck(:id)
end
def find_or_create_label_ids
labels = params.delete(:labels)
return unless labels
- params[:label_ids] = labels.split(",").map do |label_name|
- project.labels.create_with(color: Label::DEFAULT_COLOR)
- .find_or_create_by(title: label_name.strip)
- .id
+ params[:label_ids] = labels.split(',').map do |label_name|
+ service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip)
+ label = service.execute
+
+ label.id
end
end
@@ -111,6 +112,10 @@ class IssuableBaseService < BaseService
new_label_ids
end
+ def available_labels
+ LabelsFinder.new(current_user, project_id: @project.id).execute
+ end
+
def merge_slash_commands_into_params!(issuable)
description, command_params =
SlashCommands::InterpretService.new(project, current_user).
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index ab667456db7..a2a5f57d069 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -52,8 +52,12 @@ module Issues
end
def cloneable_label_ids
- @new_project.labels
- .where(title: @old_issue.labels.pluck(:title)).pluck(:id)
+ params = {
+ project_id: @new_project.id,
+ title: @old_issue.labels.pluck(:title)
+ }
+
+ LabelsFinder.new(current_user, params).execute.pluck(:id)
end
def cloneable_milestone_id
diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb
new file mode 100644
index 00000000000..74291312c4e
--- /dev/null
+++ b/app/services/labels/find_or_create_service.rb
@@ -0,0 +1,33 @@
+module Labels
+ class FindOrCreateService
+ def initialize(current_user, project, params = {})
+ @current_user = current_user
+ @group = project.group
+ @project = project
+ @params = params.dup
+ end
+
+ def execute
+ find_or_create_label
+ end
+
+ private
+
+ attr_reader :current_user, :group, :project, :params
+
+ def available_labels
+ @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute
+ end
+
+ def find_or_create_label
+ new_label = available_labels.find_by(title: title)
+ new_label ||= project.labels.create(params)
+
+ new_label
+ end
+
+ def title
+ params[:title] || params[:name]
+ end
+ end
+end
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
new file mode 100644
index 00000000000..514679ed29d
--- /dev/null
+++ b/app/services/labels/transfer_service.rb
@@ -0,0 +1,78 @@
+# Labels::TransferService class
+#
+# User for recreate the missing group labels at project level
+#
+module Labels
+ class TransferService
+ def initialize(current_user, old_group, project)
+ @current_user = current_user
+ @old_group = old_group
+ @project = project
+ end
+
+ def execute
+ return unless old_group.present?
+
+ Label.transaction do
+ labels_to_transfer.find_each do |label|
+ new_label_id = find_or_create_label!(label)
+
+ next if new_label_id == label.id
+
+ update_label_links(group_labels_applied_to_issues, old_label_id: label.id, new_label_id: new_label_id)
+ update_label_links(group_labels_applied_to_merge_requests, old_label_id: label.id, new_label_id: new_label_id)
+ update_label_priorities(old_label_id: label.id, new_label_id: new_label_id)
+ end
+ end
+ end
+
+ private
+
+ attr_reader :current_user, :old_group, :project
+
+ def labels_to_transfer
+ label_ids = []
+ label_ids << group_labels_applied_to_issues.select(:id)
+ label_ids << group_labels_applied_to_merge_requests.select(:id)
+
+ union = Gitlab::SQL::Union.new(label_ids)
+
+ Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq
+ end
+
+ def group_labels_applied_to_issues
+ Label.joins(:issues).
+ where(
+ issues: { project_id: project.id },
+ labels: { type: 'GroupLabel', group_id: old_group.id }
+ )
+ end
+
+ def group_labels_applied_to_merge_requests
+ Label.joins(:merge_requests).
+ where(
+ merge_requests: { target_project_id: project.id },
+ labels: { type: 'GroupLabel', group_id: old_group.id }
+ )
+ end
+
+ def find_or_create_label!(label)
+ params = label.attributes.slice('title', 'description', 'color')
+ new_label = FindOrCreateService.new(current_user, project, params).execute
+
+ new_label.id
+ end
+
+ def update_label_links(labels, old_label_id:, new_label_id:)
+ LabelLink.joins(:label).
+ merge(labels).
+ where(label_id: old_label_id).
+ update_all(label_id: new_label_id)
+ end
+
+ def update_label_priorities(old_label_id:, new_label_id:)
+ LabelPriority.where(project_id: project.id, label_id: old_label_id).
+ update_all(label_id: new_label_id)
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index b037780c431..ab9056a3250 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -11,14 +11,14 @@ module MergeRequests
def execute(merge_request)
@merge_request = merge_request
- return error('Merge request is not mergeable') unless @merge_request.mergeable?
+ return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable?
merge_request.in_locked_state do
if commit
after_merge
success
else
- error('Can not merge changes')
+ log_merge_error('Can not merge changes', true)
end
end
end
@@ -46,8 +46,8 @@ module MergeRequests
merge_request.update(merge_error: e.message)
false
rescue StandardError => e
- merge_request.update(merge_error: "Something went wrong during merge")
- Rails.logger.error(e.message)
+ merge_request.update(merge_error: "Something went wrong during merge: #{e.message}")
+ log_merge_error(e.message)
false
ensure
merge_request.update(in_progress_merge_commit_sha: nil)
@@ -65,5 +65,17 @@ module MergeRequests
def branch_deletion_user
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
end
+
+ def log_merge_error(message, http_error = false)
+ Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}")
+
+ error(message) if http_error
+ end
+
+ def merge_request_info
+ project = merge_request.project
+
+ "#{project.to_reference}#{merge_request.to_reference}"
+ end
end
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index f578f8dbea2..015f2828921 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -13,7 +13,7 @@ module Projects
end
def labels
- @project.labels.select([:title, :color])
+ LabelsFinder.new(current_user, project_id: project.id).execute.select([:title, :color])
end
def commands(noteable, type)
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index bc7f8bf433b..28470f59807 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -28,6 +28,7 @@ module Projects
Project.transaction do
old_path = project.path_with_namespace
old_namespace = project.namespace
+ old_group = project.group
new_path = File.join(new_namespace.try(:path) || '', project.path)
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
@@ -57,6 +58,9 @@ module Projects
# Move wiki repo also if present
gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki")
+ # Move missing group labels to project
+ Labels::TransferService.new(current_user, old_group, project).execute
+
# clear project cached events
project.reset_events_cache
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index e4ae3dec8aa..5a81194a5f4 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -116,8 +116,10 @@ module SlashCommands
desc 'Add label(s)'
params '~label1 ~"label 2"'
condition do
+ available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
+
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
- project.labels.any?
+ available_labels.any?
end
command :label do |labels_param|
label_ids = find_label_ids(labels_param)
@@ -248,7 +250,7 @@ module SlashCommands
def find_label_ids(labels_param)
label_ids_by_reference = extract_references(labels_param, :label).map(&:id)
- labels_ids_by_name = @project.labels.where(name: labels_param.split).select(:id)
+ labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id)
label_ids_by_reference | labels_ids_by_name
end
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index e82a08cdb0c..1d381ad7893 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -1,6 +1,6 @@
= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do
.form-group
- = label_tag 'Username or email', for: :username
+ = label_tag :username, 'Username or email'
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true }
.form-group
= label_tag :password
@@ -10,4 +10,4 @@
%label{for: "remember_me"}
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = button_tag "Sign in", class: "btn-save btn"
+ = submit_tag "Sign in", class: "btn-save btn"
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index b26efbb4535..c18bc2ac413 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,6 +1,6 @@
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do
.form-group
- = label_tag "#{server['label']} Login", for: :username
+ = label_tag :username, "#{server['label']} Username"
= text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true }
.form-group
= label_tag :password
@@ -10,4 +10,4 @@
%label{for: "remember_me"}
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = button_tag "Sign in", class: "btn-save btn"
+ = submit_tag "Sign in", class: "btn-save btn"
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index 810dd5ab687..86edaf14e43 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -1,15 +1,18 @@
-#login-pane.login-box{ role: 'tabpanel', class: 'tab-pane active' }
- .login-body
- - if form_based_providers.any?
- - if crowd_enabled?
- %div.tab-pane.active{id: "tab-crowd"}
- = render 'devise/sessions/new_crowd'
- - @ldap_servers.each_with_index do |server, i|
- %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)}
- = render 'devise/sessions/new_ldap', server: server
- - if signin_enabled?
- %div#tab-signin.tab-pane
- = render 'devise/sessions/new_base'
+- if form_based_providers.any?
+ - if crowd_enabled?
+ .login-box.tab-pane.active{id: "crowd", role: 'tabpanel', class: 'tab-pane'}
+ .login-body
+ = render 'devise/sessions/new_crowd'
+ - @ldap_servers.each_with_index do |server, i|
+ .login-box.tab-pane{id: "#{server['provider_name']}", role: 'tabpanel', class: (:active if i.zero? && !crowd_enabled?)}
+ .login-body
+ = render 'devise/sessions/new_ldap', server: server
+ - if signin_enabled?
+ .login-box.tab-pane{id: 'ldap-standard', role: 'tabpanel'}
+ .login-body
+ = render 'devise/sessions/new_base'
- - elsif signin_enabled?
+- elsif signin_enabled?
+ .login-box.tab-pane.active{id: 'login-pane', role: 'tabpanel'}
+ .login-body
= render 'devise/sessions/new_base'
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index e276e91433a..a057f126c45 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -1,10 +1,10 @@
%ul.new-session-tabs.nav-links.nav-tabs
- if crowd_enabled?
%li.active
- = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab'
+ = link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero? && !crowd_enabled?)}
- = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
+ = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
- if signin_enabled?
%li
- = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
+ = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
diff --git a/app/views/groups/labels/destroy.js.haml b/app/views/groups/labels/destroy.js.haml
new file mode 100644
index 00000000000..3dfbfc77c0d
--- /dev/null
+++ b/app/views/groups/labels/destroy.js.haml
@@ -0,0 +1,2 @@
+- if @group.labels.empty?
+ $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml
new file mode 100644
index 00000000000..836981fc6fd
--- /dev/null
+++ b/app/views/groups/labels/edit.html.haml
@@ -0,0 +1,7 @@
+- page_title 'Edit', @label.name, 'Labels'
+
+%h3.page-title
+ Edit Label
+%hr
+
+= render 'shared/labels/form', url: group_label_path(@group, @label), back_path: @previous_labels_path
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
new file mode 100644
index 00000000000..70783a63409
--- /dev/null
+++ b/app/views/groups/labels/index.html.haml
@@ -0,0 +1,20 @@
+- page_title 'Labels'
+
+.top-area.adjust
+ .nav-text
+ Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
+
+ .nav-controls
+ - if can?(current_user, :admin_label, @group)
+ = link_to new_group_label_path(@group), class: "btn btn-new" do
+ New label
+
+.labels
+ .other-labels
+ - if @labels.present?
+ %ul.content-list.manage-labels-list.js-other-labels
+ = render partial: 'shared/label', collection: @labels, as: :label
+ = paginate @labels, theme: 'gitlab'
+ - else
+ .nothing-here-block
+ No labels created yet.
diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml
new file mode 100644
index 00000000000..2be87460b1d
--- /dev/null
+++ b/app/views/groups/labels/new.html.haml
@@ -0,0 +1,8 @@
+- page_title 'New Label'
+- header_title group_title(@group, 'Labels', group_labels_path(@group))
+
+%h3.page-title
+ New Label
+%hr
+
+= render 'shared/labels/form', url: group_labels_path, back_path: @previous_labels_path
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 27ac1760166..f7edb47b666 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -13,6 +13,10 @@
= link_to activity_group_path(@group), title: 'Activity' do
%span
Activity
+ = nav_link(controller: [:group, :labels]) do
+ = link_to group_labels_path(@group), title: 'Labels' do
+ %span
+ Labels
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones' do
%span
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index ec02c2e2d90..0995826775a 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -1,9 +1,9 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{lang: "en"}
%head
- %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/
+ %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
%meta{content: "width=device-width, initial-scale=1", name: "viewport"}/
- %meta{content: "IE=edge", "http-equiv": "X-UA-Compatible"}/
+ %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 0fdf118c9bc..cf9c1d4d72c 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -1,9 +1,9 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{lang: "en"}
%head
- %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/
+ %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
%meta{content: "width=device-width, initial-scale=1", name: "viewport"}/
- %meta{content: "IE=edge", "http-equiv": "X-UA-Compatible"}/
+ %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml
new file mode 100644
index 00000000000..d2c1e943db1
--- /dev/null
+++ b/app/views/projects/_customize_workflow.html.haml
@@ -0,0 +1,8 @@
+.row-content-block.project-home-empty
+ %div.text-center{ class: container_class }
+ %h4
+ Customize your workflow!
+ %p
+ Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production!
+ - if can?(current_user, :admin_project, @project)
+ = link_to "Get started", edit_project_path(@project), class: "btn btn-success"
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 5590198a20e..d3987fc9c4f 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -22,5 +22,6 @@
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
- .project-clone-holder
- = render "shared/clone_panel"
+ - if @project.feature_available?(:repository, current_user)
+ .project-clone-holder
+ = render "shared/clone_panel"
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
new file mode 100644
index 00000000000..f00422dd7c0
--- /dev/null
+++ b/app/views/projects/_wiki.html.haml
@@ -0,0 +1,19 @@
+- if @wiki_home.present?
+ %div{ class: container_class }
+ .wiki-holder.prepend-top-default.append-bottom-default
+ .wiki
+ = preserve do
+ = render_wiki_content(@wiki_home)
+- else
+ - can_create_wiki = can?(current_user, :create_wiki, @project)
+ .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
+ %div.text-center{ class: container_class }
+ %h4
+ This project does not have a wiki homepage yet
+ - if can_create_wiki
+ %p
+ Add a homepage to your wiki that contains information about your project
+ %p
+ We recommend you
+ = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home)
+ to your project's wiki and GitLab will show it here instead of this message.
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 966633f1f89..b1053028279 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -1,5 +1,4 @@
-- builds = @build.pipeline.builds.latest.to_a
-- statuses = ["failed", "pending", "running", "canceled", "success", "skipped"]
+- builds = @build.pipeline.builds.to_a
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
@@ -124,9 +123,9 @@
%a.stage-item= stage
.builds-container
- - statuses.each do |build_status|
+ - HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
- .build-job{class: ('active' if build == @build), data: {stage: build.stage}}
+ .build-job{class: sidebar_build_class(build, @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= icon('arrow-right')
= ci_icon_for_status(build.status)
@@ -135,11 +134,5 @@
= build.name
- else
= build.id
-
- - if @build.retried?
- %li.active
- %a
- Build ##{@build.id}
- &middot;
- %i.fa.fa-warning
- This build was retried.
+ - if build.retried?
+ %i.fa.fa-refresh.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'Build was retried'}
diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/builds/_user.html.haml
index 2642de8021d..83f299da651 100644
--- a/app/views/projects/builds/_user.html.haml
+++ b/app/views/projects/builds/_user.html.haml
@@ -1,4 +1,7 @@
by
%a{ href: user_path(@build.user) }
- = image_tag avatar_icon(@build.user, 24), class: "avatar s24"
- %strong= @build.user.to_reference
+ %span.hidden-xs
+ = image_tag avatar_icon(@build.user, 24), class: "avatar s24"
+ %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } }
+ = @build.user.name
+ %strong.visible-xs-inline= @build.user.to_reference
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 9248adfde80..94632056b15 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -6,7 +6,7 @@
- coverage = local_assigns.fetch(:coverage, false)
- allow_retry = local_assigns.fetch(:allow_retry, false)
-%tr.build.commit
+%tr.build.commit{class: ('retried' if retried)}
%td.status
- if can?(current_user, :read_build, build)
= ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
@@ -27,7 +27,7 @@
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else
.light none
- .icon-container
+ .icon-container.commit-icon
= custom_icon("icon_commit")
- if commit_sha
@@ -35,8 +35,9 @@
- if build.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+
- if retried
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
+ = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried')
.label-container
- if build.tags.any?
@@ -47,8 +48,6 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
- - if retried
- %span.label.label-warning retried
- if build.manual?
%span.label.label-info manual
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 36eadbd2bf1..c6f359f5679 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -9,17 +9,15 @@
= ci_icon_for_status(status)
- else
= ci_status_with_icon(status)
- %td.branch-commit
+
+ %td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
- %span ##{pipeline.id}
- - if pipeline.ref && show_branch
- .icon-container
- = pipeline.tag? ? icon('tag') : icon('code-fork')
- = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
- - if show_commit
- .icon-container
- = custom_icon("icon_commit")
- = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
+ %span.pipeline-id ##{pipeline.id}
+ %span by
+ - if pipeline.user
+ = user_avatar(user: pipeline.user, size: 20)
+ - else
+ %span.api.monospace API
- if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered?
@@ -29,6 +27,16 @@
- if pipeline.builds.any?(&:stuck?)
%span.label.label-warning stuck
+ %td.branch-commit
+ - if pipeline.ref && show_branch
+ .icon-container
+ = pipeline.tag? ? icon('tag') : icon('code-fork')
+ = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
+ - if show_commit
+ .icon-container.commit-icon
+ = custom_icon("icon_commit")
+ = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
+
%p.commit-title
- if commit = pipeline.commit
= author_avatar(commit, size: 20)
@@ -36,16 +44,15 @@
- else
Cant find HEAD commit for this branch
-
- - stages_status = pipeline.statuses.relevant.latest.stages_status
- %td.stage-cell
- - stages.each do |stage|
- - status = stages_status[stage]
- - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- - if status
- .stage-container
- = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
- = ci_icon_for_status(status)
+ - stages_status = pipeline.statuses.relevant.latest.stages_status
+ %td.stage-cell
+ - stages.each do |stage|
+ - status = stages_status[stage]
+ - tooltip = "#{stage.titleize}: #{status || 'not found'}"
+ - if status
+ .stage-container
+ = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
+ = ci_icon_for_status(status)
%td
- if pipeline.duration
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 640651e93f5..ac451441eec 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -8,6 +8,7 @@
%tbody
%th Status
%th Pipeline
+ %th Commit
%th Stages
%th
%th
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 7f346df8797..b647882efa0 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -2,10 +2,10 @@
- page_title "Cycle Analytics"
= render "projects/pipelines/head"
-#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
+#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) }}
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
- = icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
+ = icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()")
.row
.col-sm-3.col-xs-12.svg-container
= custom_icon('icon_cycle_analytics_splash')
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 22c4a75d213..58a214bdbd1 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -1,28 +1,15 @@
-- if can?(current_user, :create_deployment, deployment) && deployment.deployable
- .pull-right
-
- - external_url = deployment.environment.external_url
- - if external_url
- = link_to external_url, target: '_blank', class: 'btn external-url' do
- = icon('external-link')
-
- - actions = deployment.manual_actions
- - if actions.present?
- .inline
- .dropdown
- %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
- = custom_icon('icon_play')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - actions.each do |action|
- %li
- = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
- = custom_icon('icon_play')
- %span= action.name.humanize
+- if can?(current_user, :create_deployment, deployment)
+ - actions = deployment.manual_actions
+ - if actions.present?
+ .inline
+ .dropdown
+ %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+ = custom_icon('icon_play')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - actions.each do |action|
+ %li
+ = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
+ = custom_icon('icon_play')
+ %span= action.name.humanize
- - if local_assigns.fetch(:allow_rollback, false)
- = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
- - if deployment.last?
- Re-deploy
- - else
- Rollback
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 28813babd7b..ff250eeca50 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -3,7 +3,7 @@
.icon-container
= deployment.tag? ? icon('tag') : icon('code-fork')
= link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace branch-name"
- .icon-container
+ .icon-container.commit-icon
= custom_icon("icon_commit")
= link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace"
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index ca0005abd0c..9238f232c7e 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -17,4 +17,6 @@
#{time_ago_with_tooltip(deployment.created_at)}
%td.hidden-xs
- = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
+ .pull-right
+ = render 'projects/deployments/actions', deployment: deployment
+ = render 'projects/deployments/rollback', deployment: deployment
diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml
new file mode 100644
index 00000000000..5941e01c6f1
--- /dev/null
+++ b/app/views/projects/deployments/_rollback.haml
@@ -0,0 +1,6 @@
+- if can?(current_user, :create_deployment, deployment) && deployment.deployable
+ = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
+ - if deployment.last?
+ Re-deploy
+ - else
+ Rollback
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c8f84b96cb7..fb776e3a3e7 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -50,24 +50,39 @@
.form_group.prepend-top-20
.row
.col-md-9
- = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
- %span.help-block Lightweight issue tracking system for this project
- .col-md-3
- = project_feature_access_select(:issues_access_level)
+ = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
+ %span.help-block Push files to be stored in this project
+ .col-md-3.js-repo-access-level
+ = project_feature_access_select(:repository_access_level)
+
+ .col-sm-12
+ .row
+ .col-md-9.project-feature-nested
+ = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
+ %span.help-block Submit changes to be merged upstream
+ .col-md-3
+ = project_feature_access_select(:merge_requests_access_level)
+
+ .row
+ .col-md-9.project-feature-nested
+ = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+ %span.help-block Submit, test and deploy your changes before merge
+ .col-md-3
+ = project_feature_access_select(:builds_access_level)
.row
.col-md-9
- = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
- %span.help-block Submit changes to be merged upstream
+ = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
+ %span.help-block Share code pastes with others out of Git repository
.col-md-3
- = project_feature_access_select(:merge_requests_access_level)
+ = project_feature_access_select(:snippets_access_level)
.row
.col-md-9
- = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
- %span.help-block Submit Test and deploy your changes before merge
+ = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
+ %span.help-block Lightweight issue tracking system for this project
.col-md-3
- = project_feature_access_select(:builds_access_level)
+ = project_feature_access_select(:issues_access_level)
.row
.col-md-9
@@ -76,24 +91,17 @@
.col-md-3
= project_feature_access_select(:wiki_access_level)
- .row
- .col-md-9
- = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
- %span.help-block Share code pastes with others out of Git repository
- .col-md-3
- = project_feature_access_select(:snippets_access_level)
-
- if Gitlab.config.lfs.enabled && current_user.admin?
- .row
- .col-md-9
- = f.label :lfs_enabled, 'LFS', class: 'label-light'
- %span.help-block
+ .checkbox
+ = f.label :lfs_enabled do
+ = f.check_box :lfs_enabled
+ %strong LFS
+ %br
+ %span.descr
Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- .col-md-3
- = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' }
- - if Gitlab.config.registry.enabled
+ - if Gitlab.config.lfs.enabled && current_user.admin?
.form-group
.checkbox
= f.label :container_registry_enabled do
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index 251694e897c..b75d5df4150 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -28,4 +28,8 @@
#{time_ago_with_tooltip(last_deployment.created_at)}
%td.hidden-xs
- = render 'projects/deployments/actions', deployment: last_deployment
+ .pull-right
+ = render 'projects/environments/external_url', environment: environment
+ = render 'projects/deployments/actions', deployment: last_deployment
+ = render 'projects/environments/stop', environment: environment
+ = render 'projects/deployments/rollback', deployment: last_deployment
diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml
new file mode 100644
index 00000000000..4c8fe1c271b
--- /dev/null
+++ b/app/views/projects/environments/_external_url.html.haml
@@ -0,0 +1,3 @@
+- if environment.external_url && can?(current_user, :read_environment, environment)
+ = link_to environment.external_url, target: '_blank', class: 'btn external-url' do
+ = icon('external-link')
diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml
new file mode 100644
index 00000000000..69848123c17
--- /dev/null
+++ b/app/views/projects/environments/_stop.html.haml
@@ -0,0 +1,5 @@
+- if can?(current_user, :create_deployment, environment) && environment.stoppable?
+ .inline
+ = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post,
+ class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do
+ = icon('stop', class: 'stop-env-icon')
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 721ba156334..8f555afcf11 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -3,14 +3,27 @@
= render "projects/pipelines/head"
%div{ class: container_class }
- - if can?(current_user, :create_environment, @project) && !@environments.blank?
- .top-area
+ .top-area
+ %ul.nav-links
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_environments_path(@project) do
+ Available
+ %span.badge.js-available-environments-count
+ = number_with_delimiter(@all_environments.available.count)
+
+ %li{class: ('active' if @scope == 'stopped')}
+ = link_to project_environments_path(@project, scope: :stopped) do
+ Stopped
+ %span.badge.js-stopped-environments-count
+ = number_with_delimiter(@all_environments.stopped.count)
+
+ - if can?(current_user, :create_environment, @project) && !@all_environments.blank?
.nav-controls
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
.environments-container
- - if @environments.blank?
+ - if @all_environments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
You don't have any environments right now.
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 90c59223a35..bcac73d3698 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -3,14 +3,16 @@
= render "projects/pipelines/head"
%div{ class: container_class }
- .top-area
+ .top-area.adjust
.col-md-9
%h3.page-title= @environment.name.capitalize
.col-md-3
.nav-controls
+ = render 'projects/environments/external_url', environment: @environment
- if can?(current_user, :update_environment, @environment)
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
- = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
+ - if can?(current_user, :create_deployment, @environment) && @environment.stoppable?
+ = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.deployments-container
- if @deployments.blank?
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 8b1a8a8a2d9..c80210d6ff4 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -50,7 +50,7 @@
- if issue.labels.any?
&nbsp;
- issue.labels.each do |label|
- = link_to_label(label, project: issue.project)
+ = link_to_label(label, subject: issue.project)
- if issue.tasks?
&nbsp;
%span.task-status
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index a2c31c0b4c5..a4b752ad86d 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,5 +1,5 @@
%ul.content-list.issues-list.issuable-list
- = render @issues
+ = render partial: "projects/issues/issue", collection: @issues
- if @issues.blank?
%li
.nothing-here-block No issues to show
diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml
index 3a6fbbc7fbc..1b7d878c38c 100644
--- a/app/views/projects/issues/edit.html.haml
+++ b/app/views/projects/issues/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues"
+- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues"
%h3.page-title
Edit Issue ##{@issue.iid}
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 09347ad5fff..6f3f238a436 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@issue.to_reference} #{@issue.title}", "Issues"
+- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
deleted file mode 100644
index 71f7f354d72..00000000000
--- a/app/views/projects/labels/_label.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-- label_css_id = dom_id(label)
-%li{id: label_css_id, data: { id: label.id } }
- = render "shared/label_row", label: label
-
- .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
- %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } }
- Options
- = icon('caret-down')
- .dropdown-menu.dropdown-menu-align-right
- %ul
- %li
- = link_to_label(label, type: :merge_request) do
- = pluralize label.open_merge_requests_count, 'merge request'
- %li
- = link_to_label(label) do
- = pluralize label.open_issues_count(current_user), 'open issue'
- - if current_user
- %li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
- %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } }
- %span= label_subscription_toggle_button_text(label)
- - if can? current_user, :admin_label, @project
- %li
- = link_to "Edit", edit_namespace_project_label_path(@project.namespace, @project, label)
- %li
- = link_to "Delete", namespace_project_label_path(@project.namespace, @project, label), title: "Delete", method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
-
- .pull-right.hidden-xs.hidden-sm.hidden-md
- = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
- = pluralize label.open_merge_requests_count, 'merge request'
- = link_to_label(label, css_class: 'btn btn-transparent btn-action') do
- = pluralize label.open_issues_count(current_user), 'open issue'
-
- - if current_user
- .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
- %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } }
- %span.sr-only= label_subscription_toggle_button_text(label)
- = icon('eye', class: 'label-subscribe-button-icon')
- = icon('spinner spin', class: 'label-subscribe-button-loading')
-
- - if can? current_user, :admin_label, @project
- = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
- %span.sr-only Edit
- = icon('pencil-square-o')
- = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do
- %span.sr-only Delete
- = icon('trash-o')
-
- - if current_user
- :javascript
- new Subscription('##{dom_id(label)} .label-subscription');
diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml
index d59563b122a..8d09e2bda11 100644
--- a/app/views/projects/labels/destroy.js.haml
+++ b/app/views/projects/labels/destroy.js.haml
@@ -1,2 +1,2 @@
-- if @project.labels.size == 0
+- if @labels.empty?
$('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 52b187e7e58..a80a07b52e6 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -6,4 +6,4 @@
%h3.page-title
Edit Label
%hr
- = render 'form'
+ = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index db66a0edbd8..f135bf6f6b4 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -16,21 +16,22 @@
.labels
- if can?(current_user, :admin_label, @project)
-# Only show it in the first page
- - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1')
+ - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
- if @prioritized_labels.present?
- = render @prioritized_labels
+ = render partial: 'shared/label', collection: @prioritized_labels, as: :label
+
.other-labels
- if can?(current_user, :admin_label, @project)
%h5{ class: ('hide' if hide) } Other Labels
- - if @labels.present?
- %ul.content-list.manage-labels-list.js-other-labels
- = render @labels
+ %ul.content-list.manage-labels-list.js-other-labels
+ - if @labels.present?
+ = render partial: 'shared/label', collection: @labels, as: :label
= paginate @labels, theme: 'gitlab'
- - else
+ - if @labels.blank?
.nothing-here-block
- if can?(current_user, :admin_label, @project)
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index a1bb66cfb6c..f0d9be744d1 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -6,4 +6,4 @@
%h3.page-title
New Label
%hr
- = render 'form'
+ = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project)
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 68fb7d5a414..12408068834 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -62,7 +62,7 @@
- if merge_request.labels.any?
&nbsp;
- merge_request.labels.each do |label|
- = link_to_label(label, project: merge_request.project, type: 'merge_request')
+ = link_to_label(label, subject: merge_request.project, type: :merge_request)
- if merge_request.tasks?
&nbsp;
%span.task-status
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index da6927879a4..9c6f562f7db 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -29,7 +29,11 @@
= link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
Commits
%span.badge= @commits.size
- - if @pipeline
+ - if @pipelines.any?
+ %li.builds-tab
+ = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
+ Pipelines
+ %span.badge= @pipelines.size
%li.builds-tab
= link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
Builds
@@ -44,9 +48,11 @@
= render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane
- # This tab is always loaded via AJAX
- - if @pipeline
+ - if @pipelines.any?
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
+ #pipelines.pipelines.tab-pane
+ = render "projects/merge_requests/show/pipelines"
.mr-loading-status
= spinner
@@ -59,5 +65,5 @@
:javascript
var merge_request = new MergeRequest({
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
- buildsLoaded: "#{@pipeline ? 'true' : 'false'}"
+ buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}"
});
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 662463bc72b..cd98aaf8d75 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,4 +1,4 @@
-- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
+- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
@@ -61,7 +61,7 @@
%li.pipelines-tab
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
Pipelines
- %span.badge= @merge_request.all_pipelines.size
+ %span.badge= @pipelines.size
%li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
Builds
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 7c3ac6652ee..03159f123f3 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,4 +1,4 @@
-- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
+- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 9eeef5f57b4..4bc49072f35 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -45,11 +45,12 @@
.table-holder
%table.table.ci-table
%thead
- %th.col-xs-1.col-sm-1 Status
- %th.col-xs-2.col-sm-4 Pipeline
- %th.col-xs-2.col-sm-2 Stages
- %th.col-xs-2.col-sm-2
- %th.hidden-xs.col-sm-3
+ %th Status
+ %th Pipeline
+ %th Commit
+ %th Stages
+ %th
+ %th.hidden-xs
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
= paginate @pipelines, theme: 'gitlab'
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
index 8c7222bfe3d..bebf0ccd54d 100644
--- a/app/views/projects/pipelines_settings/show.html.haml
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -7,7 +7,7 @@
.col-lg-9
%h5.prepend-top-0
Pipelines
- = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f|
+ = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
%fieldset.builds-feature
- unless @repository.gitlab_ci_yml
.form-group
@@ -64,8 +64,8 @@
.checkbox
= f.label :public_builds do
= f.check_box :public_builds
- %strong Public pipelines
- .help-block Allow everyone to access pipelines for Public and Internal projects
+ %strong Public builds
+ .help-block Allow everyone to access builds traces for Public and Internal projects
.form-group.append-bottom-default
= f.label :runners_token, "Runners token", class: 'label-light'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index ea4deb6cb28..ba16c641462 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,72 +12,74 @@
= render 'projects/last_push'
= render "home_panel"
-%nav.project-stats{ class: (container_class) }
- %ul.nav
- %li
- = link_to project_files_path(@project) do
- Files (#{repository_size})
- %li
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
- #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
- %li
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
- %li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
-
- - if default_project_view != 'readme' && @repository.readme
+- if @project.feature_available?(:repository, current_user)
+ %nav.project-stats{ class: container_class }
+ %ul.nav
%li
- = link_to 'Readme', readme_path(@project)
-
- - if @repository.changelog
+ = link_to project_files_path(@project) do
+ Files (#{repository_size})
%li
- = link_to 'Changelog', changelog_path(@project)
-
- - if @repository.license_blob
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
- = link_to license_short_name(@project), license_path(@project)
-
- - if @repository.contribution_guide
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
- = link_to 'Contribution guide', contribution_guide_path(@project)
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- - if @repository.gitlab_ci_yml
- %li
- = link_to 'CI configuration', ci_configuration_path(@project)
-
- - if current_user && can_push_branch?(@project, @project.default_branch)
- - unless @repository.changelog
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
- Add Changelog
- - unless @repository.license_blob
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'LICENSE') do
- Add License
- - unless @repository.contribution_guide
- %li.missing
- = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
- Add Contribution guide
- - unless @repository.gitlab_ci_yml
- %li.missing
- = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
- Set Up CI
-
- %li.project-repo-buttons-right
- .project-repo-buttons.project-right-buttons
- - if current_user
- = render 'shared/members/access_request_buttons', source: @project
- = render "projects/buttons/koding"
-
- = render 'projects/buttons/download', project: @project, ref: @ref
- = render 'projects/buttons/dropdown'
-
- = render 'shared/notifications/button', notification_setting: @notification_setting
-- if @repository.commit
- .project-last-commit{ class: container_class }
- = render 'projects/last_commit', commit: @repository.commit, project: @project
+ - if default_project_view != 'readme' && @repository.readme
+ %li
+ = link_to 'Readme', readme_path(@project)
+
+ - if @repository.changelog
+ %li
+ = link_to 'Changelog', changelog_path(@project)
+
+ - if @repository.license_blob
+ %li
+ = link_to license_short_name(@project), license_path(@project)
+
+ - if @repository.contribution_guide
+ %li
+ = link_to 'Contribution guide', contribution_guide_path(@project)
+
+ - if @repository.gitlab_ci_yml
+ %li
+ = link_to 'CI configuration', ci_configuration_path(@project)
+
+ - if current_user && can_push_branch?(@project, @project.default_branch)
+ - unless @repository.changelog
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
+ Add Changelog
+ - unless @repository.license_blob
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'LICENSE') do
+ Add License
+ - unless @repository.contribution_guide
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
+ Add Contribution guide
+ - unless @repository.gitlab_ci_yml
+ %li.missing
+ = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
+ Set Up CI
+
+ %li.project-repo-buttons-right
+ .project-repo-buttons.project-right-buttons
+ - if current_user
+ = render 'shared/members/access_request_buttons', source: @project
+ = render "projects/buttons/koding"
+
+ .btn-group.project-repo-btn-group
+ = render 'projects/buttons/download', project: @project, ref: @ref
+ = render 'projects/buttons/dropdown'
+
+ = render 'shared/notifications/button', notification_setting: @notification_setting
+ - if @repository.commit
+ .project-last-commit{ class: container_class }
+ = render 'projects/last_commit', commit: @repository.commit, project: @project
%div{ class: container_class }
- if @project.archived?
@@ -86,5 +88,7 @@
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
- %div{class: "project-show-#{default_project_view}"}
- = render default_project_view
+ - view_path = default_project_view
+
+ %div{ class: project_child_container_class(view_path) }
+ = render view_path
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
new file mode 100644
index 00000000000..40c8d2af226
--- /dev/null
+++ b/app/views/shared/_label.html.haml
@@ -0,0 +1,53 @@
+- label_css_id = dom_id(label)
+- open_issues_count = label.open_issues_count(current_user, @project)
+- open_merge_requests_count = label.open_merge_requests_count(current_user, @project)
+
+%li{id: label_css_id, data: { id: label.id } }
+ = render "shared/label_row", label: label
+
+ .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown
+ %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } }
+ Options
+ = icon('caret-down')
+ .dropdown-menu.dropdown-menu-align-right
+ %ul
+ %li
+ = link_to_label(label, subject: @project, type: :merge_request) do
+ = pluralize open_merge_requests_count, 'merge request'
+ %li
+ = link_to_label(label, subject: @project) do
+ = pluralize open_issues_count, 'open issue'
+ - if current_user
+ %li.label-subscription{ data: toggle_subscription_data(label) }
+ %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } }
+ %span= label_subscription_toggle_button_text(label)
+ - if can?(current_user, :admin_label, label)
+ %li
+ = link_to 'Edit', edit_label_path(label)
+ %li
+ = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'}
+
+ .pull-right.hidden-xs.hidden-sm.hidden-md
+ = link_to_label(label, subject: @project, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
+ = pluralize open_merge_requests_count, 'merge request'
+ = link_to_label(label, subject: @project, css_class: 'btn btn-transparent btn-action') do
+ = pluralize open_issues_count, 'open issue'
+
+ - if current_user
+ .label-subscription.inline{ data: toggle_subscription_data(label) }
+ %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } }
+ %span.sr-only= label_subscription_toggle_button_text(label)
+ = icon('eye', class: 'label-subscribe-button-icon', disabled: label.is_a?(GroupLabel))
+ = icon('spinner spin', class: 'label-subscribe-button-loading')
+
+ - if can?(current_user, :admin_label, label)
+ = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
+ %span.sr-only Edit
+ = icon('pencil-square-o')
+ = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
+ %span.sr-only Delete
+ = icon('trash-o')
+
+ - if current_user && label.is_a?(ProjectLabel)
+ :javascript
+ new Subscription('##{dom_id(label)} .label-subscription');
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 6f593e8dff9..d28f9421ecf 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -3,13 +3,16 @@
.draggable-handler
= icon('bars')
.js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
- dom_id: dom_id(label) } }
+ dom_id: dom_id(label), type: label.type } }
%button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' }
= icon('star-o')
%button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' }
= icon('star')
%span.label-name
- = link_to_label(label, tooltip: false)
+ = link_to_label(label, subject: @project, tooltip: false)
+ - if defined?(@project) && @project.group.present?
+ %span.label-type
+ = label.model_name.human.titleize
- if label.description
%span.label-description
= markdown_field(label, :description)
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
index e324d0e5203..21b37a7c9ae 100644
--- a/app/views/shared/_labels_row.html.haml
+++ b/app/views/shared/_labels_row.html.haml
@@ -1,5 +1,5 @@
- labels.each do |label|
%span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" }
- = link_to_label(label, css_class: 'btn btn-transparent')
+ = link_to_label(label, subject: @project, css_class: 'btn btn-transparent')
%button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
= icon("times")
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 31620297be0..ed93857e6d4 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -29,8 +29,9 @@
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
- .filter-item.inline.reset-filters
- %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
+ - if issuable_filters_present
+ .filter-item.inline.reset-filters
+ %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
.pull-right
- if boards_page
@@ -77,11 +78,10 @@
= hidden_field_tag :state_event, params[:state_event]
.filter-item.inline
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
-
- - if !@labels.nil?
- .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
- - if @labels.any?
- = render "shared/labels_row", labels: @labels
+ - has_labels = @labels && @labels.any?
+ .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
+ - if has_labels
+ = render 'shared/labels_row', labels: @labels
:javascript
new UsersSelect();
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index a7944a60130..d410755cad1 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -88,19 +88,19 @@
- if issuable.assignee_id
= f.hidden_field :assignee_id
= dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
- placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } })
+ placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
.form-group.issue-milestone
= f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
+ = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group
- - has_labels = issuable.project.labels.any?
+ - has_labels = @labels && @labels.any?
= f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
= f.hidden_field :label_ids, multiple: true, value: ''
.col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
.issuable-form-select-holder
- = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label"
+ = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label"
- if has_due_date
.col-lg-6
.form-group
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index ba9f0c27661..7363ead09ff 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -107,7 +107,7 @@
= dropdown_content do
.js-due-date-calendar
- - if issuable.project.labels.any?
+ - if @labels && @labels.any?
- selected_labels = issuable.labels
.block.labels
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml
index 6ab6ae50389..647e05e5ff7 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/shared/labels/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
+= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
= form_errors(@label)
.form-group
@@ -30,4 +30,4 @@
= f.submit 'Save changes', class: 'btn btn-save js-save-button'
- else
= f.submit 'Create Label', class: 'btn btn-create js-save-button'
- = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', back_path, class: 'btn btn-cancel'
diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb
index 916c2e633c1..d9e2cc37bb3 100644
--- a/app/workers/expire_build_instance_artifacts_worker.rb
+++ b/app/workers/expire_build_instance_artifacts_worker.rb
@@ -2,10 +2,14 @@ class ExpireBuildInstanceArtifactsWorker
include Sidekiq::Worker
def perform(build_id)
- build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id)
- return unless build
+ build = Ci::Build
+ .with_expired_artifacts
+ .reorder(nil)
+ .find_by(id: build_id)
- Rails.logger.info "Removing artifacts build #{build.id}..."
+ return unless build.try(:project)
+
+ Rails.logger.info "Removing artifacts for build #{build.id}..."
build.erase_artifacts!
end
end