Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 2018-12-05 #1416

Merged
merged 29 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
870f9a2
Don't recreate the same channel due to https errors. Closes #1348
yachtcaptain23 Nov 16, 2018
c0ba7c3
Initial commit
yachtcaptain23 Nov 19, 2018
d66883e
Revert "Don't recreate the same channel due to https errors. Closes #…
yachtcaptain23 Nov 20, 2018
d90358d
Change requirements for abandoned channels
yachtcaptain23 Nov 20, 2018
0dc3275
Adjust code readability for CleanAbandonedSiteChannelsJob
yachtcaptain23 Nov 20, 2018
2fd4e6d
Ensure site channel verification method is set when publishers visit …
nvonpentz Nov 28, 2018
9caca9f
Use memberships and organizations to designate a partner as part of an
yachtcaptain23 Nov 14, 2018
d4268ef
Updated tests and allows creation of Organizations and Memberships
yachtcaptain23 Nov 28, 2018
f7b6131
Include uniqueness to make sure double membership isn't included
yachtcaptain23 Nov 28, 2018
9036ad5
UI for organizations
yachtcaptain23 Nov 28, 2018
a137efb
Updated syntax in partners_controller
yachtcaptain23 Dec 1, 2018
61c6b2c
Removed unused changes from db/schema
yachtcaptain23 Dec 3, 2018
dc05d38
Merge pull request #1355 from yachtcaptain23/bugfix/verification_later
yachtcaptain23 Dec 4, 2018
be5247c
Merge pull request #1349 from yachtcaptain23/feature/user_groups
yachtcaptain23 Dec 4, 2018
a8dae85
Auto-select end-date for tomorrow to cover timezone issues
yachtcaptain23 Dec 4, 2018
02eccc1
Use UTC date rather than using
yachtcaptain23 Dec 4, 2018
b3c8009
Refresh payout report contents in the worker process (#1409)
nvonpentz Dec 4, 2018
c1d7510
Merge pull request #1407 from yachtcaptain23/uiux/promo_report_day
yachtcaptain23 Dec 5, 2018
7dccb26
Increase frequency referral stats sync (#1415)
nvonpentz Dec 5, 2018
436ce0c
Public API for verified channels and publisher counts. Closes #1413
yachtcaptain23 Dec 5, 2018
21f0b1c
Updated route
yachtcaptain23 Dec 6, 2018
5eaaaeb
Revert "Updated route"
yachtcaptain23 Dec 6, 2018
0de7205
Include site count
yachtcaptain23 Dec 6, 2018
dca8f10
Updated UI on admin dashboard to include total number of publishers b…
yachtcaptain23 Dec 6, 2018
8502b46
Rename to statistical_tools
yachtcaptain23 Dec 6, 2018
d0ca738
Revert "Rename to statistical_tools"
yachtcaptain23 Dec 6, 2018
32952cb
Use single line syntax for class methods
yachtcaptain23 Dec 6, 2018
b8297e6
Add HTML sanitization tags
yachtcaptain23 Dec 6, 2018
bdd7201
Merge pull request #1418 from yachtcaptain23/feature/verified_publish…
yachtcaptain23 Dec 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/controllers/admin/organizations_controller.rb
@@ -0,0 +1,11 @@
module Admin
class OrganizationsController < AdminController
def index
@organizations = Organization.paginate(page: params[:page])
end

def show
@organization = Organization.find_by(id: params[:id])
end
end
end
14 changes: 14 additions & 0 deletions app/controllers/admin/partners_controller.rb
Expand Up @@ -24,15 +24,21 @@ def new
def create
# Find any existing publishers so we don't create duplicate entries
@partner = partner
@organization = organization

if @partner.persisted? && (@partner.partner? || @partner.admin?)
flash.now[:alert] = "Email is already a partner"
render :new
elsif @organization.persisted?
flash.now[:alert] = "The organization '#{organization.name}' already exists. Please have a partner of the organization add the user you want or ask Engineering team for assistance"
render :new
else
# Ensure publisher gets the right role
@partner.role = Publisher::PARTNER
@partner.created_by = current_user
@partner.save
@organization.save
Membership.create(member: @partner, organization: @organization)
MailerServices::PartnerLoginLinkEmailer.new(partner: @partner).perform
redirect_to admin_publisher_path(@partner.id), flash: { notice: "Email sent" }
end
Expand All @@ -52,8 +58,16 @@ def partner
existing_publisher || Partner.new(email: email_params)
end

def organization
Organization.find_or_initialize_by(name: params[:organization_name])
end

def email_params
params.require(:email)
end

def organization_name
params.require(:organization_name)
end
end
end
5 changes: 5 additions & 0 deletions app/controllers/admin/payout_reports_controller.rb
Expand Up @@ -17,6 +17,11 @@ def download
type: :json
end

def refresh
UpdatePayoutReportContentsJob.perform_later(payout_report_ids: [params[:id]])
redirect_to admin_payout_reports_path, flash: { notice: "Refreshing report JSON. Please try downloading in a couple minutes." }
end

def create
EnqueuePublishersForPayoutJob.perform_later(final: params[:final].present?,
should_send_notifications: params[:should_send_notifications].present?)
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/api/v1/public/channels_controller.rb
Expand Up @@ -7,4 +7,8 @@ def channels
end
render(json: channels_json, status: 200)
end
end

def totals
render(json: Channel.statistical_totals, status: 200)
end
end
5 changes: 5 additions & 0 deletions app/controllers/api/v1/public/publishers_controller.rb
@@ -0,0 +1,5 @@
class Api::V1::Public::PublishersController < Api::V1::Public::BaseController
def totals
render(json: Publisher.statistical_totals, status: 200)
end
end
1 change: 1 addition & 0 deletions app/controllers/publishers_controller.rb
Expand Up @@ -443,6 +443,7 @@ def authenticate_via_token
return if publisher_id.blank? || token.blank?

publisher = Publisher.find(publisher_id)

publisher_created_through_youtube_auth = publisher_created_through_youtube_auth?(publisher)
if publisher_created_through_youtube_auth
session[:publisher_created_through_youtube_auth] = publisher_created_through_youtube_auth
Expand Down
19 changes: 12 additions & 7 deletions app/controllers/site_channels_controller.rb
Expand Up @@ -23,8 +23,13 @@ class SiteChannelsController < ApplicationController
verification_github
verification_wordpress
download_verification_file)
before_action :update_site_verification_method,
only: %i(verify)
before_action :update_site_verification_method,
only: %i(verification_dns_record
verification_public_file
verification_support_queue
verification_github
verification_wordpress)

before_action :require_publisher_email_not_verified_through_youtube_auth,
only: %i(create)

Expand Down Expand Up @@ -132,14 +137,14 @@ def require_https_enabled_site
end

def update_site_verification_method
case params[:verification_method]
when "dns_record"
case params[:action]
when "verification_dns_record"
current_channel.details.verification_method = "dns_record"
when "public_file"
when "verification_public_file"
current_channel.details.verification_method = "public_file"
when "github"
when "verification_github"
current_channel.details.verification_method = "github"
when "wordpress"
when "verification_wordpress"
current_channel.details.verification_method = "wordpress"
else
raise "unknown action"
Expand Down
12 changes: 5 additions & 7 deletions app/jobs/clean_abandoned_site_channels_job.rb
Expand Up @@ -6,15 +6,13 @@ def perform
# and the publisher's session has expired. This ensures the channel will not be visible to them again and can
# be safely deleted.

channel_details = SiteChannelDetails.abandoned
channels = Channel.not_visible_site_channels
n = 0
channel_details.each do |details|
raise unless details.verification_token.nil?

details.channel.destroy
channels.joins(:site_channel_details).each do |channel|
raise if channel.details.verification_method.present?
channel.destroy
n = n + 1

Rails.logger.info("Cleaned abandoned site channel #{ details.brave_publisher_id } for #{ details.channel.publisher_id }.")
Rails.logger.info("Cleaned abandoned site channel #{ channel.details.brave_publisher_id } for publisher #{ channel.publisher_id }.")
end
Rails.logger.info("CleanAbandonedSiteChannelsJob cleared #{n} abandoned site channels.")
end
Expand Down
15 changes: 15 additions & 0 deletions app/jobs/update_payout_report_contents_job.rb
@@ -0,0 +1,15 @@
class UpdatePayoutReportContentsJob < ApplicationJob
queue_as :scheduler

def perform(payout_report_ids: [])
if payout_report_ids.present?
payout_reports = PayoutReport.where(id: payout_report_ids)
else
payout_reports = PayoutReport.all
end

payout_reports.each do |payout_report|
payout_report.update_report_contents
end
end
end
12 changes: 12 additions & 0 deletions app/models/channel.rb
Expand Up @@ -69,6 +69,9 @@ class Channel < ApplicationRecord
scope :visible_site_channels, -> {
site_channels.where('channels.verified = true or NOT site_channel_details.verification_method IS NULL')
}
scope :not_visible_site_channels, -> {
site_channels.where(verified: [false, nil]).where(site_channel_details: {verification_method: nil})
}
scope :visible_youtube_channels, -> {
youtube_channels.where.not('youtube_channel_details.youtube_channel_id': nil)
}
Expand Down Expand Up @@ -103,6 +106,15 @@ class Channel < ApplicationRecord
end
}

def self.statistical_totals
{
all_channels: Channel.verified.count,
twitch: Channel.verified.twitch_channels.count,
youtube: Channel.verified.youtube_channels.count,
site: Channel.verified.site_channels.count
}
end

def publication_title
details.publication_title
end
Expand Down
14 changes: 14 additions & 0 deletions app/models/membership.rb
@@ -0,0 +1,14 @@
class Membership < ActiveRecord::Base
belongs_to :organization, class_name: "Organization"

# Publisher model will be responsible for casting to the right sub-role
# E.g. casting from Publisher to Partner
belongs_to :member, class_name: "Publisher", foreign_key: :user_id

validates_presence_of :organization_id
validates_presence_of :user_id

# (Albert Wang) Honestly haven't thought this through as to whether or not an user could be part of different organizations.
# I think we might allow 1 org for advertising, 1 org for publishing.
validates_uniqueness_of :user_id, scope: :organization_id
end
4 changes: 4 additions & 0 deletions app/models/organization.rb
@@ -0,0 +1,4 @@
class Organization < ActiveRecord::Base
has_many :memberships
validates :name, presence: true, uniqueness: { case_sensitive: false }
end
1 change: 1 addition & 0 deletions app/models/partner.rb
@@ -1,6 +1,7 @@
class Partner < Publisher
default_scope { where(role: PARTNER) }
validates :created_by, presence: true
has_one :membership, dependent: :destroy, foreign_key: :user_id

