Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorHeinrich Lee Yu <heinrich@gitlab.com>2018-11-27 19:53:16 +0300
committerHeinrich Lee Yu <hleeyu@gmail.com>2018-12-19 12:58:53 +0300
commit9f9765485e998ece87660aa30a1b4339bb940d14 (patch)
tree0615fe12d0780847dee1c0dca8c0f0d59674048f /app
parent6c79e9307e116d6115f6d76ac796176952fb83cd (diff)
Refactor sidebar to use data from serializer
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/right_sidebar.js9
-rw-r--r--app/controllers/concerns/issuable_actions.rb10
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/helpers/issuables_helper.rb90
-rw-r--r--app/helpers/milestones_helper.rb6
-rw-r--r--app/models/user.rb4
-rw-r--r--app/serializers/entity_date_helper.rb17
-rw-r--r--app/serializers/issuable_sidebar_entity.rb103
-rw-r--r--app/serializers/issuable_sidebar_todo_entity.rb11
-rw-r--r--app/serializers/issue_serializer.rb6
-rw-r--r--app/serializers/issue_sidebar_entity.rb5
-rw-r--r--app/serializers/merge_request_serializer.rb6
-rw-r--r--app/serializers/merge_request_sidebar_entity.rb7
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml3
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml162
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml46
-rw-r--r--app/views/shared/issuable/_sidebar_todo.html.haml26
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml3
21 files changed, 337 insertions, 195 deletions
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 225e21ad322..93a37e17126 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget);
- ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post';
- if ($this.attr('data-delete-path')) {
- url = '' + $this.attr('data-delete-path');
+ ajaxType = $this.data('deletePath') ? 'delete' : 'post';
+
+ if ($this.data('deletePath')) {
+ url = '' + $this.data('deletePath');
} else {
- url = '' + $this.data('url');
+ url = '' + $this.data('createPath');
}
$this.tooltip('hide');
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index ad9cc0925b7..3d64ae8b775 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -5,7 +5,6 @@ module IssuableActions
include Gitlab::Utils::StrongMemoize
included do
- before_action :labels, only: [:show, :new, :edit]
before_action :authorize_destroy_issuable!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
end
@@ -25,7 +24,10 @@ module IssuableActions
def show
respond_to do |format|
- format.html
+ format.html do
+ @issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
format.json do
render json: serializer.represent(issuable, serializer: params[:serializer])
end
@@ -168,10 +170,6 @@ module IssuableActions
end
end
- def labels
- @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
- end
-
def authorize_destroy_issuable!
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index ac1969adc6e..26219012121 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
def show
respond_to do |format|
format.html do
- labels
+ @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
end
format.json do
@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
end
end
+ private
+
def authorize_can_resolve_conflicts!
@conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
end
+
+ def serializer
+ MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index da9316d5f22..8b8eac7ff8e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
format.html
format.json do
render json: {
- html: view_to_html_string("projects/merge_requests/_merge_requests"),
- labels: @labels.as_json(methods: :text_color)
+ html: view_to_html_string("projects/merge_requests/_merge_requests")
}
end
end
@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
-
- labels
+ @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
set_pipeline_variables
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index ea861bd2fc4..5e277a4c5ed 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -23,23 +23,36 @@ module IssuablesHelper
end
end
- def sidebar_due_date_tooltip_label(issuable)
- if issuable.due_date
- "#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
+ def sidebar_milestone_tooltip_label(milestone)
+ if milestone && milestone[:due_date]
+ "#{milestone[:title]}<br/>#{sidebar_milestone_remaining_days(milestone)}"
else
- _('Due date')
+ _('Milestone') + (milestone ? "<br/>#{milestone[:title]}" : "")
end
end
- def due_date_remaining_days(issuable)
- remaining_days_in_words = remaining_days_in_words(issuable)
+ def sidebar_milestone_remaining_days(milestone)
+ due_date_remaining_days(due_date: milestone[:due_date], start_date: milestone[:start_date]) if milestone[:due_date]
+ end
+
+ def sidebar_due_date_tooltip_label(due_date)
+ _('Due date') + (due_date ? "<br/>#{due_date_remaining_days(due_date)}" : "")
+ end
+
+ def due_date_remaining_days(due_date, start_date = nil)
+ "#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date: due_date, start_date: start_date)})"
+ end
+
+ def sidebar_label_filter_path(base_path, label_name)
+ query_params = {label_name: [label_name]}.to_query
- "#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})"
+ "#{base_path}?#{query_params}"
end
def multi_label_name(current_labels, default_label)
if current_labels && current_labels.any?
- title = current_labels.first.try(:title)
+ title = current_labels.first.try(:title) || current_labels.first[:title]
+
if current_labels.size > 1
"#{title} +#{current_labels.size - 1} more"
else
@@ -50,13 +63,13 @@ module IssuablesHelper
end
end
- def issuable_json_path(issuable, url_params = {})
+ def issuable_json_path(issuable)
project = issuable.project
if issuable.is_a?(MergeRequest)
- project_merge_request_path(project, issuable.iid, :json, url_params)
+ project_merge_request_path(project, issuable.iid, :json)
else
- project_issue_path(project, issuable.iid, :json, url_params)
+ project_issue_path(project, issuable.iid, :json)
end
end
@@ -197,19 +210,11 @@ module IssuablesHelper
output.join.html_safe
end
- # rubocop: disable CodeReuse/ActiveRecord
- def issuable_todo(issuable)
- if current_user
- current_user.todos.find_by(target: issuable, state: :pending)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index { |_, i| i < limit }
if labels && labels.any?
- label_names = first.collect(&:name)
+ label_names = first.collect { |l| l[:title] }
label_names << "and #{last.size} more" unless last.empty?
label_names.join(', ')
@@ -356,12 +361,6 @@ module IssuablesHelper
issuable.model_name.human.downcase
end
- def selected_labels
- Array(params[:label_name]).map do |label_name|
- Label.new(title: label_name)
- end
- end
-
def has_filter_bar_param?
finder.class.scalar_params.any? { |p| params[p].present? }
end
@@ -386,22 +385,6 @@ module IssuablesHelper
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end
- def issuable_todo_button_data(issuable, todo, is_collapsed)
- {
- todo_text: "Add todo",
- mark_text: "Mark todo as done",
- todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil),
- mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil),
- issuable_id: issuable.id,
- issuable_type: issuable.class.name.underscore,
- url: project_todos_path(@project),
- delete_path: (dashboard_todo_path(todo) if todo),
- placement: (is_collapsed ? 'left' : nil),
- container: (is_collapsed ? 'body' : nil),
- boundary: 'viewport'
- }
- end
-
def close_reopen_params(issuable, action)
{
issuable.model_name.to_s.underscore => { state_event: action }
@@ -418,27 +401,20 @@ module IssuablesHelper
end
end
- def issuable_sidebar_options(issuable, can_edit_issuable)
+ def issuable_sidebar_options(sidebar_data)
{
- endpoint: issuable_json_path(issuable, serializer: 'sidebar_extras'),
- toggleSubscriptionEndpoint: toggle_subscription_path(issuable),
- moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable),
- projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id),
- editable: can_edit_issuable,
- currentUser: UserSerializer.new.represent(current_user),
+ endpoint: "#{sidebar_data[:issuable_json_path]}?serializer=sidebar_extras",
+ toggleSubscriptionEndpoint: sidebar_data[:toggle_subscription_path],
+ moveIssueEndpoint: sidebar_data[:move_issue_path],
+ projectsAutocompleteEndpoint: sidebar_data[:projects_autocomplete_path],
+ editable: sidebar_data[:can_edit],
+ currentUser: sidebar_data[:current_user],
rootPath: root_path,
- fullPath: @project.full_path
+ fullPath: sidebar_data[:project_full_path]
}
end
def parent
@project || @group
end
-
- def issuable_milestone_tooltip_title(issuable)
- if issuable.milestone
- milestone_tooltip = milestone_tooltip_title(issuable.milestone)
- _('Milestone') + (milestone_tooltip ? ': ' + milestone_tooltip : '')
- end
- end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 9666080092b..e3dc598e98a 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -114,12 +114,6 @@ module MilestonesHelper
end
end
- def milestone_tooltip_title(milestone)
- if milestone
- "#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}"
- end
- end
-
def milestone_time_for(date, date_type)
title = date_type == :start ? "Start date" : "End date"
diff --git a/app/models/user.rb b/app/models/user.rb
index f20756d1cc3..6b60932a1b2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1422,6 +1422,10 @@ class User < ActiveRecord::Base
todos.where(id: ids)
end
+ def pending_todo_for(target)
+ todos.find_by(target: target, state: :pending)
+ end
+
# @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb
index cc0c2abf863..87bba87a0ed 100644
--- a/app/serializers/entity_date_helper.rb
+++ b/app/serializers/entity_date_helper.rb
@@ -45,13 +45,16 @@ module EntityDateHelper
# If due date is provided, it returns "# days|weeks|months remaining|ago"
# If start date is provided and elapsed, with no due date, it returns "# days elapsed"
def remaining_days_in_words(entity)
- if entity.try(:expired?)
+ start_date = entity.try(:start_date) || entity.try(:[], :start_date)
+ due_date = entity.try(:due_date) || entity.try(:[], :due_date)
+
+ if due_date && due_date.past?
content_tag(:strong, 'Past due')
- elsif entity.try(:upcoming?)
+ elsif start_date && start_date.future?
content_tag(:strong, 'Upcoming')
- elsif entity.due_date
- is_upcoming = (entity.due_date - Date.today).to_i > 0
- time_ago = time_ago_in_words(entity.due_date)
+ elsif due_date
+ is_upcoming = (due_date - Date.today).to_i > 0
+ time_ago = time_ago_in_words(due_date)
# https://gitlab.com/gitlab-org/gitlab-ce/issues/49440
#
@@ -63,8 +66,8 @@ module EntityDateHelper
remaining_or_ago = is_upcoming ? _("remaining") : _("ago")
"#{content} #{remaining_or_ago}".html_safe
- elsif entity.start_date && entity.start_date.past?
- days = entity.elapsed_days
+ elsif start_date && start_date.past?
+ days = (Date.today - start_date).to_i
"#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe
end
end
diff --git a/app/serializers/issuable_sidebar_entity.rb b/app/serializers/issuable_sidebar_entity.rb
index 9af2276b362..11666049959 100644
--- a/app/serializers/issuable_sidebar_entity.rb
+++ b/app/serializers/issuable_sidebar_entity.rb
@@ -3,6 +3,109 @@
class IssuableSidebarEntity < Grape::Entity
include RequestAwareEntity
+ with_options if: { include_basic: true } do
+ expose :id
+ expose :type do |issuable|
+ issuable.to_ability_name
+ end
+ expose :author_id
+ expose :project_id do |issuable|
+ issuable.project.id
+ end
+ expose :discussion_locked?, as: :discussion_locked
+ expose :reference do |issuable|
+ issuable.to_reference(issuable.project, full: true)
+ end
+
+ expose :current_user, using: UserEntity do |issuable|
+ request.current_user
+ end
+
+ # Relationships
+ expose :todo, using: IssuableSidebarTodoEntity do |issuable|
+ request.current_user.pending_todo_for(issuable) if request.current_user
+ end
+ expose :milestone, using: ::API::Entities::Milestone
+ expose :labels, using: LabelEntity
+
+ # Permissions
+ expose :signed_in do |issuable|
+ request.current_user.present?
+ end
+
+ expose :can_edit do |issuable|
+ can?(request.current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+ end
+
+ expose :can_move do |issuable|
+ request.current_user && issuable.can_move?(request.current_user)
+ end
+
+ expose :can_admin_label do |issuable|
+ can?(request.current_user, :admin_label, issuable.project)
+ end
+
+ # Paths
+ expose :issuable_json_path do |issuable|
+ if issuable.is_a?(MergeRequest)
+ project_merge_request_path(issuable.project, issuable.iid, :json)
+ else
+ project_issue_path(issuable.project, issuable.iid, :json)
+ end
+ end
+
+ expose :namespace_path do |issuable|
+ issuable.project.namespace.full_path
+ end
+
+ expose :project_path do |issuable|
+ issuable.project.path
+ end
+
+ expose :project_full_path do |issuable|
+ issuable.project.full_path
+ end
+
+ expose :project_issuables_path do |issuable|
+ project = issuable.project
+ namespace = project.namespace
+
+ if issuable.is_a?(MergeRequest)
+ namespace_project_merge_requests_path(namespace, project)
+ else
+ namespace_project_issues_path(namespace, project)
+ end
+ end
+
+ expose :create_todo_path do |issuable|
+ project_todos_path(issuable.project)
+ end
+
+ expose :project_milestones_path do |issuable|
+ project_milestones_path(issuable.project, :json)
+ end
+
+ expose :project_labels_path do |issuable|
+ project_labels_path(issuable.project, :json, include_ancestor_groups: true)
+ end
+
+ expose :toggle_subscription_path do |issuable|
+ toggle_subscription_path(issuable)
+ end
+
+ expose :move_issue_path do |issuable|
+ move_namespace_project_issue_path(
+ namespace_id: issuable.project.namespace.to_param,
+ project_id: issuable.project,
+ id: issuable
+ )
+ end
+
+ expose :projects_autocomplete_path do |issuable|
+ autocomplete_projects_path(project_id: issuable.project.id)
+ end
+ end
+
with_options if: { include_extras: true } do
include TimeTrackableEntity
diff --git a/app/serializers/issuable_sidebar_todo_entity.rb b/app/serializers/issuable_sidebar_todo_entity.rb
new file mode 100644
index 00000000000..b2c98433f05
--- /dev/null
+++ b/app/serializers/issuable_sidebar_todo_entity.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class IssuableSidebarTodoEntity < Grape::Entity
+ include Gitlab::Routing
+
+ expose :id
+
+ expose :delete_path do |todo|
+ dashboard_todo_path(todo) if todo
+ end
+end
diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb
index cdb386b83b9..93625ff3afc 100644
--- a/app/serializers/issue_serializer.rb
+++ b/app/serializers/issue_serializer.rb
@@ -7,9 +7,9 @@ class IssueSerializer < BaseSerializer
def represent(issue, opts = {})
entity =
case opts[:serializer]
- when 'sidebar_extras'
- opts[:include_basic] = false
- opts[:include_extras] = true
+ when 'sidebar', 'sidebar_extras'
+ opts[:include_basic] = (opts[:serializer] == 'sidebar')
+ opts[:include_extras] = (opts[:serializer] == 'sidebar_extras')
IssueSidebarEntity
when 'board'
IssueBoardEntity
diff --git a/app/serializers/issue_sidebar_entity.rb b/app/serializers/issue_sidebar_entity.rb
index 1b73ead5cd7..d50e19ad046 100644
--- a/app/serializers/issue_sidebar_entity.rb
+++ b/app/serializers/issue_sidebar_entity.rb
@@ -1,6 +1,11 @@
# frozen_string_literal: true
class IssueSidebarEntity < IssuableSidebarEntity
+ with_options if: { include_basic: true } do
+ expose :due_date
+ expose :confidential
+ end
+
with_options if: { include_extras: true } do
expose :assignees, using: API::Entities::UserBasic
end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index e252d9a3501..4d236273d49 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -7,9 +7,9 @@ class MergeRequestSerializer < BaseSerializer
def represent(merge_request, opts = {})
entity =
case opts[:serializer]
- when 'sidebar_extras'
- opts[:include_basic] = false
- opts[:include_extras] = true
+ when 'sidebar', 'sidebar_extras'
+ opts[:include_basic] = (opts[:serializer] == 'sidebar')
+ opts[:include_extras] = (opts[:serializer] == 'sidebar_extras')
MergeRequestSidebarEntity
when 'basic'
MergeRequestBasicEntity
diff --git a/app/serializers/merge_request_sidebar_entity.rb b/app/serializers/merge_request_sidebar_entity.rb
index 70607230642..a861f2ea73b 100644
--- a/app/serializers/merge_request_sidebar_entity.rb
+++ b/app/serializers/merge_request_sidebar_entity.rb
@@ -1,4 +1,11 @@
# frozen_string_literal: true
class MergeRequestSidebarEntity < IssuableSidebarEntity
+ with_options if: { include_basic: true } do
+ expose :assignee, using: API::Entities::UserBasic
+
+ expose :can_merge do |issuable|
+ issuable.can_be_merged_by?(issuable.assignee) if issuable.assignee
+ end
+ end
end
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index b50b3ca207b..d862640197a 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -88,4 +88,4 @@
%section.issuable-discussion
= render 'projects/issues/discussion'
-= render 'shared/issuable/sidebar', issuable: @issue
+= render 'shared/issuable/sidebar', issuable: @issue, issuable_sidebar: @issuable_sidebar
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index a6e2565a485..ede37c11e64 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -6,7 +6,7 @@
.merge-request-details.issuable-details
= render "projects/merge_requests/mr_box"
-= render 'shared/issuable/sidebar', issuable: @merge_request
+= render 'shared/issuable/sidebar', issuable: @merge_request, issuable_sidebar: @issuable_sidebar
#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index cc9292b54d7..4ebf8afd669 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -86,7 +86,8 @@
.mr-loading-status
= spinner
-= render 'shared/issuable/sidebar', issuable: @merge_request
+= render 'shared/issuable/sidebar', issuable: @merge_request, issuable_sidebar: @issuable_sidebar
+
- if @merge_request.can_be_reverted?(current_user)
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked?
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 9eecfa39390..18093e5996b 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,32 +1,34 @@
-- todo = issuable_todo(issuable)
+- issuable_type = issuable_sidebar[:type]
+- signed_in = issuable_sidebar[:signed_in]
+- can_edit_issuable = issuable_sidebar[:can_edit]
-%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
- .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
- - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+ .issuable-sidebar
.block.issuable-sidebar-header
- - if current_user
+ - if signed_in
%span.issuable-header-text.hide-collapsed.float-left
= _('Todo')
%a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }
= sidebar_gutter_toggle_icon
- - if current_user
- = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable
+ - if signed_in
+ = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
- - if current_user
+ = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
+ - if signed_in
.block.todo.hide-expanded
- = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true
+ = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar, is_collapsed: true
.block.assignee.qa-assignee-block
- = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
+ = render "shared/issuable/sidebar_assignees", issuable: issuable, issuable_sidebar: issuable_sidebar
- = render_if_exists 'shared/issuable/sidebar_item_epic', issuable: issuable
+ = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar
+ - milestone = issuable_sidebar[:milestone] || {}
.block.milestone
- .sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
+ .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
= icon('clock-o', 'aria-hidden': 'true')
%span.milestone-title.collapse-truncated-title
- - if issuable.milestone
- = issuable.milestone.title
+ - if milestone.present?
+ = milestone[:title]
- else
= _('None')
.title.hide-collapsed
@@ -35,49 +37,50 @@
- if can_edit_issuable
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.hide-collapsed
- - if issuable.milestone
- = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 'true', boundary: 'viewport' }
+ - if milestone.present?
+ = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport' }
- else
%span.no-value
= _('None')
.selectbox.hide-collapsed
- = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
- = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true, display: 'static' }})
- - if issuable.has_attribute?(:time_estimate)
- #issuable-time-tracker.block
- // Fallback while content is loading
- .title.hide-collapsed
- = _('Time tracking')
- = icon('spinner spin', 'aria-hidden': 'true')
- - if issuable.has_attribute?(:due_date)
+ = f.hidden_field 'milestone_id', value: milestone[:id], id: nil
+ = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
+
+ #issuable-time-tracker.block
+ // Fallback while content is loading
+ .title.hide-collapsed
+ = _('Time tracking')
+ = icon('spinner spin', 'aria-hidden': 'true')
+
+ - if issuable_sidebar.has_key?(:due_date)
.block.due_date
- .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable) }
+ .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable_sidebar[:due_date]) }
= icon('calendar', 'aria-hidden': 'true')
%span.js-due-date-sidebar-value
- = issuable.due_date.try(:to_s, :medium) || 'None'
+ = issuable_sidebar[:due_date].try(:to_s, :medium) || 'None'
.title.hide-collapsed
= _('Due date')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.hide-collapsed
%span.value-content
- - if issuable.due_date
- %span.bold= issuable.due_date.to_s(:medium)
+ - if issuable_sidebar[:due_date]
+ %span.bold= issuable_sidebar[:due_date].to_s(:medium)
- else
%span.no-value
= _('No due date')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) }
+ - if can_edit_issuable
+ %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable_sidebar[:due_date].nil?) }
\-
%a.js-remove-due-date{ href: "#", role: "button" }
= _('remove due date')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
.selectbox.hide-collapsed
- = f.hidden_field :due_date, value: issuable.due_date.try(:strftime, 'yy-mm-dd')
+ = f.hidden_field :due_date, value: issuable_sidebar[:due_date].try(:strftime, 'yy-mm-dd')
.dropdown
- %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), display: 'static' } }
+ %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable_type}[due_date]", ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], display: 'static' } }
%span.dropdown-toggle-text
= _('Due date')
= icon('chevron-down', 'aria-hidden': 'true')
@@ -86,56 +89,56 @@
= dropdown_content do
.js-due-date-calendar
- - if @labels
- - selected_labels = issuable.labels
- .block.labels
- .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body", boundary: 'viewport' } }
- = icon('tags', 'aria-hidden': 'true')
- %span
- = selected_labels.size
- .title.hide-collapsed
- = _('Labels')
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
- .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
- - if selected_labels.any?
- - selected_labels.each do |label|
- = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
- - else
- %span.no-value
- = _('None')
- .selectbox.hide-collapsed
+ - selected_labels = issuable_sidebar[:labels]
+ .block.labels
+ .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(selected_labels), data: { placement: "left", container: "body", boundary: 'viewport' } }
+ = icon('tags', 'aria-hidden': 'true')
+ %span
+ = selected_labels.size
+ .title.hide-collapsed
+ = _('Labels')
+ = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
+ - if can_edit_issuable
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
+ .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
+ - if selected_labels.any?
- selected_labels.each do |label|
- = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
- .dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path_with_defaults if @project), display: 'static' } }
- %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
- = multi_label_name(selected_labels, "Labels")
- = icon('chevron-down', 'aria-hidden': 'true')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- = render partial: "shared/issuable/label_page_default"
- - if can? current_user, :admin_label, @project and @project
- = render partial: "shared/issuable/label_page_create"
-
- = render_if_exists 'shared/issuable/sidebar_weight', issuable: issuable
-
- - if issuable.has_attribute?(:confidential)
+ = link_to sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label[:title]) do
+ %span.badge.color-label.has-tooltip{ style: "background-color: #{label[:color]}; color: #{label[:text_color]}", title: label[:description], data: { container: "body" } }
+ = label[:title]
+ - else
+ %span.no-value
+ = _('None')
+ .selectbox.hide-collapsed
+ - selected_labels.each do |label|
+ = hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil
+ .dropdown
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable_type}[label_names][]", ability_name: issuable_type, show_no: "true", show_any: "true", namespace_path: issuable_sidebar[:namespace_path], project_path: issuable_sidebar[:project_path], issue_update: issuable_sidebar[:issuable_json_path], labels: issuable_sidebar[:project_labels_path], display: 'static' } }
+ %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
+ = multi_label_name(selected_labels, "Labels")
+ = icon('chevron-down', 'aria-hidden': 'true')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ = render partial: "shared/issuable/label_page_default"
+ - if issuable_sidebar[:can_admin_label]
+ = render partial: "shared/issuable/label_page_create"
+
+ = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar
+
+ - if issuable_sidebar.has_key?(:confidential)
-# haml-lint:disable InlineJavaScript
- %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe
+ %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
#js-confidential-entry-point
- - if issuable.has_attribute?(:discussion_locked)
- -# haml-lint:disable InlineJavaScript
- %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable.discussion_locked?, is_editable: can_edit_issuable }.to_json.html_safe
- #js-lock-entry-point
+ -# haml-lint:disable InlineJavaScript
+ %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
+ #js-lock-entry-point
.js-sidebar-participants-entry-point
- - if current_user
+ - if signed_in
.js-sidebar-subscriptions-entry-point
- - project_ref = cross_project_reference(@project, issuable)
+ - project_ref = issuable_sidebar[:reference]
.block.project-reference
.sidebar-collapsed-icon.dont-change-state
= clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport')
@@ -145,7 +148,8 @@
%cite{ title: project_ref }
= project_ref
= clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport')
- - if current_user && issuable.can_move?(current_user)
+
+ - if issuable_sidebar[:can_move]
.block.js-sidebar-move-issue-block
.sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
= custom_icon('icon_arrow_right')
@@ -164,4 +168,4 @@
= icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
-# haml-lint:disable InlineJavaScript
- %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable, can_edit_issuable).to_json.html_safe
+ %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index 8a13c7a3b83..25e4f0dc14e 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,11 +1,15 @@
-- if issuable.is_a?(Issue)
- #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } }
+- issuable_type = issuable_sidebar[:type]
+- signed_in = issuable_sidebar[:signed_in]
+- can_edit_issuable = issuable_sidebar[:can_edit]
+
+- if issuable_type == "issue"
+ #js-vue-sidebar-assignees{ data: { field: "#{issuable_type}[assignee_ids]", signed_in: signed_in } }
.title.hide-collapsed
= _('Assignee')
= icon('spinner spin')
- else
- .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: sidebar_assignee_tooltip_label(issuable) }
- - if issuable.assignee
+ .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: (issuable_sidebar.dig(:assignee, :name) || _('Assignee')) }
+ - if issuable_sidebar[:assignee]
= link_to_member(@project, issuable.assignee, size: 24)
- else
= icon('user', 'aria-hidden': 'true')
@@ -18,13 +22,13 @@
%a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') }
= sidebar_gutter_toggle_icon
.value.hide-collapsed
- - if issuable.assignee
+ - if issuable_sidebar[:assignee]
= link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- - if !issuable.can_be_merged_by?(issuable.assignee)
+ - if issuable_sidebar[:can_merge]
%span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') }
= icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username
- = issuable.assignee.to_reference
+ @#{issuable_sidebar[:assignee][:username]}
- else
%span.assign-yourself.no-value
= _('No assignee')
@@ -34,19 +38,33 @@
= _('assign yourself')
.selectbox.hide-collapsed
- - issuable.assignees.each do |assignee|
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
+ - if issuable.assignees.none?
+ = hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil
+ - else
+ - issuable.assignees.each do |assignee|
+ = hidden_field_tag "#{issuable_type}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- - options = { toggle_class: 'js-user-search js-author-search', title: _('Assign to'), filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: _('Search users'), data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true, display: 'static' } }
+ - options = { toggle_class: 'js-user-search js-author-search',
+ title: _('Assign to'),
+ filter: true,
+ dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author',
+ placeholder: _('Search users'),
+ data: { first_user: issuable_sidebar.dig(:current_user, :username),
+ current_user: true,
+ project_id: issuable_sidebar[:project_id],
+ author_id: issuable_sidebar[:author_id],
+ field_name: "#{issuable_type}[assignee_ids][]",
+ issue_update: issuable_sidebar[:issuable_json_path],
+ ability_name: issuable_type,
+ null_user: true,
+ display: 'static' } }
- title = _('Select assignee')
- - if issuable.is_a?(Issue)
- - unless issuable.assignees.any?
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
+ - if issuable_type == "issue"
- dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data'
- - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
+ - data = { field_name: "#{issuable_type}[assignee_ids][]" }
- data[:multi_select] = true
- data['dropdown-title'] = title
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml
index 660ee6d5777..8411327566b 100644
--- a/app/views/shared/issuable/_sidebar_todo.html.haml
+++ b/app/views/shared/issuable/_sidebar_todo.html.haml
@@ -1,14 +1,28 @@
- is_collapsed = local_assigns.fetch(:is_collapsed, false)
-- mark_content = is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : _('Mark todo as done')
-- todo_content = is_collapsed ? sprite_icon('todo-add') : _('Add todo')
+- todo = issuable_sidebar[:todo] || {}
+
+- todo_text = _('Add todo')
+- mark_text = _('Mark todo as done')
+- todo_icon = sprite_icon('todo-add')
+- mark_icon = sprite_icon('todo-done', css_class: 'todo-undone')
+
+- mark_content = is_collapsed ? mark_icon : mark_text
+- todo_content = is_collapsed ? todo_icon : todo_text
%button.issuable-todo-btn.js-issuable-todo{ type: 'button',
class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'),
- title: (todo.nil? ? _('Add todo') : _('Mark todo as done')),
- 'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')),
- data: issuable_todo_button_data(issuable, todo, is_collapsed) }
+ title: (todo[:id] ? mark_text : todo_text),
+ 'aria-label' => (todo[:id] ? mark_text : todo_text),
+ data: { todo_text: todo_text,
+ mark_text: mark_text,
+ todo_icon: is_collapsed ? todo_icon : nil,
+ mark_icon: is_collapsed ? mark_icon : nil,
+ issuable_id: issuable_sidebar[:id],
+ issuable_type: issuable_sidebar[:type],
+ create_path: issuable_sidebar[:create_todo_path],
+ delete_path: todo[:delete_path] } }
%span.issuable-todo-inner.js-issuable-todo-inner<
- - if todo
+ - if todo[:id]
= mark_content
- else
= todo_content
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index ac8d58c0bfe..e370dff9526 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -19,10 +19,9 @@
.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_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group.row
- - has_labels = @labels && @labels.any?
= form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
= form.hidden_field :label_ids, multiple: true, value: ''
- .col-sm-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
+ .col-sm-10{ class: "#{"col-md-8" if has_due_date}" }
.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 }, dropdown_title: "Select label"