diff --git a/app/assets/javascripts/initializers/initializeFetchFollowedArticles.js.erb b/app/assets/javascripts/initializers/initializeFetchFollowedArticles.js.erb index e8b6927a2..40a8051ce 100644 --- a/app/assets/javascripts/initializers/initializeFetchFollowedArticles.js.erb +++ b/app/assets/javascripts/initializers/initializeFetchFollowedArticles.js.erb @@ -31,7 +31,14 @@ function insertNewArticles(user){ var articlePoints = 0 var containsUserID = findOne([article.user_id], user.followed_user_ids || []) var containsOrganizationID = findOne([article.organization_id], user.followed_organization_ids || []) - articlePoints = articlePoints + (intersect_arrays(user.followed_tag_names, article.cached_tag_list_array).length*2) + article.positive_reactions_count + var intersectedTags = intersect_arrays(user.followed_tag_names, article.cached_tag_list_array) + var followedPoints = 1 + JSON.parse(user.followed_tags).map(function(tag) { + if (intersectedTags.includes(tag.name)) { + followedPoints = followedPoints + tag.points + } + }) + articlePoints = articlePoints + (followedPoints*2) + article.positive_reactions_count if (containsUserID || article.user_id === user.id) { articlePoints = articlePoints + 16 } @@ -61,7 +68,14 @@ function insertTopArticles(user){ var articlePoints = 0 var containsUserID = findOne([article.user_id], user.followed_user_ids || []) var containsOrganizationID = findOne([article.organization_id], user.followed_organization_ids || []) - articlePoints = articlePoints + intersect_arrays(user.followed_tag_names, article.cached_tag_list_array).length + var intersectedTags = intersect_arrays(user.followed_tag_names, article.cached_tag_list_array) + var followedPoints = 1 + JSON.parse(user.followed_tags).map(function(tag) { + if (intersectedTags.includes(tag.name)) { + followedPoints = followedPoints + tag.points + } + }) + articlePoints = articlePoints + followedPoints if (containsUserID) { articlePoints = articlePoints + 1 } diff --git a/app/assets/stylesheets/dashboard.scss b/app/assets/stylesheets/dashboard.scss index ae329bb66..912ccd846 100644 --- a/app/assets/stylesheets/dashboard.scss +++ b/app/assets/stylesheets/dashboard.scss @@ -164,7 +164,7 @@ } h2{ font-weight:500; - margin-bottom:5px; + margin-bottom:10px; margin-top:8px; font-size:22px; @media screen and ( min-width: 800px ) { @@ -182,6 +182,30 @@ font-size: 0.6em; vertical-align:0.16em; } + form { + background: $light-gray; + padding: 8px 15px; + margin-bottom: 10px; + border-radius: 3px; + @media screen and ( min-width: 750px ) { + display: inline-block; + float: right; + } + input { + font-size: 16px; + border-radius: 3px; + padding: 3px 8px; + width: 50px; + border: 1px solid $black; + font-weight: bold; + &[type="submit"] { + width: auto; + background: $green; + font-family: $helvetica-condensed; + margin-left: 5px; + } + } + } } h4{ margin-top:0px; diff --git a/app/controllers/async_info_controller.rb b/app/controllers/async_info_controller.rb index 8a14d3caf..b749cea2a 100644 --- a/app/controllers/async_info_controller.rb +++ b/app/controllers/async_info_controller.rb @@ -38,7 +38,7 @@ class AsyncInfoController < ApplicationController username: @user.username, profile_image_90: ProfileImage.new(@user).get(90), followed_tag_names: @user.cached_followed_tag_names, - followed_tags: @user.cached_followed_tags.to_json(only: %i[id name bg_color_hex text_color_hex]), + followed_tags: @user.cached_followed_tags.to_json(only: %i[id name bg_color_hex text_color_hex], methods: [:points]), followed_user_ids: @user.cached_following_users_ids, followed_organization_ids: @user.cached_following_organizations_ids, reading_list_ids: ReadingList.new(@user).cached_ids_of_articles, diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb index 92c470239..34237a93a 100644 --- a/app/controllers/dashboards_controller.rb +++ b/app/controllers/dashboards_controller.rb @@ -10,9 +10,11 @@ class DashboardsController < ApplicationController current_user end authorize (@user || User), :dashboard_show? - if params[:which] == "following_users" + if params[:which] == "following" || params[:which] == "following_users" @follows = @user.follows_by_type("User"). order("created_at DESC").includes(:followable).limit(80) + @followed_tags = @user.follows_by_type("ActsAsTaggableOn::Tag"). + order("points DESC").includes(:followable).limit(80) elsif params[:which] == "user_followers" @follows = Follow.where(followable_id: @user.id, followable_type: "User"). includes(:follower).order("created_at DESC").limit(80) diff --git a/app/controllers/follows_controller.rb b/app/controllers/follows_controller.rb index 255837509..c18df0503 100644 --- a/app/controllers/follows_controller.rb +++ b/app/controllers/follows_controller.rb @@ -37,4 +37,18 @@ class FollowsController < ApplicationController current_user.touch render json: { outcome: @result } end + + def update + @follow = Follow.find(params[:id]) + authorize @follow + if @follow.update(follow_params) + redirect_to "/dashboard/following" + end + end + + private + + def follow_params + params.require(:follow).permit(policy(Follow).permitted_attributes) + end end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 3510d1c1b..3d5f6f197 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -107,7 +107,7 @@ class StoriesController < ApplicationController def handle_base_index @home_page = true @page = (params[:page] || 1).to_i - num_articles = 15 + num_articles = 25 @stories = article_finder(num_articles) add_param_context(:page, :timeframe) diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index 7bad166c2..39e488faa 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -2,8 +2,14 @@ class UserDecorator < ApplicationDecorator delegate_all def cached_followed_tags - Rails.cache.fetch("user-#{id}-#{updated_at}/followed_tags", expires_in: 20.hours) do - Tag.where(id: Follow.where(follower_id: id, followable_type: "ActsAsTaggableOn::Tag").pluck(:followable_id)).order("hotness_score DESC") + Rails.cache.fetch("user-#{id}-#{updated_at}/followed_tags_11-30", expires_in: 20.hours) do + follows_query = Follow.where(follower_id: id, followable_type: "ActsAsTaggableOn::Tag").pluck(:followable_id, :points) + tags = Tag.where(id: follows_query.map { |f| f[0] }).order("hotness_score DESC") + tags.each do |t| + follow_query_item = follows_query.detect{|f| f[0] == t.id} + t.points = follow_query_item[1] + end + tags end end diff --git a/app/models/tag.rb b/app/models/tag.rb index 2a4b84241..53cc4e855 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -1,4 +1,7 @@ class Tag < ActsAsTaggableOn::Tag + + attr_accessor :points + include AlgoliaSearch acts_as_followable resourcify diff --git a/app/policies/follow_policy.rb b/app/policies/follow_policy.rb index b86c86f91..4b1256f14 100644 --- a/app/policies/follow_policy.rb +++ b/app/policies/follow_policy.rb @@ -2,4 +2,18 @@ class FollowPolicy < ApplicationPolicy def create? !user_is_banned? end + + def update? + user_is_follower? + end + + def permitted_attributes + %i[points] + end + + private + + def user_is_follower? + record.follower_id == user.id && record.follower_type == "User" + end end diff --git a/app/views/dashboards/show.html.erb b/app/views/dashboards/show.html.erb index cb5631d38..ac426ad07 100644 --- a/app/views/dashboards/show.html.erb +++ b/app/views/dashboards/show.html.erb @@ -10,9 +10,9 @@ FOLLOWERS (<%= @user.followers_count %>) - " href="/dashboard/following_users"> + " href="/dashboard/following"> FOLLOWING - (<%= @user.following_users_count %>) + (<%= @user.following_users_count + @user.following_tags_count %>) <% if @user.org_admin && @user.organization && (params[:which].blank? || params[:which] == "organization") %> @@ -32,7 +32,31 @@ <%= render "dashboard_article", article: article, org_admin: true %> <% end %> - <% elsif params[:which] == "user_followers" || params[:which] == "following_users" || params[:which] == "organization_user_followers" %> + <% elsif params[:which] == "user_followers" || params[:which] == "following_users" || params[:which] == "following" || params[:which] == "organization_user_followers" %> + <% if @followed_tags %> +

Followed tags (<%= @user.following_tags_count %>)

+

Adjust Follow Weight to make a tag show up less or more in your feed (default 1.0)

+ <% @followed_tags.each do |follow| %> + <% tag = follow.followable %> + <% if tag %> + <% color = HexComparer.new([tag.bg_color_hex || "#0000000",tag.text_color_hex || "#ffffff"]).brightness(0.8) %> +
+

+ + <%= tag.name %> + + <%= form_for(follow) do |f| %> + <%= f.label(:points, "Follow Weight:") %> + <%= f.text_field(:points) %> + <%= f.submit "Submit" %> + <% end %> +

+ +
+ <% end %> + <% end %> +

Followed users (<%= @user.following_users_count %>)

+ <% end %> <% @follows.each do |follow| %> <% user = params[:which].include?("followers") ? follow.follower : follow.followable %> <% if user %> diff --git a/config/routes.rb b/config/routes.rb index 270cab818..d13110378 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -102,7 +102,7 @@ Rails.application.routes.draw do get "/reports/:slug", to: "feedback_messages#show" resources :organizations, only: %i[update create] resources :followed_articles, only: [:index] - resources :follows, only: %i[show create] + resources :follows, only: %i[show create update] resources :giveaways, only: %i[create update] resources :image_uploads, only: [:create] resources :blocks @@ -238,7 +238,7 @@ Rails.application.routes.draw do get "/dashboard" => "dashboards#show" get "/dashboard/:which" => "dashboards#show", constraints: { - which: /organization|organization_user_followers|user_followers|following_users|reading/ + which: /organization|organization_user_followers|user_followers|following_users|following|reading/ } get "/dashboard/:username" => "dashboards#show" diff --git a/db/migrate/20181129222416_add_points_to_follows.rb b/db/migrate/20181129222416_add_points_to_follows.rb new file mode 100644 index 000000000..b20631f9c --- /dev/null +++ b/db/migrate/20181129222416_add_points_to_follows.rb @@ -0,0 +1,5 @@ +class AddPointsToFollows < ActiveRecord::Migration[5.1] + def change + add_column :follows, :points, :float, default: 1.0 + end +end diff --git a/db/schema.rb b/db/schema.rb index e9a314e28..b45953acb 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: 20181127173004) do +ActiveRecord::Schema.define(version: 20181129222416) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -323,6 +323,7 @@ ActiveRecord::Schema.define(version: 20181127173004) do t.string "followable_type", null: false t.integer "follower_id", null: false t.string "follower_type", null: false + t.float "points", default: 1.0 t.datetime "updated_at" t.index ["followable_id", "followable_type"], name: "fk_followables" t.index ["follower_id", "follower_type"], name: "fk_follows" @@ -440,7 +441,7 @@ ActiveRecord::Schema.define(version: 20181127173004) do t.index ["chat_channel_id"], name: "index_messages_on_chat_channel_id" t.index ["user_id"], name: "index_messages_on_user_id" end - + create_table "notes", id: :serial, force: :cascade do |t| t.integer "author_id" t.text "content" diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index cfa5ad2c6..8ec7506ce 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -351,6 +351,39 @@ RSpec.describe User, type: :model do end end + describe "#cached_followed_tags" do + let(:tag1) { create(:tag) } + let(:tag2) { create(:tag) } + let(:tag3) { create(:tag) } + let(:tag4) { create(:tag) } + it "returns empty if no tags followed" do + expect(user.decorate.cached_followed_tags.size).to eq(0) + end + + it "returns array of tags if user follows them" do + user.follow(tag1) + user.follow(tag2) + user.follow(tag3) + expect(user.decorate.cached_followed_tags.size).to eq(3) + end + + it "returns tag object with name" do + user.follow(tag1) + expect(user.decorate.cached_followed_tags.first.name).to eq(tag1.name) + end + + it "returns follow points for tag" do + user.follow(tag1) + expect(user.decorate.cached_followed_tags.first.points).to eq(1.0) + end + + it "returns adjusted points for tag" do + user.follow(tag1) + Follow.last.update(points: 0.1) + expect(user.decorate.cached_followed_tags.first.points).to eq(0.1) + end + end + it "inserts into mailchimp" do expect(user.subscribe_to_mailchimp_newsletter_without_delay).to eq true end diff --git a/spec/requests/dashboard_spec.rb b/spec/requests/dashboard_spec.rb index 8ae1ce25b..8cc146bf7 100644 --- a/spec/requests/dashboard_spec.rb +++ b/spec/requests/dashboard_spec.rb @@ -70,6 +70,14 @@ RSpec.describe "Dashboards", type: :request do get "/dashboard/following_users" expect(response.body).to include CGI.escapeHTML(second_user.name) end + it "renders the current user's tag followings" do + user.follow second_user + tag = create(:tag) + user.follow tag + login_as user + get "/dashboard/following" + expect(response.body).to include CGI.escapeHTML(tag.name) + end end end diff --git a/spec/requests/follows_update_spec.rb b/spec/requests/follows_update_spec.rb new file mode 100644 index 000000000..369847f41 --- /dev/null +++ b/spec/requests/follows_update_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +RSpec.describe "Following/Unfollowing", type: :request do + let(:user) { create(:user) } + let(:user_2) { create(:user) } + let(:tag) { create(:tag) } + + before do + login_as user + end + + describe "PUT follows/:id" do + it "updates user to offer mentorship" do + user.follow(tag) + put "/follows/#{Follow.last.id}", + params: { follow: { points: 3.0 } } + expect(Follow.last.points).to eq(3.0) + end + + it "does not update if follow does not belong to user" do + user_2.follow(tag) + expect do + put "/follows/#{Follow.last.id}", + params: { follow: { points: 3.0 } } + end.to raise_error(Pundit::NotAuthorizedError) + end + end +end