after_initialize :ensure_role

Expand Down
4 changes: 2 additions & 2 deletions app/models/payout_report.rb
Expand Up @@ -32,8 +32,8 @@ def num_payments
def update_report_contents
# Do not update json contents for legacy reports
return if created_at <= LEGACY_PAYOUT_REPORT_TRANSITION_DATE
payout_report_json = JsonBuilders::PayoutReportJsonBuilder.new(payout_report: self).build
self.contents = payout_report_json.to_json
payout_report_hash = JsonBuilders::PayoutReportJsonBuilder.new(payout_report: self).build
self.contents = payout_report_hash.to_json
save!
end

Expand Down
14 changes: 11 additions & 3 deletions app/models/publisher.rb
Expand Up @@ -5,9 +5,9 @@ class Publisher < ApplicationRecord
UPHOLD_CODE_TIMEOUT = 5.minutes
UPHOLD_ACCESS_PARAMS_TIMEOUT = 2.hours
PROMO_STATS_UPDATE_DELAY = 10.minutes
ADMIN = "admin"
PARTNER = "partner"
PUBLISHER = "publisher"
ADMIN = "admin".freeze
PARTNER = "partner".freeze
PUBLISHER = "publisher".freeze
ROLES = [ADMIN, PARTNER, PUBLISHER]
JAVASCRIPT_DETECTED_RELEASE_TIME = "2018-06-19 22:51:51".freeze

Expand Down Expand Up @@ -116,6 +116,14 @@ class Publisher < ApplicationRecord
joins(:channels).where('channels.verified = true').distinct
}

def self.statistical_totals
{
email_verified_with_a_verified_channel: Publisher.where(role: Publisher::PUBLISHER).email_verified.joins(:channels).where(channels: { verified: true}).distinct(:id).count,
email_verified_with_a_channel: Publisher.where(role: Publisher::PUBLISHER).email_verified.joins(:channels).distinct(:id).count,
email_verified: Publisher.where(role: Publisher::PUBLISHER).email_verified.distinct(:id).count,
}
end

# API call to eyeshade
def wallet
return @_wallet if @_wallet
Expand Down
9 changes: 0 additions & 9 deletions app/models/site_channel_details.rb
Expand Up @@ -45,15 +45,6 @@ def validate_each(record, attribute, value)
.where("channels.updated_at": max_age.ago..Time.now)
}

# Channels with no verification_token will not be accessible once the user is no longer on the page, these
# are considered abandoned. If we wait a day we can be reasonable sure the users session will have timed out.
scope :abandoned, -> {
joins(:channel)
.where(verification_token: nil)
.where("channels.verified": [false, nil])
.where("site_channel_details.updated_at < :updated_at", updated_at: Time.now - 1.day)
}

def initialized?
brave_publisher_id.present? || brave_publisher_id_unnormalized.present?
end
Expand Down
25 changes: 22 additions & 3 deletions app/views/admin/dashboard/index.html.slim
@@ -1,6 +1,25 @@
. = "Contributions processed: #{PayoutReport.total_amount}"
. = "Piwik data (updated daily) last updated: #{Rails.cache.fetch(Cache::PiwikDataJob::PIWIK_CACHE_LAST_UPDATED)}"
. = "Global Alexa Rank: #{Rails.cache.fetch(Cache::PiwikDataJob::SEO_INFO).select{|d| d["label"] == "Alexa Rank" }[0]["rank"]}"
div.row
div.col-sm-12
section.panel
div.panel-body
div.adv-table
table.display.table.table-bordered.table-striped.dynamic-table id="dynamic-table"
tbody
tr
td Contributions processed
td = PayoutReport.total_amount
tr
td Piwik data (updated daily) last updated
td = Rails.cache.fetch(Cache::PiwikDataJob::PIWIK_CACHE_LAST_UPDATED)
tr
td publishers.basicattentiontoken.org Global Alexa Rank
td = Rails.cache.fetch(Cache::PiwikDataJob::SEO_INFO).select{|d| d["label"] == "Alexa Rank" }[0]["rank"]
tr
td Total # of verified publishers with channels
td = sanitize Publisher.statistical_totals.map { |scope, amount| "#{scope}=#{amount}"}.join("<br/>").html_safe
tr
td Totals for verified channels
td = sanitize Channel.statistical_totals.map { |channel_type, amount| "#{channel_type}=#{amount}"}.join("<br/>").html_safe

