Commit fcf0a4a1 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into issue-board-sidebar

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