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.
|
||||
|
||||
## 8.14.0 (2016-11-22)
|
||||
|
||||
## 8.13.0 (2016-10-22)
|
||||
|
||||
- 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
|
||||
- Add link from system note to compare with previous version
|
||||
- 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
|
||||
- Add `/projects/visible` API endpoint (Ben Boeckel)
|
||||
- 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
|
||||
- Add group level labels. (!6425)
|
||||
- Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun)
|
||||
- Cancelled pipelines could be retried. !6927
|
||||
- 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
|
||||
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
|
||||
- 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
|
||||
- Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska)
|
||||
- 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)
|
||||
- Fixes padding in all clipboard icons that have .btn class
|
||||
- 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
|
||||
- Add docs for request profiling
|
||||
- Delete dynamic environments
|
||||
- Fix buggy iOS tooltip layering behavior.
|
||||
- Make guests unable to view MRs on private projects
|
||||
|
||||
## 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-google-oauth2', '~> 0.4.1'
|
||||
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-twitter', '~> 1.2.0'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
|
|
|
@ -473,9 +473,9 @@ GEM
|
|||
omniauth-oauth2 (1.3.1)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-saml (1.6.0)
|
||||
omniauth-saml (1.7.0)
|
||||
omniauth (~> 1.3)
|
||||
ruby-saml (~> 1.3)
|
||||
ruby-saml (~> 1.4)
|
||||
omniauth-shibboleth (1.2.1)
|
||||
omniauth (>= 1.0.0)
|
||||
omniauth-twitter (1.2.1)
|
||||
|
@ -635,7 +635,7 @@ GEM
|
|||
crack (~> 0.4)
|
||||
ruby-prof (0.16.2)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby-saml (1.3.0)
|
||||
ruby-saml (1.4.1)
|
||||
nokogiri (>= 1.5.10)
|
||||
ruby_parser (3.8.2)
|
||||
sexp_processor (~> 4.1)
|
||||
|
@ -915,7 +915,7 @@ DEPENDENCIES
|
|||
omniauth-gitlab (~> 1.0.0)
|
||||
omniauth-google-oauth2 (~> 0.4.1)
|
||||
omniauth-kerberos (~> 0.3.0)
|
||||
omniauth-saml (~> 1.6.0)
|
||||
omniauth-saml (~> 1.7.0)
|
||||
omniauth-shibboleth (~> 1.2.0)
|
||||
omniauth-twitter (~> 1.2.0)
|
||||
omniauth_crowd (~> 2.2.0)
|
||||
|
|
|
@ -168,6 +168,8 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
new ShortcutsBlob(true);
|
||||
break;
|
||||
case 'groups:labels:new':
|
||||
case 'groups:labels:edit':
|
||||
case 'projects:labels:new':
|
||||
case 'projects:labels:edit':
|
||||
new Labels();
|
||||
|
|
|
@ -266,7 +266,7 @@
|
|||
},
|
||||
fieldName: $dropdown.data('field-name'),
|
||||
id: function(label) {
|
||||
if (label.id <= 0) return;
|
||||
if (label.id <= 0) return label.title;
|
||||
|
||||
if ($dropdown.hasClass('js-issuable-form-dropdown')) {
|
||||
return label.id;
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
View on <%- external_url_formatted %>
|
||||
</a>
|
||||
</span>
|
||||
<span class="stop-env-container js-stop-env-link">
|
||||
<a href="<%- stop_url %>" class="close-evn-link" data-method="post" rel="nofollow" data-confirm="Are you sure you want to stop this environment?">
|
||||
<i class="fa fa-stop-circle-o"/>
|
||||
Stop environment
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
@ -205,6 +211,11 @@
|
|||
if ($(`.mr-state-widget #${ environment.id }`).length) return;
|
||||
const $template = $(DEPLOYMENT_TEMPLATE);
|
||||
if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
|
||||
|
||||
if (!environment.stop_url) {
|
||||
$('.js-stop-env-link', $template).remove();
|
||||
}
|
||||
|
||||
if (environment.deployed_at && environment.deployed_at_formatted) {
|
||||
environment.deployed_at = $.timeago(environment.deployed_at) + '.';
|
||||
} else {
|
||||
|
|
|
@ -167,7 +167,6 @@
|
|||
*/
|
||||
&.code {
|
||||
padding: 0;
|
||||
-webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,3 +27,15 @@ body {
|
|||
.container-limited {
|
||||
max-width: $fixed-layout-width;
|
||||
}
|
||||
|
||||
|
||||
/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch,
|
||||
which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side
|
||||
effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children
|
||||
of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */
|
||||
|
||||
.navbar,
|
||||
.page-gutter,
|
||||
.page-with-sidebar {
|
||||
-webkit-overflow-scrolling: auto;
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@
|
|||
background: #fff;
|
||||
color: #333;
|
||||
border-radius: 0 0 3px 3px;
|
||||
-webkit-overflow-scrolling: auto;
|
||||
|
||||
.unfold {
|
||||
cursor: pointer;
|
||||
|
|
|
@ -38,6 +38,14 @@
|
|||
color: $gl-dark-link-color;
|
||||
}
|
||||
|
||||
.stop-env-link {
|
||||
color: $table-text-gray;
|
||||
|
||||
.stop-env-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.deployment {
|
||||
.build-column {
|
||||
|
||||
|
|
|
@ -69,6 +69,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
|
@ -209,6 +223,13 @@
|
|||
}
|
||||
|
||||
.label-subscribe-button {
|
||||
.label-subscribe-button-icon {
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.label-subscribe-button-loading {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -183,6 +183,15 @@
|
|||
.ci-coverage {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.stop-env-container {
|
||||
color: $gl-text-color;
|
||||
float: right;
|
||||
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mr_source_commit,
|
||||
|
|
|
@ -2,6 +2,7 @@ module IssuableActions
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :labels, only: [:show, :new, :edit]
|
||||
before_action :authorize_destroy_issuable!, only: :destroy
|
||||
before_action :authorize_admin_issuable!, only: :bulk_update
|
||||
end
|
||||
|
@ -25,6 +26,10 @@ module IssuableActions
|
|||
|
||||
private
|
||||
|
||||
def labels
|
||||
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||
end
|
||||
|
||||
def authorize_destroy_issuable!
|
||||
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
|
||||
return access_denied!
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
class Dashboard::LabelsController < Dashboard::ApplicationController
|
||||
def index
|
||||
labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title)
|
||||
labels = LabelsFinder.new(current_user).execute
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: labels }
|
||||
format.json { render json: labels.as_json(only: [:id, :title, :color]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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)
|
||||
resource.as_json(
|
||||
labels: true,
|
||||
only: [:iid, :title, :confidential, :due_date],
|
||||
include: {
|
||||
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] },
|
||||
milestone: { only: [:id, :title] }
|
||||
},
|
||||
user: current_user
|
||||
|
|
|
@ -76,9 +76,8 @@ module Projects
|
|||
resource.as_json(
|
||||
only: [:id, :list_type, :position],
|
||||
methods: [:title],
|
||||
include: {
|
||||
label: { only: [:id, :title, :description, :color, :priority] }
|
||||
})
|
||||
label: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,19 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
layout 'project'
|
||||
before_action :authorize_read_environment!
|
||||
before_action :authorize_create_environment!, only: [:new, :create]
|
||||
before_action :authorize_update_environment!, only: [:edit, :update, :destroy]
|
||||
before_action :environment, only: [:show, :edit, :update, :destroy]
|
||||
before_action :authorize_create_deployment!, only: [:stop]
|
||||
before_action :authorize_update_environment!, only: [:edit, :update]
|
||||
before_action :environment, only: [:show, :edit, :update, :stop]
|
||||
|
||||
def index
|
||||
@environments = project.environments
|
||||
@scope = params[:scope]
|
||||
@all_environments = project.environments
|
||||
@environments =
|
||||
if @scope == 'stopped'
|
||||
@all_environments.stopped
|
||||
else
|
||||
@all_environments.available
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
|
@ -38,14 +46,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @environment.destroy
|
||||
flash[:notice] = 'Environment was successfully removed.'
|
||||
else
|
||||
flash[:alert] = 'Failed to remove environment.'
|
||||
end
|
||||
def stop
|
||||
return render_404 unless @environment.stoppable?
|
||||
|
||||
redirect_to namespace_project_environments_path(project.namespace, project)
|
||||
new_action = @environment.stop!(current_user)
|
||||
redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, new_action])
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -26,7 +26,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@issues = issues_collection
|
||||
@issues = @issues.page(params[:page])
|
||||
|
||||
@labels = @project.labels.where(title: params[:label_name])
|
||||
if params[:label_name].present?
|
||||
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -3,21 +3,22 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
|
||||
before_action :module_enabled
|
||||
before_action :label, only: [:edit, :update, :destroy]
|
||||
before_action :find_labels, only: [:index, :set_priorities, :remove_priority]
|
||||
before_action :authorize_read_label!
|
||||
before_action :authorize_admin_labels!, only: [
|
||||
:new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities
|
||||
]
|
||||
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
|
||||
:generate, :destroy, :remove_priority,
|
||||
:set_priorities]
|
||||
|
||||
respond_to :js, :html
|
||||
|
||||
def index
|
||||
@labels = @project.labels.unprioritized.page(params[:page])
|
||||
@prioritized_labels = @project.labels.prioritized
|
||||
@prioritized_labels = @available_labels.prioritized(@project)
|
||||
@labels = @available_labels.unprioritized(@project).page(params[:page])
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: @project.labels
|
||||
render json: @available_labels.as_json(only: [:id, :title, :color])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -36,7 +37,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render 'new' }
|
||||
format.html { render :new }
|
||||
format.json { render json: { message: @label.errors.messages }, status: 400 }
|
||||
end
|
||||
end
|
||||
|
@ -49,7 +50,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
if @label.update_attributes(label_params)
|
||||
redirect_to namespace_project_labels_path(@project.namespace, @project)
|
||||
else
|
||||
render 'edit'
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -68,6 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
|
||||
def destroy
|
||||
@label.destroy
|
||||
@labels = find_labels
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
@ -80,20 +82,24 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
|
||||
def remove_priority
|
||||
respond_to do |format|
|
||||
if label.update_attribute(:priority, nil)
|
||||
label = @available_labels.find(params[:id])
|
||||
|
||||
if label.unprioritize!(project)
|
||||
format.json { render json: label }
|
||||
else
|
||||
message = label.errors.full_messages.uniq.join('. ')
|
||||
format.json { render json: { message: message }, status: :unprocessable_entity }
|
||||
format.json { head :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_priorities
|
||||
Label.transaction do
|
||||
params[:label_ids].each_with_index do |label_id, index|
|
||||
label = @project.labels.find_by_id(label_id)
|
||||
label.update_attribute(:priority, index) if label
|
||||
available_labels_ids = @available_labels.where(id: params[:label_ids]).pluck(:id)
|
||||
label_ids = params[:label_ids].select { |id| available_labels_ids.include?(id.to_i) }
|
||||
|
||||
label_ids.each_with_index do |label_id, index|
|
||||
label = @available_labels.find(label_id)
|
||||
label.prioritize!(project, index)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -119,6 +125,10 @@ class Projects::LabelsController < Projects::ApplicationController
|
|||
end
|
||||
alias_method :subscribable_resource, :label
|
||||
|
||||
def find_labels
|
||||
@available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities)
|
||||
end
|
||||
|
||||
def authorize_admin_labels!
|
||||
return render_404 unless can?(current_user, :admin_label, @project)
|
||||
end
|
||||
|
|
|
@ -40,7 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@merge_requests = @merge_requests.page(params[:page])
|
||||
@merge_requests = @merge_requests.preload(:target_project)
|
||||
|
||||
@labels = @project.labels.where(title: params[:label_name])
|
||||
if params[:label_name].present?
|
||||
labels_params = { project_id: @project.id, title: params[:label_name] }
|
||||
@labels = LabelsFinder.new(current_user, labels_params).execute
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
@ -422,10 +425,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
project = environment.project
|
||||
deployment = environment.first_deployment_for(@merge_request.diff_head_commit)
|
||||
|
||||
stop_url =
|
||||
if environment.stoppable? && can?(current_user, :create_deployment, environment)
|
||||
stop_namespace_project_environment_path(project.namespace, project, environment)
|
||||
end
|
||||
|
||||
{
|
||||
id: environment.id,
|
||||
name: environment.name,
|
||||
url: namespace_project_environment_path(project.namespace, project, environment),
|
||||
stop_url: stop_url,
|
||||
external_url: environment.external_url,
|
||||
external_url_formatted: environment.formatted_external_url,
|
||||
deployed_at: deployment.try(:created_at),
|
||||
|
@ -483,13 +492,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@noteable = @merge_request
|
||||
@commits_count = @merge_request.commits.count
|
||||
|
||||
@pipeline = @merge_request.pipeline
|
||||
@statuses = @pipeline.statuses.relevant if @pipeline
|
||||
|
||||
if @merge_request.locked_long_ago?
|
||||
@merge_request.unlock_mr
|
||||
@merge_request.close
|
||||
end
|
||||
|
||||
define_pipelines_vars
|
||||
end
|
||||
|
||||
# Discussion tab data is rendered on html responses of actions
|
||||
|
@ -517,7 +525,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
|
||||
def define_widget_vars
|
||||
@pipeline = @merge_request.pipeline
|
||||
@pipelines = [@pipeline].compact
|
||||
end
|
||||
|
||||
def define_commit_vars
|
||||
|
@ -544,6 +551,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
)
|
||||
end
|
||||
|
||||
def define_pipelines_vars
|
||||
@pipelines = @merge_request.all_pipelines
|
||||
|
||||
if @pipelines.any?
|
||||
@pipeline = @pipelines.first
|
||||
@statuses = @pipeline.statuses.relevant
|
||||
end
|
||||
end
|
||||
|
||||
def define_new_vars
|
||||
@noteable = @merge_request
|
||||
|
||||
|
@ -559,10 +575,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
@commit = @merge_request.diff_head_commit
|
||||
@base_commit = @merge_request.diff_base_commit
|
||||
|
||||
@pipeline = @merge_request.pipeline
|
||||
@statuses = @pipeline.statuses.relevant if @pipeline
|
||||
@note_counts = Note.where(commit_id: @commits.map(&:id)).
|
||||
group(:commit_id).count
|
||||
|
||||
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||
|
||||
define_pipelines_vars
|
||||
end
|
||||
|
||||
def invalid_mr
|
||||
|
|
|
@ -124,15 +124,12 @@ class IssuableFinder
|
|||
def labels
|
||||
return @labels if defined?(@labels)
|
||||
|
||||
if labels? && !filter_by_no_label?
|
||||
@labels = Label.where(title: label_names)
|
||||
|
||||
if projects
|
||||
@labels = @labels.where(project: projects)
|
||||
@labels =
|
||||
if labels? && !filter_by_no_label?
|
||||
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute
|
||||
else
|
||||
Label.none
|
||||
end
|
||||
else
|
||||
@labels = Label.none
|
||||
end
|
||||
end
|
||||
|
||||
def assignee?
|
||||
|
@ -274,8 +271,10 @@ class IssuableFinder
|
|||
items = items.without_label
|
||||
else
|
||||
items = items.with_label(label_names, params[:sort])
|
||||
|
||||
if projects
|
||||
items = items.where(labels: { project_id: projects })
|
||||
label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id)
|
||||
items = items.where(labels: { id: label_ids })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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)
|
||||
# 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
|
||||
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
|
||||
end
|
||||
|
|
|
@ -124,6 +124,10 @@ module IssuablesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def issuable_filters_present
|
||||
params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name]
|
||||
end
|
||||
|
||||
def issuables_count_for_state(issuable_type, state)
|
||||
issuables_finder = public_send("#{issuable_type}_finder")
|
||||
issuables_finder.params[:state] = state
|
||||
|
|
|
@ -4,9 +4,8 @@ module LabelsHelper
|
|||
# Link to a Label
|
||||
#
|
||||
# label - Label object to link to
|
||||
# project - Project object which will be used as the context for the label's
|
||||
# link. If omitted, defaults to `@project`, or the label's own
|
||||
# project.
|
||||
# subject - Project/Group object which will be used as the context for the
|
||||
# label's link. If omitted, defaults to the label's own group/project.
|
||||
# type - The type of item the link will point to (:issue or
|
||||
# :merge_request). If omitted, defaults to :issue.
|
||||
# block - An optional block that will be passed to `link_to`, forming the
|
||||
|
@ -15,15 +14,14 @@ module LabelsHelper
|
|||
#
|
||||
# Examples:
|
||||
#
|
||||
# # Allow the generated link to use the label's own project
|
||||
# # Allow the generated link to use the label's own subject
|
||||
# link_to_label(label)
|
||||
#
|
||||
# # Force the generated link to use @project
|
||||
# @project = Project.first
|
||||
# link_to_label(label)
|
||||
# # Force the generated link to use a provided group
|
||||
# link_to_label(label, subject: Group.last)
|
||||
#
|
||||
# # Force the generated link to use a provided project
|
||||
# link_to_label(label, project: Project.last)
|
||||
# link_to_label(label, subject: Project.last)
|
||||
#
|
||||
# # Force the generated link to point to merge requests instead of issues
|
||||
# link_to_label(label, type: :merge_request)
|
||||
|
@ -32,9 +30,8 @@ module LabelsHelper
|
|||
# link_to_label(label) { "My Custom Label Text" }
|
||||
#
|
||||
# Returns a String
|
||||
def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block)
|
||||
project ||= @project || label.project
|
||||
link = label_filter_path(project, label, type: type)
|
||||
def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block)
|
||||
link = label_filter_path(subject || label.subject, label, type: type)
|
||||
|
||||
if block_given?
|
||||
link_to link, class: css_class, &block
|
||||
|
@ -43,15 +40,40 @@ module LabelsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def label_filter_path(project, label, type: issue)
|
||||
send("namespace_project_#{type.to_s.pluralize}_path",
|
||||
project.namespace,
|
||||
project,
|
||||
label_name: [label.name])
|
||||
def label_filter_path(subject, label, type: :issue)
|
||||
case subject
|
||||
when Group
|
||||
send("#{type.to_s.pluralize}_group_path",
|
||||
subject,
|
||||
label_name: [label.name])
|
||||
when Project
|
||||
send("namespace_project_#{type.to_s.pluralize}_path",
|
||||
subject.namespace,
|
||||
subject,
|
||||
label_name: [label.name])
|
||||
end
|
||||
end
|
||||
|
||||
def project_label_names
|
||||
@project.labels.pluck(:title)
|
||||
def edit_label_path(label)
|
||||
case label
|
||||
when GroupLabel then edit_group_label_path(label.group, label)
|
||||
when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_label_path(label)
|
||||
case label
|
||||
when GroupLabel then group_label_path(label.group, label)
|
||||
when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label)
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_subscription_data(label)
|
||||
return unless label.is_a?(ProjectLabel)
|
||||
|
||||
{
|
||||
url: toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label)
|
||||
}
|
||||
end
|
||||
|
||||
def render_colored_label(label, label_suffix = '', tooltip: true)
|
||||
|
@ -68,8 +90,8 @@ module LabelsHelper
|
|||
span.html_safe
|
||||
end
|
||||
|
||||
def render_colored_cross_project_label(label, tooltip: true)
|
||||
label_suffix = label.project.name_with_namespace
|
||||
def render_colored_cross_project_label(label, source_project = nil, tooltip: true)
|
||||
label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace
|
||||
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
|
||||
render_colored_label(label, label_suffix, tooltip: tooltip)
|
||||
end
|
||||
|
@ -115,7 +137,10 @@ module LabelsHelper
|
|||
end
|
||||
|
||||
def labels_filter_path
|
||||
return group_labels_path(@group, :json) if @group
|
||||
|
||||
project = @target_project || @project
|
||||
|
||||
if project
|
||||
namespace_project_labels_path(project.namespace, project, :json)
|
||||
else
|
||||
|
@ -124,11 +149,24 @@ module LabelsHelper
|
|||
end
|
||||
|
||||
def label_subscription_status(label)
|
||||
label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed'
|
||||
case label
|
||||
when GroupLabel then 'Subscribing to group labels is currently not supported.'
|
||||
when ProjectLabel then label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed'
|
||||
end
|
||||
end
|
||||
|
||||
def label_subscription_toggle_button_text(label)
|
||||
label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe'
|
||||
case label
|
||||
when GroupLabel then 'Subscribing to group labels is currently not supported.'
|
||||
when ProjectLabel then label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe'
|
||||
end
|
||||
end
|
||||
|
||||
def label_deletion_confirm_text(label)
|
||||
case label
|
||||
when GroupLabel then 'Remove this label? This will affect all projects within the group. Are you sure?'
|
||||
when ProjectLabel then 'Remove this label? Are you sure?'
|
||||
end
|
||||
end
|
||||
|
||||
# Required for Banzai::Filter::LabelReferenceFilter
|
||||
|
|
|
@ -19,7 +19,7 @@ module Ci
|
|||
validates_presence_of :status, unless: :importing?
|
||||
validate :valid_commit_sha, unless: :importing?
|
||||
|
||||
after_save :keep_around_commits, unless: :importing?
|
||||
after_create :keep_around_commits, unless: :importing?
|
||||
|
||||
delegate :stages, to: :statuses
|
||||
|
||||
|
|
|
@ -145,8 +145,14 @@ module Issuable
|
|||
end
|
||||
|
||||
def order_labels_priority(excluded_labels: [])
|
||||
condition_field = "#{table_name}.id"
|
||||
highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql
|
||||
params = {
|
||||
target_type: name,
|
||||
target_column: "#{table_name}.id",
|
||||
project_column: "#{table_name}.#{project_foreign_key}",
|
||||
excluded_labels: excluded_labels
|
||||
}
|
||||
|
||||
highest_priority = highest_label_priority(params).to_sql
|
||||
|
||||
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
|
||||
group(arel_table[:id]).
|
||||
|
@ -230,18 +236,6 @@ module Issuable
|
|||
labels.order('title ASC').pluck(:title)
|
||||
end
|
||||
|
||||
def remove_labels
|
||||
labels.delete_all
|
||||
end
|
||||
|
||||
def add_labels_by_names(label_names)
|
||||
label_names.each do |label_name|
|
||||
label = project.labels.create_with(color: Label::DEFAULT_COLOR).
|
||||
find_or_create_by(title: label_name.strip)
|
||||
self.labels << label
|
||||
end
|
||||
end
|
||||
|
||||
# Convert this Issuable class name to a format usable by Ability definitions
|
||||
#
|
||||
# Examples:
|
||||
|
|
|
@ -38,11 +38,13 @@ module Sortable
|
|||
|
||||
private
|
||||
|
||||
def highest_label_priority(object_types, condition_field, excluded_labels: [])
|
||||
query = Label.select(Label.arel_table[:priority].minimum).
|
||||
def highest_label_priority(target_type:, target_column:, project_column:, excluded_labels: [])
|
||||
query = Label.select(LabelPriority.arel_table[:priority].minimum).
|
||||
left_join_priorities.
|
||||
joins(:label_links).
|
||||
where(label_links: { target_type: object_types }).
|
||||
where("label_links.target_id = #{condition_field}").
|
||||
where("label_priorities.project_id = #{project_column}").
|
||||
where(label_links: { target_type: target_type }).
|
||||
where("label_links.target_id = #{target_column}").
|
||||
reorder(nil)
|
||||
|
||||
query.where.not(title: excluded_labels) if excluded_labels.present?
|
||||
|
|
|
@ -34,7 +34,7 @@ class Deployment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def manual_actions
|
||||
deployable.try(:other_actions)
|
||||
@manual_actions ||= deployable.try(:other_actions)
|
||||
end
|
||||
|
||||
def includes_commit?(commit)
|
||||
|
@ -84,6 +84,17 @@ class Deployment < ActiveRecord::Base
|
|||
take
|
||||
end
|
||||
|
||||
def stop_action
|
||||
return nil unless on_stop.present?
|
||||
return nil unless manual_actions
|
||||
|
||||
@stop_action ||= manual_actions.find_by(name: on_stop)
|
||||
end
|
||||
|
||||
def stoppable?
|
||||
stop_action.present?
|
||||
end
|
||||
|
||||
def formatted_deployment_time
|
||||
created_at.to_time.in_time_zone.to_s(:medium)
|
||||
end
|
||||
|
|
|
@ -19,6 +19,24 @@ class Environment < ActiveRecord::Base
|
|||
allow_nil: true,
|
||||
addressable_url: true
|
||||
|
||||
delegate :stop_action, to: :last_deployment, allow_nil: true
|
||||
|
||||
scope :available, -> { with_state(:available) }
|
||||
scope :stopped, -> { with_state(:stopped) }
|
||||
|
||||
state_machine :state, initial: :available do
|
||||
event :start do
|
||||
transition stopped: :available
|
||||
end
|
||||
|
||||
event :stop do
|
||||
transition available: :stopped
|
||||
end
|
||||
|
||||
state :available
|
||||
state :stopped
|
||||
end
|
||||
|
||||
def last_deployment
|
||||
deployments.last
|
||||
end
|
||||
|
@ -66,4 +84,14 @@ class Environment < ActiveRecord::Base
|
|||
|
||||
external_url.gsub(/\A.*?:\/\//, '')
|
||||
end
|
||||
|
||||
def stoppable?
|
||||
available? && stop_action.present?
|
||||
end
|
||||
|
||||
def stop!(current_user)
|
||||
return unless stoppable?
|
||||
|
||||
stop_action.play(current_user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,11 +29,6 @@ class ExternalIssue
|
|||
@project
|
||||
end
|
||||
|
||||
# Pattern used to extract `JIRA-123` issue references from text
|
||||
def self.reference_pattern
|
||||
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
|
||||
end
|
||||
|
||||
def to_reference(_from_project = nil)
|
||||
id
|
||||
end
|
||||
|
|
|
@ -19,6 +19,7 @@ class Group < Namespace
|
|||
has_many :project_group_links, dependent: :destroy
|
||||
has_many :shared_projects, through: :project_group_links, source: :project
|
||||
has_many :notification_settings, dependent: :destroy, as: :source
|
||||
has_many :labels, class_name: 'GroupLabel'
|
||||
|
||||
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
|
||||
validate :visibility_level_allowed_by_projects
|
||||
|
|
|
@ -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
|
||||
end
|
||||
|
||||
def self.project_foreign_key
|
||||
'project_id'
|
||||
end
|
||||
|
||||
def self.sort(method, excluded_labels: [])
|
||||
case method.to_s
|
||||
when 'due_date_asc' then order_due_date_asc
|
||||
|
@ -278,6 +282,14 @@ class Issue < ActiveRecord::Base
|
|||
def as_json(options = {})
|
||||
super(options).tap do |json|
|
||||
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
|
||||
|
|
|
@ -15,34 +15,49 @@ class Label < ActiveRecord::Base
|
|||
|
||||
default_value_for :color, DEFAULT_COLOR
|
||||
|
||||
belongs_to :project
|
||||
|
||||
has_many :lists, dependent: :destroy
|
||||
has_many :priorities, class_name: 'LabelPriority'
|
||||
has_many :label_links, dependent: :destroy
|
||||
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
|
||||
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
|
||||
|
||||
validates :color, color: true, allow_blank: false
|
||||
validates :project, presence: true, unless: Proc.new { |service| service.template? }
|
||||
|
||||
# Don't allow ',' for label titles
|
||||
validates :title,
|
||||
presence: true,
|
||||
format: { with: /\A[^,]+\z/ },
|
||||
uniqueness: { scope: :project_id }
|
||||
|
||||
before_save :nullify_priority
|
||||
validates :title, presence: true, format: { with: /\A[^,]+\z/ }
|
||||
validates :title, uniqueness: { scope: [:group_id, :project_id] }
|
||||
|
||||
default_scope { order(title: :asc) }
|
||||
|
||||
scope :templates, -> { where(template: true) }
|
||||
scope :templates, -> { where(template: true) }
|
||||
scope :with_title, ->(title) { where(title: title) }
|
||||
|
||||
def self.prioritized
|
||||
where.not(priority: nil).reorder(:priority, :title)
|
||||
def self.prioritized(project)
|
||||
joins(:priorities)
|
||||
.where(label_priorities: { project_id: project })
|
||||
.reorder('label_priorities.priority ASC, labels.title ASC')
|
||||
end
|
||||
|
||||
def self.unprioritized
|
||||
where(priority: nil)
|
||||
def self.unprioritized(project)
|
||||
labels = Label.arel_table
|
||||
priorities = LabelPriority.arel_table
|
||||
|
||||
label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin).
|
||||
on(labels[:id].eq(priorities[:label_id]).and(priorities[:project_id].eq(project.id))).
|
||||
join_sources
|
||||
|
||||
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
|
||||
|
||||
alias_attribute :name, :title
|
||||
|
@ -77,40 +92,30 @@ class Label < ActiveRecord::Base
|
|||
nil
|
||||
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(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
|
||||
def open_issues_count(user = nil, project = nil)
|
||||
issues_count(user, project_id: project.try(:id) || project_id, state: 'opened')
|
||||
end
|
||||
|
||||
def open_issues_count(user = nil)
|
||||
issues.visible_to_user(user).opened.count
|
||||
def closed_issues_count(user = nil, project = nil)
|
||||
issues_count(user, project_id: project.try(:id) || project_id, state: 'closed')
|
||||
end
|
||||
|
||||
def closed_issues_count(user = nil)
|
||||
issues.visible_to_user(user).closed.count
|
||||
def open_merge_requests_count(user = nil, project = nil)
|
||||
merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened')
|
||||
end
|
||||
|
||||
def open_merge_requests_count
|
||||
merge_requests.opened.count
|
||||
def prioritize!(project, value)
|
||||
label_priority = priorities.find_or_initialize_by(project_id: project.id)
|
||||
label_priority.priority = value
|
||||
label_priority.save!
|
||||
end
|
||||
|
||||
def unprioritize!(project)
|
||||
priorities.where(project: project).delete_all
|
||||
end
|
||||
|
||||
def priority(project)
|
||||
priorities.find_by(project: project).try(:priority)
|
||||
end
|
||||
|
||||
def template?
|
||||
|
@ -118,15 +123,61 @@ class Label < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def text_color
|
||||
LabelsHelper::text_color_for_bg(self.color)
|
||||
LabelsHelper.text_color_for_bg(self.color)
|
||||
end
|
||||
|
||||
def title=(value)
|
||||
write_attribute(:title, sanitize_title(value)) if value.present?
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the String necessary to reference this Label in Markdown
|
||||
#
|
||||
# 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
|
||||
|
||||
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)
|
||||
raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
|
||||
|
||||
|
@ -137,10 +188,6 @@ class Label < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def nullify_priority
|
||||
self.priority = nil if priority.blank?
|
||||
end
|
||||
|
||||
def sanitize_title(value)
|
||||
CGI.unescapeHTML(Sanitize.clean(value.to_s))
|
||||
end
|
||||
|
|
|
@ -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
|
||||
end
|
||||
|
||||
def as_json(options = {})
|
||||
super(options).tap do |json|
|
||||
if options.has_key?(:label)
|
||||
json[:label] = label.as_json(
|
||||
project: board.project,
|
||||
only: [:id, :title, :description, :color]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_be_destroyed
|
||||
|
|
|
@ -137,6 +137,10 @@ class MergeRequest < ActiveRecord::Base
|
|||
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
|
||||
end
|
||||
|
||||
def self.project_foreign_key
|
||||
'target_project_id'
|
||||
end
|
||||
|
||||
# Returns all the merge requests from an ActiveRecord:Relation.
|
||||
#
|
||||
# This method uses a UNION as it usually operates on the result of
|
||||
|
@ -787,21 +791,21 @@ class MergeRequest < ActiveRecord::Base
|
|||
def all_pipelines
|
||||
return unless source_project
|
||||
|
||||
@all_pipelines ||= begin
|
||||
sha = if persisted?
|
||||
all_commits_sha
|
||||
else
|
||||
diff_head_sha
|
||||
end
|
||||
|
||||
source_project.pipelines.order(id: :desc).
|
||||
where(sha: sha, ref: source_branch)
|
||||
end
|
||||
@all_pipelines ||= source_project.pipelines
|
||||
.where(sha: all_commits_sha, ref: source_branch)
|
||||
.order(id: :desc)
|
||||
end
|
||||
|
||||
# Note that this could also return SHA from now dangling commits
|
||||
#
|
||||
def all_commits_sha
|
||||
merge_request_diffs.flat_map(&:commits_sha).uniq
|
||||
if persisted?
|
||||
merge_request_diffs.flat_map(&:commits_sha).uniq
|
||||
elsif compare_commits
|
||||
compare_commits.to_a.reverse.map(&:id)
|
||||
else
|
||||
[diff_head_sha]
|
||||
end
|
||||
end
|
||||
|
||||
def merge_commit
|
||||
|
|
|
@ -107,7 +107,7 @@ class Project < ActiveRecord::Base
|
|||
# Merge requests from source project should be kept when source project was removed
|
||||
has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
|
||||
has_many :issues, dependent: :destroy
|
||||
has_many :labels, dependent: :destroy
|
||||
has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
|
||||
has_many :services, dependent: :destroy
|
||||
has_many :events, dependent: :destroy
|
||||
has_many :milestones, dependent: :destroy
|
||||
|
@ -388,6 +388,10 @@ class Project < ActiveRecord::Base
|
|||
Project.count
|
||||
end
|
||||
end
|
||||
|
||||
def group_ids
|
||||
joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id)
|
||||
end
|
||||
end
|
||||
|
||||
def lfs_enabled?
|
||||
|
@ -664,6 +668,10 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def issue_reference_pattern
|
||||
issues_tracker.reference_pattern
|
||||
end
|
||||
|
||||
def default_issues_tracker?
|
||||
!external_issue_tracker
|
||||
end
|
||||
|
@ -729,10 +737,8 @@ class Project < ActiveRecord::Base
|
|||
|
||||
def create_labels
|
||||
Label.templates.each do |label|
|
||||
label = label.dup
|
||||
label.template = nil
|
||||
label.project_id = self.id
|
||||
label.save
|
||||
params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
|
||||
Labels::FindOrCreateService.new(owner, self, params).execute
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1293,7 +1299,7 @@ class Project < ActiveRecord::Base
|
|||
environment_ids.where(ref: ref)
|
||||
end
|
||||
|
||||
environments.where(id: environment_ids).select do |environment|
|
||||
environments.available.where(id: environment_ids).select do |environment|
|
||||
environment.includes_commit?(commit)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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'
|
||||
|
||||
# Pattern used to extract links from comments
|
||||
# Override this method on services that uses different patterns
|
||||
def reference_pattern
|
||||
@reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
|
||||
end
|
||||
|
||||
def default?
|
||||
default
|
||||
end
|
||||
|
|
|
@ -13,6 +13,11 @@ class JiraService < IssueTrackerService
|
|||
|
||||
before_update :reset_password
|
||||
|
||||
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
|
||||
def reference_pattern
|
||||
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
|
||||
end
|
||||
|
||||
def reset_password
|
||||
# don't reset the password if a new one is provided
|
||||
if api_url_changed? && !password_touched?
|
||||
|
|
|
@ -52,7 +52,13 @@ class Todo < ActiveRecord::Base
|
|||
# Todos with highest priority first then oldest todos
|
||||
# Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue"
|
||||
def order_by_labels_priority
|
||||
highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql
|
||||
params = {
|
||||
target_type: ['Issue', 'MergeRequest'],
|
||||
target_column: "todos.target_id",
|
||||
project_column: "todos.project_id"
|
||||
}
|
||||
|
||||
highest_priority = highest_label_priority(params).to_sql
|
||||
|
||||
select("#{table_name}.*, (#{highest_priority}) AS highest_priority").
|
||||
order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')).
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class GroupLabelPolicy < BasePolicy
|
||||
def rules
|
||||
delegate! @subject.group
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ class GroupPolicy < BasePolicy
|
|||
if master
|
||||
can! :create_projects
|
||||
can! :admin_milestones
|
||||
can! :admin_label
|
||||
end
|
||||
|
||||
# Only group owner and administrators can admin group
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class ProjectLabelPolicy < BasePolicy
|
||||
def rules
|
||||
delegate! @subject.project
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@ module Boards
|
|||
class CreateService < BaseService
|
||||
def execute(board)
|
||||
List.transaction do
|
||||
label = project.labels.find(params[:label_id])
|
||||
label = available_labels.find(params[:label_id])
|
||||
position = next_position(board)
|
||||
|
||||
create_list(board, label, position)
|
||||
|
@ -12,6 +12,10 @@ module Boards
|
|||
|
||||
private
|
||||
|
||||
def available_labels
|
||||
LabelsFinder.new(current_user, project_id: project.id).execute
|
||||
end
|
||||
|
||||
def next_position(board)
|
||||
max_position = board.lists.movable.maximum(:position)
|
||||
max_position.nil? ? 0 : max_position.succ
|
||||
|
|
|
@ -19,8 +19,7 @@ module Boards
|
|||
end
|
||||
|
||||
def find_or_create_label(params)
|
||||
project.labels.create_with(color: params[:color])
|
||||
.find_or_create_by(name: params[:name])
|
||||
::Labels::FindOrCreateService.new(current_user, project, params).execute
|
||||
end
|
||||
|
||||
def label_params
|
||||
|
|
|
@ -6,7 +6,13 @@ class CreateDeploymentService < BaseService
|
|||
|
||||
ActiveRecord::Base.transaction do
|
||||
@deployable = deployable
|
||||
@environment = prepare_environment
|
||||
|
||||
@environment = environment
|
||||
@environment.external_url = expanded_url if expanded_url
|
||||
@environment.fire_state_event(action)
|
||||
|
||||
return unless @environment.save
|
||||
return if @environment.stopped?
|
||||
|
||||
deploy.tap do |deployment|
|
||||
deployment.update_merge_request_metrics!
|
||||
|
@ -27,13 +33,12 @@ class CreateDeploymentService < BaseService
|
|||
tag: params[:tag],
|
||||
sha: params[:sha],
|
||||
user: current_user,
|
||||
deployable: @deployable)
|
||||
deployable: @deployable,
|
||||
on_stop: options[:on_stop])
|
||||
end
|
||||
|
||||
def prepare_environment
|
||||
project.environments.find_or_create_by(name: expanded_name) do |environment|
|
||||
environment.external_url = expanded_url
|
||||
end
|
||||
def environment
|
||||
@environment ||= project.environments.find_or_create_by(name: expanded_name)
|
||||
end
|
||||
|
||||
def expanded_name
|
||||
|
@ -61,4 +66,8 @@ class CreateDeploymentService < BaseService
|
|||
def variables
|
||||
params[:variables] || []
|
||||
end
|
||||
|
||||
def action
|
||||
options[:action] || 'start'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,17 +80,18 @@ class IssuableBaseService < BaseService
|
|||
def filter_labels_in_param(key)
|
||||
return if params[key].to_a.empty?
|
||||
|
||||
params[key] = project.labels.where(id: params[key]).pluck(:id)
|
||||
params[key] = available_labels.where(id: params[key]).pluck(:id)
|
||||
end
|
||||
|
||||
def find_or_create_label_ids
|
||||
labels = params.delete(:labels)
|
||||
return unless labels
|
||||
|
||||
params[:label_ids] = labels.split(",").map do |label_name|
|
||||
project.labels.create_with(color: Label::DEFAULT_COLOR)
|
||||
.find_or_create_by(title: label_name.strip)
|
||||
.id
|
||||
params[:label_ids] = labels.split(',').map do |label_name|
|
||||
service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip)
|
||||
label = service.execute
|
||||
|
||||
label.id
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -111,6 +112,10 @@ class IssuableBaseService < BaseService
|
|||
new_label_ids
|
||||
end
|
||||
|
||||
def available_labels
|
||||
LabelsFinder.new(current_user, project_id: @project.id).execute
|
||||
end
|
||||
|
||||
def merge_slash_commands_into_params!(issuable)
|
||||
description, command_params =
|
||||
SlashCommands::InterpretService.new(project, current_user).
|
||||
|
|
|
@ -52,8 +52,12 @@ module Issues
|
|||
end
|
||||
|
||||
def cloneable_label_ids
|
||||
@new_project.labels
|
||||
.where(title: @old_issue.labels.pluck(:title)).pluck(:id)
|
||||
params = {
|
||||
project_id: @new_project.id,
|
||||
title: @old_issue.labels.pluck(:title)
|
||||
}
|
||||
|
||||
LabelsFinder.new(current_user, params).execute.pluck(:id)
|
||||
end
|
||||
|
||||
def cloneable_milestone_id
|
||||
|
|
|
@ -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)
|
||||
@merge_request = merge_request
|
||||
|
||||
return error('Merge request is not mergeable') unless @merge_request.mergeable?
|
||||
return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable?
|
||||
|
||||
merge_request.in_locked_state do
|
||||
if commit
|
||||
after_merge
|
||||
success
|
||||
else
|
||||
error('Can not merge changes')
|
||||
log_merge_error('Can not merge changes', true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -46,8 +46,8 @@ module MergeRequests
|
|||
merge_request.update(merge_error: e.message)
|
||||
false
|
||||
rescue StandardError => e
|
||||
merge_request.update(merge_error: "Something went wrong during merge")
|
||||
Rails.logger.error(e.message)
|
||||
merge_request.update(merge_error: "Something went wrong during merge: #{e.message}")
|
||||
log_merge_error(e.message)
|
||||
false
|
||||
ensure
|
||||
merge_request.update(in_progress_merge_commit_sha: nil)
|
||||
|
@ -65,5 +65,17 @@ module MergeRequests
|
|||
def branch_deletion_user
|
||||
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
|
||||
end
|
||||
|
||||
def log_merge_error(message, http_error = false)
|
||||
Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}")
|
||||
|
||||
error(message) if http_error
|
||||
end
|
||||
|
||||
def merge_request_info
|
||||
project = merge_request.project
|
||||
|
||||
"#{project.to_reference}#{merge_request.to_reference}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module Projects
|
|||
end
|
||||
|
||||
def labels
|
||||
@project.labels.select([:title, :color])
|
||||
LabelsFinder.new(current_user, project_id: project.id).execute.select([:title, :color])
|
||||
end
|
||||
|
||||
def commands(noteable, type)
|
||||
|
|
|
@ -28,6 +28,7 @@ module Projects
|
|||
Project.transaction do
|
||||
old_path = project.path_with_namespace
|
||||
old_namespace = project.namespace
|
||||
old_group = project.group
|
||||
new_path = File.join(new_namespace.try(:path) || '', project.path)
|
||||
|
||||
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
|
||||
|
@ -57,6 +58,9 @@ module Projects
|
|||
# Move wiki repo also if present
|
||||
gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki")
|
||||
|
||||
# Move missing group labels to project
|
||||
Labels::TransferService.new(current_user, old_group, project).execute
|
||||
|
||||
# clear project cached events
|
||||
project.reset_events_cache
|
||||
|
||||
|
|
|
@ -116,8 +116,10 @@ module SlashCommands
|
|||
desc 'Add label(s)'
|
||||
params '~label1 ~"label 2"'
|
||||
condition do
|
||||
available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
|
||||
|
||||
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
|
||||
project.labels.any?
|
||||
available_labels.any?
|
||||
end
|
||||
command :label do |labels_param|
|
||||
label_ids = find_label_ids(labels_param)
|
||||
|
@ -248,7 +250,7 @@ module SlashCommands
|
|||
|
||||
def find_label_ids(labels_param)
|
||||
label_ids_by_reference = extract_references(labels_param, :label).map(&:id)
|
||||
labels_ids_by_name = @project.labels.where(name: labels_param.split).select(:id)
|
||||
labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id)
|
||||
|
||||
label_ids_by_reference | labels_ids_by_name
|
||||
end
|
||||
|
|
|
@ -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
|
||||
%span
|
||||
Activity
|
||||
= nav_link(controller: [:group, :labels]) do
|
||||
= link_to group_labels_path(@group), title: 'Labels' do
|
||||
%span
|
||||
Labels
|
||||
= nav_link(controller: [:group, :milestones]) do
|
||||
= link_to group_milestones_path(@group), title: 'Milestones' do
|
||||
%span
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
by
|
||||
%a{ href: user_path(@build.user) }
|
||||
= image_tag avatar_icon(@build.user, 24), class: "avatar s24"
|
||||
%strong= @build.user.to_reference
|
||||
%span.hidden-xs
|
||||
= image_tag avatar_icon(@build.user, 24), class: "avatar s24"
|
||||
%strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } }
|
||||
= @build.user.name
|
||||
%strong.visible-xs-inline= @build.user.to_reference
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
- page_title "Cycle Analytics"
|
||||
= render "projects/pipelines/head"
|
||||
|
||||
#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}}
|
||||
#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) }}
|
||||
|
||||
.bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"}
|
||||
= icon('times', class: 'dismiss-icon', "@click": "dismissLanding()")
|
||||
= icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()")
|
||||
.row
|
||||
.col-sm-3.col-xs-12.svg-container
|
||||
= custom_icon('icon_cycle_analytics_splash')
|
||||
|
|
|
@ -1,28 +1,15 @@
|
|||
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
|
||||
.pull-right
|
||||
- if can?(current_user, :create_deployment, deployment)
|
||||
- actions = deployment.manual_actions
|
||||
- if actions.present?
|
||||
.inline
|
||||
.dropdown
|
||||
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= custom_icon('icon_play')
|
||||
= icon('caret-down')
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- actions.each do |action|
|
||||
%li
|
||||
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
|
||||
= custom_icon('icon_play')
|
||||
%span= action.name.humanize
|
||||
|
||||
- external_url = deployment.environment.external_url
|
||||
- if external_url
|
||||
= link_to external_url, target: '_blank', class: 'btn external-url' do
|
||||
= icon('external-link')
|
||||
|
||||
- actions = deployment.manual_actions
|
||||
- if actions.present?
|
||||
.inline
|
||||
.dropdown
|
||||
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
|
||||
= custom_icon('icon_play')
|
||||
= icon('caret-down')
|
||||
%ul.dropdown-menu.dropdown-menu-align-right
|
||||
- actions.each do |action|
|
||||
%li
|
||||
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
|
||||
= custom_icon('icon_play')
|
||||
%span= action.name.humanize
|
||||
|
||||
- if 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)}
|
||||
|
||||
%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)}
|
||||
|
||||
%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"
|
||||
|
||||
%div{ class: container_class }
|
||||
- if can?(current_user, :create_environment, @project) && !@environments.blank?
|
||||
.top-area
|
||||
.top-area
|
||||
%ul.nav-links
|
||||
%li{class: ('active' if @scope.nil?)}
|
||||
= link_to project_environments_path(@project) do
|
||||
Available
|
||||
%span.badge.js-available-environments-count
|
||||
= number_with_delimiter(@all_environments.available.count)
|
||||
|
||||
%li{class: ('active' if @scope == 'stopped')}
|
||||
= link_to project_environments_path(@project, scope: :stopped) do
|
||||
Stopped
|
||||
%span.badge.js-stopped-environments-count
|
||||
= number_with_delimiter(@all_environments.stopped.count)
|
||||
|
||||
- if can?(current_user, :create_environment, @project) && !@all_environments.blank?
|
||||
.nav-controls
|
||||
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
|
||||
New environment
|
||||
|
||||
.environments-container
|
||||
- if @environments.blank?
|
||||
- if @all_environments.blank?
|
||||
.blank-state.blank-state-no-icon
|
||||
%h2.blank-state-title
|
||||
You don't have any environments right now.
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
= render "projects/pipelines/head"
|
||||
|
||||
%div{ class: container_class }
|
||||
.top-area
|
||||
.top-area.adjust
|
||||
.col-md-9
|
||||
%h3.page-title= @environment.name.capitalize
|
||||
.col-md-3
|
||||
.nav-controls
|
||||
= render 'projects/environments/external_url', environment: @environment
|
||||
- if can?(current_user, :update_environment, @environment)
|
||||
= link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
|
||||
= link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
|
||||
- if can?(current_user, :create_deployment, @environment) && @environment.stoppable?
|
||||
= link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
|
||||
|
||||
.deployments-container
|
||||
- if @deployments.blank?
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
- if issue.labels.any?
|
||||
|
||||
- issue.labels.each do |label|
|
||||
= link_to_label(label, project: issue.project)
|
||||
= link_to_label(label, subject: issue.project)
|
||||
- if issue.tasks?
|
||||
|
||||
%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
|
||||
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_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)
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
%h3.page-title
|
||||
Edit Label
|
||||
%hr
|
||||
= render 'form'
|
||||
= render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project)
|
||||
|
|
|
@ -16,21 +16,22 @@
|
|||
.labels
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
-# Only show it in the first page
|
||||
- hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1')
|
||||
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
|
||||
.prioritized-labels{ class: ('hide' if hide) }
|
||||
%h5 Prioritized Labels
|
||||
%ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
|
||||
%p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
|
||||
- if @prioritized_labels.present?
|
||||
= render @prioritized_labels
|
||||
= render partial: 'shared/label', collection: @prioritized_labels, as: :label
|
||||
|
||||
.other-labels
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
%h5{ class: ('hide' if hide) } Other Labels
|
||||
- if @labels.present?
|
||||
%ul.content-list.manage-labels-list.js-other-labels
|
||||
= render @labels
|
||||
%ul.content-list.manage-labels-list.js-other-labels
|
||||
- if @labels.present?
|
||||
= render partial: 'shared/label', collection: @labels, as: :label
|
||||
= paginate @labels, theme: 'gitlab'
|
||||
- else
|
||||
- if @labels.blank?
|
||||
.nothing-here-block
|
||||
- if can?(current_user, :admin_label, @project)
|
||||
Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
%h3.page-title
|
||||
New Label
|
||||
%hr
|
||||
= render 'form'
|
||||
= render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project)
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
- if merge_request.labels.any?
|
||||
|
||||
- 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?
|
||||
|
||||
%span.task-status
|
||||
|
|
|
@ -29,7 +29,11 @@
|
|||
= link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do
|
||||
Commits
|
||||
%span.badge= @commits.size
|
||||
- if @pipeline
|
||||
- if @pipelines.any?
|
||||
%li.builds-tab
|
||||
= link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do
|
||||
Pipelines
|
||||
%span.badge= @pipelines.size
|
||||
%li.builds-tab
|
||||
= link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
|
||||
Builds
|
||||
|
@ -44,9 +48,11 @@
|
|||
= render "projects/merge_requests/show/commits"
|
||||
#diffs.diffs.tab-pane
|
||||
- # This tab is always loaded via AJAX
|
||||
- if @pipeline
|
||||
- if @pipelines.any?
|
||||
#builds.builds.tab-pane
|
||||
= render "projects/merge_requests/show/builds"
|
||||
#pipelines.pipelines.tab-pane
|
||||
= render "projects/merge_requests/show/pipelines"
|
||||
|
||||
.mr-loading-status
|
||||
= spinner
|
||||
|
@ -59,5 +65,5 @@
|
|||
:javascript
|
||||
var merge_request = new MergeRequest({
|
||||
action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}",
|
||||
buildsLoaded: "#{@pipeline ? 'true' : 'false'}"
|
||||
buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}"
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
|
||||
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
|
||||
- page_description @merge_request.description
|
||||
- page_card_attributes @merge_request.card_attributes
|
||||
- content_for :page_specific_javascripts do
|
||||
|
@ -61,7 +61,7 @@
|
|||
%li.pipelines-tab
|
||||
= link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
|
||||
Pipelines
|
||||
%span.badge= @merge_request.all_pipelines.size
|
||||
%span.badge= @pipelines.size
|
||||
%li.builds-tab
|
||||
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do
|
||||
Builds
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests"
|
||||
- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
|
||||
|
||||
%h3.page-title
|
||||
Edit Merge Request #{@merge_request.to_reference}
|
||||
|
|
|
@ -64,8 +64,8 @@
|
|||
.checkbox
|
||||
= f.label :public_builds do
|
||||
= f.check_box :public_builds
|
||||
%strong Public pipelines
|
||||
.help-block Allow everyone to access pipelines for Public and Internal projects
|
||||
%strong Public builds
|
||||
.help-block Allow everyone to access builds traces for Public and Internal projects
|
||||
|
||||
.form-group.append-bottom-default
|
||||
= f.label :runners_token, "Runners token", class: 'label-light'
|
||||
|
|
|
@ -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
|
||||
= icon('bars')
|
||||
.js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
|
||||
dom_id: dom_id(label) } }
|
||||
dom_id: dom_id(label), type: label.type } }
|
||||
%button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' }
|
||||
= icon('star-o')
|
||||
%button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' }
|
||||
= icon('star')
|
||||
%span.label-name
|
||||
= link_to_label(label, tooltip: false)
|
||||
= link_to_label(label, subject: @project, tooltip: false)
|
||||
- if defined?(@project) && @project.group.present?
|
||||
%span.label-type
|
||||
= label.model_name.human.titleize
|
||||
- if label.description
|
||||
%span.label-description
|
||||
= markdown_field(label, :description)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- labels.each do |label|
|
||||
%span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" }
|
||||
= link_to_label(label, css_class: 'btn btn-transparent')
|
||||
= link_to_label(label, subject: @project, css_class: 'btn btn-transparent')
|
||||
%button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
|
||||
= icon("times")
|
||||
|
|
|
@ -29,8 +29,9 @@
|
|||
.filter-item.inline.labels-filter
|
||||
= render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
|
||||
|
||||
.filter-item.inline.reset-filters
|
||||
%a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
|
||||
- if issuable_filters_present
|
||||
.filter-item.inline.reset-filters
|
||||
%a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters
|
||||
|
||||
.pull-right
|
||||
- if boards_page
|
||||
|
@ -77,11 +78,10 @@
|
|||
= hidden_field_tag :state_event, params[:state_event]
|
||||
.filter-item.inline
|
||||
= button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
|
||||
|
||||
- if !@labels.nil?
|
||||
.row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) }
|
||||
- if @labels.any?
|
||||
= render "shared/labels_row", labels: @labels
|
||||
- has_labels = @labels && @labels.any?
|
||||
.row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
|
||||
- if has_labels
|
||||
= render 'shared/labels_row', labels: @labels
|
||||
|
||||
:javascript
|
||||
new UsersSelect();
|
||||
|
|
|
@ -88,19 +88,19 @@
|
|||
- if issuable.assignee_id
|
||||
= f.hidden_field :assignee_id
|
||||
= dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
|
||||
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } })
|
||||
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
|
||||
.form-group.issue-milestone
|
||||
= f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
|
||||
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
|
||||
.issuable-form-select-holder
|
||||
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
|
||||
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
|
||||
.form-group
|
||||
- has_labels = issuable.project.labels.any?
|
||||
- has_labels = @labels && @labels.any?
|
||||
= f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
|
||||
= f.hidden_field :label_ids, multiple: true, value: ''
|
||||
.col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
|
||||
.issuable-form-select-holder
|
||||
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label"
|
||||
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label"
|
||||
- if has_due_date
|
||||
.col-lg-6
|
||||
.form-group
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
= dropdown_content do
|
||||
.js-due-date-calendar
|
||||
|
||||
- if issuable.project.labels.any?
|
||||
- if @labels && @labels.any?
|
||||
- selected_labels = issuable.labels
|
||||
.block.labels
|
||||
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
|
||||
= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
|
||||
= form_errors(@label)
|
||||
|
||||
.form-group
|
||||
|
@ -30,4 +30,4 @@
|
|||
= f.submit 'Save changes', class: 'btn btn-save js-save-button'
|
||||
- else
|
||||
= f.submit 'Create Label', class: 'btn btn-create js-save-button'
|
||||
= link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
|
||||
= link_to 'Cancel', back_path, class: 'btn btn-cancel'
|
|
@ -5,6 +5,7 @@ en:
|
|||
hello: "Hello world"
|
||||
errors:
|
||||
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})"
|
||||
size_too_small: "is too small (should be at least %{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]
|
||||
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
|
||||
|
||||
resources :labels, except: [:show], constraints: { id: /\d+/ }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -319,7 +319,11 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
|
|||
end
|
||||
end
|
||||
|
||||
resources :environments
|
||||
resources :environments, except: [:destroy] do
|
||||
member do
|
||||
post :stop
|
||||
end
|
||||
end
|
||||
|
||||
resource :cycle_analytics, only: [:show]
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ class Gitlab::Seeder::Pipelines
|
|||
{ name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending },
|
||||
{ name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running },
|
||||
{ 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: '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