diff --git a/app/assets/javascripts/report-comment.js b/app/assets/javascripts/report-comment.js new file mode 100644 index 00000000..87195800 --- /dev/null +++ b/app/assets/javascripts/report-comment.js @@ -0,0 +1,9 @@ +/* + * When comment is submitted successfully + * it clears out the text field + */ +$(function() { + $('#new_comment').on('ajax:success', function(a, b,c ) { + $(this).find('#comment_body').val(''); + }); +}); diff --git a/app/assets/stylesheets/backstage.css b/app/assets/stylesheets/backstage.css index 4a97b3f2..35552337 100644 --- a/app/assets/stylesheets/backstage.css +++ b/app/assets/stylesheets/backstage.css @@ -17,7 +17,7 @@ border: solid 5px var(--black-color); -moz-box-sizing: border-box; -webkit-box-sizing: border-box; - box-sizing: border-box; + box-sizing: border-box; } #backstage-container .panel-title { @@ -74,7 +74,7 @@ #backstage-container .panel-menu a:focus { cursor: pointer; - background-color: var(--bright-blue-color); + background-color: var(--bright-blue-color); outline: none; } @@ -92,7 +92,7 @@ #backstage-container .panel-filters a.selected { font-weight: 700; - color: var(--light-black-color); + color: var(--light-black-color); } #backstage-container .panel-filters a:hover { @@ -119,12 +119,21 @@ #backstage-container .record-row:hover { cursor: pointer; - background: var(--orange-overlay); + background: var(--orange-overlay); } #backstage-container .record-row:active { cursor: pointer; - background: var(--blue-overlay); + background: var(--blue-overlay); +} + +#backstage-container .record-row .icons-space { + width: 4.5em; +} + +#backstage-container .record-row .icons-space .icon { + font-family: var(--icon-font); + margin-right: 0.3em; } #backstage-container .record-row .row-title { @@ -160,12 +169,12 @@ width: auto; padding: 2em 2em 2em 2em; } - + #backstage-container .control-panel { min-width: 16em; padding: 2em 2em 2em 2em; } - + #backstage-container .panel-descriptor { text-align: left; } diff --git a/app/assets/stylesheets/comments.css b/app/assets/stylesheets/comments.css new file mode 100644 index 00000000..afad32db --- /dev/null +++ b/app/assets/stylesheets/comments.css @@ -0,0 +1,4 @@ +/* + Place all the styles related to the matching controller here. + They will automatically be included in application.css. +*/ diff --git a/app/assets/stylesheets/reports.css b/app/assets/stylesheets/reports.css index 182a1cf4..8d080981 100644 --- a/app/assets/stylesheets/reports.css +++ b/app/assets/stylesheets/reports.css @@ -54,6 +54,71 @@ font-size: 0.9em; } +#backstage-container .comment-row { + padding: 0.3em 1.25em 0.3em 0em; + font-size: 0.9em; + line-height: 1.75em; +} + +#backstage-container .comment-row img.comment-avatar{ + min-width: 24px; + min-height: 24px; + border-radius: 12px; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; +} + +#backstage-container .comment-row .comment-avatar-container { + margin-right: 0.5em; + margin-top: 0.4em; +} + +#backstage-container .comment-row .row-name { + margin-right: 1em; + font-weight: 500; +} + +#backstage-container .comment-row .row-date { + margin-right: 1em; + font-size: 0.8em; + color: var(--gray-color); +} + +#backstage-container .comment-row .row-detail { + line-height: 1.5em; +} + +#backstage-container .comment-field { + margin-top: 0.5em; +} + +#backstage-container .comment-field #comment_body{ + width: 80%; + height: 3em; +} + +#backstage-container .comment-field .send-button, +#backstage-container .comment-field .send-button:hover { + font-family: var(--solid-icon-font); + font-size: 1.8em; + border: none; + color: var(--bright-green-color); + background-color: inherit; + padding: 0.2em; + border-radius: 20%; + margin-left: 0.3em; +} + +#backstage-container .comment-field .send-button:hover { + cursor: pointer; + background-color: var(--dark-white-color); +} + +#backstage-container .button.send-button { + font-family: var(--solid-icon-font); + color: pink; +} + #backstage-container .report-buttons { margin-top: 2em; } @@ -99,7 +164,7 @@ #backstage-container .report-buttons .button:focus { cursor: pointer; - background-color: var(--bright-blue-color); + background-color: var(--bright-blue-color); outline: none; } @@ -118,7 +183,7 @@ margin-left: 0em; margin-top: 2em; } - + #backstage-container .report-buttons a.stealth { margin-left: auto; margin-right: auto; diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 00000000..c724c51a --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,20 @@ +class CommentsController < ApplicationController + + def create + @comment = Comment.new(comment_params) + @comment.commenter = current_user + respond_to do |format| + if @comment.save + format.js + else + #TODO if not saved give alert? + end + end + end + + private + + def comment_params + params.require(:comment).permit(:body, :report_id) + end +end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 396dcacb..14e89406 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,13 +1,13 @@ class ReportsController < ApplicationController - + layout "backstage", only: [ :index, :show ] - + before_action :authenticate_user!, only: [ :index, :show, :dismiss, :undismiss ] before_action :ensure_staff, only: [ :index, :show, :dismiss, :undismiss ] before_action :find_report, only: [ :show, :dismiss, :undismiss ] before_action :find_reported_twitch_user, only: [ :show ] around_action :display_timezone - + def index # f is used to filter reports by scope if params[:f].present? && Report::AVAILABLE_SCOPES.key?(params[:f].to_sym) @@ -19,7 +19,7 @@ def index @filter_category = "unresolved" end end - + def show # Create keybot advice message if @reported_twitch_user == nil @@ -29,30 +29,32 @@ def show else @message = "The reported Twitch user did not sign the pledge." end - + + @comment = Comment.new(report: @report) + @comments = @report.comments.includes(:commenter) # TODO: check if reporter has pledged (lookup email/Twitch name) and add info to keybot message # TODO: check if incident stream owner has pledged (Twitch name) and add info to keybot message end - + def new @report = Report.new end def create @report = Report.new(report_params) - + if @report.save # Email notification to staff StaffMailer.notify_staff_new_report(@report).deliver_now - + flash[:notice] = "You've successfully submitted the report. Thank you." redirect_to root_path - else + else flash.now[:alert] ||= "" @report.errors.full_messages.each do |message| flash.now[:alert] << message + ". " - end + end render(action: :new) end end @@ -65,14 +67,14 @@ def dismiss end redirect_to reports_path end - + def undismiss @report.dismissed = false @report.reviewer = nil if @report.save flash[:notice] = "You undismissed the report about #{@report.reported_twitch_name}. It can now be reviewed again." redirect_to report_path(@report) - else + else redirect_to reports_path end end @@ -83,11 +85,11 @@ def find_report rescue ActiveRecord::RecordNotFound redirect_to staff_index_path end - + def find_reported_twitch_user # Check if reported_twitch_name exists on Twitch response = HTTParty.get(URI.escape("#{ENV['TWITCH_API_BASE_URL']}/users?login=#{@report.reported_twitch_name}"), headers: {"Client-ID": ENV['TWITCH_CLIENT_ID'], "Authorization": "Bearer #{TwitchToken.first.valid_token!}"}) - + if response["data"].blank? @reported_twitch_user = nil else @@ -95,20 +97,20 @@ def find_reported_twitch_user end end - private + private def ensure_staff unless current_user.is_moderator? || current_user.is_admin? redirect_to root_url end end - + def display_timezone timezone = Time.find_zone( cookies[:browser_timezone] ) Time.use_zone(timezone) { yield } end - + def report_params params.require(:report).permit(:reporter_email, :reporter_twitch_name, :reported_twitch_name, :incident_stream, :incident_occurred, :incident_description, :recommended_response, :image) end - + end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 00000000..0ec9ca5f --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,2 @@ +module CommentsHelper +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 00000000..7e02768c --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,11 @@ +class Comment < ApplicationRecord + validates_presence_of :report_id, + :commenter_id, + :body + + belongs_to :commenter, class_name: :User, foreign_key: :commenter_id + belongs_to :report + belongs_to :parent_comment, class_name: :Comment, foreign_key: :parent_comment_id, optional: true + has_many :replies, class_name: :Comment, foreign_key: :parent_comment_id + +end diff --git a/app/models/report.rb b/app/models/report.rb index a36a0acb..590f8420 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -1,5 +1,5 @@ class Report < ApplicationRecord - + AVAILABLE_SCOPES = { unresolved: "Unresolved", dismissed: "Dismissed", @@ -7,12 +7,12 @@ class Report < ApplicationRecord revoked: "Revoked", all: "All" }.freeze - + IMAGE_STYLES = { thumb: { resize: "120x120" }, preview: { resize: "240x240" } }.freeze - + validates_presence_of :reported_twitch_name, :incident_stream, :incident_description, @@ -24,22 +24,23 @@ class Report < ApplicationRecord validates :incident_description, length: { maximum: 1000 } - + validates :recommended_response, length: { maximum: 500 } validate :ensure_sane_review belongs_to :reviewer, class_name: :User, foreign_key: :reviewer_id, optional: true - + has_many :comments, class_name: :Comment, foreign_key: :report_id + image_accessor :image - + scope :dismissed, lambda { where("#{table_name}.dismissed IS TRUE") } scope :warned, lambda { where("#{table_name}.warned IS TRUE") } scope :revoked, lambda { where("#{table_name}.revoked IS TRUE") } scope :unresolved, lambda { where("#{table_name}.dismissed IS FALSE AND #{table_name}.warned IS FALSE AND #{table_name}.revoked IS FALSE") } - - + + def image_url(style = :thumb) if style == :original self.image.remote_url @@ -47,7 +48,7 @@ def image_url(style = :thumb) process_image(style).url end end - + protected def ensure_sane_review if (self.dismissed || self.warned || self.revoked) && !(self.dismissed ^ self.warned ^ self.revoked) @@ -62,10 +63,10 @@ def ensure_sane_review end end end - + private - def process_image(style) + def process_image(style) self.image.process(:auto_orient).thumb(Report::IMAGE_STYLES[style][:resize]) end - + end diff --git a/app/views/comments/create.js.erb b/app/views/comments/create.js.erb new file mode 100644 index 00000000..11094d37 --- /dev/null +++ b/app/views/comments/create.js.erb @@ -0,0 +1,6 @@ +/* + * When new comment is created + * it gets added to the view + */ + +$('#comment-view').append("<%= j(render 'reports/comment', comment: @comment) %>"); diff --git a/app/views/reports/_comment.html.erb b/app/views/reports/_comment.html.erb new file mode 100644 index 00000000..288fe531 --- /dev/null +++ b/app/views/reports/_comment.html.erb @@ -0,0 +1,13 @@ +
+
+
+ <%= image_tag(user_image_src(comment.commenter, :tiny), size: "24x24", class: "comment-avatar flexbox center") %>
+
+
+
<%= comment.commenter.first_name %>
+
<%= l(comment.created_at, format: "%b. %-d, %Y · %-l:%M%P ") %>
+
+
<%= comment.body %>
+
+
+
diff --git a/app/views/reports/_row.html.erb b/app/views/reports/_row.html.erb index 7e1935f7..6a8a41be 100644 --- a/app/views/reports/_row.html.erb +++ b/app/views/reports/_row.html.erb @@ -1,6 +1,13 @@
flexbox vertical stretch" data-url="<%= report_path(report, back: request.fullpath) %>">
-
<%= report.reported_twitch_name %>
+
+
+ <% if report.comments.present? %> + + <% end %> +
+
<%= report.reported_twitch_name %>
+
<%= l(report.created_at, format: "%b. %-d, %Y · %-l:%M%P ") %>
-
\ No newline at end of file + diff --git a/app/views/reports/show.html.erb b/app/views/reports/show.html.erb index b8e6e098..a8122d63 100644 --- a/app/views/reports/show.html.erb +++ b/app/views/reports/show.html.erb @@ -11,7 +11,20 @@ <%= render(partial: "shared/report_body") %>

- Keybot says: <%= @message %> +
+
Comments by Mods
+
+
<%= render(partial: "comment", collection: @comments, as: :comment) %>
+ <%= form_with model: @comment, id: "new_comment", html: { autocomplete: "off" } do |form| %> +
+ <%= form.text_area :body, placeholder: "Leave comments for other moderators" %> + <%= form.hidden_field :report_id, value: @report.id %> + <%= form.submit class: "send-button", data: { disable_with: false }, value: "".html_safe %> +
+ <% end %> +
+
+
Keybot says: <%= @message %>
<% if @report.dismissed %> @@ -19,7 +32,7 @@ <% elsif @report.warned %>
Warned
<% elsif @report.revoked %> -
Revoked
+
Revoked
<% else %> <% if @pledge %> <%= link_to("Revoke", new_report_revocation_path(@report, back: request.fullpath), class: 'revoke-button button') %> diff --git a/config/routes.rb b/config/routes.rb index 92887d6b..58a0af85 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,18 +1,18 @@ Rails.application.routes.draw do - + devise_for :users, controllers: { invitations: "users/invitations" }, path_names: { sign_in: "login", sign_out: "logout" } - + scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do - + authenticated :user do root to: "home#index", as: :authenticated_user_root end - + unauthenticated do root to: "home#index" end - + resources :pledges, only: [ :index, :create, :show ] get '/pledge', to: 'pledges#new', as: :new_pledge get '/take-the-pledge', to: 'pledges#new' @@ -22,8 +22,8 @@ resources :affiliates, only: [ :index, :new, :create, :edit, :update ] resources :resources, only: [ :index ] get '/research', to: 'resources#index', as: :research - get '/keystone-code', to: 'resources#keystone_code', as: :keystone_code - get '/inclusion-101', to: 'resources#inclusion_101', as: :inclusion_101 + get '/keystone-code', to: 'resources#keystone_code', as: :keystone_code + get '/inclusion-101', to: 'resources#inclusion_101', as: :inclusion_101 resources :stories, only: [ :index, :new, :create, :edit, :update ] get '/changemakers', to: 'stories#changemakers', as: :changemakers resources :reports, only: [ :index, :show, :new, :create ] do @@ -39,7 +39,7 @@ resources :staff, only: [ :index ] resources :users, only: [ :index, :show, :edit, :update ] post '/users/:id/remove_avatar', to: 'users#remove_avatar', as: :remove_avatar - + get '/about', to: 'about#index', as: :about get '/contact', to: 'about#contact', as: :contact get '/data-policy', to: 'about#data_policy', as: :data_policy @@ -48,6 +48,8 @@ get '/donate', to: 'donate#index', as: :donate get '/donate/success', to: 'donate#success', as: :donate_success + resources :comments, only: [ :new, :create] + end - + end diff --git a/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb b/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb deleted file mode 100644 index 7715639f..00000000 --- a/db/migrate/20190904053235_change_desired_outcome_to_recommended_response.rb +++ /dev/null @@ -1,5 +0,0 @@ -class ChangeDesiredOutcomeToRecommendedResponse < ActiveRecord::Migration[6.0] - def change - rename_column :reports, :desired_outcome, :recommended_response - end -end diff --git a/db/migrate/20220630164320_create_comments.rb b/db/migrate/20220630164320_create_comments.rb new file mode 100644 index 00000000..043ce701 --- /dev/null +++ b/db/migrate/20220630164320_create_comments.rb @@ -0,0 +1,12 @@ +class CreateComments < ActiveRecord::Migration[6.0] + def change + create_table :comments do |t| + t.integer :report_id + t.integer :commenter_id + t.text :body + t.integer :parent_comment_id + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index baa309b8..04c5b34b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_02_23_190839) do +ActiveRecord::Schema.define(version: 2022_06_30_164320) do create_table "affiliates", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "name" @@ -29,6 +29,15 @@ t.string "mixer" end + create_table "comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci", force: :cascade do |t| + t.integer "report_id" + t.integer "commenter_id" + t.text "body" + t.integer "parent_comment_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "conduct_warnings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci", force: :cascade do |t| t.integer "pledge_id" t.integer "report_id" @@ -55,6 +64,7 @@ t.datetime "twitch_authed_on" t.integer "referrer_id" t.integer "referrals_count", default: 0 + t.integer "reports_count" end create_table "reports", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci", force: :cascade do |t| @@ -72,6 +82,7 @@ t.boolean "dismissed", default: false t.boolean "warned", default: false t.boolean "revoked", default: false + t.integer "twitch_id" t.index ["reviewer_id"], name: "index_reports_on_reviewer_id" end @@ -96,7 +107,7 @@ t.datetime "published_on" end - create_table "twitch_tokens", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "twitch_tokens", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci", force: :cascade do |t| t.string "access_token" t.integer "expires_in" t.datetime "created_at", precision: 6, null: false diff --git a/test/controllers/comments_controller_test.rb b/test/controllers/comments_controller_test.rb new file mode 100644 index 00000000..a812ddae --- /dev/null +++ b/test/controllers/comments_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class CommentsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/comments.yml b/test/fixtures/comments.yml new file mode 100644 index 00000000..39882706 --- /dev/null +++ b/test/fixtures/comments.yml @@ -0,0 +1,7 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + report_id: 1 + +two: + report_id: 1 diff --git a/test/models/comment_test.rb b/test/models/comment_test.rb new file mode 100644 index 00000000..b6d6131a --- /dev/null +++ b/test/models/comment_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class CommentTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end