hr
- [Cache::PiwikDataJob::PIWIK_VISITS_SUMMARY, Cache::PiwikDataJob::PIWIK_EVENTS_CATEGORY, Cache::PiwikDataJob::PIWIK_DEVICES_DETECTION_TYPE, Cache::PiwikDataJob::PIWIK_DEVICES_DETECTION_BROWSER_VERSIONS].each do |statistic_key|
Expand Down
18 changes: 18 additions & 0 deletions app/views/admin/organizations/index.html.slim
@@ -0,0 +1,18 @@
div.row
div.col-sm-12
section.panel
header.panel-heading.organization-container
h4 Organizations
br
div.panel-body
div.adv-table
table.display.table.table-bordered.table-striped.dynamic-table id="dynamic-table"
tr
th ID
th Name
tbody
- @organizations.each do |organization|
tr.gradeX
td = link_to organization.id, admin_organization_path(organization.id)
td = organization.name
= will_paginate @organizations
17 changes: 17 additions & 0 deletions app/views/admin/organizations/show.html.slim
@@ -0,0 +1,17 @@
div.row
div.col-sm-12
section.panel
header.panel-heading.organization-container
h4 Organization: #{@organization.name}
br
div.panel-body
div.adv-table
table.display.table.table-bordered.table-striped.dynamic-table id="dynamic-table"
tr
th ID
th Email
tbody
- @organization.memberships.joins(:member).each do |membership|
tr.gradeX
td = link_to membership.user_id, admin_publisher_path(membership.user_id)
td = membership.member.email
2 changes: 2 additions & 0 deletions app/views/admin/partners/index.html.slim
Expand Up @@ -25,10 +25,12 @@ div.row
th ID
th Name
th Email
th Organization
tbody
- @partners.each do |partner|
tr.gradeX
td = link_to partner.id, admin_publisher_path(partner.id)
td = partner.name
td = partner.email
td = partner&.membership&.organization&.name
= will_paginate @partners
8 changes: 6 additions & 2 deletions app/views/admin/partners/new.html.slim
Expand Up @@ -3,7 +3,7 @@
.notifications
.alert.alert-warning.flash = msg

h1 Create a new Partner
h1 Create a new Partner and Organization

= form_with url: admin_partners_path, method: :post do |f|
.form-group
Expand All @@ -12,4 +12,8 @@ h1 Create a new Partner
class: "form-control",
placeholder: "Enter Partner email",
required: true
= submit_tag "Create Partner", class: "btn btn-block btn-primary"
= f.text_field :organization_name,
class: "form-control",
placeholder: "Enter Organization Name",
required: true
= submit_tag "Create Partner and Organization", class: "btn btn-block btn-primary"
5 changes: 4 additions & 1 deletion app/views/admin/payout_reports/index.html.slim
Expand Up @@ -9,6 +9,7 @@ table.display.table.table-bordered.table-striped
th # Payments
th Amount
th Fees
th Refresh JSON
th Download
tbody
- @payout_reports.each do |report|
Expand All @@ -19,7 +20,9 @@ table.display.table.table-bordered.table-striped
td = report.num_payments
td = "#{'%.2f' % (report.amount.to_d / 1E18)} BAT"
td = "#{'%.2f' % (report.fees.to_d / 1E18)} BAT"
td = link_to "download", download_admin_payout_report_path(report.id)
td = form_tag refresh_admin_payout_report_path(report.id), method: :patch do
= submit_tag "refresh", class: "btn btn-info"
td = link_to "download", download_admin_payout_report_path(report.id), class: "btn btn-primary"

table.display.table.table-bordered.table-striped.dynamic-table id="dynamic-table"
tr
Expand Down
3 changes: 3 additions & 0 deletions app/views/admin/shared/_sidebar.html.slim
Expand Up @@ -12,6 +12,9 @@ aside
li
= link_to admin_stats_referrals_path
span Top Publisher Referrals
li
= link_to admin_organizations_path
span Organizations
li
= link_to admin_partners_path
span Partners
Expand Down