[deploy] Add subscriptions to the dashboard (#9161)
* Add /dashboard/subscriptions route * Dashboard controller updates * Update ArticlePolicy with subscriptions? * Update article row view in dashboard * Add subscriptions view * Add specs * Add comment for pagination limit * Use cached user_subscriptions count * Add spec for cache counter * Add path helper
This commit is contained in:
parent
7bb194b305
commit
9f9236164c
10 changed files with 186 additions and 8 deletions
|
|
@ -2,6 +2,7 @@ class DashboardsController < ApplicationController
|
|||
before_action :set_no_cache_header
|
||||
before_action :authenticate_user!
|
||||
before_action :fetch_and_authorize_user, except: :pro
|
||||
before_action :set_source, only: %i[subscriptions]
|
||||
before_action -> { limit_per_page(default: 80, max: 1000) }, except: %i[show pro]
|
||||
after_action :verify_authorized
|
||||
|
||||
|
|
@ -70,8 +71,22 @@ class DashboardsController < ApplicationController
|
|||
@organizations = current_user.member_organizations
|
||||
end
|
||||
|
||||
def subscriptions
|
||||
authorize @source
|
||||
@subscriptions = @source.user_subscriptions.
|
||||
includes(:subscriber).order(created_at: :desc).page(params[:page]).per(100)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_source
|
||||
source_type = params[:source_type]
|
||||
not_found unless UserSubscription::ALLOWED_TYPES.include? source_type
|
||||
|
||||
source = source_type.constantize.find_by(id: params[:source_id])
|
||||
@source = source || not_found
|
||||
end
|
||||
|
||||
def fetch_and_authorize_user
|
||||
@user = if params[:username] && current_user.any_admin?
|
||||
User.find_by(username: params[:username])
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ class UserSubscription < ApplicationRecord
|
|||
ALLOWED_TYPES = %w[Article].freeze
|
||||
|
||||
counter_culture :subscriber, column_name: "subscribed_to_user_subscriptions_count"
|
||||
counter_culture :user_subscription_sourceable, column_name: "user_subscriptions_count"
|
||||
|
||||
belongs_to :author, class_name: "User", inverse_of: :source_authored_user_subscriptions
|
||||
belongs_to :subscriber, class_name: "User", inverse_of: :subscribed_to_user_subscriptions
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ class ArticlePolicy < ApplicationPolicy
|
|||
archived]
|
||||
end
|
||||
|
||||
def subscriptions?
|
||||
user_is_author? || user_admin?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_is_author?
|
||||
|
|
|
|||
|
|
@ -58,11 +58,11 @@
|
|||
<% end %>
|
||||
</span>
|
||||
|
||||
<% if false %>
|
||||
<span class="flex items-center p-1 ml-1" title="Data">
|
||||
<% if article.user_subscriptions_count > 0 %>
|
||||
<a href="<%= dashboard_subscriptions_path(source_type: "Article", source_id: article.id) %>" class="flex items-center crayons-btn crayons-btn--ghost crayons-btn--s fw-normal color-base-70" title="Subscriptions">
|
||||
<%= inline_svg_tag("small-data.svg", aria: true, class: "crayons-icon mr-1", title: "Data") %>
|
||||
4
|
||||
</span>
|
||||
<%= article.user_subscriptions_count %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -94,9 +94,12 @@
|
|||
<%= inline_svg_tag("overflow-horizontal.svg", aria: true, class: "crayons-icon", title: "More...") %>
|
||||
</button>
|
||||
|
||||
<div class="crayons-dropdown top-100 right-0 z-10 align-left js-dashboard-row-more-dropdown">
|
||||
<div class="crayons-dropdown top-100 right-0 z-10 align-left js-dashboard-row-more-dropdown p-1">
|
||||
<% if article.user_subscriptions_count > 0 %>
|
||||
<a href="<%= dashboard_subscriptions_path(source_type: "Article", source_id: article.id) %>" class="crayons-link crayons-link--block w-100"><%= "Subscriptions (#{article.user_subscriptions_count})" %></a>
|
||||
<% end %>
|
||||
<% if @current_user_pro %>
|
||||
<a href="<%= article.path %>/stats" class="crayons-link crayons-link--block w-100 border-0 bg-transparent">Stats</a>
|
||||
<a href="<%= article.path %>/stats" class="crayons-link crayons-link--block w-100">Stats</a>
|
||||
<% end %>
|
||||
<%= form_for(article, method: :patch, remote: true, authenticity_token: true, html: { "data-type": "json", class: "js-archive-toggle" }) do |f| %>
|
||||
<%= f.hidden_field :archived, value: !article.archived, id: "article_archived_#{article.id}" %>
|
||||
|
|
|
|||
39
app/views/dashboards/subscriptions.html.erb
Normal file
39
app/views/dashboards/subscriptions.html.erb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<% title "Subscriptions #{@source.title}" %>
|
||||
|
||||
<div class="crayons-layout crayons-layout--1-col">
|
||||
<header class="flex items-center justify-between p-2 m:p-0">
|
||||
<div>
|
||||
<h1 class="fs-2xl s:fs-3xl">Subscriptions</h1>
|
||||
<h2 class="fs-base s:fs-xl fw-medium">
|
||||
<em class="fs-italic fw-normal color-base-60">for</em>
|
||||
<a href=<%= @source.path %>><%= @source.title %></a>
|
||||
</h2>
|
||||
</div>
|
||||
<%#TODO: [@thepracticaldev/delightful]: uncomment this when ready to implement CSV exports%>
|
||||
<%#<button type="button" class="crayons-btn crayons-btn--secondary">Export CSV</button>%>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<% if @subscriptions.any? %>
|
||||
<table class="crayons-table w-100">
|
||||
<tbody class="crayons-card">
|
||||
<% @subscriptions.each do |subscription| %>
|
||||
<% subscriber = subscription.subscriber %>
|
||||
<tr>
|
||||
<td class="fw-medium"><a href=<%= user_profile_path(subscriber.username) %> <%= "#{subscriber.name} DEV profile" %>><%= subscriber.name %></a></td>
|
||||
<td><a href="<%= "mailto: #{subscription.subscriber_email}" %>"><%= subscription.subscriber_email %></a></td>
|
||||
<td class="fs-s color-base-60"><%= "#{time_ago_in_words(subscription.created_at)} ago" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="my-4 pl-3">
|
||||
<%= paginate @subscriptions, params: { i: nil } %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="p-9 crayons-card crayons-card--secondary align-center fs-l h-100 flex items-center justify-center">
|
||||
You don't have any subscribers for this <%= @source.class.name.downcase %> yet...
|
||||
</div>
|
||||
<% end %>
|
||||
</main>
|
||||
</div>
|
||||
|
|
@ -392,6 +392,7 @@ Rails.application.routes.draw do
|
|||
get "dashboard/following_users" => "dashboards#following_users"
|
||||
get "dashboard/following_organizations" => "dashboards#following_organizations"
|
||||
get "dashboard/following_podcasts" => "dashboards#following_podcasts"
|
||||
get "/dashboard/subscriptions" => "dashboards#subscriptions"
|
||||
get "/dashboard/:which" => "dashboards#followers", :constraints => { which: /user_followers/ }
|
||||
get "/dashboard/:which/:org_id" => "dashboards#show",
|
||||
:constraints => {
|
||||
|
|
@ -469,7 +470,7 @@ Rails.application.routes.draw do
|
|||
get "/:username/:slug" => "stories#show"
|
||||
get "/:sitemap" => "sitemaps#show",
|
||||
:constraints => { format: /xml/, sitemap: /sitemap-.+/ }
|
||||
get "/:username" => "stories#index"
|
||||
get "/:username" => "stories#index", :as => "user_profile"
|
||||
|
||||
root "stories#index"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
class AddUserSubscriptionsCountToArticles < ActiveRecord::Migration[6.0]
|
||||
def self.up
|
||||
add_column :articles, :user_subscriptions_count, :integer, null: false, default: 0
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :articles, :user_subscriptions_count
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2020_07_02_143618) do
|
||||
ActiveRecord::Schema.define(version: 2020_07_06_184804) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
|
@ -136,6 +136,7 @@ ActiveRecord::Schema.define(version: 2020_07_02_143618) do
|
|||
t.string "title"
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id"
|
||||
t.integer "user_subscriptions_count", default: 0, null: false
|
||||
t.string "video"
|
||||
t.string "video_closed_caption_track_url"
|
||||
t.string "video_code"
|
||||
|
|
|
|||
|
|
@ -39,4 +39,23 @@ RSpec.shared_examples "UserSubscriptionSourceable" do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "counter_culture" do
|
||||
context "when a UserSubscription is created" do
|
||||
it "increments user_subscriptions_count" do
|
||||
expect do
|
||||
create(:user_subscription, user_subscription_sourceable: source)
|
||||
end.to change { source.reload.user_subscriptions_count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a UserSubscription is destroyed" do
|
||||
it "decrements user_subscriptions_count" do
|
||||
user_subscription = create(:user_subscription, user_subscription_sourceable: source)
|
||||
expect do
|
||||
user_subscription.destroy
|
||||
end.to change { source.reload.user_subscriptions_count }.by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,6 +39,19 @@ RSpec.describe "Dashboards", type: :request do
|
|||
expect(response.body).to include "Delete"
|
||||
end
|
||||
|
||||
it "renders subscriptions for articles with subscriptions" do
|
||||
user.add_role(:admin) # TODO: (Alex Smith) - update roles before release
|
||||
article_with_user_subscription_tag = create(:article, user: user, with_user_subscription_tag: true)
|
||||
create(:user_subscription,
|
||||
subscriber_id: second_user.id,
|
||||
subscriber_email: second_user.email,
|
||||
author_id: article_with_user_subscription_tag.user_id,
|
||||
user_subscription_sourceable: article_with_user_subscription_tag)
|
||||
|
||||
get "/dashboard"
|
||||
expect(response.body).to include "Subscriptions"
|
||||
end
|
||||
|
||||
it "renders pagination if minimum amount of posts" do
|
||||
create_list(:article, 52, user: user)
|
||||
get "/dashboard"
|
||||
|
|
@ -300,4 +313,77 @@ RSpec.describe "Dashboards", type: :request do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: (Alex Smith) - update roles before release
|
||||
describe "GET /dashboard/subscriptions" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "renders subscriptions" do
|
||||
user.add_role(:admin)
|
||||
article_with_user_subscription_tag = create(:article, user: user, with_user_subscription_tag: true)
|
||||
user_subscription = create(:user_subscription,
|
||||
subscriber_id: second_user.id,
|
||||
subscriber_email: second_user.email,
|
||||
author_id: article_with_user_subscription_tag.user_id,
|
||||
user_subscription_sourceable: article_with_user_subscription_tag)
|
||||
|
||||
get "/dashboard/subscriptions", params: { source_type: article_with_user_subscription_tag.class.name, source_id: article_with_user_subscription_tag.id }
|
||||
expect(response.body).to include(user_subscription.subscriber_email)
|
||||
end
|
||||
|
||||
it "displays a message if no subscriptions are found" do
|
||||
get "/dashboard/subscriptions", params: { source_type: article.class.name, source_id: article.id }
|
||||
expect(response.body).to include("You don't have any subscribers for this")
|
||||
end
|
||||
|
||||
it "raises unauthorized when trying to access a source the user doesn't own" do
|
||||
user.add_role(:admin)
|
||||
article_with_user_subscription_tag = create(:article, :with_user_subscription_tag_role_user, with_user_subscription_tag: true)
|
||||
create(:user_subscription,
|
||||
subscriber_id: second_user.id,
|
||||
subscriber_email: second_user.email,
|
||||
author_id: article_with_user_subscription_tag.user_id,
|
||||
user_subscription_sourceable: article_with_user_subscription_tag)
|
||||
|
||||
expect do
|
||||
get "/dashboard/subscriptions", params: { source_type: article_with_user_subscription_tag.class.name, source_id: article_with_user_subscription_tag.id }
|
||||
end.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
|
||||
it "raises an error for disallowed source_types" do
|
||||
expect do
|
||||
get "/dashboard/subscriptions", params: { source_type: "Comment", source_id: 1 }
|
||||
end.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it "raises an error when the source can't be found" do
|
||||
expect do
|
||||
get "/dashboard/subscriptions", params: { source_type: article.class.name, source_id: article.id + 999 }
|
||||
end.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it "renders pagination if minimum amount of subscriptions" do
|
||||
user.add_role(:admin)
|
||||
article_with_user_subscription_tag = create(:article, user: user, with_user_subscription_tag: true)
|
||||
create_list(:user_subscription,
|
||||
102, # Current pagination limit is 100
|
||||
author: user,
|
||||
user_subscription_sourceable: article_with_user_subscription_tag)
|
||||
get "/dashboard/subscriptions", params: { source_type: article_with_user_subscription_tag.class.name, source_id: article_with_user_subscription_tag.id }
|
||||
expect(response.body).to include "pagination"
|
||||
end
|
||||
|
||||
it "does not render pagination if less than one full page" do
|
||||
user.add_role(:admin)
|
||||
article_with_user_subscription_tag = create(:article, user: user, with_user_subscription_tag: true)
|
||||
create_list(:user_subscription,
|
||||
5,
|
||||
author: user,
|
||||
user_subscription_sourceable: article_with_user_subscription_tag)
|
||||
get "/dashboard/subscriptions", params: { source_type: article_with_user_subscription_tag.class.name, source_id: article_with_user_subscription_tag.id }
|
||||
expect(response.body).not_to include "pagination"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue