Commit 06cef29a authored by Mike Greiling's avatar Mike Greiling

Merge branch '44984-use-serializer-for-issuable-sidebar' into 'master'

Resolve "Create a serializer to render issuables sidebar"

Closes #44984

See merge request gitlab-org/gitlab-ce!23379
parents 88094c44 95aae95a
...@@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) { ...@@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url; var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget); $this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post'; ajaxType = $this.data('deletePath') ? 'delete' : 'post';
if ($this.attr('data-delete-path')) {
url = '' + $this.attr('data-delete-path'); if ($this.data('deletePath')) {
url = '' + $this.data('deletePath');
} else { } else {
url = '' + $this.data('url'); url = '' + $this.data('createPath');
} }
$this.tooltip('hide'); $this.tooltip('hide');
...@@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) { ...@@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) {
.removeClass('is-loading') .removeClass('is-loading')
.enable() .enable()
.attr('aria-label', $el.data(`${attrPrefix}Text`)) .attr('aria-label', $el.data(`${attrPrefix}Text`))
.attr('data-delete-path', deletePath) .attr('title', $el.data(`${attrPrefix}Text`))
.attr('title', $el.data(`${attrPrefix}Text`)); .data('deletePath', deletePath);
if ($el.hasClass('has-tooltip')) { if ($el.hasClass('has-tooltip')) {
$el.tooltip('_fixTitle'); $el.tooltip('_fixTitle');
} }
if ($el.data(`${attrPrefix}Icon`)) { if (typeof $el.data('isCollapsed') !== 'undefined') {
$elText.html($el.data(`${attrPrefix}Icon`)); $elText.html($el.data(`${attrPrefix}Icon`));
} else { } else {
$elText.text($el.data(`${attrPrefix}Text`)); $elText.text($el.data(`${attrPrefix}Text`));
......
...@@ -71,7 +71,7 @@ export default class SidebarStore { ...@@ -71,7 +71,7 @@ export default class SidebarStore {
} }
findAssignee(findAssignee) { findAssignee(findAssignee) {
return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0]; return this.assignees.find(assignee => assignee.id === findAssignee.id);
} }
removeAssignee(removeAssignee) { removeAssignee(removeAssignee) {
......
...@@ -5,7 +5,6 @@ module IssuableActions ...@@ -5,7 +5,6 @@ module IssuableActions
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
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,7 +24,10 @@ module IssuableActions ...@@ -25,7 +24,10 @@ module IssuableActions
def show def show
respond_to do |format| respond_to do |format|
format.html format.html do
@issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
format.json do format.json do
render json: serializer.represent(issuable, serializer: params[:serializer]) render json: serializer.represent(issuable, serializer: params[:serializer])
end end
...@@ -168,10 +170,6 @@ module IssuableActions ...@@ -168,10 +170,6 @@ module IssuableActions
end end
end end
def labels
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
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!
......
...@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap ...@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
def show def show
respond_to do |format| respond_to do |format|
format.html do format.html do
labels @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
end end
format.json do format.json do
...@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap ...@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
end end
end end
private
def authorize_can_resolve_conflicts! def authorize_can_resolve_conflicts!
@conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request) @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
return render_404 unless @conflicts_list.can_be_resolved_by?(current_user) return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
end end
def serializer
MergeRequestSerializer.new(current_user: current_user, project: project)
end
end end
...@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
format.html format.html
format.json do format.json do
render json: { render json: {
html: view_to_html_string("projects/merge_requests/_merge_requests"), html: view_to_html_string("projects/merge_requests/_merge_requests")
labels: @labels.as_json(methods: :text_color)
} }
end end
end end
...@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request @noteable = @merge_request
@commits_count = @merge_request.commits_count @commits_count = @merge_request.commits_count
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
labels
set_pipeline_variables set_pipeline_variables
......
...@@ -23,30 +23,41 @@ module IssuablesHelper ...@@ -23,30 +23,41 @@ module IssuablesHelper
end end
end end
def sidebar_due_date_tooltip_label(issuable) def sidebar_milestone_tooltip_label(milestone)
if issuable.due_date return _('Milestone') unless milestone.present?
"#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
else [milestone[:title], sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>')
_('Due date') end
end
def sidebar_milestone_remaining_days(milestone)
due_date_with_remaining_days(milestone[:due_date], milestone[:start_date])
end
def sidebar_due_date_tooltip_label(due_date)
[_('Due date'), due_date_with_remaining_days(due_date)].compact.join('<br/>')
end end
def due_date_remaining_days(issuable) def due_date_with_remaining_days(due_date, start_date = nil)
remaining_days_in_words = remaining_days_in_words(issuable) return unless due_date
"#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})" "#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
end
def sidebar_label_filter_path(base_path, label_name)
query_params = { label_name: [label_name] }.to_query
"#{base_path}?#{query_params}"
end end
def multi_label_name(current_labels, default_label) def multi_label_name(current_labels, default_label)
if current_labels && current_labels.any? return default_label if current_labels.blank?
title = current_labels.first.try(:title)
if current_labels.size > 1 title = current_labels.first.try(:title) || current_labels.first[:title]
"#{title} +#{current_labels.size - 1} more"
else if current_labels.size > 1
title "#{title} +#{current_labels.size - 1} more"
end
else else
default_label title
end end
end end
...@@ -197,19 +208,11 @@ module IssuablesHelper ...@@ -197,19 +208,11 @@ module IssuablesHelper
output.join.html_safe output.join.html_safe
end 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) def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index { |_, i| i < limit } first, last = labels.partition.with_index { |_, i| i < limit }
if labels && labels.any? if labels && labels.any?
label_names = first.collect(&:name) label_names = first.collect { |label| label.fetch(:title) }
label_names << "and #{last.size} more" unless last.empty? label_names << "and #{last.size} more" unless last.empty?
label_names.join(', ') label_names.join(', ')
...@@ -356,12 +359,6 @@ module IssuablesHelper ...@@ -356,12 +359,6 @@ module IssuablesHelper
issuable.model_name.human.downcase issuable.model_name.human.downcase
end end
def selected_labels
Array(params[:label_name]).map do |label_name|
Label.new(title: label_name)
end
end
def has_filter_bar_param? def has_filter_bar_param?
finder.class.scalar_params.any? { |p| params[p].present? } finder.class.scalar_params.any? { |p| params[p].present? }
end end
...@@ -386,19 +383,20 @@ module IssuablesHelper ...@@ -386,19 +383,20 @@ module IssuablesHelper
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] } params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end end
def issuable_todo_button_data(issuable, todo, is_collapsed) def issuable_todo_button_data(issuable, is_collapsed)
{ {
todo_text: "Add todo", todo_text: _('Add todo'),
mark_text: "Mark todo as done", mark_text: _('Mark todo as done'),
todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil), todo_icon: sprite_icon('todo-add'),
mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil), mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'),
issuable_id: issuable.id, issuable_id: issuable[:id],
issuable_type: issuable.class.name.underscore, issuable_type: issuable[:type],
url: project_todos_path(@project), create_path: issuable[:create_todo_path],
delete_path: (dashboard_todo_path(todo) if todo), delete_path: issuable.dig(:current_user, :todo, :delete_path),
placement: (is_collapsed ? 'left' : nil), placement: is_collapsed ? 'left' : nil,
container: (is_collapsed ? 'body' : nil), container: is_collapsed ? 'body' : nil,
boundary: 'viewport' boundary: 'viewport',
is_collapsed: is_collapsed
} }
end end
...@@ -418,27 +416,20 @@ module IssuablesHelper ...@@ -418,27 +416,20 @@ module IssuablesHelper
end end
end end
def issuable_sidebar_options(issuable, can_edit_issuable) def issuable_sidebar_options(issuable)
{ {
endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar", endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras",
toggleSubscriptionEndpoint: toggle_subscription_path(issuable), toggleSubscriptionEndpoint: issuable[:toggle_subscription_path],
moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable), moveIssueEndpoint: issuable[:move_issue_path],
projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id), projectsAutocompleteEndpoint: issuable[:projects_autocomplete_path],
editable: can_edit_issuable, editable: issuable.dig(:current_user, :can_edit),
currentUser: UserSerializer.new.represent(current_user), currentUser: issuable[:current_user],
rootPath: root_path, rootPath: root_path,
fullPath: @project.full_path fullPath: issuable[:project_full_path]
} }
end end
def parent def parent
@project || @group @project || @group
end 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 end
...@@ -114,12 +114,6 @@ module MilestonesHelper ...@@ -114,12 +114,6 @@ module MilestonesHelper
end end
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) def milestone_time_for(date, date_type)
title = date_type == :start ? "Start date" : "End date" title = date_type == :start ? "Start date" : "End date"
...@@ -173,7 +167,7 @@ module MilestonesHelper ...@@ -173,7 +167,7 @@ module MilestonesHelper
def milestone_tooltip_due_date(milestone) def milestone_tooltip_due_date(milestone)
if milestone.due_date if milestone.due_date
"#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})" "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})"
else else
_('Milestone') _('Milestone')
end end
......
...@@ -1422,6 +1422,10 @@ class User < ActiveRecord::Base ...@@ -1422,6 +1422,10 @@ class User < ActiveRecord::Base
todos.where(id: ids) todos.where(id: ids)
end end
def pending_todo_for(target)
todos.find_by(target: target, state: :pending)
end
# @deprecated # @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
......
...@@ -44,14 +44,14 @@ module EntityDateHelper ...@@ -44,14 +44,14 @@ module EntityDateHelper
# It returns "Upcoming" for upcoming entities # It returns "Upcoming" for upcoming entities
# If due date is provided, it returns "# days|weeks|months remaining|ago" # 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" # If start date is provided and elapsed, with no due date, it returns "# days elapsed"
def remaining_days_in_words(entity) def remaining_days_in_words(due_date, start_date = nil)
if entity.try(:expired?) if due_date&.past?
content_tag(:strong, 'Past due') content_tag(:strong, 'Past due')
elsif entity.try(:upcoming?) elsif start_date&.future?
content_tag(:strong, 'Upcoming') content_tag(:strong, 'Upcoming')
elsif entity.due_date elsif due_date
is_upcoming = (entity.due_date - Date.today).to_i > 0 is_upcoming = (due_date - Date.today).to_i > 0
time_ago = time_ago_in_words(entity.due_date) time_ago = time_ago_in_words(due_date)
# https://gitlab.com/gitlab-org/gitlab-ce/issues/49440 # https://gitlab.com/gitlab-org/gitlab-ce/issues/49440
# #
...@@ -63,8 +63,8 @@ module EntityDateHelper ...@@ -63,8 +63,8 @@ module EntityDateHelper
remaining_or_ago = is_upcoming ? _("remaining") : _("ago") remaining_or_ago = is_upcoming ? _("remaining") : _("ago")
"#{content} #{remaining_or_ago}".html_safe "#{content} #{remaining_or_ago}".html_safe
elsif entity.start_date && entity.start_date.past? elsif start_date&.past?
days = entity.elapsed_days days = (Date.today - start_date).to_i
"#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe "#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe
end end
end end
......
# frozen_string_literal: true
class IssuableSidebarBasicEntity < Grape::Entity
include RequestAwareEntity
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
expose :reference do |issuable|
issuable.to_reference(issuable.project, full: true)
end
expose :milestone, using: ::API::Entities::Milestone
expose :labels, using: LabelEntity
expose :current_user, if: lambda { |_issuable| current_user } do
expose :current_user, merge: true, using: API::Entities::UserBasic
expose :todo, using: IssuableSidebarTodoEntity do |issuable|
current_user.pending_todo_for(issuable)
end
expose :can_edit do |issuable|
can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
end
expose :can_move do |issuable|
issuable.can_move?(current_user)
end
expose :can_admin_label do |issuable|
can?(current_user, :admin_label, issuable.project)
end
end
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
private
def current_user
request.current_user
end
end
# frozen_string_literal: true # frozen_string_literal: true
class IssuableSidebarEntity < Grape::Entity class IssuableSidebarExtrasEntity < Grape::Entity
include TimeTrackableEntity
include RequestAwareEntity include RequestAwareEntity
include TimeTrackableEntity
expose :participants, using: ::API::Entities::UserBasic do |issuable| expose :participants, using: ::API::Entities::UserBasic do |issuable|
issuable.participants(request.current_user) issuable.participants(request.current_user)
......
# 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
...@@ -37,7 +37,7 @@ class IssueBoardEntity < Grape::Entity ...@@ -37,7 +37,7 @@ class IssueBoardEntity < Grape::Entity
end end
expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue| expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue|
project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar') project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar_extras')
end end
expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue| expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue|
......
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
class IssueSerializer < BaseSerializer class IssueSerializer < BaseSerializer
# This overrided method takes care of which entity should be used # This overrided method takes care of which entity should be used
# to serialize the `issue` based on `basic` key in `opts` param. # to serialize the `issue` based on `serializer` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope. # Hence, `entity` doesn't need to be declared on the class scope.
def represent(issue, opts = {}) def represent(issue, opts = {})
entity = entity =
case opts[:serializer] case opts[:serializer]
when 'sidebar' when 'sidebar'
IssueSidebarEntity IssueSidebarBasicEntity
when 'sidebar_extras'
IssueSidebarExtrasEntity
when 'board' when 'board'
IssueBoardEntity IssueBoardEntity
else else
......
# frozen_string_literal: true
class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
expose :due_date
expose :confidential
end
# frozen_string_literal: true # frozen_string_literal: true
class IssueSidebarEntity < IssuableSidebarEntity class IssueSidebarExtrasEntity < IssuableSidebarExtrasEntity
expose :assignees, using: API::Entities::UserBasic expose :assignees, using: API::Entities::UserBasic
end end
# frozen_string_literal: true # frozen_string_literal: true
class MergeRequestBasicEntity < IssuableSidebarEntity class MergeRequestBasicEntity < Grape::Entity
expose :assignee_id expose :assignee_id
expose :merge_status expose :merge_status
expose :merge_error expose :merge_error
......
# frozen_string_literal: true
class MergeRequestBasicSerializer < BaseSerializer
entity MergeRequestBasicEntity
end
...@@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer ...@@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer
def represent(merge_request, opts = {}) def represent(merge_request, opts = {})
entity = entity =
case opts[:serializer] case opts[:serializer]
when 'basic', 'sidebar' when 'sidebar'
MergeRequestSidebarBasicEntity
when 'sidebar_extras'
IssuableSidebarExtrasEntity
when 'basic'
MergeRequestBasicEntity MergeRequestBasicEntity
else # It's 'widget' else
# fallback to widget for old poll requests without `serializer` set
MergeRequestWidgetEntity MergeRequestWidgetEntity
end end
......
# frozen_string_literal: true
class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
expose :assignee, if: lambda { |issuable| issuable.assignee } do
expose :assignee, merge: true, using: API::Entities::UserBasic
expose :can_merge do |issuable|
issuable.can_be_merged_by?(issuable.assignee)
end
end
end
...@@ -88,4 +88,4 @@ ...@@ -88,4 +88,4 @@
%section.issuable-discussion %section.issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details
= render "projects/merge_requests/mr_box"
= render 'shared/issuable/sidebar', issuable: @merge_request
#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) } }
.loading{ "v-if" => "isLoading" }
%i.fa.fa-spinner.fa-spin
.nothing-here-block{ "v-if" => "hasError" }
{{conflictsData.errorMessage}}
= render partial: "projects/merge_requests/conflicts/commit_stats"
.files-wrapper{ "v-if" => "!isLoading && !hasError" }
.files
.diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
.js-file-title.file-title
%i.fa.fa-fw{ ":class" => "file.iconClass" }
%strong {{file.filePath}}
= render partial: 'projects/merge_requests/conflicts/file_actions'
.diff-content.diff-wrap-lines
.diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
.diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
%parallel-conflict-lines{ ":file" => "file" }
%div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" }
= render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
= render partial: "projects/merge_requests/conflicts/submit_form"
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.merge-request-details.issuable-details .merge-request-details.issuable-details
= render "projects/merge_requests/mr_box" = render "projects/merge_requests/mr_box"
= render 'shared/issuable/sidebar', issuable: @merge_request = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees
#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json), #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) } } resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
......
...@@ -86,7 +86,8 @@ ...@@ -86,7 +86,8 @@
.mr-loading-status .mr-loading-status
= spinner = spinner
= render 'shared/issuable/sidebar', issuable: @merge_request = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees
- if @merge_request.can_be_reverted?(current_user) - if @merge_request.can_be_reverted?(current_user)
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked? - if @merge_request.can_be_cherry_picked?
......
- if issuable.is_a?(Issue) - issuable_type = issuable_sidebar[:type]
#js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } } - signed_in = !!issuable_sidebar.dig(:current_user, :id)
- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit)
- if issuable_type == "issue"
#js-vue-sidebar-assignees{ data: { field: "#{issuable_type}[assignee_ids]", signed_in: signed_in } }
.title.hide-collapsed .title.hide-collapsed
= _('Assignee') = _('Assignee')
= icon('spinner spin') = icon('spinner spin')
- else - else
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: sidebar_assignee_tooltip_label(issuable) } - assignee = assignees.first
- 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')) }
= link_to_member(@project, issuable.assignee, size: 24) - if issuable_sidebar[:assignee]
= link_to_member(@project, assignee, size: 24)
- else - else
= icon('user', 'aria-hidden': 'true') = icon('user', 'aria-hidden': 'true')
.title.hide-collapsed .title.hide-collapsed
...@@ -18,13 +23,13 @@ ...@@ -18,13 +23,13 @@
%a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') } %a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') }
= sidebar_gutter_toggle_icon = sidebar_gutter_toggle_icon
.value.hide-collapsed .value.hide-collapsed
- if issuable.assignee - if issuable_sidebar[:assignee]
= link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do = link_to_member(@project, assignee, size: 32, extra_class: 'bold') do
- if !issuable.can_be_merged_by?(issuable.assignee) - if issuable_sidebar[:assignee][:can_merge]
%span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') } %span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') }
= icon('exclamation-triangle', 'aria-hidden': 'true') = icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username %span.username
= issuable.assignee.to_reference @#{issuable_sidebar[:assignee][:username]}
- else - else
%span.assign-yourself.no-value %span.assign-yourself.no-value
= _('No assignee') = _('No assignee')
...@@ -34,19 +39,33 @@ ...@@ -34,19 +39,33 @@
= _('assign yourself') = _('assign yourself')
.selectbox.hide-collapsed .selectbox.hide-collapsed
- issuable.assignees.each do |assignee| - if assignees.none?
= 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 } = hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil
- else
- 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') - title = _('Select assignee')
- if issuable.is_a?(Issue) - if issuable_type == "issue"
- unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
- dropdown_options = issue_assignees_dropdown_options - dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title] - title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data' - 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[:multi_select] = true
- data['dropdown-title'] = title - data['dropdown-title'] = title
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header'] - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
......
- is_collapsed = local_assigns.fetch(:is_collapsed, false) - is_collapsed = local_assigns.fetch(:is_collapsed, false)
- mark_content = is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : _('Mark todo as done') - has_todo = !!issuable_sidebar.dig(:current_user, :todo, :id)
- todo_content = is_collapsed ? sprite_icon('todo-add') : _('Add todo')
- todo_button_data = issuable_todo_button_data(issuable_sidebar, is_collapsed)
- button_title = has_todo ? todo_button_data[:mark_text] : todo_button_data[:todo_text]
- button_icon = has_todo ? todo_button_data[:mark_icon] : todo_button_data[:todo_icon]
%button.issuable-todo-btn.js-issuable-todo{ type: 'button', %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'), 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')), title: button_title,
'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')), 'aria-label' => button_title,
data: issuable_todo_button_data(issuable, todo, is_collapsed) } data: todo_button_data }
%span.issuable-todo-inner.js-issuable-todo-inner< %span.issuable-todo-inner.js-issuable-todo-inner<
- if todo = is_collapsed ? button_icon : button_title
= mark_content
- else
= todo_content
= icon('spin spinner', 'aria-hidden': 'true') = icon('spin spinner', 'aria-hidden': 'true')
...@@ -19,10 +19,9 @@ ...@@ -19,10 +19,9 @@
.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_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown 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, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group.row .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.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: '' = 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 .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" = 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"
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
%span.bold= milestone.due_date.to_s(:medium) %span.bold= milestone.due_date.to_s(:medium)
- else - else
%span.no-value No due date %span.no-value No due date
- remaining_days = remaining_days_in_words(milestone) - remaining_days = remaining_days_in_words(milestone.due_date, milestone.start_date)
- if remaining_days.present? - if remaining_days.present?
= surround '(', ')' do = surround '(', ')' do
%span.remaining-days= remaining_days %span.remaining-days= remaining_days
......
---
title: Refactor issuable sidebar to use serializer
merge_request: 23379
author:
type: other
...@@ -30,19 +30,6 @@ describe Projects::MergeRequestsController do ...@@ -30,19 +30,6 @@ describe Projects::MergeRequestsController do
end end
end end
shared_examples "loads labels" do |action|
it "loads labels into the @labels variable" do
get action,
params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid
},
format: 'html'
expect(assigns(:labels)).not_to be_nil
end
end
describe "GET show" do describe "GET show" do
def go(extra_params = {}) def go(extra_params = {})
params = { params = {
...@@ -54,8 +41,6 @@ describe Projects::MergeRequestsController do ...@@ -54,8 +41,6 @@ describe Projects::MergeRequestsController do
get :show, params: params.merge(extra_params) get :show, params: params.merge(extra_params)
end end
it_behaves_like "loads labels", :show
describe 'as html' do describe 'as html' do
context 'when diff files were cleaned' do context 'when diff files were cleaned' do
render_views render_views
......
{
"type": ["object", "null"],
"properties" : {
"id": { "type": "integer" },
"delete_path": { "type": "string" }
},
"additionalProperties": false
}
...@@ -2,20 +2,46 @@ ...@@ -2,20 +2,46 @@
"type": "object", "type": "object",
"properties" : { "properties" : {
"id": { "type": "integer" }, "id": { "type": "integer" },
"iid": { "type": "integer" }, "type": { "type": "string" },
"subscribed": { "type": "boolean" }, "author_id": { "type": "integer" },
"time_estimate": { "type": "integer" }, "project_id": { "type": "integer" },
"total_time_spent": { "type": "integer" }, "discussion_locked": { "type": ["boolean", "null"] },
"human_time_estimate": { "type": ["integer", "null"] }, "due_date": { "type": "date" },
"human_total_time_spent": { "type": ["integer", "null"] }, "confidential": { "type": "boolean" },
"participants": { "reference": { "type": "string" },
"type": "array", "current_user": {
"items": { "$ref": "../public_api/v4/user/basic.json" } "allOf": [
{ "$ref": "../public_api/v4/user/basic.json" },
{ "type": "object",
"properties" : {
"todo": { "$ref": "issuable_sidebar_todo.json" },
"can_edit": { "type": "boolean" },
"can_move": { "type": "boolean" },
"can_admin_label": { "type": "boolean" }
}
}
]
},
"milestone": {
"oneOf": [
{ "type": "null" },
{ "$ref": "../public_api/v4/milestone.json" }
]
}, },
"assignees": { "labels": {
"type": "array", "type": "array",
"items": { "$ref": "../public_api/v4/user/basic.json" } "items": { "$ref": "label.json" }
} },
}, "issuable_json_path": { "type": "string" },
"additionalProperties": false "namespace_path": { "type": "string" },
"project_path": { "type": "string" },
"project_full_path": { "type": "string" },
"project_issuables_path": { "type": "string" },
"create_todo_path": { "type": "string" },
"project_milestones_path": { "type": "string" },
"project_labels_path": { "type": "string" },
"toggle_subscription_path": { "type": "string" },
"move_issue_path": { "type": "string" },
"projects_autocomplete_path": { "type": "string" }
}
} }
{
"type": "object",
"properties" : {
"subscribed": { "type": "boolean" },
"time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" },
"human_time_estimate": { "type": ["integer", "null"] },
"human_total_time_spent": { "type": ["integer", "null"] },
"participants": {
"type": "array",
"items": { "$ref": "../public_api/v4/user/basic.json" }
},
"assignees": {
"type": "array",
"items": { "$ref": "../public_api/v4/user/basic.json" }
}
}
}
...@@ -4,15 +4,9 @@ ...@@ -4,15 +4,9 @@
"state": { "type": "string" }, "state": { "type": "string" },
"merge_status": { "type": "string" }, "merge_status": { "type": "string" },
"source_branch_exists": { "type": "boolean" }, "source_branch_exists": { "type": "boolean" },
"time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" },
"human_time_estimate": { "type": ["string", "null"] },
"human_total_time_spent": { "type": ["string", "null"] },
"merge_error": { "type": ["string", "null"] }, "merge_error": { "type": ["string", "null"] },
"rebase_in_progress": { "type": "boolean" }, "rebase_in_progress": { "type": "boolean" },
"assignee_id": { "type": ["integer", "null"] }, "assignee_id": { "type": ["integer", "null"] },
"subscribed": { "type": ["boolean", "null"] },
"participants": { "type": "array" },
"allow_collaboration": { "type": "boolean"}, "allow_collaboration": { "type": "boolean"},
"allow_maintainer_to_push": { "type": "boolean"}, "allow_maintainer_to_push": { "type": "boolean"},
"assignee": { "assignee": {
......
{
"type": "object",
"properties" : {
"id": { "type": "integer" },
"type": { "type": "string" },
"author_id": { "type": "integer" },
"project_id": { "type": "integer" },
"discussion_locked": { "type": ["boolean", "null"] },
"reference": { "type": "string" },
"current_user": {
"allOf": [
{ "$ref": "../public_api/v4/user/basic.json" },
{ "type": "object",
"properties" : {
"todo": { "$ref": "issuable_sidebar_todo.json" },
"can_edit": { "type": "boolean" },
"can_move": { "type": "boolean" },
"can_admin_label": { "type": "boolean" }
}
}
]
},
"milestone": {
"oneOf": [
{ "type": "null" },
{ "$ref": "../public_api/v4/milestones.json" }
]
},
"labels": {
"type": "array",
"items": { "$ref": "label.json" }
},
"assignee": {
"allOf": [
{ "$ref": "../public_api/v4/user/basic.json" },
{ "type": "object",
"properties" : {
"can_merge": { "type": "boolean" }
}
}
]
},
"issuable_json_path": { "type": "string" },
"namespace_path": { "type": "string" },
"project_path": { "type": "string" },
"project_full_path": { "type": "string" },
"project_issuables_path": { "type": "string" },
"create_todo_path": { "type": "string" },
"project_milestones_path": { "type": "string" },
"project_labels_path": { "type": "string" },
"toggle_subscription_path": { "type": "string" },
"move_issue_path": { "type": "string" },
"projects_autocomplete_path": { "type": "string" }
},
"additionalProperties": false
}
{
"type": "object",
"properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
"subscribed": { "type": "boolean" },
"time_estimate": { "type": "integer" },
"total_time_spent": { "type": "integer" },
"human_time_estimate": { "type": ["integer", "null"] },
"human_total_time_spent": { "type": ["integer", "null"] },
"participants": {
"type": "array",
"items": { "$ref": "../public_api/v4/user/basic.json" }
},
"assignees": {
"type": "array",
"items": { "$ref": "../public_api/v4/user/basic.json" }
}
},
"additionalProperties": false
}
{
"type": "object",
"properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
"project_id": { "type": ["integer", "null"] },
"group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
"due_date": { "type": "date" },
"web_url": { "type": "string" }
},
"required": [
"id", "iid", "title", "description", "state",
"state", "created_at", "updated_at", "start_date", "due_date"
],
"additionalProperties": false
}
{ {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "$ref": "./milestone.json"
"properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
"project_id": { "type": ["integer", "null"] },
"group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
"due_date": { "type": "date" },
"web_url": { "type": "string" }
},
"required": [
"id", "iid", "title", "description", "state",
"state", "created_at", "updated_at", "start_date", "due_date"
],
"additionalProperties": false
} }
} }
...@@ -43,16 +43,19 @@ describe IssuablesHelper do ...@@ -43,16 +43,19 @@ describe IssuablesHelper do
end end
describe '#issuable_labels_tooltip' do describe '#issuable_labels_tooltip' do
let(:label_entity) { LabelEntity.represent(label).as_json }
let(:label2_entity) { LabelEntity.represent(label2).as_json }
it 'returns label text with no labels' do it 'returns label text with no labels' do
expect(issuable_labels_tooltip([])).to eq("Labels") expect(issuable_labels_tooltip([])).to eq("Labels")
end end
it 'returns label text with labels within max limit' do it 'returns label text with labels within max limit' do
expect(issuable_labels_tooltip([label])).to eq(label.title) expect(issuable_labels_tooltip([label_entity])).to eq(label[:title])
end end
it 'returns label text with labels exceeding max limit' do it 'returns label text with labels exceeding max limit' do
expect(issuable_labels_tooltip([label, label2], limit: 1)).to eq("#{label.title}, and 1 more") expect(issuable_labels_tooltip([label_entity, label2_entity], limit: 1)).to eq("#{label[:title]}, and 1 more")
end end
end end
...@@ -197,33 +200,4 @@ describe IssuablesHelper do ...@@ -197,33 +200,4 @@ describe IssuablesHelper do
expect(helper.issuable_initial_data(issue)).to eq(expected_data) expect(helper.issuable_initial_data(issue)).to eq(expected_data)
end end
end end
describe '#selected_labels' do
context 'if label_name param is a string' do
it 'returns a new label with title' do
allow(helper).to receive(:params)
.and_return(ActionController::Parameters.new(label_name: 'test label'))
labels = helper.selected_labels
expect(labels).to be_an(Array)
expect(labels.size).to eq(1)
expect(labels.first.title).to eq('test label')
end
end
context 'if label_name param is an array' do
it 'returns a new label with title for each element' do
allow(helper).to receive(:params)
.and_return(ActionController::Parameters.new(label_name: ['test label 1', 'test label 2']))
labels = helper.selected_labels
expect(labels).to be_an(Array)
expect(labels.size).to eq(2)
expect(labels.first.title).to eq('test label 1')
expect(labels.second.title).to eq('test label 2')
end
end
end
end end
...@@ -66,7 +66,7 @@ const RESPONSE_MAP = { ...@@ -66,7 +66,7 @@ const RESPONSE_MAP = {
}, },
labels: [], labels: [],
}, },
'/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar': { '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras': {
assignees: [ assignees: [
{ {
name: 'User 0', name: 'User 0',
...@@ -181,7 +181,7 @@ const RESPONSE_MAP = { ...@@ -181,7 +181,7 @@ const RESPONSE_MAP = {
const mockData = { const mockData = {
responseMap: RESPONSE_MAP, responseMap: RESPONSE_MAP,
mediator: { mediator: {
endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar', endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras',
toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription', toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move', moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15', projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
......
...@@ -37,7 +37,7 @@ describe('Sidebar mediator', function() { ...@@ -37,7 +37,7 @@ describe('Sidebar mediator', function() {
it('fetches the data', done => { it('fetches the data', done => {
const mockData = const mockData =
Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras'];
spyOn(this.mediator, 'processFetchedData').and.callThrough(); spyOn(this.mediator, 'processFetchedData').and.callThrough();
this.mediator this.mediator
...@@ -51,7 +51,7 @@ describe('Sidebar mediator', function() { ...@@ -51,7 +51,7 @@ describe('Sidebar mediator', function() {
it('processes fetched data', () => { it('processes fetched data', () => {
const mockData = const mockData =
Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar']; Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras'];
this.mediator.processFetchedData(mockData); this.mediator.processFetchedData(mockData);
expect(this.mediator.store.assignees).toEqual(mockData.assignees); expect(this.mediator.store.assignees).toEqual(mockData.assignees);
......
...@@ -50,7 +50,7 @@ describe EntityDateHelper do ...@@ -50,7 +50,7 @@ describe EntityDateHelper do
end end
context 'when less than 31 days remaining' do context 'when less than 31 days remaining' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 12.days.from_now.utc)) } let(:milestone_remaining) { date_helper_class.remaining_days_in_words(12.days.from_now.utc.to_date) }
it 'returns days remaining' do it 'returns days remaining' do
expect(milestone_remaining).to eq("<strong>12</strong> days remaining") expect(milestone_remaining).to eq("<strong>12</strong> days remaining")
...@@ -58,7 +58,7 @@ describe EntityDateHelper do ...@@ -58,7 +58,7 @@ describe EntityDateHelper do
end end
context 'when less than 1 year and more than 30 days remaining' do context 'when less than 1 year and more than 30 days remaining' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.months.from_now.utc)) } let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.months.from_now.utc.to_date) }
it 'returns months remaining' do it 'returns months remaining' do
expect(milestone_remaining).to eq("<strong>2</strong> months remaining") expect(milestone_remaining).to eq("<strong>2</strong> months remaining")
...@@ -66,7 +66,7 @@ describe EntityDateHelper do ...@@ -66,7 +66,7 @@ describe EntityDateHelper do
end end
context 'when more than 1 year remaining' do context 'when more than 1 year remaining' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: (1.year.from_now + 2.days).utc)) } let(:milestone_remaining) { date_helper_class.remaining_days_in_words((1.year.from_now + 2.days).utc.to_date) }
it 'returns years remaining' do it 'returns years remaining' do
expect(milestone_remaining).to eq("<strong>1</strong> year remaining") expect(milestone_remaining).to eq("<strong>1</strong> year remaining")
...@@ -74,7 +74,7 @@ describe EntityDateHelper do ...@@ -74,7 +74,7 @@ describe EntityDateHelper do
end end
context 'when milestone is expired' do context 'when milestone is expired' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.days.ago.utc)) } let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.days.ago.utc.to_date) }
it 'returns "Past due"' do it 'returns "Past due"' do
expect(milestone_remaining).to eq("<strong>Past due</strong>") expect(milestone_remaining).to eq("<strong>Past due</strong>")
...@@ -82,7 +82,7 @@ describe EntityDateHelper do ...@@ -82,7 +82,7 @@ describe EntityDateHelper do
end end
context 'when milestone has start_date in the future' do context 'when milestone has start_date in the future' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.from_now.utc)) } let(:milestone_remaining) { date_helper_class.remaining_days_in_words(nil, 2.days.from_now.utc.to_date) }
it 'returns "Upcoming"' do it 'returns "Upcoming"' do
expect(milestone_remaining).to eq("<strong>Upcoming</strong>") expect(milestone_remaining).to eq("<strong>Upcoming</strong>")
...@@ -90,7 +90,7 @@ describe EntityDateHelper do ...@@ -90,7 +90,7 @@ describe EntityDateHelper do
end end
context 'when milestone has start_date in the past' do context 'when milestone has start_date in the past' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.ago.utc)) } let(:milestone_remaining) { date_helper_class.remaining_days_in_words(nil, 2.days.ago.utc.to_date) }
it 'returns days elapsed' do it 'returns days elapsed' do
expect(milestone_remaining).to eq("<strong>2</strong> days elapsed") expect(milestone_remaining).to eq("<strong>2</strong> days elapsed")
......
...@@ -20,11 +20,19 @@ describe IssueSerializer do ...@@ -20,11 +20,19 @@ describe IssueSerializer do
context 'sidebar issue serialization' do context 'sidebar issue serialization' do
let(:serializer) { 'sidebar' } let(:serializer) { 'sidebar' }
it 'matches sidebar issue json schema' do it 'matches issue_sidebar json schema' do
expect(json_entity).to match_schema('entities/issue_sidebar') expect(json_entity).to match_schema('entities/issue_sidebar')
end end
end end
context 'sidebar extras issue serialization' do
let(:serializer) { 'sidebar_extras' }
it 'matches issue_sidebar_extras json schema' do
expect(json_entity).to match_schema('entities/issue_sidebar_extras')
end
end
context 'board issue serialization' do context 'board issue serialization' do
let(:serializer) { 'board' } let(:serializer) { 'board' }
......
require 'spec_helper'
describe MergeRequestBasicSerializer do
let(:resource) { create(:merge_request) }
let(:user) { create(:user) }
let(:json_entity) do
described_class.new(current_user: user)
.represent(resource, serializer: 'basic')
.with_indifferent_access
end
it 'matches basic merge request json' do
expect(json_entity).to match_schema('entities/merge_request_basic')
end
end
...@@ -20,8 +20,16 @@ describe MergeRequestSerializer do ...@@ -20,8 +20,16 @@ describe MergeRequestSerializer do
context 'sidebar merge request serialization' do context 'sidebar merge request serialization' do
let(:serializer) { 'sidebar' } let(:serializer) { 'sidebar' }
it 'matches basic merge request json schema' do it 'matches merge_request_sidebar json schema' do
expect(json_entity).to match_schema('entities/merge_request_basic') expect(json_entity).to match_schema('entities/merge_request_sidebar')
end
end
context 'sidebar_extras merge request serialization' do
let(:serializer) { 'sidebar_extras' }
it 'matches merge_request_sidebar_extras json schema' do
expect(json_entity).to match_schema('entities/merge_request_sidebar_extras')
end end
end end
......
...@@ -32,6 +32,11 @@ describe 'projects/merge_requests/show.html.haml' do ...@@ -32,6 +32,11 @@ describe 'projects/merge_requests/show.html.haml' do
assign(:noteable, closed_merge_request) assign(:noteable, closed_merge_request)
assign(:notes, []) assign(:notes, [])
assign(:pipelines, Ci::Pipeline.none) assign(:pipelines, Ci::Pipeline.none)
assign(
:issuable_sidebar,
MergeRequestSerializer.new(current_user: user, project: project)
.represent(closed_merge_request, serializer: 'sidebar')
)
preload_view_requirements preload_view_requirements
......
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