[WIP] Improve article boosting abilities and insights (#237)
* Update email subjects split test * Create alternate editor * Add proof of concept to emails * Add utm for internal nav * Add ability to boost in DEV Digest * Adjust articles.scss * Adjust article_form.scss * Remove unnecessary files * Remove files from PR * Adjust boosted email settings * Conditionally include organization in additional box * Modify schema.rb
This commit is contained in:
parent
5a72d0ec56
commit
f2101410ea
21 changed files with 236 additions and 43 deletions
|
|
@ -24,7 +24,7 @@ module Api
|
|||
tag_list = if params[:tag_list].present?
|
||||
params[:tag_list].split(",")
|
||||
else
|
||||
["career","discuss","productivity"]
|
||||
["career", "discuss", "productivity"]
|
||||
end
|
||||
@articles = []
|
||||
4.times do
|
||||
|
|
|
|||
|
|
@ -69,6 +69,22 @@ class Internal::ArticlesController < Internal::ApplicationController
|
|||
page(params[:page]).
|
||||
per(100).
|
||||
limited_columns_internal_select
|
||||
when "boosted-additional-articles"
|
||||
@articles = Article.
|
||||
includes(:user).
|
||||
order("published_at DESC").
|
||||
boosted_via_additional_articles.
|
||||
page(params[:page]).
|
||||
per(100).
|
||||
limited_columns_internal_select
|
||||
when "boosted-dev-digest"
|
||||
@articles = Article.
|
||||
includes(:user).
|
||||
order("published_at DESC").
|
||||
boosted_via_dev_digest_email.
|
||||
page(params[:page]).
|
||||
per(100).
|
||||
limited_columns_internal_select
|
||||
else #MIX
|
||||
@articles = Article.
|
||||
where(published: true).
|
||||
|
|
@ -97,6 +113,9 @@ class Internal::ArticlesController < Internal::ApplicationController
|
|||
article.featured = article_params[:featured].to_s == "true"
|
||||
article.approved = article_params[:approved].to_s == "true"
|
||||
article.live_now = article_params[:live_now].to_s == "true"
|
||||
article.email_digest_eligible = article_params[:email_digest_eligible].to_s == "true"
|
||||
article.boosted_additional_articles = article_params[:boosted_additional_articles].to_s == "true"
|
||||
article.boosted_dev_digest_email = article_params[:boosted_dev_digest_email].to_s == "true"
|
||||
article.update!(article_params)
|
||||
if article.live_now
|
||||
Article.where.not(id: article.id).where(live_now: true).update_all(live_now: false)
|
||||
|
|
@ -115,6 +134,9 @@ class Internal::ArticlesController < Internal::ApplicationController
|
|||
:body_markdown,
|
||||
:approved,
|
||||
:live_now,
|
||||
:email_digest_eligible,
|
||||
:boosted_additional_articles,
|
||||
:boosted_dev_digest_email,
|
||||
:main_image_background_hex_color,
|
||||
:featured_number)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,4 +42,13 @@ class ArticleDecorator < ApplicationDecorator
|
|||
"short"
|
||||
end
|
||||
end
|
||||
|
||||
def internal_utm_params(place="additional_box")
|
||||
campaign = if boosted
|
||||
"#{organization&.slug}_boosted"
|
||||
else
|
||||
"regular"
|
||||
end
|
||||
"?utm_source=#{place}&utm_medium=internal&utm_campaign=#{ campaign }&booster_org=#{organization&.slug}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,16 +2,20 @@ import { h, render } from 'preact';
|
|||
import Onboarding from '../src/Onboarding';
|
||||
import { getUserData } from '../src/utils/getUserData';
|
||||
|
||||
HTMLDocument.prototype.ready = new Promise((resolve) => {
|
||||
if (document.readyState !== 'loading') { return resolve(); }
|
||||
HTMLDocument.prototype.ready = new Promise(resolve => {
|
||||
if (document.readyState !== 'loading') {
|
||||
return resolve();
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => resolve());
|
||||
});
|
||||
|
||||
function shouldShowOnboarding() {
|
||||
return document.head.getElementsByTagName('meta')[2].content === 'true' &&
|
||||
return (
|
||||
document.head.getElementsByTagName('meta')[2].content === 'true' &&
|
||||
document.body.getAttribute('data-user') &&
|
||||
document.body.getAttribute('data-user') !== 'undefined' &&
|
||||
JSON.parse(document.body.getAttribute('data-user')).saw_onboarding === false
|
||||
);
|
||||
}
|
||||
|
||||
function renderPage() {
|
||||
|
|
@ -22,8 +26,8 @@ function renderPage() {
|
|||
}
|
||||
}
|
||||
|
||||
document.ready
|
||||
.then(getUserData()
|
||||
.then(() => {
|
||||
renderPage();
|
||||
}));
|
||||
document.ready.then(
|
||||
getUserData().then(() => {
|
||||
renderPage();
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,19 @@ class BoostedArticle
|
|||
@not_ids = options[:not_ids]
|
||||
end
|
||||
|
||||
def get
|
||||
Article.where(boosted: true).
|
||||
includes(:user).
|
||||
includes(:organization).
|
||||
where.not(id: not_ids, organization_id: nil).
|
||||
tagged_with(tags, any: true).sample
|
||||
def get(area = "additional_articles")
|
||||
if area == "additional_articles"
|
||||
Article.boosted_via_additional_articles.
|
||||
includes(:user).
|
||||
includes(:organization).
|
||||
where.not(id: not_ids, organization_id: nil).
|
||||
tagged_with(tags, any: true).sample
|
||||
else
|
||||
Article.boosted_via_dev_digest_email.
|
||||
includes(:user).
|
||||
includes(:organization).
|
||||
where.not(id: not_ids, organization_id: nil).
|
||||
tagged_with(tags, any: true).sample
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class EmailLogic
|
|||
articles = if user_has_followings?
|
||||
@user.followed_articles.
|
||||
where("published_at > ?", fresh_date).
|
||||
where(published: true).
|
||||
where(published: true, email_digest_eligible: true).
|
||||
where.not(user_id: @user.id).
|
||||
where("positive_reactions_count > ?", 15).
|
||||
order("positive_reactions_count DESC").
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ class DigestMailer < ApplicationMailer
|
|||
@user = user
|
||||
@articles = articles.first(6)
|
||||
@unsubscribe = generate_unsubscribe_token(@user.id, :email_digest_periodic)
|
||||
@boosted_article = BoostedArticle.new(@user,
|
||||
@articles.first,
|
||||
{not_ids: @articles.pluck(:id)}).get("dev_digest_email")
|
||||
subject = generate_title(@articles)
|
||||
mail(to: @user.email, subject: subject)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ class Article < ApplicationRecord
|
|||
include CloudinaryHelper
|
||||
include ActionView::Helpers
|
||||
include AlgoliaSearch
|
||||
include Storext.model
|
||||
|
||||
acts_as_taggable_on :tags
|
||||
|
||||
|
|
@ -63,14 +64,23 @@ class Article < ApplicationRecord
|
|||
scope :limited_columns_internal_select, -> {
|
||||
select(:path, :title, :id, :featured, :approved, :published,
|
||||
:comments_count, :positive_reactions_count, :cached_tag_list,
|
||||
:main_image, :main_image_background_hex_color, :updated_at,
|
||||
:main_image, :main_image_background_hex_color, :updated_at, :boost_states,
|
||||
:video, :user_id, :organization_id, :video_source_url, :video_code,
|
||||
:video_thumbnail_url, :video_closed_caption_track_url, :social_image,
|
||||
:published_from_feed, :crossposted_at, :published_at, :featured_number,
|
||||
:live_now, :last_buffered, :facebook_last_buffered, :created_at, :body_markdown
|
||||
:live_now, :last_buffered, :facebook_last_buffered, :created_at, :body_markdown,
|
||||
:email_digest_eligible
|
||||
)
|
||||
}
|
||||
|
||||
scope :boosted_via_additional_articles, -> {
|
||||
where("boost_states ->> 'boosted_additional_articles' = 'true'")
|
||||
}
|
||||
|
||||
scope :boosted_via_dev_digest_email, -> {
|
||||
where("boost_states ->> 'boosted_dev_digest_email' = 'true'")
|
||||
}
|
||||
|
||||
algoliasearch per_environment: true, enqueue: :trigger_delayed_index do
|
||||
add_index "searchables",
|
||||
id: :index_id,
|
||||
|
|
@ -142,6 +152,12 @@ class Article < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
store_attributes :boost_states do
|
||||
boosted_additional_articles Boolean, default: false
|
||||
boosted_dev_digest_email Boolean, default: false
|
||||
end
|
||||
|
||||
|
||||
def self.filter_excluded_tags(tag = nil)
|
||||
if tag == "hiring"
|
||||
tagged_with("hiring")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
</div>
|
||||
<div class="secondary-content-display">
|
||||
<%= render "additional_content_boxes/article_followable_area",
|
||||
article: article,
|
||||
followable: article.organization || article.user,
|
||||
classification: classification,
|
||||
follow_cue: follow_cue %>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<h2><a href="<%= article.path %>" class="<%= 'partner-link' if classification == "boosted" %>" data-details="<%= organization&.slug %>__<%= article.slug %>"><%= article.title %></a></h2>
|
||||
<h2><a href="<%= article.path + article.decorate.internal_utm_params %>" class="<%= 'partner-link' if classification == "boosted" %>" data-details="<%= organization&.slug %>__<%= article.slug %>"><%= article.title %></a></h2>
|
||||
<div class="content-author">
|
||||
<a href="<%= article.user.path %>">
|
||||
<a href="<%= article.user.path + article.decorate.internal_utm_params %>">
|
||||
<img class="profile-pic" src="<%= ProfileImage.new(article.user).get(50)%>" alt="<%= article.user.username %> profile image"/>
|
||||
<span><%= article.user.name %></span>
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<a href="<%= article.path %>" class="<%= 'partner-link' if classification == "boosted" %>" data-details="<%= organization&.slug %>__<%= article.slug %>">
|
||||
<a href="<%= article.path + article.decorate.internal_utm_params%>" class="<%= 'partner-link' if classification == "boosted" %>" data-details="<%= organization&.slug %>__<%= article.slug %>">
|
||||
<%= article.description %>
|
||||
</a>
|
||||
<div class="engagement-count">
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</p>
|
||||
<a href="<%= article.path %>" class="cta<%= ' partner-link' if classification == "boosted" %>" data-details="<%= organization&.slug %>__<%= article.slug %>">READ POST</a>
|
||||
<a href="<%= article.path + article.decorate.internal_utm_params%>" class="cta<%= ' partner-link' if classification == "boosted" %>" data-details="<%= organization&.slug %>__<%= article.slug %>">READ POST</a>
|
||||
<button
|
||||
class="article-engagement-count bookmark-engage<%= ' partner-link' if classification == "boosted" %>"
|
||||
data-reactable-id="<%= article.id %>"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<% if followable.class.name == "Organization" && followable.nav_image.present? %>
|
||||
<div class="profile-pic-wrapper wide-profile-image-wrapper">
|
||||
<a href="<%= followable.path %>" />
|
||||
<a href="<%= followable.path + article.decorate.internal_utm_params %>" />
|
||||
<img class="wide-image" src="<%= CloudinaryHelper.cl_image_path(followable.nav_image_url,
|
||||
type: "fetch",
|
||||
crop: "fill",
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
<% else %>
|
||||
<div class="profile-pic-wrapper">
|
||||
<a href="<%= followable.path %>"
|
||||
<a href="<%= followable.path + article.decorate.internal_utm_params %>"
|
||||
class="<%= ' partner-link' if classification == "boosted" %>"
|
||||
data-details="<%= followable&.slug if classification == "boosted" %>__PROFILE"/>
|
||||
<img class="profile-image"
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
style="border: 4px solid <%= followable.bg_color_hex %>" />
|
||||
</div>
|
||||
<div class="org-name">
|
||||
<a href="<%= followable.path %>"
|
||||
<a href="<%= followable.path + article.decorate.internal_utm_params %>"
|
||||
class="<%= ' partner-link' if classification == "boosted" %>"
|
||||
data-details="<%= followable&.slug if classification == "boosted" %>__PROFILE" /><%= followable.name %></a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
article: @boosted_article,
|
||||
classification: "boosted",
|
||||
classification_text: "From one of our Community Sponsors",
|
||||
follow_cue: @boosted_article.organization.tag_line || @boosted_article.organization.summary %>
|
||||
follow_cue: @boosted_article.organization&.tag_line || @boosted_article.organization&.tag_line %>
|
||||
<% elsif @alt_classic %>
|
||||
<%= render "additional_content_boxes/article_box",
|
||||
article: @alt_classic,
|
||||
|
|
|
|||
|
|
@ -37,9 +37,13 @@
|
|||
  Featured: <input name="article[featured]" type="checkbox" <%= "checked" if article.featured %>>
|
||||
  Approved: <input name="article[approved]" type="checkbox" <%= "checked" if article.approved %>>
|
||||
  Live Now: <input name="article[live_now]" type="checkbox" <%= "checked" if article.live_now %>>
|
||||
  Image BG color: <input name="article[main_image_background_hex_color]" value="<%= article.main_image_background_hex_color %>" id="featured_number_<%= article.id %>"><br/><br/>
|
||||
  Email Digest Eligible: <input name="article[email_digest_eligible]" type="checkbox" <%= "checked" if article.email_digest_eligible %>>
|
||||
  Image BG color: <input name="article[main_image_background_hex_color]" value="<%= article.main_image_background_hex_color %>" id="featured_number_<%= article.id %>">
|
||||
Social Image: <input name="article[social_image]" value="<%= article.social_image %>" id="featured_number_<%= article.id %>">
|
||||
|
||||
<br/><br/>
|
||||
Boosted (Additional articles): <input name="article[boosted_additional_articles]" type="checkbox" <%= "checked" if article.boosted_additional_articles %>>
|
||||
  Boosted (DEV Digest): <input name="article[boosted_dev_digest_email]" type="checkbox" <%= "checked" if article.boosted_dev_digest_email %>>
|
||||
|
||||
<% if params[:state]&.include?("classic") %>
|
||||
<br/><br>
|
||||
<textarea cols="70" rows="6" wrap="hard" name="article[body_markdown]" /><%= article.body_markdown %></textarea>
|
||||
|
|
@ -47,6 +51,18 @@
|
|||
|
||||
    <button class="btn btn-primary">SUBMIT</button>
|
||||
</form>
|
||||
|
||||
<% if article.boosted_dev_digest_email %>
|
||||
<br/><br/>
|
||||
<% phrase = "#{article.path}?booster_org=#{article.organization&.slug || "generic"}" %>
|
||||
<b>BOOSTED IN <%= EmailMessage.where("subject LIKE ?", "%#{phrase}%").where.not(opened_at:nil).size %> EMAILS</b>
|
||||
<br/>
|
||||
<b>BOOSTED IN <%= EmailMessage.where("subject LIKE ?", "%#{phrase}%").where.not(opened_at:nil).where("sent_at > ?", 1.week.ago).size %> EMAILS IN THE PAST WEEK</b>
|
||||
<br/></br/>
|
||||
<b>BOOSTED IN <%= EmailMessage.where("subject LIKE ?", "%#{phrase}%").where.not(opened_at:nil).where.not(opened_at:nil).size %> OPENED EMAILS</b>
|
||||
<br/>
|
||||
<b>BOOSTED IN <%= EmailMessage.where("subject LIKE ?", "%#{phrase}%").where.not(opened_at:nil).where.not(opened_at:nil).where("sent_at > ?", 1.week.ago).size %> OPENED EMAILS IN THE PAST WEEK</b>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% unless params[:state]&.include?("classic") %>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
<a href="/internal/articles?state=spam" style="padding:10px;<%= "background:#89ffba" if params[:state] == "spam" %>">Spam?</a>
|
||||
<a href="/internal/articles?state=classic" style="padding:10px;<%= "background:#89ffba" if params[:state] == "classic" %>">Classic</a>
|
||||
<a href="/internal/articles?state=classic-candidate" style="padding:10px;<%= "background:#89ffba" if params[:state] == "classic-candidate" %>">Classic Candidate</a>
|
||||
<a href="/internal/articles?state=boosted-additional-articles" style="padding:10px;<%= "background:#89ffba" if params[:state] == "boosted-additional-articles" %>">Boosted (Additional articles)</a>
|
||||
<a href="/internal/articles?state=boosted-dev-digest" style="padding:10px;<%= "background:#89ffba" if params[:state] == "boosted-dev-digest" %>">Boosted (DEV Digest)</a>
|
||||
</h3>
|
||||
<br/>
|
||||
<% if params[:state] && params[:state].include?("top-") && params[:state] != "top-3" && params[:state] != "top-6" %>
|
||||
|
|
|
|||
|
|
@ -1 +1,92 @@
|
|||
<%= render "individual_article", article: @article %>
|
||||
|
||||
|
||||
<script>
|
||||
function timeNow(objectID){
|
||||
var seconds = new Date().getTime() / 1000;
|
||||
document.getElementById("featured_number_"+objectID).value = Math.round(seconds);
|
||||
}
|
||||
function sink(objectID){
|
||||
var seconds = new Date().getTime() / 1080;
|
||||
document.getElementById("featured_number_"+objectID).value = Math.round(seconds);
|
||||
}
|
||||
|
||||
$('.row').on("submit", "form", function() {
|
||||
var form = $(this);
|
||||
var valuesToSubmit = $(this).serialize();
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: $(this).attr('action'), //sumbits it to the given url of the form
|
||||
data: valuesToSubmit,
|
||||
// dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
|
||||
}).success(function(json){
|
||||
console.log("success")
|
||||
form.parents(".row").addClass("highlighted-bg")
|
||||
form.parents(".row").addClass("highlighted-border")
|
||||
setTimeout(function(){
|
||||
form.parents(".row").removeClass("highlighted-bg")
|
||||
},350)
|
||||
});
|
||||
return false; // prevents normal behaviour
|
||||
});
|
||||
|
||||
document.getElementById('image-upload-button').onclick = function (e) {
|
||||
e.preventDefault();
|
||||
document.getElementById('image-upload').click();
|
||||
}
|
||||
|
||||
document.getElementById('image-upload').onchange = function (e) {
|
||||
var image = document.getElementById('image-upload').files;
|
||||
if (image.length > 0) {
|
||||
document.getElementById('image-upload-file-label').style = 'color:#888888';
|
||||
document.getElementById('image-upload-file-label').innerHTML = "Uploading...";
|
||||
document.getElementById('uploaded-image').style = 'display:none';
|
||||
document.getElementById('image-upload-submit').value = "uploading";
|
||||
setTimeout(function(){
|
||||
document.getElementById('image-upload-submit').click(function(){
|
||||
});
|
||||
},50)
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('image-upload-submit').onclick = function (e) {
|
||||
e.preventDefault();
|
||||
var image = document.getElementById('image-upload').files;
|
||||
if (image.length > 0) {
|
||||
var ajaxReq = createAjaxReq();
|
||||
ajaxReq.onreadystatechange = uploadImageCb.bind(this, ajaxReq);
|
||||
ajaxReq.open('POST', '/image_uploads', true);
|
||||
ajaxReq.send(generateUploadFormdata(image));
|
||||
}
|
||||
}
|
||||
|
||||
function generateUploadFormdata(image) {
|
||||
var token = document.getElementsByName('authenticity_token')[0].value;
|
||||
var formData = new FormData();
|
||||
formData.append('authenticity_token', token);
|
||||
formData.append('image', image[0]);
|
||||
return formData;
|
||||
}
|
||||
|
||||
function uploadImageCb(ajaxReq) {
|
||||
if (ajaxReq.status === 200 && ajaxReq.readyState === XMLHttpRequest.DONE) {
|
||||
var address = document.getElementById('uploaded-image');
|
||||
address.value = JSON.parse(ajaxReq.response).link;
|
||||
address.style.display = "inline-block";
|
||||
address.style.width = "90%";
|
||||
address.select();
|
||||
var uploadedMessage = '';
|
||||
document.getElementById('image-upload-file-label').innerHTML = uploadedMessage;
|
||||
document.getElementById('image-upload-file-label').style.color = '#00c673';
|
||||
document.getElementById('image-upload-submit').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function createAjaxReq() {
|
||||
if (window.XMLHttpRequest) {
|
||||
return new XMLHttpRequest();
|
||||
} else {
|
||||
return new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,18 +7,20 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:3% 12% 2% 4%;font-size:16px;line-height:22px;color:#7d7d7d">
|
||||
<%if @unsubscribe %>
|
||||
<%= link_to "Unsubscribe", email_subscriptions_unsubscribe_url(ut: @unsubscribe) %> | <a href="https://dev.to/settings/notifications">Adjust your email settings</a>
|
||||
<% else %>
|
||||
Don't want to get emails like this? Adjust your email settings at <a href="https://dev.to/settings/notifications">dev.to/settings/notifications</a>
|
||||
<% end %>
|
||||
<br/><br/>
|
||||
<td style="padding:3% 12% 2% 4%;font-size:18px;line-height:22px;color:#7d7d7d">
|
||||
<% if @user.twitter_username.blank? %>
|
||||
Reminder: You used <b>GitHub</b> to authenticate your account, so use that to sign in if prompted.
|
||||
<% else %>
|
||||
Reminder: You used <b>Twitter</b> to authenticate your account, so use that to sign in if prompted.
|
||||
<% end %>
|
||||
<br/><br/>
|
||||
<div style="font-size:0.78em">
|
||||
<%if @unsubscribe %>
|
||||
<%= link_to "Unsubscribe", email_subscriptions_unsubscribe_url(ut: @unsubscribe), style:"color:#7d7d7d" %> | <a href="https://dev.to/settings/notifications" style="color:#7d7d7d">Adjust your email settings</a>
|
||||
<% else %>
|
||||
Don't want to get emails like this? Adjust your email settings at <a href="https://dev.to/settings/notifications">dev.to/settings/notifications</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,20 @@
|
|||
DEV Digest
|
||||
</h1>
|
||||
<h4 style="text-align: center;">Recent posts you might find valuable based on your interests ❤️</h4>
|
||||
<% @articles.each do |article| %>
|
||||
<ul>
|
||||
<li>
|
||||
<ul>
|
||||
<% @articles.each do |article| %>
|
||||
<li style="margin:18px auto">
|
||||
<a href="https://dev.to<%= article.path %>" style="font-weight: bold;color:#0045ff;font-size:1.06em;"><%= article.title.strip %></a> <%= truncate(article.description, length: 80) %>
|
||||
</li>
|
||||
</ul>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if @boosted_article && @user.display_sponsors %>
|
||||
<h4 style="background:#e3fffc;display:inline-block;margin:0;">And sponsor-boosted</h4>
|
||||
<li style="margin:18px auto">
|
||||
<a href="https://dev.to<%= @boosted_article.path %>?booster_org=<%= @boosted_article.organization&.slug || "generic" %>" style="font-weight: bold;color:#0045ff;font-size:1.06em;"><%= @boosted_article.title.strip %></a> <%= truncate(@boosted_article.description, length: 80) %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
|
||||
<center style="margin-top:50px;">
|
||||
<em>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
class AddEmailDigestEligibleToArticles < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
add_column :articles, :email_digest_eligible, :boolean, default: true
|
||||
end
|
||||
end
|
||||
|
|
@ -8,9 +8,9 @@
|
|||
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
# It's strongly recommended that you check this file into your version control system. ❤️
|
||||
|
||||
ActiveRecord::Schema.define(version: 20180427160903) do
|
||||
ActiveRecord::Schema.define(version: 20180502160428) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
|
@ -57,6 +57,7 @@ ActiveRecord::Schema.define(version: 20180427160903) do
|
|||
t.datetime "crossposted_at"
|
||||
t.string "description"
|
||||
t.datetime "edited_at"
|
||||
t.boolean "email_digest_eligible", default: true
|
||||
t.datetime "facebook_last_buffered"
|
||||
t.boolean "featured", default: false
|
||||
t.integer "featured_number"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ RSpec.describe EmailLogic do
|
|||
h = described_class.new(user).analyze
|
||||
expect(h.should_receive_email?).to eq(false)
|
||||
end
|
||||
it "marks as not ready if there isn't at least 3 email-digest-eligible articles" do
|
||||
2.times { create(:article, positive_reactions_count: 40) }
|
||||
2.times { create(:article, positive_reactions_count: 40, email_digest_eligible: false) }
|
||||
h = described_class.new(user).analyze
|
||||
expect(h.should_receive_email?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when a user's open_percentage is low " do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue