Add CRUD to comments

This commit is contained in:
aignatov-bio 2020-11-20 20:25:28 +01:00
parent da4b8aaf2f
commit 60ce51149d
11 changed files with 348 additions and 154 deletions

View file

@ -1,3 +1,5 @@
/* global SmartAnnotation */
var CommentsSidebar = (function() {
const SIDEBAR = '.comments-sidebar';
@ -19,13 +21,97 @@ var CommentsSidebar = (function() {
});
}
function initSendButton() {
$(document).on('click', `${SIDEBAR} .send-comment, ${SIDEBAR} .update-comment`, function() {
var requestUrl;
var requestType;
var updateMode = $(SIDEBAR).find('.sidebar-footer').hasClass('update');
var inputField = $(SIDEBAR).find('.comment-input-field');
if (updateMode) {
requestType = 'PATCH';
requestUrl = inputField.data('update-url');
} else {
requestType = 'POST';
requestUrl = $(SIDEBAR).data('comments-url');
}
$.ajax({
url: requestUrl,
type: requestType,
dataType: 'json',
data: {
object_type: $(SIDEBAR).data('object-type'),
object_id: $(SIDEBAR).data('object-id'),
message: inputField.val()
},
success: (result) => {
if (updateMode) {
$('.comment-container.edit').replaceWith(result.html);
} else {
$(result.html).appendTo(`${SIDEBAR} .comments-list`);
}
$(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
}
});
});
}
function initCancelButton() {
$(document).on('click', `${SIDEBAR} .cancel-button`, function() {
$(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
});
}
function initDeleteButton() {
$(document).on('click', `${SIDEBAR} .delete-comment`, function(e) {
var deleteUrl = $(this).data('delete-url');
var commentContainer = $(this).closest('.comment-container');
e.preventDefault();
$.ajax({
url: deleteUrl,
type: 'DELETE',
dataType: 'json',
success: () => {
commentContainer.remove();
}
});
});
}
function initEditButton() {
$(document).on('click', `${SIDEBAR} .edit-comment`, function(e) {
e.preventDefault();
$('.comment-container').removeClass('edit');
$(this).closest('.comment-container').addClass('edit');
$(SIDEBAR).find('.sidebar-footer').addClass('update');
$(SIDEBAR).find('.comment-input-field')
.val($(this).data('comment-raw'))
.data('update-url', $(this).data('update-url'));
});
}
function initInputField() {
$(document).on('ready', function() {
SmartAnnotation.init($(SIDEBAR).find('.comment-input-field'));
});
}
return {
init: function() {
initCloseButton();
initSendButton();
initDeleteButton();
initInputField();
initEditButton();
initCancelButton();
},
open: function(objectType, objectId) {
$(SIDEBAR).find('.comments-subject-title').empty();
$(SIDEBAR).find('.comments-list').empty();
$(SIDEBAR).find('.comment-input-field').val('');
$(SIDEBAR).find('.sidebar-footer').removeClass('update');
$(SIDEBAR).data('object-type', objectType).data('object-id', objectId);
$(SIDEBAR).addClass('open loading');
loadCommentsList();

View file

@ -91,6 +91,17 @@
width: 2.5em;
}
}
.update-buttons {
display: none;
margin-top: .5em;
}
&.update {
.update-buttons {
display: block;
}
}
}
}
@ -120,12 +131,37 @@
.comment-message {
@include font-main;
color: $color-volcano;
padding: 1em;
padding: .5em 1em;
}
}
.comment-footer {
padding: 0 1em 1em;
align-items: center;
display: flex;
padding: 0 1em .5em;
.comment-menu-container {
margin-left: auto;
.open-dropdown-btn {
cursor: pointer;
line-height: 2em;
text-align: center;
width: 2em;
}
.comment-dropdown-menu {
@include font-button;
a {
padding: .5em 1em;
}
.fas {
margin-right: .5em;
}
}
}
.comment-create-date {
@include font-small;

View file

@ -1,11 +1,16 @@
# frozen_string_literal: true
class CommentsController < ApplicationController
include ActionView::Helpers::TextHelper
include InputSanitizeHelper
include ApplicationHelper
include CommentHelper
before_action :load_object, only: %i(index create)
before_action :load_comment, only: %i(update delete)
before_action :load_comment, only: %i(update destroy)
before_action :check_view_permissions, only: :index
before_action :check_create_permissions, only: :create
before_action :check_manage_permissions, only: %i(update delete)
before_action :check_manage_permissions, only: %i(update destroy)
def index
comments = @commentable.comments
@ -17,11 +22,24 @@ class CommentsController < ApplicationController
}
end
def create; end
def create
@comment = @commentable.comments.new(
message: params[:message],
user: current_user,
associated_id: @commentable.id
)
comment_create_helper(@comment, 'comment')
end
def update; end
def update
old_text = @comment.message
@comment.message = params[:message]
comment_update_helper(@comment, old_text, 'comment')
end
def delete; end
def destroy
comment_destroy_helper(@comment)
end
private
@ -42,12 +60,16 @@ class CommentsController < ApplicationController
def load_comment
@comment = Comment.find_by(id: params[:id])
render_404 and return unless @commentable
render_404 and return unless @comment
@commentable = @comment.commentable
render_404 and return unless @commentable
end
def comment_params
params.permit(:message)
end
def check_view_permissions
case @commentable
when Project
@ -79,12 +101,6 @@ class CommentsController < ApplicationController
end
def check_manage_permissions
if @commentable.class == Project
render_403 and return unless can_manage_comment_in_project?(@comment)
elsif [MyModule, Step, Result].include? @commentable.class
render_403 and return unless can_manage_comment_in_module?(@comment.becomes(Comment))
else
render_403 and return
end
comment_editable?(@comment)
end
end

View file

@ -65,36 +65,4 @@ class MyModuleCommentsController < ApplicationController
def comment_params
params.require(:comment).permit(:message)
end
def my_module_comment_annotation_notification(old_text = nil)
smart_annotation_notification(
old_text: old_text,
new_text: @comment.message,
title: t('notifications.my_module_comment_annotation_title',
my_module: @my_module.name,
user: current_user.full_name),
message: t('notifications.my_module_annotation_message_html',
project: link_to(@my_module.experiment.project.name,
project_url(@my_module
.experiment
.project)),
experiment: link_to(@my_module.experiment.name,
canvas_experiment_url(@my_module
.experiment)),
my_module: link_to(@my_module.name,
protocols_my_module_url(
@my_module
)))
)
end
def log_activity(type_of)
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
team: @my_module.experiment.project.team,
project: @my_module.experiment.project,
subject: @my_module,
message_items: { my_module: @my_module.id })
end
end

View file

@ -63,26 +63,4 @@ class ProjectCommentsController < ApplicationController
def comment_params
params.require(:comment).permit(:message)
end
def project_comment_annotation_notification(old_text = nil)
smart_annotation_notification(
old_text: old_text,
new_text: @comment.message,
title: t('notifications.project_comment_annotation_title',
project: @project.name,
user: current_user.full_name),
message: t('notifications.project_annotation_message_html',
project: link_to(@project.name, project_url(@project)))
)
end
def log_activity(type_of)
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: @project,
team: @project.team,
project: @project,
message_items: { project: @project.id })
end
end

View file

@ -65,36 +65,4 @@ class ResultCommentsController < ApplicationController
def comment_params
params.require(:comment).permit(:message)
end
def result_comment_annotation_notification(old_text = nil)
smart_annotation_notification(
old_text: old_text,
new_text: @comment.message,
title: t('notifications.result_comment_annotation_title',
result: @result.name,
user: current_user.full_name),
message: t('notifications.result_annotation_message_html',
project: link_to(@result.my_module.experiment.project.name,
project_url(@result.my_module
.experiment
.project)),
experiment: link_to(@result.my_module.experiment.name,
canvas_experiment_url(@result.my_module
.experiment)),
my_module: link_to(@result.my_module.name,
protocols_my_module_url(
@result.my_module
)))
)
end
def log_activity(type_of)
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: @result,
team: @result.my_module.experiment.project.team,
project: @result.my_module.experiment.project,
message_items: { result: @result.id })
end
end

View file

@ -66,30 +66,6 @@ class StepCommentsController < ApplicationController
params.require(:comment).permit(:message)
end
def step_comment_annotation_notification(old_text = nil)
smart_annotation_notification(
old_text: old_text,
new_text: comment_params[:message],
title: t('notifications.step_comment_annotation_title',
step: @step.name,
user: current_user.full_name),
message: t('notifications.step_annotation_message_html',
project: link_to(@step.my_module.experiment.project.name,
project_url(@step.my_module
.experiment
.project)),
experiment: link_to(@step.my_module.experiment.name,
canvas_experiment_url(@step.my_module
.experiment)),
my_module: link_to(@step.my_module.name,
protocols_my_module_url(
@step.my_module
)),
step: link_to(@step.name,
protocols_my_module_url(@step.my_module)))
)
end
def log_activity(type_of)
Activities::CreateActivityService
.call(activity_type: type_of,

View file

@ -26,33 +26,33 @@ module CommentHelper
}
end
def comment_create_helper(comment)
def comment_create_helper(comment, partial = 'item')
if comment.save
case comment.type
when 'StepComment'
step_comment_annotation_notification
log_activity(:add_comment_to_step)
step_comment_annotation_notification(comment)
log_step_activity(:add_comment_to_step, comment)
when 'ResultComment'
result_comment_annotation_notification
log_activity(:add_comment_to_result)
result_comment_annotation_notification(comment)
log_result_activity(:add_comment_to_result, comment)
when 'ProjectComment'
project_comment_annotation_notification
log_activity(:add_comment_to_project)
project_comment_annotation_notification(comment)
log_project_activity(:add_comment_to_project, comment)
when 'TaskComment'
my_module_comment_annotation_notification
log_activity(:add_comment_to_module)
my_module_comment_annotation_notification(comment)
log_my_module_activity(:add_comment_to_module, comment)
end
render json: {
html: render_to_string(
partial: '/shared/comments/item.html.erb',
partial: "/shared/comments/#{partial}.html.erb",
locals: {
comment: comment
}
)
}
else
render json: { errors: comment.errors.to_hash(true) }, status: :error
render json: { errors: comment.errors.to_hash(true) }, status: :unprocessable_entity
end
end
@ -67,25 +67,36 @@ module CommentHelper
end
end
def comment_update_helper(comment, old_text)
def comment_update_helper(comment, old_text, partial = nil)
if comment.save
case comment.type
when 'StepComment'
step_comment_annotation_notification(old_text)
log_activity(:edit_step_comment)
step_comment_annotation_notification(comment, old_text)
log_step_activity(:edit_step_comment, comment)
when 'ResultComment'
result_comment_annotation_notification(old_text)
log_activity(:edit_result_comment)
result_comment_annotation_notification(comment, old_text)
log_result_activity(:edit_result_comment, comment)
when 'ProjectComment'
project_comment_annotation_notification(old_text)
log_activity(:edit_project_comment)
project_comment_annotation_notification(comment, old_text)
log_project_activity(:edit_project_comment, comment)
when 'TaskComment'
my_module_comment_annotation_notification(old_text)
log_activity(:edit_module_comment)
my_module_comment_annotation_notification(comment, old_text)
log_my_module_activity(:edit_module_comment, comment)
end
message = custom_auto_link(comment.message, team: current_team, simple_format: true)
render json: { comment: message }, status: :ok
if partial
render json: {
html: render_to_string(
partial: "/shared/comments/#{partial}.html.erb",
locals: {
comment: comment
}
)
}
else
message = custom_auto_link(comment.message, team: current_team, simple_format: true)
render json: { comment: message }, status: :ok
end
else
render json: { errors: comment.errors.to_hash(true) },
status: :unprocessable_entity
@ -96,13 +107,13 @@ module CommentHelper
if comment.destroy
case comment.type
when 'StepComment'
log_activity(:delete_step_comment)
log_step_activity(:delete_step_comment, comment)
when 'ResultComment'
log_activity(:delete_result_comment)
log_result_activity(:delete_result_comment, comment)
when 'ProjectComment'
log_activity(:delete_project_comment)
log_project_activity(:delete_project_comment, comment)
when 'TaskComment'
log_activity(:delete_module_comment)
log_my_module_activity(:delete_module_comment, comment)
end
render json: {}, status: :ok
else
@ -110,4 +121,136 @@ module CommentHelper
status: :unprocessable_entity
end
end
def result_comment_annotation_notification(comment, old_text = nil)
result = comment.result
smart_annotation_notification(
old_text: old_text,
new_text: comment.message,
title: t('notifications.result_comment_annotation_title',
result: result.name,
user: current_user.full_name),
message: t('notifications.result_annotation_message_html',
project: link_to(result.my_module.experiment.project.name,
project_url(result.my_module
.experiment
.project)),
experiment: link_to(result.my_module.experiment.name,
canvas_experiment_url(result.my_module
.experiment)),
my_module: link_to(result.my_module.name,
protocols_my_module_url(
result.my_module
)))
)
end
def project_comment_annotation_notification(comment, old_text = nil)
project = comment.project
smart_annotation_notification(
old_text: old_text,
new_text: comment.message,
title: t('notifications.project_comment_annotation_title',
project: project.name,
user: current_user.full_name),
message: t('notifications.project_annotation_message_html',
project: link_to(project.name, project_url(project)))
)
end
def step_comment_annotation_notification(comment, old_text = nil)
step = comment.step
smart_annotation_notification(
old_text: old_text,
new_text: comment.message,
title: t('notifications.step_comment_annotation_title',
step: step.name,
user: current_user.full_name),
message: t('notifications.step_annotation_message_html',
project: link_to(step.my_module.experiment.project.name,
project_url(step.my_module
.experiment
.project)),
experiment: link_to(step.my_module.experiment.name,
canvas_experiment_url(step.my_module
.experiment)),
my_module: link_to(step.my_module.name,
protocols_my_module_url(
step.my_module
)),
step: link_to(step.name,
protocols_my_module_url(step.my_module)))
)
end
def my_module_comment_annotation_notification(comment, old_text = nil)
my_module = comment.my_module
smart_annotation_notification(
old_text: old_text,
new_text: comment.message,
title: t('notifications.my_module_comment_annotation_title',
my_module: my_module.name,
user: current_user.full_name),
message: t('notifications.my_module_annotation_message_html',
project: link_to(my_module.experiment.project.name,
project_url(my_module
.experiment
.project)),
experiment: link_to(my_module.experiment.name,
canvas_experiment_url(my_module
.experiment)),
my_module: link_to(my_module.name,
protocols_my_module_url(
my_module
)))
)
end
def log_my_module_activity(type_of, comment)
my_module = comment.my_module
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
team: my_module.experiment.project.team,
project: my_module.experiment.project,
subject: my_module,
message_items: { my_module: my_module.id })
end
def log_project_activity(type_of, comment)
project = comment.project
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: project,
team: project.team,
project: project,
message_items: { project: project.id })
end
def log_step_activity(type_of, comment)
step = comment.step
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: step.protocol,
team: step.my_module.experiment.project.team,
project: step.my_module.experiment.project,
message_items: {
my_module: step.my_module.id,
step: step.id,
step_position: { id: step.id, value_for: 'position_plus_one' }
})
end
def log_result_activity(type_of, comment)
result = comment.result
Activities::CreateActivityService
.call(activity_type: type_of,
owner: current_user,
subject: result,
team: result.my_module.experiment.project.team,
project: result.my_module.experiment.project,
message_items: { result: result.id })
end
end

View file

@ -1,5 +1,5 @@
<% user = comment.user %>
<div class="comment-container <%= 'current-user' if user == current_user %>">
<div class="comment-container <%= 'current-user' if user == current_user %>" data-comment-id="<%= comment.id %>">
<div class="comment-header">
<%= image_tag avatar_path(comment.user, :icon_small), class: 'user-avatar' %>
<div class="user-name">
@ -8,12 +8,31 @@
</div>
<div class="comment-body">
<div class="comment-message">
<%= comment.message %>
<%= custom_auto_link(comment.message, team: current_team, simple_format: false) %>
</div>
<div class="comment-footer">
<div class="comment-create-date">
<%= I18n.l(comment.created_at, format: :full_date) %>
</div>
<% if comment_editable?(comment) %>
<div class="dropdown comment-menu-container">
<div id="comment-dropdown-menu-<%= comment.id %>" class="fas fa-ellipsis-h open-dropdown-btn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"></div>
<ul class="dropdown-menu comment-dropdown-menu dropdown-menu-right" aria-labelledby="comment-dropdown-menu-<%= comment.id %>">
<li>
<a href="#" class="edit-comment" data-comment-raw="<%= comment.message %>" data-update-url="<%= comment_path(comment) %>" data-comment-id="<%= comment.id %>">
<i class="fas fa-pen"></i>
Edit
</a>
</li>
<li>
<a href="#" data-delete-url="<%= comment_path(comment) %>" class="delete-comment">
<i class="fas fa-trash"></i>
Delete
</a>
</li>
</ul>
</div>
<% end %>
</div>
</div>
</div>

View file

@ -23,6 +23,10 @@
<textarea class="comment-input-field smart-text-area textarea-sm"></textarea>
<i class="fas fa-paper-plane send-comment"></i>
</div>
<div class="update-buttons sci-btn-group">
<button class="btn btn-secondary cancel-button">Cancel</button>
<button class="btn btn-primary update-comment">Save changes</button>
</div>
</div>
</div>
</div>

View file

@ -508,7 +508,7 @@ Rails.application.routes.draw do
end
end
resources :comments, only: %i(index create update delete)
resources :comments, only: %i(index create update destroy)
resources :repositories do
post 'repository_index',