Merge branch 'master' into issue-board-sidebar
This commit is contained in:
commit
fcf0a4a12d
|
@ -1,5 +1,7 @@
|
||||||
Please view this file on the master branch, on stable branches it's out of date.
|
Please view this file on the master branch, on stable branches it's out of date.
|
||||||
|
|
||||||
|
## 8.14.0 (2016-11-22)
|
||||||
|
|
||||||
## 8.13.0 (2016-10-22)
|
## 8.13.0 (2016-10-22)
|
||||||
|
|
||||||
- Fix save button on project pipeline settings page. (!6955)
|
- Fix save button on project pipeline settings page. (!6955)
|
||||||
|
@ -13,10 +15,13 @@ Please view this file on the master branch, on stable branches it's out of date.
|
||||||
- Update runner version only when updating contacted_at
|
- Update runner version only when updating contacted_at
|
||||||
- Add link from system note to compare with previous version
|
- Add link from system note to compare with previous version
|
||||||
- Use gitlab-shell v3.6.6
|
- Use gitlab-shell v3.6.6
|
||||||
|
- Ignore references to internal issues when using external issues tracker
|
||||||
- Ability to resolve merge request conflicts with editor !6374
|
- Ability to resolve merge request conflicts with editor !6374
|
||||||
- Add `/projects/visible` API endpoint (Ben Boeckel)
|
- Add `/projects/visible` API endpoint (Ben Boeckel)
|
||||||
- Fix centering of custom header logos (Ashley Dumaine)
|
- Fix centering of custom header logos (Ashley Dumaine)
|
||||||
|
- Keep around commits only pipeline creation as pipeline data doesn't change over time
|
||||||
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
|
- ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup
|
||||||
|
- Add group level labels. (!6425)
|
||||||
- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun)
|
- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun)
|
||||||
- Cancelled pipelines could be retried. !6927
|
- Cancelled pipelines could be retried. !6927
|
||||||
- Updating verbiage on git basics to be more intuitive
|
- Updating verbiage on git basics to be more intuitive
|
||||||
|
@ -105,7 +110,6 @@ Please view this file on the master branch, on stable branches it's out of date.
|
||||||
- Optimize GitHub importing for speed and memory
|
- Optimize GitHub importing for speed and memory
|
||||||
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
|
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
|
||||||
- Notify the Merger about merge after successful build (Dimitris Karakasilis)
|
- Notify the Merger about merge after successful build (Dimitris Karakasilis)
|
||||||
- Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein)
|
|
||||||
- Reduce queries needed to find users using their SSH keys when pushing commits
|
- Reduce queries needed to find users using their SSH keys when pushing commits
|
||||||
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
|
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
|
||||||
- Fix broken repository 500 errors in project list
|
- Fix broken repository 500 errors in project list
|
||||||
|
@ -123,8 +127,11 @@ Please view this file on the master branch, on stable branches it's out of date.
|
||||||
- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
|
- Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
|
||||||
- Fixes padding in all clipboard icons that have .btn class
|
- Fixes padding in all clipboard icons that have .btn class
|
||||||
- Fix a typo in doc/api/labels.md
|
- Fix a typo in doc/api/labels.md
|
||||||
|
- Fix double-escaping in activities tab (Alexandre Maia)
|
||||||
- API: all unknown routing will be handled with 404 Not Found
|
- API: all unknown routing will be handled with 404 Not Found
|
||||||
- Add docs for request profiling
|
- Add docs for request profiling
|
||||||
|
- Delete dynamic environments
|
||||||
|
- Fix buggy iOS tooltip layering behavior.
|
||||||
- Make guests unable to view MRs on private projects
|
- Make guests unable to view MRs on private projects
|
||||||
|
|
||||||
## 8.12.7
|
## 8.12.7
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -29,7 +29,7 @@ gem 'omniauth-github', '~> 1.1.1'
|
||||||
gem 'omniauth-gitlab', '~> 1.0.0'
|
gem 'omniauth-gitlab', '~> 1.0.0'
|
||||||
gem 'omniauth-google-oauth2', '~> 0.4.1'
|
gem 'omniauth-google-oauth2', '~> 0.4.1'
|
||||||
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
|
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
|
||||||
gem 'omniauth-saml', '~> 1.6.0'
|
gem 'omniauth-saml', '~> 1.7.0'
|
||||||
gem 'omniauth-shibboleth', '~> 1.2.0'
|
gem 'omniauth-shibboleth', '~> 1.2.0'
|
||||||
gem 'omniauth-twitter', '~> 1.2.0'
|
gem 'omniauth-twitter', '~> 1.2.0'
|
||||||
gem 'omniauth_crowd', '~> 2.2.0'
|
gem 'omniauth_crowd', '~> 2.2.0'
|
||||||
|
|
|
@ -473,9 +473,9 @@ GEM
|
||||||
omniauth-oauth2 (1.3.1)
|
omniauth-oauth2 (1.3.1)
|
||||||
oauth2 (~> 1.0)
|
oauth2 (~> 1.0)
|
||||||
omniauth (~> 1.2)
|
omniauth (~> 1.2)
|
||||||
omniauth-saml (1.6.0)
|
omniauth-saml (1.7.0)
|
||||||
omniauth (~> 1.3)
|
omniauth (~> 1.3)
|
||||||
ruby-saml (~> 1.3)
|
ruby-saml (~> 1.4)
|
||||||
omniauth-shibboleth (1.2.1)
|
omniauth-shibboleth (1.2.1)
|
||||||
omniauth (>= 1.0.0)
|
omniauth (>= 1.0.0)
|
||||||
omniauth-twitter (1.2.1)
|
omniauth-twitter (1.2.1)
|
||||||
|
@ -635,7 +635,7 @@ GEM
|
||||||
crack (~> 0.4)
|
crack (~> 0.4)
|
||||||
ruby-prof (0.16.2)
|
ruby-prof (0.16.2)
|
||||||
ruby-progressbar (1.8.1)
|
ruby-progressbar (1.8.1)
|
||||||
ruby-saml (1.3.0)
|
ruby-saml (1.4.1)
|
||||||
nokogiri (>= 1.5.10)
|
nokogiri (>= 1.5.10)
|
||||||
ruby_parser (3.8.2)
|
ruby_parser (3.8.2)
|
||||||
sexp_processor (~> 4.1)
|
sexp_processor (~> 4.1)
|
||||||
|
@ -915,7 +915,7 @@ DEPENDENCIES
|
||||||
omniauth-gitlab (~> 1.0.0)
|
omniauth-gitlab (~> 1.0.0)
|
||||||
omniauth-google-oauth2 (~> 0.4.1)
|
omniauth-google-oauth2 (~> 0.4.1)
|
||||||
omniauth-kerberos (~> 0.3.0)
|
omniauth-kerberos (~> 0.3.0)
|
||||||
omniauth-saml (~> 1.6.0)
|
omniauth-saml (~> 1.7.0)
|
||||||
omniauth-shibboleth (~> 1.2.0)
|
omniauth-shibboleth (~> 1.2.0)
|
||||||
omniauth-twitter (~> 1.2.0)
|
omniauth-twitter (~> 1.2.0)
|
||||||
omniauth_crowd (~> 2.2.0)
|
omniauth_crowd (~> 2.2.0)
|
||||||
|
|
|
@ -168,6 +168,8 @@
|
||||||
shortcut_handler = new ShortcutsNavigation();
|
shortcut_handler = new ShortcutsNavigation();
|
||||||
new ShortcutsBlob(true);
|
new ShortcutsBlob(true);
|
||||||
break;
|
break;
|
||||||
|
case 'groups:labels:new':
|
||||||
|
case 'groups:labels:edit':
|
||||||
case 'projects:labels:new':
|
case 'projects:labels:new':
|
||||||
case 'projects:labels:edit':
|
case 'projects:labels:edit':
|
||||||
new Labels();
|
new Labels();
|
||||||
|
|
|
@ -266,7 +266,7 @@
|
||||||
},
|
},
|
||||||
fieldName: $dropdown.data('field-name'),
|
fieldName: $dropdown.data('field-name'),
|
||||||
id: function(label) {
|
id: function(label) {
|
||||||
if (label.id <= 0) return;
|
if (label.id <= 0) return label.title;
|
||||||
|
|
||||||
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
|
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||||
return label.id;
|
return label.id;
|
||||||
|
|
|
@ -17,6 +17,12 @@
|
||||||
View on <%- external_url_formatted %>
|
View on <%- external_url_formatted %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</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>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
@ -205,6 +211,11 @@
|
||||||
if ($(`.mr-state-widget #${ environment.id }`).length) return;
|
if ($(`.mr-state-widget #${ environment.id }`).length) return;
|
||||||
const $template = $(DEPLOYMENT_TEMPLATE);
|
const $template = $(DEPLOYMENT_TEMPLATE);
|
||||||
if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
|
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) {
|
if (environment.deployed_at && environment.deployed_at_formatted) {
|
||||||
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
|
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -167,7 +167,6 @@
|
||||||
*/
|
*/
|
||||||
&.code {
|
&.code {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
-webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,3 +27,15 @@ body {
|
||||||
.container-limited {
|
.container-limited {
|
||||||
max-width: $fixed-layout-width;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,6 @@
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #333;
|
color: #333;
|
||||||
border-radius: 0 0 3px 3px;
|
border-radius: 0 0 3px 3px;
|
||||||
-webkit-overflow-scrolling: auto;
|
|
||||||
|
|
||||||
.unfold {
|
.unfold {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -38,6 +38,14 @@
|
||||||
color: $gl-dark-link-color;
|
color: $gl-dark-link-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stop-env-link {
|
||||||
|
color: $table-text-gray;
|
||||||
|
|
||||||
|
.stop-env-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.deployment {
|
.deployment {
|
||||||
.build-column {
|
.build-column {
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,21 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
max-width: 100%;
|
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 {
|
.label-description {
|
||||||
|
@ -209,6 +223,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-subscribe-button {
|
.label-subscribe-button {
|
||||||
|
.label-subscribe-button-icon {
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.label-subscribe-button-loading {
|
.label-subscribe-button-loading {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,6 +183,15 @@
|
||||||
.ci-coverage {
|
.ci-coverage {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stop-env-container {
|
||||||
|
color: $gl-text-color;
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $gl-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr_source_commit,
|
.mr_source_commit,
|
||||||
|
|
|
@ -2,6 +2,7 @@ module IssuableActions
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
before_action :labels, only: [:show, :new, :edit]
|
||||||
before_action :authorize_destroy_issuable!, only: :destroy
|
before_action :authorize_destroy_issuable!, only: :destroy
|
||||||
before_action :authorize_admin_issuable!, only: :bulk_update
|
before_action :authorize_admin_issuable!, only: :bulk_update
|
||||||
end
|
end
|
||||||
|
@ -25,6 +26,10 @@ module IssuableActions
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def labels
|
||||||
|
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||||
|
end
|
||||||
|
|
||||||
def authorize_destroy_issuable!
|
def authorize_destroy_issuable!
|
||||||
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
|
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
|
||||||
return access_denied!
|
return access_denied!
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
class Dashboard::LabelsController < Dashboard::ApplicationController
|
class Dashboard::LabelsController < Dashboard::ApplicationController
|
||||||
def index
|
def index
|
||||||
labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title)
|
labels = LabelsFinder.new(current_user).execute
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json { render json: labels }
|
format.json { render json: labels.as_json(only: [:id, :title, :color]) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -72,10 +72,10 @@ module Projects
|
||||||
|
|
||||||
def serialize_as_json(resource)
|
def serialize_as_json(resource)
|
||||||
resource.as_json(
|
resource.as_json(
|
||||||
|
labels: true,
|
||||||
only: [:iid, :title, :confidential, :due_date],
|
only: [:iid, :title, :confidential, :due_date],
|
||||||
include: {
|
include: {
|
||||||
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||||
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] },
|
|
||||||
milestone: { only: [:id, :title] }
|
milestone: { only: [:id, :title] }
|
||||||
},
|
},
|
||||||
user: current_user
|
user: current_user
|
||||||
|
|
|
@ -76,9 +76,8 @@ module Projects
|
||||||
resource.as_json(
|
resource.as_json(
|
||||||
only: [:id, :list_type, :position],
|
only: [:id, :list_type, :position],
|
||||||
methods: [:title],
|
methods: [:title],
|
||||||
include: {
|
label: true
|
||||||
label: { only: [:id, :title, :description, :color, :priority] }
|
)
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,11 +2,19 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
layout 'project'
|
layout 'project'
|
||||||
before_action :authorize_read_environment!
|
before_action :authorize_read_environment!
|
||||||
before_action :authorize_create_environment!, only: [:new, :create]
|
before_action :authorize_create_environment!, only: [:new, :create]
|
||||||
before_action :authorize_update_environment!, only: [:edit, :update, :destroy]
|
before_action :authorize_create_deployment!, only: [:stop]
|
||||||
before_action :environment, only: [:show, :edit, :update, :destroy]
|
before_action :authorize_update_environment!, only: [:edit, :update]
|
||||||
|
before_action :environment, only: [:show, :edit, :update, :stop]
|
||||||
|
|
||||||
def index
|
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
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -38,14 +46,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def stop
|
||||||
if @environment.destroy
|
return render_404 unless @environment.stoppable?
|
||||||
flash[:notice] = 'Environment was successfully removed.'
|
|
||||||
else
|
|
||||||
flash[:alert] = 'Failed to remove environment.'
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -26,7 +26,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
@issues = issues_collection
|
@issues = issues_collection
|
||||||
@issues = @issues.page(params[:page])
|
@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|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
|
|
@ -3,21 +3,22 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||||
|
|
||||||
before_action :module_enabled
|
before_action :module_enabled
|
||||||
before_action :label, only: [:edit, :update, :destroy]
|
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_read_label!
|
||||||
before_action :authorize_admin_labels!, only: [
|
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
|
||||||
:new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities
|
:generate, :destroy, :remove_priority,
|
||||||
]
|
:set_priorities]
|
||||||
|
|
||||||
respond_to :js, :html
|
respond_to :js, :html
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@labels = @project.labels.unprioritized.page(params[:page])
|
@prioritized_labels = @available_labels.prioritized(@project)
|
||||||
@prioritized_labels = @project.labels.prioritized
|
@labels = @available_labels.unprioritized(@project).page(params[:page])
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.json do
|
format.json do
|
||||||
render json: @project.labels
|
render json: @available_labels.as_json(only: [:id, :title, :color])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -36,7 +37,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { render 'new' }
|
format.html { render :new }
|
||||||
format.json { render json: { message: @label.errors.messages }, status: 400 }
|
format.json { render json: { message: @label.errors.messages }, status: 400 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -49,7 +50,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||||
if @label.update_attributes(label_params)
|
if @label.update_attributes(label_params)
|
||||||
redirect_to namespace_project_labels_path(@project.namespace, @project)
|
redirect_to namespace_project_labels_path(@project.namespace, @project)
|
||||||
else
|
else
|
||||||
render 'edit'
|
render :edit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -68,6 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@label.destroy
|
@label.destroy
|
||||||
|
@labels = find_labels
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
|
@ -80,20 +82,24 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||||
|
|
||||||
def remove_priority
|
def remove_priority
|
||||||
respond_to do |format|
|
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 }
|
format.json { render json: label }
|
||||||
else
|
else
|
||||||
message = label.errors.full_messages.uniq.join('. ')
|
format.json { head :unprocessable_entity }
|
||||||
format.json { render json: { message: message }, status: :unprocessable_entity }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_priorities
|
def set_priorities
|
||||||
Label.transaction do
|
Label.transaction do
|
||||||
params[:label_ids].each_with_index do |label_id, index|
|
available_labels_ids = @available_labels.where(id: params[:label_ids]).pluck(:id)
|
||||||
label = @project.labels.find_by_id(label_id)
|
label_ids = params[:label_ids].select { |id| available_labels_ids.include?(id.to_i) }
|
||||||
label.update_attribute(:priority, index) if label
|
|
||||||
|
label_ids.each_with_index do |label_id, index|
|
||||||
|
label = @available_labels.find(label_id)
|
||||||
|
label.prioritize!(project, index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -119,6 +125,10 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
alias_method :subscribable_resource, :label
|
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!
|
def authorize_admin_labels!
|
||||||
return render_404 unless can?(current_user, :admin_label, @project)
|
return render_404 unless can?(current_user, :admin_label, @project)
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,7 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
@merge_requests = @merge_requests.page(params[:page])
|
@merge_requests = @merge_requests.page(params[:page])
|
||||||
@merge_requests = @merge_requests.preload(:target_project)
|
@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|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
|
@ -422,10 +425,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
project = environment.project
|
project = environment.project
|
||||||
deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
|
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,
|
id: environment.id,
|
||||||
name: environment.name,
|
name: environment.name,
|
||||||
url: namespace_project_environment_path(project.namespace, project, environment),
|
url: namespace_project_environment_path(project.namespace, project, environment),
|
||||||
|
stop_url: stop_url,
|
||||||
external_url: environment.external_url,
|
external_url: environment.external_url,
|
||||||
external_url_formatted: environment.formatted_external_url,
|
external_url_formatted: environment.formatted_external_url,
|
||||||
deployed_at: deployment.try(:created_at),
|
deployed_at: deployment.try(:created_at),
|
||||||
|
@ -483,13 +492,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
@noteable = @merge_request
|
@noteable = @merge_request
|
||||||
@commits_count = @merge_request.commits.count
|
@commits_count = @merge_request.commits.count
|
||||||
|
|
||||||
@pipeline = @merge_request.pipeline
|
|
||||||
@statuses = @pipeline.statuses.relevant if @pipeline
|
|
||||||
|
|
||||||
if @merge_request.locked_long_ago?
|
if @merge_request.locked_long_ago?
|
||||||
@merge_request.unlock_mr
|
@merge_request.unlock_mr
|
||||||
@merge_request.close
|
@merge_request.close
|
||||||
end
|
end
|
||||||
|
|
||||||
|
define_pipelines_vars
|
||||||
end
|
end
|
||||||
|
|
||||||
# Discussion tab data is rendered on html responses of actions
|
# Discussion tab data is rendered on html responses of actions
|
||||||
|
@ -517,7 +525,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
|
|
||||||
def define_widget_vars
|
def define_widget_vars
|
||||||
@pipeline = @merge_request.pipeline
|
@pipeline = @merge_request.pipeline
|
||||||
@pipelines = [@pipeline].compact
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def define_commit_vars
|
def define_commit_vars
|
||||||
|
@ -544,6 +551,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
)
|
)
|
||||||
end
|
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
|
def define_new_vars
|
||||||
@noteable = @merge_request
|
@noteable = @merge_request
|
||||||
|
|
||||||
|
@ -559,10 +575,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||||
@commit = @merge_request.diff_head_commit
|
@commit = @merge_request.diff_head_commit
|
||||||
@base_commit = @merge_request.diff_base_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)).
|
@note_counts = Note.where(commit_id: @commits.map(&:id)).
|
||||||
group(:commit_id).count
|
group(:commit_id).count
|
||||||
|
|
||||||
|
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||||
|
|
||||||
|
define_pipelines_vars
|
||||||
end
|
end
|
||||||
|
|
||||||
def invalid_mr
|
def invalid_mr
|
||||||
|
|
|
@ -124,15 +124,12 @@ class IssuableFinder
|
||||||
def labels
|
def labels
|
||||||
return @labels if defined?(@labels)
|
return @labels if defined?(@labels)
|
||||||
|
|
||||||
if labels? && !filter_by_no_label?
|
@labels =
|
||||||
@labels = Label.where(title: label_names)
|
if labels? && !filter_by_no_label?
|
||||||
|
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute
|
||||||
if projects
|
else
|
||||||
@labels = @labels.where(project: projects)
|
Label.none
|
||||||
end
|
end
|
||||||
else
|
|
||||||
@labels = Label.none
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def assignee?
|
def assignee?
|
||||||
|
@ -274,8 +271,10 @@ class IssuableFinder
|
||||||
items = items.without_label
|
items = items.without_label
|
||||||
else
|
else
|
||||||
items = items.with_label(label_names, params[:sort])
|
items = items.with_label(label_names, params[:sort])
|
||||||
|
|
||||||
if projects
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -4,7 +4,7 @@ module AwardEmojiHelper
|
||||||
|
|
||||||
if awardable.is_a?(Note)
|
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)
|
# 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_id, project_id: @project.id, id: awardable.id)
|
toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace, project_id: @project, id: awardable.id)
|
||||||
else
|
else
|
||||||
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
|
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
|
||||||
end
|
end
|
||||||
|
|
|
@ -124,6 +124,10 @@ module IssuablesHelper
|
||||||
end
|
end
|
||||||
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)
|
def issuables_count_for_state(issuable_type, state)
|
||||||
issuables_finder = public_send("#{issuable_type}_finder")
|
issuables_finder = public_send("#{issuable_type}_finder")
|
||||||
issuables_finder.params[:state] = state
|
issuables_finder.params[:state] = state
|
||||||
|
|
|
@ -4,9 +4,8 @@ module LabelsHelper
|
||||||
# Link to a Label
|
# Link to a Label
|
||||||
#
|
#
|
||||||
# label - Label object to link to
|
# label - Label object to link to
|
||||||
# project - Project object which will be used as the context for the label's
|
# subject - Project/Group object which will be used as the context for the
|
||||||
# link. If omitted, defaults to `@project`, or the label's own
|
# label's link. If omitted, defaults to the label's own group/project.
|
||||||
# project.
|
|
||||||
# type - The type of item the link will point to (:issue or
|
# type - The type of item the link will point to (:issue or
|
||||||
# :merge_request). If omitted, defaults to :issue.
|
# :merge_request). If omitted, defaults to :issue.
|
||||||
# block - An optional block that will be passed to `link_to`, forming the
|
# block - An optional block that will be passed to `link_to`, forming the
|
||||||
|
@ -15,15 +14,14 @@ module LabelsHelper
|
||||||
#
|
#
|
||||||
# Examples:
|
# 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)
|
# link_to_label(label)
|
||||||
#
|
#
|
||||||
# # Force the generated link to use @project
|
# # Force the generated link to use a provided group
|
||||||
# @project = Project.first
|
# link_to_label(label, subject: Group.last)
|
||||||
# link_to_label(label)
|
|
||||||
#
|
#
|
||||||
# # Force the generated link to use a provided project
|
# # 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
|
# # Force the generated link to point to merge requests instead of issues
|
||||||
# link_to_label(label, type: :merge_request)
|
# link_to_label(label, type: :merge_request)
|
||||||
|
@ -32,9 +30,8 @@ module LabelsHelper
|
||||||
# link_to_label(label) { "My Custom Label Text" }
|
# link_to_label(label) { "My Custom Label Text" }
|
||||||
#
|
#
|
||||||
# Returns a String
|
# Returns a String
|
||||||
def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block)
|
def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block)
|
||||||
project ||= @project || label.project
|
link = label_filter_path(subject || label.subject, label, type: type)
|
||||||
link = label_filter_path(project, label, type: type)
|
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
link_to link, class: css_class, &block
|
link_to link, class: css_class, &block
|
||||||
|
@ -43,15 +40,40 @@ module LabelsHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def label_filter_path(project, label, type: issue)
|
def label_filter_path(subject, label, type: :issue)
|
||||||
send("namespace_project_#{type.to_s.pluralize}_path",
|
case subject
|
||||||
project.namespace,
|
when Group
|
||||||
project,
|
send("#{type.to_s.pluralize}_group_path",
|
||||||
label_name: [label.name])
|
subject,
|
||||||
|
label_name: [label.name])
|
||||||
|
when Project
|
||||||
|
send("namespace_project_#{type.to_s.pluralize}_path",
|
||||||
|
subject.namespace,
|
||||||
|
subject,
|
||||||
|
label_name: [label.name])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def project_label_names
|
def edit_label_path(label)
|
||||||
@project.labels.pluck(:title)
|
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 toggle_subscription_data(label)
|
||||||
|
return unless label.is_a?(ProjectLabel)
|
||||||
|
|
||||||
|
{
|
||||||
|
url: toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_colored_label(label, label_suffix = '', tooltip: true)
|
def render_colored_label(label, label_suffix = '', tooltip: true)
|
||||||
|
@ -68,8 +90,8 @@ module LabelsHelper
|
||||||
span.html_safe
|
span.html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_colored_cross_project_label(label, tooltip: true)
|
def render_colored_cross_project_label(label, source_project = nil, tooltip: true)
|
||||||
label_suffix = label.project.name_with_namespace
|
label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace
|
||||||
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
|
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
|
||||||
render_colored_label(label, label_suffix, tooltip: tooltip)
|
render_colored_label(label, label_suffix, tooltip: tooltip)
|
||||||
end
|
end
|
||||||
|
@ -115,7 +137,10 @@ module LabelsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def labels_filter_path
|
def labels_filter_path
|
||||||
|
return group_labels_path(@group, :json) if @group
|
||||||
|
|
||||||
project = @target_project || @project
|
project = @target_project || @project
|
||||||
|
|
||||||
if project
|
if project
|
||||||
namespace_project_labels_path(project.namespace, project, :json)
|
namespace_project_labels_path(project.namespace, project, :json)
|
||||||
else
|
else
|
||||||
|
@ -124,11 +149,24 @@ module LabelsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def label_subscription_status(label)
|
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
|
end
|
||||||
|
|
||||||
def label_subscription_toggle_button_text(label)
|
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
|
end
|
||||||
|
|
||||||
# Required for Banzai::Filter::LabelReferenceFilter
|
# Required for Banzai::Filter::LabelReferenceFilter
|
||||||
|
|
|
@ -19,7 +19,7 @@ module Ci
|
||||||
validates_presence_of :status, unless: :importing?
|
validates_presence_of :status, unless: :importing?
|
||||||
validate :valid_commit_sha, 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
|
delegate :stages, to: :statuses
|
||||||
|
|
||||||
|
|
|
@ -145,8 +145,14 @@ module Issuable
|
||||||
end
|
end
|
||||||
|
|
||||||
def order_labels_priority(excluded_labels: [])
|
def order_labels_priority(excluded_labels: [])
|
||||||
condition_field = "#{table_name}.id"
|
params = {
|
||||||
highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql
|
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").
|
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
|
||||||
group(arel_table[:id]).
|
group(arel_table[:id]).
|
||||||
|
@ -230,18 +236,6 @@ module Issuable
|
||||||
labels.order('title ASC').pluck(:title)
|
labels.order('title ASC').pluck(:title)
|
||||||
end
|
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
|
# Convert this Issuable class name to a format usable by Ability definitions
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
|
|
|
@ -38,11 +38,13 @@ module Sortable
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def highest_label_priority(object_types, condition_field, excluded_labels: [])
|
def highest_label_priority(target_type:, target_column:, project_column:, excluded_labels: [])
|
||||||
query = Label.select(Label.arel_table[:priority].minimum).
|
query = Label.select(LabelPriority.arel_table[:priority].minimum).
|
||||||
|
left_join_priorities.
|
||||||
joins(:label_links).
|
joins(:label_links).
|
||||||
where(label_links: { target_type: object_types }).
|
where("label_priorities.project_id = #{project_column}").
|
||||||
where("label_links.target_id = #{condition_field}").
|
where(label_links: { target_type: target_type }).
|
||||||
|
where("label_links.target_id = #{target_column}").
|
||||||
reorder(nil)
|
reorder(nil)
|
||||||
|
|
||||||
query.where.not(title: excluded_labels) if excluded_labels.present?
|
query.where.not(title: excluded_labels) if excluded_labels.present?
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Deployment < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def manual_actions
|
def manual_actions
|
||||||
deployable.try(:other_actions)
|
@manual_actions ||= deployable.try(:other_actions)
|
||||||
end
|
end
|
||||||
|
|
||||||
def includes_commit?(commit)
|
def includes_commit?(commit)
|
||||||
|
@ -84,6 +84,17 @@ class Deployment < ActiveRecord::Base
|
||||||
take
|
take
|
||||||
end
|
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
|
def formatted_deployment_time
|
||||||
created_at.to_time.in_time_zone.to_s(:medium)
|
created_at.to_time.in_time_zone.to_s(:medium)
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,24 @@ class Environment < ActiveRecord::Base
|
||||||
allow_nil: true,
|
allow_nil: true,
|
||||||
addressable_url: 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
|
def last_deployment
|
||||||
deployments.last
|
deployments.last
|
||||||
end
|
end
|
||||||
|
@ -66,4 +84,14 @@ class Environment < ActiveRecord::Base
|
||||||
|
|
||||||
external_url.gsub(/\A.*?:\/\//, '')
|
external_url.gsub(/\A.*?:\/\//, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stoppable?
|
||||||
|
available? && stop_action.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop!(current_user)
|
||||||
|
return unless stoppable?
|
||||||
|
|
||||||
|
stop_action.play(current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,11 +29,6 @@ class ExternalIssue
|
||||||
@project
|
@project
|
||||||
end
|
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)
|
def to_reference(_from_project = nil)
|
||||||
id
|
id
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,7 @@ class Group < Namespace
|
||||||
has_many :project_group_links, dependent: :destroy
|
has_many :project_group_links, dependent: :destroy
|
||||||
has_many :shared_projects, through: :project_group_links, source: :project
|
has_many :shared_projects, through: :project_group_links, source: :project
|
||||||
has_many :notification_settings, dependent: :destroy, as: :source
|
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 :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
||||||
validate :visibility_level_allowed_by_projects
|
validate :visibility_level_allowed_by_projects
|
||||||
|
|
|
@ -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
|
|
@ -138,6 +138,10 @@ class Issue < ActiveRecord::Base
|
||||||
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
|
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.project_foreign_key
|
||||||
|
'project_id'
|
||||||
|
end
|
||||||
|
|
||||||
def self.sort(method, excluded_labels: [])
|
def self.sort(method, excluded_labels: [])
|
||||||
case method.to_s
|
case method.to_s
|
||||||
when 'due_date_asc' then order_due_date_asc
|
when 'due_date_asc' then order_due_date_asc
|
||||||
|
@ -278,6 +282,14 @@ class Issue < ActiveRecord::Base
|
||||||
def as_json(options = {})
|
def as_json(options = {})
|
||||||
super(options).tap do |json|
|
super(options).tap do |json|
|
||||||
json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user)
|
json[:subscribed] = subscribed?(options[:user]) if options.has_key?(:user)
|
||||||
|
|
||||||
|
if options.has_key?(:labels)
|
||||||
|
json[:labels] = labels.as_json(
|
||||||
|
project: project,
|
||||||
|
only: [:id, :title, :description, :color, :priority],
|
||||||
|
methods: [:text_color]
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,34 +15,49 @@ class Label < ActiveRecord::Base
|
||||||
|
|
||||||
default_value_for :color, DEFAULT_COLOR
|
default_value_for :color, DEFAULT_COLOR
|
||||||
|
|
||||||
belongs_to :project
|
|
||||||
|
|
||||||
has_many :lists, dependent: :destroy
|
has_many :lists, dependent: :destroy
|
||||||
|
has_many :priorities, class_name: 'LabelPriority'
|
||||||
has_many :label_links, dependent: :destroy
|
has_many :label_links, dependent: :destroy
|
||||||
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
|
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
|
||||||
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
|
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
|
||||||
|
|
||||||
validates :color, color: true, allow_blank: false
|
validates :color, color: true, allow_blank: false
|
||||||
validates :project, presence: true, unless: Proc.new { |service| service.template? }
|
|
||||||
|
|
||||||
# Don't allow ',' for label titles
|
# Don't allow ',' for label titles
|
||||||
validates :title,
|
validates :title, presence: true, format: { with: /\A[^,]+\z/ }
|
||||||
presence: true,
|
validates :title, uniqueness: { scope: [:group_id, :project_id] }
|
||||||
format: { with: /\A[^,]+\z/ },
|
|
||||||
uniqueness: { scope: :project_id }
|
|
||||||
|
|
||||||
before_save :nullify_priority
|
|
||||||
|
|
||||||
default_scope { order(title: :asc) }
|
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
|
def self.prioritized(project)
|
||||||
where.not(priority: nil).reorder(:priority, :title)
|
joins(:priorities)
|
||||||
|
.where(label_priorities: { project_id: project })
|
||||||
|
.reorder('label_priorities.priority ASC, labels.title ASC')
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.unprioritized
|
def self.unprioritized(project)
|
||||||
where(priority: nil)
|
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
|
||||||
|
|
||||||
|
joins(label_priorities).where(priorities[:priority].eq(nil))
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
alias_attribute :name, :title
|
alias_attribute :name, :title
|
||||||
|
@ -77,40 +92,30 @@ class Label < ActiveRecord::Base
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
def open_issues_count(user = nil, project = nil)
|
||||||
# Returns the String necessary to reference this Label in Markdown
|
issues_count(user, project_id: project.try(:id) || project_id, state: 'opened')
|
||||||
#
|
|
||||||
# format - Symbol format to use (default: :id, optional: :name)
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# Label.first.to_reference # => "~1"
|
|
||||||
# Label.first.to_reference(format: :name) # => "~\"bug\""
|
|
||||||
# Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
|
|
||||||
#
|
|
||||||
# Returns a String
|
|
||||||
#
|
|
||||||
def to_reference(from_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
|
|
||||||
else
|
|
||||||
reference
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def open_issues_count(user = nil)
|
def closed_issues_count(user = nil, project = nil)
|
||||||
issues.visible_to_user(user).opened.count
|
issues_count(user, project_id: project.try(:id) || project_id, state: 'closed')
|
||||||
end
|
end
|
||||||
|
|
||||||
def closed_issues_count(user = nil)
|
def open_merge_requests_count(user = nil, project = nil)
|
||||||
issues.visible_to_user(user).closed.count
|
merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened')
|
||||||
end
|
end
|
||||||
|
|
||||||
def open_merge_requests_count
|
def prioritize!(project, value)
|
||||||
merge_requests.opened.count
|
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
|
end
|
||||||
|
|
||||||
def template?
|
def template?
|
||||||
|
@ -118,15 +123,61 @@ class Label < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def text_color
|
def text_color
|
||||||
LabelsHelper::text_color_for_bg(self.color)
|
LabelsHelper.text_color_for_bg(self.color)
|
||||||
end
|
end
|
||||||
|
|
||||||
def title=(value)
|
def title=(value)
|
||||||
write_attribute(:title, sanitize_title(value)) if value.present?
|
write_attribute(:title, sanitize_title(value)) if value.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the String necessary to reference this Label in Markdown
|
||||||
|
#
|
||||||
|
# format - Symbol format to use (default: :id, optional: :name)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# 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(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?(source_project, target_project)
|
||||||
|
source_project.to_reference + reference
|
||||||
|
else
|
||||||
|
reference
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(options = {})
|
||||||
|
super(options).tap do |json|
|
||||||
|
json[:priority] = priority(options[:project]) if options.has_key?(:project)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def cross_project_reference?(source_project, target_project)
|
||||||
|
source_project && target_project && source_project != target_project
|
||||||
|
end
|
||||||
|
|
||||||
|
def issues_count(user, params = {})
|
||||||
|
IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
|
||||||
|
.execute
|
||||||
|
.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_requests_count(user, params = {})
|
||||||
|
MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all'))
|
||||||
|
.execute
|
||||||
|
.count
|
||||||
|
end
|
||||||
|
|
||||||
def label_format_reference(format = :id)
|
def label_format_reference(format = :id)
|
||||||
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
|
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
|
||||||
|
|
||||||
|
@ -137,10 +188,6 @@ class Label < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def nullify_priority
|
|
||||||
self.priority = nil if priority.blank?
|
|
||||||
end
|
|
||||||
|
|
||||||
def sanitize_title(value)
|
def sanitize_title(value)
|
||||||
CGI.unescapeHTML(Sanitize.clean(value.to_s))
|
CGI.unescapeHTML(Sanitize.clean(value.to_s))
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -26,6 +26,17 @@ class List < ActiveRecord::Base
|
||||||
label? ? label.name : list_type.humanize
|
label? ? label.name : list_type.humanize
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def can_be_destroyed
|
def can_be_destroyed
|
||||||
|
|
|
@ -137,6 +137,10 @@ class MergeRequest < ActiveRecord::Base
|
||||||
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
|
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.project_foreign_key
|
||||||
|
'target_project_id'
|
||||||
|
end
|
||||||
|
|
||||||
# Returns all the merge requests from an ActiveRecord:Relation.
|
# Returns all the merge requests from an ActiveRecord:Relation.
|
||||||
#
|
#
|
||||||
# This method uses a UNION as it usually operates on the result of
|
# This method uses a UNION as it usually operates on the result of
|
||||||
|
@ -787,21 +791,21 @@ class MergeRequest < ActiveRecord::Base
|
||||||
def all_pipelines
|
def all_pipelines
|
||||||
return unless source_project
|
return unless source_project
|
||||||
|
|
||||||
@all_pipelines ||= begin
|
@all_pipelines ||= source_project.pipelines
|
||||||
sha = if persisted?
|
.where(sha: all_commits_sha, ref: source_branch)
|
||||||
all_commits_sha
|
.order(id: :desc)
|
||||||
else
|
|
||||||
diff_head_sha
|
|
||||||
end
|
|
||||||
|
|
||||||
source_project.pipelines.order(id: :desc).
|
|
||||||
where(sha: sha, ref: source_branch)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Note that this could also return SHA from now dangling commits
|
# Note that this could also return SHA from now dangling commits
|
||||||
|
#
|
||||||
def all_commits_sha
|
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
|
end
|
||||||
|
|
||||||
def merge_commit
|
def merge_commit
|
||||||
|
|
|
@ -107,7 +107,7 @@ class Project < ActiveRecord::Base
|
||||||
# Merge requests from source project should be kept when source project was removed
|
# 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 :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
|
||||||
has_many :issues, dependent: :destroy
|
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 :services, dependent: :destroy
|
||||||
has_many :events, dependent: :destroy
|
has_many :events, dependent: :destroy
|
||||||
has_many :milestones, dependent: :destroy
|
has_many :milestones, dependent: :destroy
|
||||||
|
@ -388,6 +388,10 @@ class Project < ActiveRecord::Base
|
||||||
Project.count
|
Project.count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def group_ids
|
||||||
|
joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def lfs_enabled?
|
def lfs_enabled?
|
||||||
|
@ -664,6 +668,10 @@ class Project < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def issue_reference_pattern
|
||||||
|
issues_tracker.reference_pattern
|
||||||
|
end
|
||||||
|
|
||||||
def default_issues_tracker?
|
def default_issues_tracker?
|
||||||
!external_issue_tracker
|
!external_issue_tracker
|
||||||
end
|
end
|
||||||
|
@ -729,10 +737,8 @@ class Project < ActiveRecord::Base
|
||||||
|
|
||||||
def create_labels
|
def create_labels
|
||||||
Label.templates.each do |label|
|
Label.templates.each do |label|
|
||||||
label = label.dup
|
params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
|
||||||
label.template = nil
|
Labels::FindOrCreateService.new(owner, self, params).execute
|
||||||
label.project_id = self.id
|
|
||||||
label.save
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1293,7 +1299,7 @@ class Project < ActiveRecord::Base
|
||||||
environment_ids.where(ref: ref)
|
environment_ids.where(ref: ref)
|
||||||
end
|
end
|
||||||
|
|
||||||
environments.where(id: environment_ids).select do |environment|
|
environments.available.where(id: environment_ids).select do |environment|
|
||||||
environment.includes_commit?(commit)
|
environment.includes_commit?(commit)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -3,6 +3,12 @@ class IssueTrackerService < Service
|
||||||
|
|
||||||
default_value_for :category, 'issue_tracker'
|
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?
|
def default?
|
||||||
default
|
default
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,11 @@ class JiraService < IssueTrackerService
|
||||||
|
|
||||||
before_update :reset_password
|
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
|
def reset_password
|
||||||
# don't reset the password if a new one is provided
|
# don't reset the password if a new one is provided
|
||||||
if api_url_changed? && !password_touched?
|
if api_url_changed? && !password_touched?
|
||||||
|
|
|
@ -52,7 +52,13 @@ class Todo < ActiveRecord::Base
|
||||||
# Todos with highest priority first then oldest todos
|
# 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"
|
# 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
|
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").
|
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
|
||||||
order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
|
order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class GroupLabelPolicy < BasePolicy
|
||||||
|
def rules
|
||||||
|
delegate! @subject.group
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,6 +19,7 @@ class GroupPolicy < BasePolicy
|
||||||
if master
|
if master
|
||||||
can! :create_projects
|
can! :create_projects
|
||||||
can! :admin_milestones
|
can! :admin_milestones
|
||||||
|
can! :admin_label
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only group owner and administrators can admin group
|
# Only group owner and administrators can admin group
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class ProjectLabelPolicy < BasePolicy
|
||||||
|
def rules
|
||||||
|
delegate! @subject.project
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@ module Boards
|
||||||
class CreateService < BaseService
|
class CreateService < BaseService
|
||||||
def execute(board)
|
def execute(board)
|
||||||
List.transaction do
|
List.transaction do
|
||||||
label = project.labels.find(params[:label_id])
|
label = available_labels.find(params[:label_id])
|
||||||
position = next_position(board)
|
position = next_position(board)
|
||||||
|
|
||||||
create_list(board, label, position)
|
create_list(board, label, position)
|
||||||
|
@ -12,6 +12,10 @@ module Boards
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def available_labels
|
||||||
|
LabelsFinder.new(current_user, project_id: project.id).execute
|
||||||
|
end
|
||||||
|
|
||||||
def next_position(board)
|
def next_position(board)
|
||||||
max_position = board.lists.movable.maximum(:position)
|
max_position = board.lists.movable.maximum(:position)
|
||||||
max_position.nil? ? 0 : max_position.succ
|
max_position.nil? ? 0 : max_position.succ
|
||||||
|
|
|
@ -19,8 +19,7 @@ module Boards
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_label(params)
|
def find_or_create_label(params)
|
||||||
project.labels.create_with(color: params[:color])
|
::Labels::FindOrCreateService.new(current_user, project, params).execute
|
||||||
.find_or_create_by(name: params[:name])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def label_params
|
def label_params
|
||||||
|
|
|
@ -6,7 +6,13 @@ class CreateDeploymentService < BaseService
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
@deployable = deployable
|
@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|
|
deploy.tap do |deployment|
|
||||||
deployment.update_merge_request_metrics!
|
deployment.update_merge_request_metrics!
|
||||||
|
@ -27,13 +33,12 @@ class CreateDeploymentService < BaseService
|
||||||
tag: params[:tag],
|
tag: params[:tag],
|
||||||
sha: params[:sha],
|
sha: params[:sha],
|
||||||
user: current_user,
|
user: current_user,
|
||||||
deployable: @deployable)
|
deployable: @deployable,
|
||||||
|
on_stop: options[:on_stop])
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare_environment
|
def environment
|
||||||
project.environments.find_or_create_by(name: expanded_name) do |environment|
|
@environment ||= project.environments.find_or_create_by(name: expanded_name)
|
||||||
environment.external_url = expanded_url
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def expanded_name
|
def expanded_name
|
||||||
|
@ -61,4 +66,8 @@ class CreateDeploymentService < BaseService
|
||||||
def variables
|
def variables
|
||||||
params[:variables] || []
|
params[:variables] || []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def action
|
||||||
|
options[:action] || 'start'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,17 +80,18 @@ class IssuableBaseService < BaseService
|
||||||
def filter_labels_in_param(key)
|
def filter_labels_in_param(key)
|
||||||
return if params[key].to_a.empty?
|
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
|
end
|
||||||
|
|
||||||
def find_or_create_label_ids
|
def find_or_create_label_ids
|
||||||
labels = params.delete(:labels)
|
labels = params.delete(:labels)
|
||||||
return unless labels
|
return unless labels
|
||||||
|
|
||||||
params[:label_ids] = labels.split(",").map do |label_name|
|
params[:label_ids] = labels.split(',').map do |label_name|
|
||||||
project.labels.create_with(color: Label::DEFAULT_COLOR)
|
service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip)
|
||||||
.find_or_create_by(title: label_name.strip)
|
label = service.execute
|
||||||
.id
|
|
||||||
|
label.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -111,6 +112,10 @@ class IssuableBaseService < BaseService
|
||||||
new_label_ids
|
new_label_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def available_labels
|
||||||
|
LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||||
|
end
|
||||||
|
|
||||||
def merge_slash_commands_into_params!(issuable)
|
def merge_slash_commands_into_params!(issuable)
|
||||||
description, command_params =
|
description, command_params =
|
||||||
SlashCommands::InterpretService.new(project, current_user).
|
SlashCommands::InterpretService.new(project, current_user).
|
||||||
|
|
|
@ -52,8 +52,12 @@ module Issues
|
||||||
end
|
end
|
||||||
|
|
||||||
def cloneable_label_ids
|
def cloneable_label_ids
|
||||||
@new_project.labels
|
params = {
|
||||||
.where(title: @old_issue.labels.pluck(:title)).pluck(:id)
|
project_id: @new_project.id,
|
||||||
|
title: @old_issue.labels.pluck(:title)
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelsFinder.new(current_user, params).execute.pluck(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cloneable_milestone_id
|
def cloneable_milestone_id
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -11,14 +11,14 @@ module MergeRequests
|
||||||
def execute(merge_request)
|
def execute(merge_request)
|
||||||
@merge_request = 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
|
merge_request.in_locked_state do
|
||||||
if commit
|
if commit
|
||||||
after_merge
|
after_merge
|
||||||
success
|
success
|
||||||
else
|
else
|
||||||
error('Can not merge changes')
|
log_merge_error('Can not merge changes', true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -46,8 +46,8 @@ module MergeRequests
|
||||||
merge_request.update(merge_error: e.message)
|
merge_request.update(merge_error: e.message)
|
||||||
false
|
false
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
merge_request.update(merge_error: "Something went wrong during merge")
|
merge_request.update(merge_error: "Something went wrong during merge: #{e.message}")
|
||||||
Rails.logger.error(e.message)
|
log_merge_error(e.message)
|
||||||
false
|
false
|
||||||
ensure
|
ensure
|
||||||
merge_request.update(in_progress_merge_commit_sha: nil)
|
merge_request.update(in_progress_merge_commit_sha: nil)
|
||||||
|
@ -65,5 +65,17 @@ module MergeRequests
|
||||||
def branch_deletion_user
|
def branch_deletion_user
|
||||||
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
|
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Projects
|
||||||
end
|
end
|
||||||
|
|
||||||
def labels
|
def labels
|
||||||
@project.labels.select([:title, :color])
|
LabelsFinder.new(current_user, project_id: project.id).execute.select([:title, :color])
|
||||||
end
|
end
|
||||||
|
|
||||||
def commands(noteable, type)
|
def commands(noteable, type)
|
||||||
|
|
|
@ -28,6 +28,7 @@ module Projects
|
||||||
Project.transaction do
|
Project.transaction do
|
||||||
old_path = project.path_with_namespace
|
old_path = project.path_with_namespace
|
||||||
old_namespace = project.namespace
|
old_namespace = project.namespace
|
||||||
|
old_group = project.group
|
||||||
new_path = File.join(new_namespace.try(:path) || '', project.path)
|
new_path = File.join(new_namespace.try(:path) || '', project.path)
|
||||||
|
|
||||||
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
|
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
|
# Move wiki repo also if present
|
||||||
gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki")
|
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
|
# clear project cached events
|
||||||
project.reset_events_cache
|
project.reset_events_cache
|
||||||
|
|
||||||
|
|
|
@ -116,8 +116,10 @@ module SlashCommands
|
||||||
desc 'Add label(s)'
|
desc 'Add label(s)'
|
||||||
params '~label1 ~"label 2"'
|
params '~label1 ~"label 2"'
|
||||||
condition do
|
condition do
|
||||||
|
available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
|
||||||
|
|
||||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
|
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
|
||||||
project.labels.any?
|
available_labels.any?
|
||||||
end
|
end
|
||||||
command :label do |labels_param|
|
command :label do |labels_param|
|
||||||
label_ids = find_label_ids(labels_param)
|
label_ids = find_label_ids(labels_param)
|
||||||
|
@ -248,7 +250,7 @@ module SlashCommands
|
||||||
|
|
||||||
def find_label_ids(labels_param)
|
def find_label_ids(labels_param)
|
||||||
label_ids_by_reference = extract_references(labels_param, :label).map(&:id)
|
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
|
label_ids_by_reference | labels_ids_by_name
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
- if @group.labels.empty?
|
||||||
|
$('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -13,6 +13,10 @@
|
||||||
= link_to activity_group_path(@group), title: 'Activity' do
|
= link_to activity_group_path(@group), title: 'Activity' do
|
||||||
%span
|
%span
|
||||||
Activity
|
Activity
|
||||||
|
= nav_link(controller: [:group, :labels]) do
|
||||||
|
= link_to group_labels_path(@group), title: 'Labels' do
|
||||||
|
%span
|
||||||
|
Labels
|
||||||
= nav_link(controller: [:group, :milestones]) do
|
= nav_link(controller: [:group, :milestones]) do
|
||||||
= link_to group_milestones_path(@group), title: 'Milestones' do
|
= link_to group_milestones_path(@group), title: 'Milestones' do
|
||||||
%span
|
%span
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
by
|
by
|
||||||
%a{ href: user_path(@build.user) }
|
%a{ href: user_path(@build.user) }
|
||||||
= image_tag avatar_icon(@build.user, 24), class: "avatar s24"
|
%span.hidden-xs
|
||||||
%strong= @build.user.to_reference
|
= 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
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
- page_title "Cycle Analytics"
|
- page_title "Cycle Analytics"
|
||||||
= render "projects/pipelines/head"
|
= 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"}
|
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
|
||||||
= icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
|
= icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()")
|
||||||
.row
|
.row
|
||||||
.col-sm-3.col-xs-12.svg-container
|
.col-sm-3.col-xs-12.svg-container
|
||||||
= custom_icon('icon_cycle_analytics_splash')
|
= custom_icon('icon_cycle_analytics_splash')
|
||||||
|
|
|
@ -1,28 +1,15 @@
|
||||||
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
|
- if can?(current_user, :create_deployment, deployment)
|
||||||
.pull-right
|
- actions = deployment.manual_actions
|
||||||
|
- if actions.present?
|
||||||
- external_url = deployment.environment.external_url
|
.inline
|
||||||
- if external_url
|
.dropdown
|
||||||
= link_to external_url, target: '_blank', class: 'btn external-url' do
|
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
|
||||||
= icon('external-link')
|
= custom_icon('icon_play')
|
||||||
|
= icon('caret-down')
|
||||||
- actions = deployment.manual_actions
|
%ul.dropdown-menu.dropdown-menu-align-right
|
||||||
- if actions.present?
|
- actions.each do |action|
|
||||||
.inline
|
%li
|
||||||
.dropdown
|
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
|
||||||
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
|
= custom_icon('icon_play')
|
||||||
= custom_icon('icon_play')
|
%span= action.name.humanize
|
||||||
= 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
|
|
||||||
|
|
|
@ -17,4 +17,6 @@
|
||||||
#{time_ago_with_tooltip(deployment.created_at)}
|
#{time_ago_with_tooltip(deployment.created_at)}
|
||||||
|
|
||||||
%td.hidden-xs
|
%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
|
||||||
|
|
|
@ -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
|
|
@ -28,4 +28,8 @@
|
||||||
#{time_ago_with_tooltip(last_deployment.created_at)}
|
#{time_ago_with_tooltip(last_deployment.created_at)}
|
||||||
|
|
||||||
%td.hidden-xs
|
%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
|
||||||
|
|
|
@ -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')
|
|
@ -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')
|
|
@ -3,14 +3,27 @@
|
||||||
= render "projects/pipelines/head"
|
= render "projects/pipelines/head"
|
||||||
|
|
||||||
%div{ class: container_class }
|
%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
|
.nav-controls
|
||||||
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
|
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
|
||||||
New environment
|
New environment
|
||||||
|
|
||||||
.environments-container
|
.environments-container
|
||||||
- if @environments.blank?
|
- if @all_environments.blank?
|
||||||
.blank-state.blank-state-no-icon
|
.blank-state.blank-state-no-icon
|
||||||
%h2.blank-state-title
|
%h2.blank-state-title
|
||||||
You don't have any environments right now.
|
You don't have any environments right now.
|
||||||
|
|
|
@ -3,14 +3,16 @@
|
||||||
= render "projects/pipelines/head"
|
= render "projects/pipelines/head"
|
||||||
|
|
||||||
%div{ class: container_class }
|
%div{ class: container_class }
|
||||||
.top-area
|
.top-area.adjust
|
||||||
.col-md-9
|
.col-md-9
|
||||||
%h3.page-title= @environment.name.capitalize
|
%h3.page-title= @environment.name.capitalize
|
||||||
.col-md-3
|
.col-md-3
|
||||||
.nav-controls
|
.nav-controls
|
||||||
|
= render 'projects/environments/external_url', environment: @environment
|
||||||
- if can?(current_user, :update_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 '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
|
.deployments-container
|
||||||
- if @deployments.blank?
|
- if @deployments.blank?
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
- if issue.labels.any?
|
- if issue.labels.any?
|
||||||
|
|
||||||
- issue.labels.each do |label|
|
- issue.labels.each do |label|
|
||||||
= link_to_label(label, project: issue.project)
|
= link_to_label(label, subject: issue.project)
|
||||||
- if issue.tasks?
|
- if issue.tasks?
|
||||||
|
|
||||||
%span.task-status
|
%span.task-status
|
||||||
|
|
|
@ -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
|
%h3.page-title
|
||||||
Edit Issue ##{@issue.iid}
|
Edit Issue ##{@issue.iid}
|
||||||
|
|
|
@ -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_description @issue.description
|
||||||
- page_card_attributes @issue.card_attributes
|
- page_card_attributes @issue.card_attributes
|
||||||
|
|
||||||
|
|
|
@ -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');
|
|
|
@ -1,2 +1,2 @@
|
||||||
- if @project.labels.size == 0
|
- if @labels.empty?
|
||||||
$('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
|
$('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
|
||||||
|
|
|
@ -6,4 +6,4 @@
|
||||||
%h3.page-title
|
%h3.page-title
|
||||||
Edit Label
|
Edit Label
|
||||||
%hr
|
%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)
|
||||||
|
|
|
@ -16,21 +16,22 @@
|
||||||
.labels
|
.labels
|
||||||
- if can?(current_user, :admin_label, @project)
|
- if can?(current_user, :admin_label, @project)
|
||||||
-# Only show it in the first page
|
-# 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) }
|
.prioritized-labels{ class: ('hide' if hide) }
|
||||||
%h5 Prioritized Labels
|
%h5 Prioritized Labels
|
||||||
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
|
%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
|
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
|
||||||
- if @prioritized_labels.present?
|
- if @prioritized_labels.present?
|
||||||
= render @prioritized_labels
|
= render partial: 'shared/label', collection: @prioritized_labels, as: :label
|
||||||
|
|
||||||
.other-labels
|
.other-labels
|
||||||
- if can?(current_user, :admin_label, @project)
|
- if can?(current_user, :admin_label, @project)
|
||||||
%h5{ class: ('hide' if hide) } Other Labels
|
%h5{ class: ('hide' if hide) } Other Labels
|
||||||
- if @labels.present?
|
%ul.content-list.manage-labels-list.js-other-labels
|
||||||
%ul.content-list.manage-labels-list.js-other-labels
|
- if @labels.present?
|
||||||
= render @labels
|
= render partial: 'shared/label', collection: @labels, as: :label
|
||||||
= paginate @labels, theme: 'gitlab'
|
= paginate @labels, theme: 'gitlab'
|
||||||
- else
|
- if @labels.blank?
|
||||||
.nothing-here-block
|
.nothing-here-block
|
||||||
- if can?(current_user, :admin_label, @project)
|
- 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}.
|
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
|
||||||
|
|
|
@ -6,4 +6,4 @@
|
||||||
%h3.page-title
|
%h3.page-title
|
||||||
New Label
|
New Label
|
||||||
%hr
|
%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)
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
- if merge_request.labels.any?
|
- if merge_request.labels.any?
|
||||||
|
|
||||||
- merge_request.labels.each do |label|
|
- 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?
|
- if merge_request.tasks?
|
||||||
|
|
||||||
%span.task-status
|
%span.task-status
|
||||||
|
|
|
@ -29,7 +29,11 @@
|
||||||
= link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
|
= link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
|
||||||
Commits
|
Commits
|
||||||
%span.badge= @commits.size
|
%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
|
%li.builds-tab
|
||||||
= link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
|
= link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
|
||||||
Builds
|
Builds
|
||||||
|
@ -44,9 +48,11 @@
|
||||||
= render "projects/merge_requests/show/commits"
|
= render "projects/merge_requests/show/commits"
|
||||||
#diffs.diffs.tab-pane
|
#diffs.diffs.tab-pane
|
||||||
- # This tab is always loaded via AJAX
|
- # This tab is always loaded via AJAX
|
||||||
- if @pipeline
|
- if @pipelines.any?
|
||||||
#builds.builds.tab-pane
|
#builds.builds.tab-pane
|
||||||
= render "projects/merge_requests/show/builds"
|
= render "projects/merge_requests/show/builds"
|
||||||
|
#pipelines.pipelines.tab-pane
|
||||||
|
= render "projects/merge_requests/show/pipelines"
|
||||||
|
|
||||||
.mr-loading-status
|
.mr-loading-status
|
||||||
= spinner
|
= spinner
|
||||||
|
@ -59,5 +65,5 @@
|
||||||
:javascript
|
:javascript
|
||||||
var merge_request = new MergeRequest({
|
var merge_request = new MergeRequest({
|
||||||
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
|
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
|
||||||
buildsLoaded: "#{@pipeline ? 'true' : 'false'}"
|
buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}"
|
||||||
});
|
});
|
||||||
|
|
|
@ -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_description @merge_request.description
|
||||||
- page_card_attributes @merge_request.card_attributes
|
- page_card_attributes @merge_request.card_attributes
|
||||||
- content_for :page_specific_javascripts do
|
- content_for :page_specific_javascripts do
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
%li.pipelines-tab
|
%li.pipelines-tab
|
||||||
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
|
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
|
||||||
Pipelines
|
Pipelines
|
||||||
%span.badge= @merge_request.all_pipelines.size
|
%span.badge= @pipelines.size
|
||||||
%li.builds-tab
|
%li.builds-tab
|
||||||
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
|
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
|
||||||
Builds
|
Builds
|
||||||
|
|
|
@ -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
|
%h3.page-title
|
||||||
Edit Merge Request #{@merge_request.to_reference}
|
Edit Merge Request #{@merge_request.to_reference}
|
||||||
|
|
|
@ -64,8 +64,8 @@
|
||||||
.checkbox
|
.checkbox
|
||||||
= f.label :public_builds do
|
= f.label :public_builds do
|
||||||
= f.check_box :public_builds
|
= f.check_box :public_builds
|
||||||
%strong Public pipelines
|
%strong Public builds
|
||||||
.help-block Allow everyone to access pipelines for Public and Internal projects
|
.help-block Allow everyone to access builds traces for Public and Internal projects
|
||||||
|
|
||||||
.form-group.append-bottom-default
|
.form-group.append-bottom-default
|
||||||
= f.label :runners_token, "Runners token", class: 'label-light'
|
= f.label :runners_token, "Runners token", class: 'label-light'
|
||||||
|
|
|
@ -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');
|
|
@ -3,13 +3,16 @@
|
||||||
.draggable-handler
|
.draggable-handler
|
||||||
= icon('bars')
|
= icon('bars')
|
||||||
.js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
|
.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' }
|
%button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' }
|
||||||
= icon('star-o')
|
= icon('star-o')
|
||||||
%button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' }
|
%button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' }
|
||||||
= icon('star')
|
= icon('star')
|
||||||
%span.label-name
|
%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
|
- if label.description
|
||||||
%span.label-description
|
%span.label-description
|
||||||
= markdown_field(label, :description)
|
= markdown_field(label, :description)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- labels.each do |label|
|
- labels.each do |label|
|
||||||
%span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" }
|
%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 } }
|
%button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
|
||||||
= icon("times")
|
= icon("times")
|
||||||
|
|
|
@ -29,8 +29,9 @@
|
||||||
.filter-item.inline.labels-filter
|
.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[]" }
|
= 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
|
- if issuable_filters_present
|
||||||
%a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
|
.filter-item.inline.reset-filters
|
||||||
|
%a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
|
||||||
|
|
||||||
.pull-right
|
.pull-right
|
||||||
- if boards_page
|
- if boards_page
|
||||||
|
@ -77,11 +78,10 @@
|
||||||
= hidden_field_tag :state_event, params[:state_event]
|
= hidden_field_tag :state_event, params[:state_event]
|
||||||
.filter-item.inline
|
.filter-item.inline
|
||||||
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
|
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
|
||||||
|
- has_labels = @labels && @labels.any?
|
||||||
- if !@labels.nil?
|
.row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
|
||||||
.row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
|
- if has_labels
|
||||||
- if @labels.any?
|
= render 'shared/labels_row', labels: @labels
|
||||||
= render "shared/labels_row", labels: @labels
|
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
new UsersSelect();
|
new UsersSelect();
|
||||||
|
|
|
@ -88,19 +88,19 @@
|
||||||
- if issuable.assignee_id
|
- if issuable.assignee_id
|
||||||
= f.hidden_field :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",
|
= 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
|
.form-group.issue-milestone
|
||||||
= f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
|
= 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) }
|
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
|
||||||
.issuable-form-select-holder
|
.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
|
.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.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
|
||||||
= f.hidden_field :label_ids, multiple: true, value: ''
|
= 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}" }
|
.col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
|
||||||
.issuable-form-select-holder
|
.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
|
- if has_due_date
|
||||||
.col-lg-6
|
.col-lg-6
|
||||||
.form-group
|
.form-group
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
= dropdown_content do
|
= dropdown_content do
|
||||||
.js-due-date-calendar
|
.js-due-date-calendar
|
||||||
|
|
||||||
- if issuable.project.labels.any?
|
- if @labels && @labels.any?
|
||||||
- selected_labels = issuable.labels
|
- selected_labels = issuable.labels
|
||||||
.block.labels
|
.block.labels
|
||||||
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
|
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
|
||||||
|
|
|
@ -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_errors(@label)
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
|
@ -30,4 +30,4 @@
|
||||||
= f.submit 'Save changes', class: 'btn btn-save js-save-button'
|
= f.submit 'Save changes', class: 'btn btn-save js-save-button'
|
||||||
- else
|
- else
|
||||||
= f.submit 'Create Label', class: 'btn btn-create js-save-button'
|
= 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'
|
|
@ -5,6 +5,7 @@ en:
|
||||||
hello: "Hello world"
|
hello: "Hello world"
|
||||||
errors:
|
errors:
|
||||||
messages:
|
messages:
|
||||||
|
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
|
||||||
wrong_size: "is the wrong size (should be %{file_size})"
|
wrong_size: "is the wrong size (should be %{file_size})"
|
||||||
size_too_small: "is too small (should be at least %{file_size})"
|
size_too_small: "is too small (should be at least %{file_size})"
|
||||||
size_too_big: "is too big (should be at most %{file_size})"
|
size_too_big: "is too big (should be at most %{file_size})"
|
||||||
|
|
|
@ -28,5 +28,7 @@ resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } do
|
||||||
|
|
||||||
resource :avatar, only: [:destroy]
|
resource :avatar, only: [:destroy]
|
||||||
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
|
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
|
||||||
|
|
||||||
|
resources :labels, except: [:show], constraints: { id: /\d+/ }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -319,7 +319,11 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :environments
|
resources :environments, except: [:destroy] do
|
||||||
|
member do
|
||||||
|
post :stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resource :cycle_analytics, only: [:show]
|
resource :cycle_analytics, only: [:show]
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ class Gitlab::Seeder::Pipelines
|
||||||
{ name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending },
|
{ name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending },
|
||||||
{ name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running },
|
{ name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running },
|
||||||
{ name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled },
|
{ name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled },
|
||||||
{ name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success },
|
{ name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, options: { environment: { on_stop: 'stop staging' } } },
|
||||||
|
{ name: 'stop staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped },
|
||||||
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
|
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
|
||||||
{ name: 'slack', stage: 'notify', when: 'manual', status: :created },
|
{ name: 'slack', stage: 'notify', when: 'manual', status: :created },
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
class AddTypeToLabels < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = true
|
||||||
|
DOWNTIME_REASON = 'Labels will not work as expected until this migration is complete.'
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :labels, :type, :string
|
||||||
|
|
||||||
|
update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query|
|
||||||
|
query.where(table[:project_id].not_eq(nil))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
class AddGroupIdToLabels < ActiveRecord::Migration
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :labels, :group_id, :integer
|
||||||
|
add_foreign_key :labels, :namespaces, column: :group_id, on_delete: :cascade
|
||||||
|
add_concurrent_index :labels, :group_id
|
||||||
|
end
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue