Merge pull request #1140 from consul/proposal-notifications

Proposal notifications and Direct Messages
This commit is contained in:
Enrique García
2016-06-16 12:35:37 +02:00
committed by GitHub
76 changed files with 1592 additions and 172 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

View File

@@ -3,6 +3,7 @@
//
// Table of Contents:
//
// 0. Custom variables
// 1. Global
// 2. Breakpoints
// 3. The Grid
@@ -43,6 +44,70 @@
@import 'util/util';
// 0. Custom variables
// --------------------
$base-font-size: rem-calc(17);
$base-line-height: rem-calc(26);
$small-font-size: rem-calc(14);
$line-height: rem-calc(24);
$brand: #004A83;
$body: #E9E9E9;
$background: #EDEFF0;
$border: #DEE0E3;
$dark: darken($brand, 10%);
$text: #222222;
$text-medium: #999999;
$text-light: #CCCCCC;
$link: #2895F1;
$link-hover: #2178BF;
$debates: #008CCF;
$votes-bg: #26AEEE;
$votes-border: #1F94CB;
$votes-like: #7BD2A8;
$votes-like-act: #5D9E7F;
$votes-unlike: #EF8585;
$votes-unlike-act: #BD6A6A;
$delete: #F04124;
$check: #46DB91;
$proposals: #FFA42D;
$proposals-border: #CC8425;
$budget: #454372;
$budget-hover: #7571BF;
$highlight: #E7F2FC;
$featured: #FED900;
$footer-bg: #DEE0E2;
$footer-color: #171819;
$footer-link: #454A4C;
$footer-border: #BFC1C3;
$success-bg: #DFF0D8;
$success-border: #D6E9C6;
$color-success: #3C763D;
$info-bg: #D9EDF7;
$info-border: #BCE8F1;
$color-info: #31708F;
$warning-bg: #FCF8E3;
$warning-border: #FAEBCC;
$color-warning: #8A6D3B;
$alert-bg: #F2DEDE;
$alert-border: #EBCCD1;
$color-alert: #A94442;
// 1. Global
// ---------
@@ -127,7 +192,7 @@ $header-color: inherit;
$header-lineheight: 1.4;
$header-margin-bottom: 0.5rem;
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$small-font-size: rem-calc(14);
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.6;
$paragraph-margin-bottom: 1rem;
@@ -510,13 +575,13 @@ $show-header-for-stacked: false;
$tab-margin: 0;
$tab-background: $white;
$tab-background-active: $light-gray;
$tab-item-font-size: rem-calc(12);
$tab-background-active: $white;
$tab-item-font-size: $base-font-size;
$tab-item-background-hover: $white;
$tab-item-padding: 1.25rem 1.5rem;
$tab-item-padding: $line-height/2 0;
$tab-expand-max: 6;
$tab-content-background: $white;
$tab-content-border: $light-gray;
$tab-content-border: $border;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 1rem;
@@ -563,66 +628,3 @@ $topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 1rem;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;
// 37. Custom variables
// --------------------
$base-font-size: rem-calc(17);
$base-line-height: rem-calc(26);
$small-font-size: rem-calc(14);
$line-height: rem-calc(24);
$brand: #004A83;
$body: #E9E9E9;
$background: #EDEFF0;
$border: #DEE0E3;
$dark: darken($brand, 10%);
$text: #222222;
$text-medium: #999999;
$text-light: #CCCCCC;
$link: #2895F1;
$link-hover: #2178BF;
$debates: #008CCF;
$votes-bg: #26AEEE;
$votes-border: #1F94CB;
$votes-like: #7BD2A8;
$votes-like-act: #5D9E7F;
$votes-unlike: #EF8585;
$votes-unlike-act: #BD6A6A;
$delete: #F04124;
$check: #46DB91;
$proposals: #FFA42D;
$proposals-border: #CC8425;
$budget: #454372;
$budget-hover: #7571BF;
$highlight: #E7F2FC;
$featured: #FED900;
$footer-bg: #DEE0E2;
$footer-color: #171819;
$footer-link: #454A4C;
$footer-border: #BFC1C3;
$success-bg: #DFF0D8;
$success-border: #D6E9C6;
$color-success: #3C763D;
$info-bg: #D9EDF7;
$info-border: #BCE8F1;
$color-info: #31708F;
$warning-bg: #FCF8E3;
$warning-border: #FAEBCC;
$color-warning: #8A6D3B;
$alert-bg: #F2DEDE;
$alert-border: #EBCCD1;
$color-alert: #A94442;

View File

@@ -219,10 +219,44 @@ a {
float: left;
}
.tabs-content {
border: 0;
}
.tabs {
border: {
left: 0;
right: 0;
top: 0;
};
margin-bottom: $line-height;
.tabs-title > a {
color: $text-medium;
margin-bottom: rem-calc(-1);
margin-right: $line-height;
&[aria-selected='true'],
&.is-active {
color: $brand;
border-bottom: 2px solid $brand;
font-weight: bold;
}
}
h2 {
font-size: $base-font-size;
}
}
.no-max-width {
max-width: none;
}
.button.float-right ~ .button.float-right {
margin: 0 $line-height/2;
}
// 02. Header
// ----------
@@ -1464,7 +1498,6 @@ table {
.comments {
background: $white;
background-repeat: repeat-x;
padding-top: $line-height;
padding-bottom: $line-height*4;
h2 {
@@ -1488,7 +1521,7 @@ table {
.comment-votes {
color: $text-medium;
font-size: $small-font-size;
font-size: rem-calc(14);
line-height: $line-height;
a {
@@ -1651,7 +1684,7 @@ table {
&:first-child {
padding-left: $line-height*1.5;
width: 80%;
width: 75%;
}
&:before {

View File

@@ -715,6 +715,16 @@
}
}
.more-info {
clear: both;
color: $text-medium;
font-size: $small-font-size;
a {
color: $text-medium;
}
}
.debate {
.votes {

View File

@@ -25,7 +25,7 @@ class AccountController < ApplicationController
if @account.organization?
params.require(:account).permit(:phone_number, :email_on_comment, :email_on_comment_reply, :newsletter, organization_attributes: [:name, :responsible_name])
else
params.require(:account).permit(:username, :public_activity, :email_on_comment, :email_on_comment_reply, :newsletter)
params.require(:account).permit(:username, :public_activity, :email_on_comment, :email_on_comment_reply, :email_on_direct_message, :email_digest, :newsletter)
end
end

View File

@@ -0,0 +1,36 @@
class DirectMessagesController < ApplicationController
load_and_authorize_resource
def new
@receiver = User.find(params[:user_id])
@direct_message = DirectMessage.new(receiver: @receiver)
end
def create
@sender = current_user
@receiver = User.find(params[:user_id])
@direct_message = DirectMessage.new(parsed_params)
if @direct_message.save
Mailer.direct_message_for_receiver(@direct_message).deliver_later
Mailer.direct_message_for_sender(@direct_message).deliver_later
redirect_to [@receiver, @direct_message], notice: I18n.t("flash.actions.create.direct_message")
else
render :new
end
end
def show
@direct_message = DirectMessage.find(params[:id])
end
private
def direct_message_params
params.require(:direct_message).permit(:title, :body)
end
def parsed_params
direct_message_params.merge(sender: @sender, receiver: @receiver)
end
end

View File

@@ -13,6 +13,7 @@ class Management::ProposalsController < Management::BaseController
def show
super
@notifications = @proposal.notifications
redirect_to management_proposal_path(@proposal), status: :moved_permanently if request.path != management_proposal_path(@proposal)
end

View File

@@ -9,7 +9,7 @@ class NotificationsController < ApplicationController
def show
@notification = current_user.notifications.find(params[:id])
redirect_to url_for(@notification.notifiable)
redirect_to url_for(@notification.linkable_resource)
end
def mark_all_as_read

View File

@@ -0,0 +1,33 @@
class ProposalNotificationsController < ApplicationController
load_and_authorize_resource except: [:new]
def new
@proposal = Proposal.find(params[:proposal_id])
@notification = ProposalNotification.new(proposal_id: @proposal.id)
authorize! :new, @notification
end
def create
@notification = ProposalNotification.new(proposal_notification_params)
@proposal = Proposal.find(proposal_notification_params[:proposal_id])
if @notification.save
@proposal.voters.each do |voter|
Notification.add(voter.id, @notification)
end
redirect_to @notification, notice: I18n.t("flash.actions.create.proposal_notification")
else
render :new
end
end
def show
@notification = ProposalNotification.find(params[:id])
end
private
def proposal_notification_params
params.require(:proposal_notification).permit(:title, :body, :proposal_id)
end
end

View File

@@ -2,7 +2,6 @@ class ProposalsController < ApplicationController
include CommentableActions
include FlagActions
before_action :parse_search_terms, only: [:index, :suggest]
before_action :parse_advanced_search_terms, only: :index
before_action :parse_tag_filter, only: :index
@@ -22,6 +21,7 @@ class ProposalsController < ApplicationController
def show
super
@notifications = @proposal.notifications
redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal)
end

View File

@@ -3,6 +3,7 @@ class UsersController < ApplicationController
load_and_authorize_resource
helper_method :authorized_for_filter?
helper_method :author?
helper_method :author_or_admin?
def show
@@ -65,8 +66,12 @@ class UsersController < ApplicationController
@user.public_activity || authorized_current_user?
end
def author?
@author ||= current_user && (current_user == @user)
end
def author_or_admin?
@author_or_admin ||= current_user && (current_user == @user || current_user.administrator?)
@author_or_admin ||= current_user && (author? || current_user.administrator?)
end
def authorized_current_user?

View File

@@ -35,4 +35,9 @@ module ApplicationHelper
}
Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe
end
def author_of?(authorable, user)
return false if authorable.blank? || user.blank?
authorable.author_id == user.id
end
end

View File

@@ -1,6 +1,4 @@
module NotificationsHelper
def notification_action(notification)
notification.notifiable_type == "Comment" ? "replies_to" : "comments_on"
end
end

View File

@@ -1,5 +1,5 @@
class ApplicationMailer < ActionMailer::Base
helper :settings
default from: "participacion@madrid.es"
default from: "Decide Madrid <no-reply@madrid.es>"
layout 'mailer'
end

View File

@@ -42,6 +42,32 @@ class Mailer < ApplicationMailer
end
end
def direct_message_for_receiver(direct_message)
@direct_message = direct_message
@receiver = @direct_message.receiver
with_user(@receiver) do
mail(to: @receiver.email, subject: t('mailers.direct_message_for_receiver.subject'))
end
end
def direct_message_for_sender(direct_message)
@direct_message = direct_message
@sender = @direct_message.sender
with_user(@sender) do
mail(to: @sender.email, subject: t('mailers.direct_message_for_sender.subject'))
end
end
def proposal_notification_digest(user)
@notifications = user.notifications.where(notifiable_type: "ProposalNotification")
with_user(user) do
mail(to: user.email, subject: t('mailers.proposal_notification_digest.title', org_name: Setting['org_name']))
end
end
private
def with_user(user, &block)

View File

@@ -46,11 +46,14 @@ module Abilities
can :vote_featured, Proposal
can :vote, SpendingProposal
can :create, SpendingProposal
can :create, DirectMessage
can :show, DirectMessage, sender_id: user.id
end
can [:create, :show], ProposalNotification, proposal: { author_id: user.id }
can :create, Annotation
can [:update, :destroy], Annotation, user_id: user.id
end
end
end

View File

@@ -10,6 +10,7 @@ module Abilities
can :read, Legislation
can :read, User
can [:search, :read], Annotation
can :new, DirectMessage
end
end
end

View File

@@ -0,0 +1,22 @@
class DirectMessage < ActiveRecord::Base
belongs_to :sender, class_name: 'User', foreign_key: 'sender_id'
belongs_to :receiver, class_name: 'User', foreign_key: 'receiver_id'
validates :title, presence: true
validates :body, presence: true
validates :sender, presence: true
validates :receiver, presence: true
validate :max_per_day
scope :today, lambda { where('DATE(created_at) = ?', Date.today) }
def max_per_day
return if errors.any?
max = Setting[:direct_message_max_per_day]
if sender.direct_messages_sent.today.count >= max.to_i
errors.add(:title, I18n.t('activerecord.errors.models.direct_message.attributes.max_per_day.invalid', max: max))
end
end
end

View File

@@ -21,4 +21,30 @@ class Notification < ActiveRecord::Base
Notification.create!(user_id: user_id, notifiable: notifiable)
end
end
def notifiable_title
case notifiable.class.name
when "ProposalNotification"
notifiable.proposal.title
when "Comment"
notifiable.commentable.title
else
notifiable.title
end
end
def notifiable_action
case notifiable_type
when "ProposalNotification"
"proposal_notification"
when "Comment"
"replies_to"
else
"comments_on"
end
end
def linkable_resource
notifiable.is_a?(ProposalNotification) ? notifiable.proposal : notifiable
end
end

View File

@@ -16,6 +16,7 @@ class Proposal < ActiveRecord::Base
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
belongs_to :geozone
has_many :comments, as: :commentable
has_many :proposal_notifications
validates :title, presence: true
validates :question, presence: true
@@ -97,6 +98,10 @@ class Proposal < ActiveRecord::Base
cached_votes_up + physical_votes
end
def voters
votes_for.voters
end
def editable?
total_votes <= Setting["max_votes_for_proposal_edit"].to_i
end
@@ -150,6 +155,10 @@ class Proposal < ActiveRecord::Base
Setting['votes_for_proposal_success'].to_i
end
def notifications
proposal_notifications
end
protected
def set_responsible_name

View File

@@ -0,0 +1,17 @@
class ProposalNotification < ActiveRecord::Base
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
belongs_to :proposal
validates :title, presence: true
validates :body, presence: true
validates :proposal, presence: true
validate :minimum_interval
def minimum_interval
return true if proposal.try(:notifications).blank?
if proposal.notifications.last.created_at > (Time.now - Setting[:proposal_notification_minimum_interval_in_days].to_i.days).to_datetime
errors.add(:title, I18n.t('activerecord.errors.models.proposal_notification.attributes.minimum_interval.invalid', interval: Setting[:proposal_notification_minimum_interval_in_days]))
end
end
end

View File

@@ -23,6 +23,8 @@ class User < ActiveRecord::Base
has_many :spending_proposals, foreign_key: :author_id
has_many :failed_census_calls
has_many :notifications
has_many :direct_messages_sent, class_name: 'DirectMessage', foreign_key: :sender_id
has_many :direct_messages_received, class_name: 'DirectMessage', foreign_key: :receiver_id
belongs_to :geozone
validates :username, presence: true, if: :username_required?
@@ -50,6 +52,7 @@ class User < ActiveRecord::Base
scope :officials, -> { where("official_level > 0") }
scope :for_render, -> { includes(:organization) }
scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) }
scope :email_digest, -> { where(email_digest: true) }
before_validation :clean_document_number

View File

@@ -34,7 +34,9 @@
<div>
<%= f.label :public_activity do %>
<%= f.check_box :public_activity, title: t('account.show.public_activity_label'), label: false %>
<span class="checkbox"><%= t("account.show.public_activity_label") %></span>
<span class="checkbox">
<%= t("account.show.public_activity_label") %>
</span>
<% end %>
</div>
@@ -43,21 +45,45 @@
<div>
<%= f.label :email_on_comment do %>
<%= f.check_box :email_on_comment, title: t('account.show.email_on_comment_label'), label: false %>
<span class="checkbox"><%= t("account.show.email_on_comment_label") %></span>
<span class="checkbox">
<%= t("account.show.email_on_comment_label") %>
</span>
<% end %>
</div>
<div>
<%= f.label :email_on_comment_reply do %>
<%= f.check_box :email_on_comment_reply, title: t('account.show.email_on_comment_reply_label'), label: false %>
<span class="checkbox"><%= t("account.show.email_on_comment_reply_label") %></span>
<span class="checkbox">
<%= t("account.show.email_on_comment_reply_label") %>
</span>
<% end %>
</div>
<div>
<%= f.label :email_newsletter_subscribed do %>
<%= f.check_box :newsletter, title: t('account.show.subscription_to_website_newsletter_label'), label: false %>
<span class="checkbox"><%= t("account.show.subscription_to_website_newsletter_label") %></span>
<span class="checkbox">
<%= t("account.show.subscription_to_website_newsletter_label") %>
</span>
<% end %>
</div>
<div>
<%= f.label :email_digest do %>
<%= f.check_box :email_digest, title: t('account.show.email_digest_label'), label: false %>
<span class="checkbox">
<%= t("account.show.email_digest_label") %>
</span>
<% end %>
</div>
<div>
<%= f.label :email_on_direct_message do %>
<%= f.check_box :email_on_direct_message, title: t('account.show.email_on_direct_message_label'), label: false %>
<span class="checkbox">
<%= t("account.show.email_on_direct_message_label") %>
</span>
<% end %>
</div>

View File

@@ -0,0 +1,47 @@
<div class="row">
<div class="small-12 medium-9 column">
<%= render 'shared/back_link' %>
<h1>
<%= t("users.direct_messages.new.title", receiver: @receiver.name) %>
</h1>
<% if not current_user %>
<div class="callout primary">
<p>
<%= t("users.direct_messages.new.authenticate",
signin: link_to(t("users.direct_messages.new.signin"), new_user_session_path),
signup: link_to(t("users.direct_messages.new.signup"), new_user_registration_path)).html_safe %>
</p>
</div>
<% elsif not @receiver.email_on_direct_message? %>
<div class="callout primary">
<p>
<%= t("users.direct_messages.new.direct_messages_bloqued") %>
</p>
</div>
<% elsif can? :create, @direct_message %>
<%= form_for [@receiver, @direct_message] do |f| %>
<%= render "shared/errors", resource: @direct_message %>
<%= f.label :title, t("users.direct_messages.new.title_label") %>
<%= f.text_field :title, label: false %>
<%= f.label :body, t("users.direct_messages.new.body_label") %>
<%= f.text_area :body, label: false, rows: "3" %>
<div class="small-12 medium-4">
<%= f.submit t("users.direct_messages.new.submit_button"), class: "button expanded" %>
</div>
<% end %>
<% else %>
<div class="callout warning">
<p>
<%= t("users.direct_messages.new.verified_only",
verify_account: link_to( t("users.direct_messages.new.verify_account"),
verification_path )).html_safe %>
</p>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,18 @@
<div class="row">
<div class="small-12 medium-9 column">
<%= link_to user_path(@direct_message.receiver), class: "back" do %>
<span class="icon-angle-left"></span>
<%= t("shared.back") %>
<% end %>
<div class="clear"></div>
<div class="margin-top">
<span><strong><%= t("users.direct_messages.show.receiver",
receiver: @direct_message.receiver.name) %></strong></span>
</div>
<h1><%= @direct_message.title %></h1>
<p><%= @direct_message.body %></p>
</div>
</div>

View File

@@ -32,6 +32,10 @@
<td style="text-align: center; border-top: 1px solid #dadfe1; padding-top: 20px;">
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; margin: 0;padding: 0;line-height: 1.5em;color: #797f7f; font-size: 12px;">
<%= setting['org_name'] %></p>
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; margin: 0;padding: 0;line-height: 1.5em;color: #222; font-size: 10px; margin-top: 12px;">
<%= t('mailers.no_reply') %></p>
</td>
</tr>
</tbody>

View File

@@ -0,0 +1,37 @@
<td style="padding-bottom: 20px; padding-left: 10px;">
<h1 style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;">
<%= @direct_message.title %>
</h1>
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;font-weight: normal;line-height: 24px;">
<%= @direct_message.body %>
</p>
<table style="width: 100%; border-top: 1px solid #DEE0E3; margin-top: 60px;">
<tbody>
<tr>
<td style="padding-bottom: 12px; padding-top: 24px; text-align: center;">
<%= link_to user_url(@direct_message.sender), style: "font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; background: #f7f5f2; border-radius: 6px; color: #3d3d66!important; font-weight: bold; margin: 0px; padding: 10px 15px; text-align: center; text-decoration: none; min-width: 200px; display: inline-block;", target: "_blank" do %>
<%= image_tag('icon_mailer_reply.png', style: "border: 0; display: inline-block; width: 100%; max-width: 12px; vertical-align: sub;", alt: "") %>
<%= t('mailers.direct_message_for_receiver.reply',
sender: @direct_message.sender.name) %>
<% end %>
</td>
</tr>
</tbody>
</table>
<table style="width: 100%;">
<tbody>
<tr>
<td style="padding-left: 10px;">
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;font-weight: normal;line-height: 24px; margin: 0; font-style: italic; padding-bottom: 20px;">
<%= t('mailers.direct_message_for_receiver.unsubscribe',
account: link_to(t('mailers.direct_message_for_receiver.unsubscribe_account'),
account_url, style: "color: #2895F1; text-decoration: none;")).html_safe %>
</p>
</td>
</tr>
</tbody>
</table>
</td>

View File

@@ -0,0 +1,15 @@
<td style="padding-bottom: 20px; padding-left: 10px;">
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; font-size: 20px;">
<%= t('mailers.direct_message_for_sender.title_html',
receiver: @direct_message.receiver.name ) %>
</p>
<h2 style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; font-size: 18px;">
<%= @direct_message.title %>
</h2>
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;font-weight: normal;line-height: 24px;">
<%= @direct_message.body %>
</p>
</td>

View File

@@ -0,0 +1,68 @@
<td>
<table style="width: 100%;">
<tbody>
<tr>
<td style="padding-left: 10px;">
<h1 style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;">
<%= t('mailers.proposal_notification_digest.title',
org_name: Setting['org_name']) %>
</h1>
</td>
</tr>
</tbody>
</table>
<% @notifications.each do |notification| %>
<table style="width: 100%; border-top: 1px solid #DEE0E3; padding-top: 12px;">
<tbody>
<tr>
<td style="padding-bottom: 20px; padding-left: 10px;">
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 20px;font-weight: bold;line-height: 24px; margin: 0;">
<%= link_to notification.notifiable.title, notification_url(notification), style: "color: #2895F1; text-decoration: none;" %>
</p>
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;font-weight: normal;line-height: 24px; margin-top: 0; color: #cccccc;">
<%= notification.notifiable.proposal.title %>&nbsp;&bull;&nbsp;
<%= notification.notifiable.proposal.created_at.to_date %>&nbsp;&bull;&nbsp;
<%= notification.notifiable.proposal.author.name %>
</p>
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;font-weight: normal;line-height: 24px;">
<%= notification.notifiable.body %>
</p>
<table style="width: 100%;">
<tbody>
<tr>
<td style="padding-bottom: 12px;">
<%= link_to proposal_url(notification.notifiable.proposal, anchor: "social-share"), style: "font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; background: #f7f5f2; border-radius: 6px; color: #3d3d66!important; font-weight: bold; margin: 0px; padding: 10px 15px; text-align: center; text-decoration: none; min-width: 160px; display: inline-block;" do %>
<%= image_tag('icon_mailer_share.png', style: "border: 0; display: inline-block; width: 100%; max-width: 16px", alt: "") %>
<%= t('mailers.proposal_notification_digest.share') %>
<% end %>
<%= link_to proposal_url(notification.notifiable.proposal, anchor: "comments"), style: "font-family: 'Open Sans','Helvetica Neue',arial,sans-serif; background: #f7f5f2; border-radius: 6px; color: #3d3d66!important; font-weight: bold; margin: 0px; padding: 10px 15px; text-align: center; text-decoration: none; min-width: 160px; display: inline-block; margin-left: 12px;" do %>
<%= image_tag('icon_mailer_comment.png', style: "border: 0; display: inline-block; width: 100%; max-width: 16px; vertical-align: middle;", alt: "") %>
<%= t('mailers.proposal_notification_digest.comment') %>
<% end %>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<% end %>
<table style="width: 100%;">
<tbody>
<tr>
<td style="padding-left: 10px;">
<p style="font-family: 'Open Sans','Helvetica Neue',arial,sans-serif;font-size: 14px;font-weight: normal;line-height: 24px; margin: 0; font-style: italic; padding-bottom: 20px;">
<%= t('mailers.proposal_notification_digest.unsubscribe',
account: link_to(t('mailers.proposal_notification_digest.unsubscribe_account'),
account_url, style: "color: #2895F1; text-decoration: none;")).html_safe %>
</p>
</td>
</tr>
</tbody>
</table>
</td>

View File

@@ -1,9 +1,13 @@
<li id="<%= dom_id(notification) %>" class="notification">
<%= link_to notification do %>
<p>
<em><%= t("notifications.index.#{notification_action(notification)}", count: notification.counter) %></em>
<strong><%= notification.notifiable.is_a?(Comment) ? notification.notifiable.commentable.title : notification.notifiable.title %></strong>
<em>
<%= t("notifications.index.#{notification.notifiable_action}",
count: notification.counter) %>
</em>
<strong><%= notification.notifiable_title %></strong>
</p>
<p class="time"><%= l notification.timestamp, format: :datetime %></p>
<% end %>
</li>

View File

@@ -0,0 +1,36 @@
<div class="row">
<div class="small-12 column">
<%= render 'shared/back_link' %>
<h1><%= t("proposal_notifications.new.title") %></h1>
<div class="callout primary">
<p>
<%= t("proposal_notifications.new.info_about_receivers_html",
count: @proposal.voters.count,
proposal_page: link_to(t("proposal_notifications.new.proposal_page"),
proposal_path(@proposal, anchor: "comments"))).html_safe %>
</p>
</div>
</div>
</div>
<div class="row">
<div class="small-12 medium-9 column">
<%= form_for @notification do |f| %>
<%= render "shared/errors", resource: @notification %>
<%= f.label :title, t("proposal_notifications.new.title_label") %>
<%= f.text_field :title, label: false %>
<%= f.label :body, t("proposal_notifications.new.body_label") %>
<%= f.text_area :body, label: false, rows: "3" %>
<%= f.hidden_field :proposal_id, value: @proposal.id %>
<div class="small-12 medium-4">
<%= f.submit t("proposal_notifications.new.submit_button"), class: "button expanded" %>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<div class="row">
<div class="small-12 medium-9">
<%= link_to user_path(current_user), class: "back" do %>
<span class="icon-angle-left"></span>
<%= t("proposal_notifications.show.back") %>
<% end %>
<h2><%= @notification.title %></h2>
<p><%= @notification.body %></p>
</div>
</div>

View File

@@ -1,12 +1,7 @@
<% cache [locale_and_user_status, @current_order, commentable_cache_key(@proposal), @comment_tree.comments, @comment_tree.comment_authors, @proposal.comments_count, @comment_flags] do %>
<section class="row-full comments">
<section class="comments">
<div class="row">
<div id="comments" class="small-12 column">
<h2>
<%= t("proposals.show.comments_title") %>
<span class="js-comments-count">(<%= @proposal.comments_count %>)</span>
</h2>
<%= render 'shared/wide_order_selector', i18n_namespace: "comments" %>
<% if user_signed_in? %>

View File

@@ -0,0 +1,22 @@
<div class="row">
<div class="small-12 column">
<ul class="tabs" data-tabs id="example-tabs">
<li class="tabs-title is-active">
<%= link_to "#tab-comments" do %>
<h2>
<%= t("proposals.show.comments_tab") %>
<span class="js-comments-count">(<%= @proposal.comments_count %>)</span>
</h2>
<% end %>
</li>
<li class="tabs-title">
<%= link_to "#tab-notifications" do %>
<h2>
<%= t("proposals.show.notifications_tab") %>
(<%= @notifications.count %>)
</h2>
<% end %>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<div class="tabs-panel" id="tab-notifications">
<div class="row">
<div class="small-12 column">
<% if @notifications.blank? %>
<div class="callout primary text-center">
<%= t('proposals.show.no_notifications') %>
</div>
<% end %>
<% @notifications.each do |notification| %>
<h3><%= notification.title %></h3>
<p class="more-info"><%= notification.created_at.to_date %></p>
<p><%= notification.body %></p>
<% end %>
</div>
</div>
</div>

View File

@@ -12,9 +12,13 @@
<div class="small-12 medium-9 column">
<%= render "shared/back_link" %>
<% if author_of?(@proposal, current_user) %>
<%= link_to t("proposals.show.send_notification"), new_proposal_notification_path(proposal_id: @proposal.id),
class: 'button hollow float-right' %>
<% end %>
<% if current_user && @proposal.editable_by?(current_user) %>
<%= link_to edit_proposal_path(@proposal), class: 'edit-proposal button success small float-right' do %>
<span class="icon-edit"></span>
<%= link_to edit_proposal_path(@proposal), class: 'edit-proposal button hollow float-right' do %>
<%= t("proposals.show.edit_proposal_link") %>
<% end %>
<% end %>
@@ -104,7 +108,7 @@
{ proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %>
</div>
</div>
<div class="sidebar-divider"></div>
<div id="social-share" class="sidebar-divider"></div>
<h3><%= t("proposals.show.share") %></h3>
<div class="social-share-full">
<%= social_share_button_tag("#{@proposal.title} #{setting['twitter_hashtag']}") %>
@@ -118,4 +122,12 @@
</div>
</section>
<% end %>
<%= render "proposals/comments" %>
<div class="tabs-content" data-tabs-content="example-tabs">
<%= render "proposals/filter_subnav" %>
<%= render "proposals/notifications" %>
<div class="tabs-panel is-active" id="tab-comments">
<%= render "proposals/comments" %>
</div>
</div>

View File

@@ -7,17 +7,25 @@
<%= proposal.summary %>
</td>
<% if author? %>
<td class="text-center">
<%= link_to t("users.proposals.send_notification"), new_proposal_notification_path(proposal_id: proposal.id),
class: 'button hollow' %>
</td>
<% end %>
<% if author_or_admin? %>
<td class="text-center">
<% if proposal.retired? %>
<span class="label alert"><%= t('users.show.retired') %></span>
<span class="label alert"><%= t('users.proposals.retired') %></span>
<% else %>
<%= link_to t('users.show.retire'),
<%= link_to t('users.proposals.retire'),
retire_form_proposal_path(proposal),
class: 'delete' %>
class: 'button hollow alert' %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</table>

View File

@@ -2,7 +2,17 @@
<div class="activity row margin-top">
<div class="small-12 column">
<h2>
<% if @user != current_user && @user.email_on_direct_message? %>
<%= link_to t("users.show.send_private_message"),
new_user_direct_message_path(@user),
class: "button hollow float-right" %>
<% else %>
<div class="callout primary float-right">
<%= t("users.show.no_private_messages") %>
</div>
<% end %>
<h2 class="inline-block">
<%= avatar_image(@user, seed: @user.id, size: 60) %>
<%= @user.name %>
<% if current_administrator? %>

View File

@@ -95,6 +95,8 @@ search:
# - '{devise,simple_form}.*'
ignore_missing:
- 'unauthorized.*'
- 'activerecord.errors.models.proposal_notification.*'
- 'activerecord.errors.models.direct_message.*'
- 'errors.messages.blank'
- 'errors.messages.taken'
- 'devise.failure.invalid'
@@ -134,6 +136,7 @@ ignore_unused:
- 'spending_proposals.index.search_form.*'
- 'notifications.index.comments_on*'
- 'notifications.index.replies_to*'
- 'notifications.index.proposal_notification*'
- 'helpers.page_entries_info.*' # kaminari
- 'views.pagination.*' # kaminari
- 'shared.suggest.*'

View File

@@ -1,11 +1,5 @@
en:
activerecord:
errors:
models:
user:
attributes:
email:
password_already_set: "This user already has a password"
models:
activity:
one: "activity"
@@ -76,11 +70,23 @@ en:
title: "Title"
errors:
models:
user:
attributes:
email:
password_already_set: "This user already has a password"
debate:
attributes:
tag_list:
less_than_or_equal_to: "tags must be less than or equal to %{count}"
direct_message:
attributes:
max_per_day:
invalid: "You can only send a maximum of %{max} direct messages per day"
proposal:
attributes:
tag_list:
less_than_or_equal_to: "tags must be less than or equal to %{count}"
proposal_notification:
attributes:
minimum_interval:
invalid: "You have to wait a minium of %{interval} days between notifications"

View File

@@ -1,11 +1,5 @@
es:
activerecord:
errors:
models:
user:
attributes:
email:
password_already_set: "Este usuario ya tiene una clave asociada"
models:
activity:
one: "actividad"
@@ -76,11 +70,23 @@ es:
title: "Título"
errors:
models:
user:
attributes:
email:
password_already_set: "Este usuario ya tiene una clave asociada"
debate:
attributes:
tag_list:
less_than_or_equal_to: "los temas deben ser menor o igual que %{count}"
direct_message:
attributes:
max_per_day:
invalid: "Sólo puedes enviar %{max} mensajes directos por día"
proposal:
attributes:
tag_list:
less_than_or_equal_to: "los temas deben ser menor o igual que %{count}"
proposal_notification:
attributes:
minimum_interval:
invalid: "Debes esperar un mínimo de %{interval} días entre notificaciones"

View File

@@ -15,6 +15,8 @@ en:
public_activity_label: Keep my list of activities public
save_changes_submit: Save changes
subscription_to_website_newsletter_label: Receive by email website relevant information
email_on_direct_message_label: Receive emails about direct messages
email_digest_label: Receive a summary of proposal notifications
title: My account
user_permission_debates: Participate on debates
user_permission_info: With your account you can...
@@ -141,11 +143,13 @@ en:
accept_terms_title: I agree to the Privacy Policy and the Terms and conditions of use
conditions: Terms and conditions of use
debate: Debate
direct_message: private message
error: error
errors: errors
not_saved: 'prevented this %{resource} from being saved:'
policy: Privacy Policy
proposal: Proposal
proposal_notification: "Notification"
spending_proposal: Spending proposal
user: Account
verification/sms: phone
@@ -221,6 +225,9 @@ en:
other: There are %{count} new comments on
empty_notifications: You don't have new notifications.
mark_all_as_read: Mark all as read
proposal_notification:
one: There is one new notification on
other: There are %{count} new notifications on
replies_to:
one: Someone replied to your comment on
other: There are %{count} new replies to your comment on
@@ -349,17 +356,30 @@ en:
one: 1 comment
other: "%{count} comments"
zero: No comments
comments_title: Comments
comments_tab: Comments
edit_proposal_link: Edit
flag: This proposal has been flagged as inappropriate by several users.
login_to_comment: You must %{signin} or %{signup} to leave a comment.
notifications_tab: Notifications
retired_warning: "The author considers this proposal should not receive more supports."
retired_warning_link_to_explanation: Read the explanation before voting for it.
retired: Proposal retired by the author
share: Share
send_notification: Send notification
no_notifications: "This proposal has any notifications."
update:
form:
submit_button: Save changes
proposal_notifications:
new:
title: "Send message"
title_label: "Title"
body_label: "Message"
submit_button: "Send message"
info_about_receivers_html: "This message will be send to <strong>%{count} people</strong> and it will be visible in %{proposal_page}.<br> Message are not sent immediately, users will receive periodically an email with all proposal notifications."
proposal_page: "the proposal's page"
show:
back: "Go back to my activity"
shared:
advanced_search:
author_type: 'By author category'
@@ -483,6 +503,20 @@ en:
manage:
all: "You do not have permission to carry out the action '%{action}' on %{subject}."
users:
direct_messages:
new:
body_label: Message
direct_messages_bloqued: "This user has decided not to receive direct messages"
submit_button: Send message
title: Send private message to %{receiver}
title_label: Title
verified_only: To send a private message %{verify_account}
verify_account: verify your account
authenticate: You must %{signin} or %{signup} to continue.
signin: sign in
signup: sign up
show:
receiver: Message sent to %{receiver}
show:
deleted: Deleted
deleted_debate: This debate has been deleted
@@ -503,9 +537,13 @@ en:
one: 1 Spending proposal
other: "%{count} Spending proposals"
no_activity: User has no public activity
no_private_messages: "This user doesn't accept private messages."
private_activity: This user decided to keep the activity list private
retire: Retire
retired: Retired
send_private_message: "Send private message"
proposals:
send_notification: "Send notification"
retire: "Retire"
retired: "Retired"
votes:
agree: I agree
anonymous: Too many anonymous votes to admit vote %{verify_account}.

View File

@@ -15,6 +15,8 @@ es:
public_activity_label: Mostrar públicamente mi lista de actividades
save_changes_submit: Guardar cambios
subscription_to_website_newsletter_label: Recibir emails con información interesante sobre la web
email_on_direct_message_label: Recibir emails con mensajes directos
email_digest_label: Recibir resumen de notificaciones sobre propuestas
title: Mi cuenta
user_permission_debates: Participar en debates
user_permission_info: Con tu cuenta ya puedes...
@@ -141,11 +143,13 @@ es:
accept_terms_title: Acepto la Política de privacidad y las Condiciones de uso
conditions: Condiciones de uso
debate: el debate
direct_message: el mensaje privado
error: error
errors: errores
not_saved: 'impidieron guardar %{resource}:'
policy: Política de privacidad
proposal: la propuesta
proposal_notification: "la notificación"
spending_proposal: la propuesta de gasto
user: la cuenta
verification/sms: el teléfono
@@ -221,6 +225,9 @@ es:
other: Hay %{count} comentarios nuevos en
empty_notifications: No tienes notificaciones nuevas.
mark_all_as_read: Marcar todas como leídas
proposal_notification:
one: Hay una nueva notificación en
other: Hay %{count} nuevas notificaciones en
replies_to:
one: Hay una respuesta nueva a tu comentario en
other: Hay %{count} nuevas respuestas a tu comentario en
@@ -349,17 +356,30 @@ es:
one: 1 Comentario
other: "%{count} Comentarios"
zero: Sin comentarios
comments_title: Comentarios
comments_tab: Comentarios
edit_proposal_link: Editar propuesta
flag: Esta propuesta ha sido marcada como inapropiada por varios usuarios.
login_to_comment: Necesitas %{signin} o %{signup} para comentar.
notifications_tab: Notificaciones
retired_warning: "El autor de esta propuesta considera que ya no debe seguir recogiendo apoyos."
retired_warning_link_to_explanation: Revisa su explicación antes de apoyarla.
retired: Propuesta retirada por el autor
send_notification: Enviar notificación
share: Compartir
no_notifications: "Esta propuesta no tiene notificaciones."
update:
form:
submit_button: Guardar cambios
proposal_notifications:
new:
title: "Enviar mensaje"
title_label: "Título"
body_label: "Mensaje"
submit_button: "Enviar mensaje"
info_about_receivers_html: "Este mensaje se enviará a <strong>%{count} usuarios</strong> y se publicará en %{proposal_page}.<br> El mensaje no se enviará inmediatamente, los usuarios recibirán periódicamente un email con todas las notificaciones de propuestas."
proposal_page: "la página de la propuesta"
show:
back: "Volver a mi actividad"
shared:
advanced_search:
author_type: 'Por categoría de autor'
@@ -483,6 +503,20 @@ es:
manage:
all: "No tienes permiso para realizar la acción '%{action}' sobre %{subject}."
users:
direct_messages:
new:
body_label: "Mensaje"
direct_messages_bloqued: "Este usuarios ha decidido no recibir mensajes directos"
submit_button: "Enviar mensaje"
title: Enviar mensaje privado a %{receiver}
title_label: "Título"
verified_only: Para enviar un mensaje privado %{verify_account}
verify_account: verifica tu cuenta
authenticate: Necesitas %{signin} o %{signup}.
signin: iniciar sesión
signup: registrarte
show:
receiver: Mensaje enviado a %{receiver}
show:
deleted: Eliminado
deleted_debate: Este debate ha sido eliminado
@@ -503,9 +537,13 @@ es:
one: 1 Propuesta de inversión
other: "%{count} Propuestas de inversión"
no_activity: Usuario sin actividad pública
no_private_messages: "Este usuario no acepta mensajes privados."
private_activity: Este usuario ha decidido mantener en privado su lista de actividades
retire: Retirar
retired: Retirada
send_private_message: "Enviar un mensaje privado"
proposals:
send_notification: "Enviar notificación"
retire: "Retirar"
retired: "Retirada"
votes:
agree: Estoy de acuerdo
anonymous: Demasiados votos anónimos, para poder votar %{verify_account}.

View File

@@ -1,6 +1,7 @@
---
en:
mailers:
no_reply: "This message was sent from an email address that does not accept replies."
comment:
hi: Hi
new_comment_by_html: There is a new comment from <b>%{commenter}</b>
@@ -29,4 +30,18 @@ en:
signatory: "DEPARTMENT OF PUBLIC PARTICIPATION"
sorry: "Sorry for the inconvenience and we again thank you for your invaluable participation."
subject: "Your investment project '%{code}' has been marked as unfeasible"
unfeasible_html: "From the Madrid City Council we want to thank you for your participation in the <strong>participatory budgets of the city of Madrid</strong>. We regret to inform you that your proposal <strong>'%{title}'</strong> will be excluded from this participatory process for the following reason:"
unfeasible_html: "From the Madrid City Council we want to thank you for your participation in the <strong>participatory budgets of the city of Madrid</strong>. We regret to inform you that your proposal <strong>'%{title}'</strong> will be excluded from this participatory process for the following reason:"
proposal_notification_digest:
title: "Proposal notifications in %{org_name}"
share: Share proposal
comment: Comment proposal
unsubscribe: "If you don't want receive proposal's notification, visit %{account} and unckeck 'Receive a summary of proposal notifications'."
unsubscribe_account: My account
direct_message_for_receiver:
subject: "You have received a new private message"
reply: Reply to %{sender}
unsubscribe: "If you don't want receive direct messages, visit %{account} and unckeck 'Receive emails about direct messages'."
unsubscribe_account: My account
direct_message_for_sender:
subject: "You have send a new private message"
title_html: "You have send a new private message to <strong>%{receiver}</strong> with the content:"

View File

@@ -1,6 +1,7 @@
---
es:
mailers:
no_reply: "Este mensaje se ha enviado desde una dirección de correo electrónico que no admite respuestas."
comment:
hi: Hola
new_comment_by_html: Hay un nuevo comentario de <b>%{commenter}</b> en
@@ -30,3 +31,17 @@ es:
sorry: "Sentimos las molestias ocasionadas y volvemos a darte las gracias por tu inestimable participación."
subject: "Tu propuesta de inversión '%{code}' ha sido marcada como inviable"
unfeasible_html: "Desde el Ayuntamiento de Madrid queremos agradecer tu participación en los <strong>Presupuestos Participativos de la ciudad de Madrid</strong>. Lamentamos informarte de que tu propuesta <strong>'%{title}'</strong> quedará excluida de este proceso participativo por el siguiente motivo:"
proposal_notification_digest:
title: "Notificaciones de propuestas en %{org_name}"
share: Compartir propuesta
comment: Comentar propuesta
unsubscribe: "Si no quieres recibir notificaciones de propuestas, puedes entrar en %{account} y desmarcar la opción 'Recibir resumen de notificaciones sobre propuestas'."
unsubscribe_account: Mi cuenta
direct_message_for_receiver:
subject: "Has recibido un nuevo mensaje privado"
reply: Responder a %{sender}
unsubscribe: "Si no quieres recibir mensajes privados, puedes entrar en %{account} y desmarcar la opción 'Recibir emails con mensajes directos'."
unsubscribe_account: Mi cuenta
direct_message_for_sender:
subject: "Has enviado un nuevo mensaje privado"
title_html: "Has enviado un nuevo mensaje privado a <strong>%{receiver}</strong> con el siguiente contenido:"

View File

@@ -5,8 +5,11 @@ en:
create:
notice: "%{resource_name} created successfully."
debate: "Debate created successfully."
direct_message: "You message has been sent successfully."
proposal: "Proposal created successfully."
proposal_notification: "Your message has been sent correctly."
spending_proposal: "Spending proposal created successfully. You can access it from %{activity}"
save_changes:
notice: Changes saved
update:

View File

@@ -5,7 +5,9 @@ es:
create:
notice: "%{resource_name} creado correctamente."
debate: "Debate creado correctamente."
direct_message: "Tu mensaje ha sido enviado correctamente."
proposal: "Propuesta creada correctamente."
proposal_notification: "Tu message ha sido enviado correctamente."
spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}"
save_changes:
notice: Cambios guardados

View File

@@ -83,7 +83,9 @@ Rails.application.routes.draw do
get :search, on: :collection
end
resources :users, only: [:show]
resources :users, only: [:show] do
resources :direct_messages, only: [:new, :create, :show]
end
resource :account, controller: "account", only: [:show, :update, :delete] do
get :erase, on: :collection
@@ -93,6 +95,8 @@ Rails.application.routes.draw do
put :mark_all_as_read, on: :collection
end
resources :proposal_notifications, only: [:new, :create, :show]
resource :verification, controller: "verification", only: [:show]
scope module: :verification do

View File

@@ -0,0 +1,12 @@
class CreateProposalNotifications < ActiveRecord::Migration
def change
create_table :proposal_notifications do |t|
t.string :title
t.text :body
t.integer :author_id
t.integer :proposal_id
t.timestamps null: false
end
end
end

View File

@@ -0,0 +1,5 @@
class AddEmailOnProposalNotificationToUsers < ActiveRecord::Migration
def change
add_column :users, :email_on_proposal_notification, :boolean, default: true
end
end

View File

@@ -0,0 +1,12 @@
class CreateDirectMessages < ActiveRecord::Migration
def change
create_table :direct_messages do |t|
t.integer :sender_id
t.integer :receiver_id
t.string :title
t.text :body
t.timestamps null: false
end
end
end

View File

@@ -0,0 +1,5 @@
class AddEmailDigestToUsers < ActiveRecord::Migration
def change
add_column :users, :email_digest, :boolean, default: true
end
end

View File

@@ -0,0 +1,5 @@
class AddEmailOnDirectMessagesToUsers < ActiveRecord::Migration
def change
add_column :users, :email_on_direct_message, :boolean, default: true
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160518141543) do
ActiveRecord::Schema.define(version: 20160614160949) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -168,6 +168,15 @@ ActiveRecord::Schema.define(version: 20160518141543) do
add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
create_table "direct_messages", force: :cascade do |t|
t.integer "sender_id"
t.integer "receiver_id"
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "failed_census_calls", force: :cascade do |t|
t.integer "user_id"
t.string "document_number"
@@ -260,6 +269,15 @@ ActiveRecord::Schema.define(version: 20160518141543) do
add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree
create_table "proposal_notifications", force: :cascade do |t|
t.string "title"
t.text "body"
t.integer "author_id"
t.integer "proposal_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "proposals", force: :cascade do |t|
t.string "title", limit: 80
t.text "description"
@@ -393,30 +411,30 @@ ActiveRecord::Schema.define(version: 20160518141543) do
add_index "tolk_translations", ["phrase_id", "locale_id"], name: "index_tolk_translations_on_phrase_id_and_locale_id", unique: true, using: :btree
create_table "users", force: :cascade do |t|
t.string "email", default: ""
t.string "encrypted_password", default: "", null: false
t.string "email", default: ""
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.boolean "email_on_comment", default: false
t.boolean "email_on_comment_reply", default: false
t.string "phone_number", limit: 30
t.boolean "email_on_comment", default: false
t.boolean "email_on_comment_reply", default: false
t.string "phone_number", limit: 30
t.string "official_position"
t.integer "official_level", default: 0
t.integer "official_level", default: 0
t.datetime "hidden_at"
t.string "sms_confirmation_code"
t.string "username", limit: 60
t.string "username", limit: 60
t.string "document_number"
t.string "document_type"
t.datetime "residence_verified_at"
@@ -427,20 +445,23 @@ ActiveRecord::Schema.define(version: 20160518141543) do
t.datetime "letter_requested_at"
t.datetime "confirmed_hide_at"
t.string "letter_verification_code"
t.integer "failed_census_calls_count", default: 0
t.integer "failed_census_calls_count", default: 0
t.datetime "level_two_verified_at"
t.string "erase_reason"
t.datetime "erased_at"
t.boolean "public_activity", default: true
t.boolean "newsletter", default: true
t.integer "notifications_count", default: 0
t.boolean "registering_with_oauth", default: false
t.boolean "public_activity", default: true
t.boolean "newsletter", default: true
t.integer "notifications_count", default: 0
t.boolean "registering_with_oauth", default: false
t.string "locale"
t.string "oauth_email"
t.integer "geozone_id"
t.string "redeemable_code"
t.string "gender", limit: 10
t.string "gender", limit: 10
t.datetime "date_of_birth"
t.boolean "email_on_proposal_notification", default: true
t.boolean "email_digest", default: true
t.boolean "email_on_direct_message", default: true
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree

View File

@@ -74,3 +74,7 @@ Setting['banner-style.banner-style-three'] = "Banner style 3"
Setting['banner-img.banner-img-one'] = "Banner image 1"
Setting['banner-img.banner-img-two'] = "Banner image 2"
Setting['banner-img.banner-img-three'] = "Banner image 3"
# Proposal notifications
Setting['proposal_notification_minimum_interval_in_days '] = 3
Setting['direct_message_max_per_day'] = 3

14
lib/email_digest.rb Normal file
View File

@@ -0,0 +1,14 @@
class EmailDigest
def initialize
end
def create
User.email_digest.each do |user|
if user.notifications.where(notifiable_type: "ProposalNotification").any?
Mailer.proposal_notification_digest(user).deliver_later
end
end
end
end

9
lib/tasks/emails.rake Normal file
View File

@@ -0,0 +1,9 @@
namespace :emails do
desc "Sends email digest of proposal notifications to each user"
task digest: :environment do
email_digest = EmailDigest.new
email_digest.create
end
end

View File

@@ -1,5 +1,4 @@
FactoryGirl.define do
sequence(:document_number) { |n| "#{n.to_s.rjust(8, '0')}X" }
factory :user do
@@ -137,7 +136,7 @@ FactoryGirl.define do
factory :proposal do
sequence(:title) { |n| "Proposal #{n} title" }
summary 'In summary, what we want is...'
sequence(:summary) { |n| "In summary, what we want is... #{n}" }
description 'Proposal description'
question 'Proposal question'
external_url 'http://external_documention.es'
@@ -323,7 +322,20 @@ FactoryGirl.define do
style {["banner-style-one", "banner-style-two", "banner-style-three"].sample}
image {["banner.banner-img-one", "banner.banner-img-two", "banner.banner-img-three"].sample}
target_url {["/proposals", "/debates" ].sample}
post_started_at Time.now - 7.days
post_ended_at Time.now + 7.days
post_started_at Time.now - 7.days
post_ended_at Time.now + 7.days
end
factory :proposal_notification do
title "Thank you for supporting my proposal"
body "Please let others know so we can make it happen"
proposal
end
factory :direct_message do
title "Hey"
body "How are You doing?"
association :sender, factory: :user
association :receiver, factory: :user
end
end

View File

@@ -35,6 +35,8 @@ feature 'Account' do
fill_in 'account_username', with: 'Larry Bird'
check 'account_email_on_comment'
check 'account_email_on_comment_reply'
uncheck 'account_email_digest'
uncheck 'account_email_on_direct_message'
click_button 'Save changes'
expect(page).to have_content "Changes saved"
@@ -42,8 +44,10 @@ feature 'Account' do
visit account_path
expect(page).to have_selector("input[value='Larry Bird']")
expect(page).to have_selector("input[id='account_email_on_comment'][value='1']")
expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']")
expect(find("#account_email_on_comment")).to be_checked
expect(find("#account_email_on_comment_reply")).to be_checked
expect(find("#account_email_digest")).to_not be_checked
expect(find("#account_email_on_direct_message")).to_not be_checked
end
scenario 'Edit Organization' do
@@ -53,6 +57,7 @@ feature 'Account' do
fill_in 'account_organization_attributes_name', with: 'Google'
check 'account_email_on_comment'
check 'account_email_on_comment_reply'
click_button 'Save changes'
expect(page).to have_content "Changes saved"
@@ -60,8 +65,8 @@ feature 'Account' do
visit account_path
expect(page).to have_selector("input[value='Google']")
expect(page).to have_selector("input[id='account_email_on_comment'][value='1']")
expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']")
expect(find("#account_email_on_comment")).to be_checked
expect(find("#account_email_on_comment_reply")).to be_checked
end
scenario "Errors on edit" do

View File

@@ -168,7 +168,10 @@ feature 'Commenting proposals' do
within "#comments" do
expect(page).to have_content 'Have you thought about...?'
expect(page).to have_content '(1)'
end
within "#tab-comments-label" do
expect(page).to have_content 'Comments (1)'
end
end

View File

@@ -0,0 +1,122 @@
require 'rails_helper'
feature 'Direct messages' do
background do
Setting[:direct_message_max_per_day] = 3
end
scenario "Create" do
sender = create(:user, :level_two)
receiver = create(:user, :level_two)
login_as(sender)
visit user_path(receiver)
click_link "Send private message"
expect(page).to have_content "Send private message to #{receiver.name}"
fill_in 'direct_message_title', with: "Hey!"
fill_in 'direct_message_body', with: "How are you doing?"
click_button "Send message"
expect(page).to have_content "You message has been sent successfully."
expect(page).to have_content "Hey!"
expect(page).to have_content "How are you doing?"
end
context "Permissions" do
scenario "Do not display link to send message to myself" do
sender = create(:user, :level_two)
login_as(sender)
visit user_path(sender)
expect(page).to_not have_link "Send private message"
end
scenario "Do not display link if direct message for user not allowed" do
sender = create(:user, :level_two)
receiver = create(:user, :level_two, email_on_direct_message: false)
login_as(sender)
visit user_path(receiver)
expect(page).to have_content "This user doesn't accept private messages."
expect(page).to_not have_link "Send private message"
end
scenario "Unverified user" do
sender = create(:user)
receiver = create(:user)
login_as(sender)
visit new_user_direct_message_path(receiver)
expect(page).to have_content "To send a private message verify your account"
expect(page).to_not have_link "Send private message"
end
scenario "User not logged in" do
sender = create(:user)
receiver = create(:user)
visit new_user_direct_message_path(receiver)
expect(page).to have_content "You must sign in or sign up to continue."
expect(page).to_not have_link "Send private message"
end
scenario "Accessing form directly" do
sender = create(:user, :level_two)
receiver = create(:user, :level_two, email_on_direct_message: false)
login_as(sender)
visit new_user_direct_message_path(receiver)
expect(page).to have_content("This user has decided not to receive direct messages")
expect(page).to_not have_css("#direct_message_title")
end
end
scenario "Error messages" do
author = create(:user)
proposal = create(:proposal, author: author)
login_as(author)
visit new_proposal_notification_path(proposal_id: proposal.id)
click_button "Send message"
expect(page).to have_content error_message
end
context "Limits" do
scenario "Can only send a maximum number of direct messages per day" do
sender = create(:user, :level_two)
receiver = create(:user, :level_two)
3.times { create(:direct_message, sender: sender) }
login_as(sender)
visit user_path(receiver)
click_link "Send private message"
expect(page).to have_content "Send private message to #{receiver.name}"
fill_in 'direct_message_title', with: "Hey!"
fill_in 'direct_message_body', with: "How are you doing?"
click_button "Send message"
expect(page).to have_content "You can only send a maximum of 3 direct messages per day"
expect(page).to_not have_content "You message has been sent successfully."
end
end
end

View File

@@ -148,4 +148,87 @@ feature 'Emails' do
expect(email).to have_body_text(spending_proposal.feasible_explanation)
end
context "Direct Message" do
scenario "Receiver email" do
sender = create(:user, :level_two)
receiver = create(:user, :level_two)
direct_message = create_direct_message(sender, receiver)
email = unread_emails_for(receiver.email).first
expect(email).to have_subject("You have received a new private message")
expect(email).to have_body_text(direct_message.title)
expect(email).to have_body_text(direct_message.body)
expect(email).to have_body_text(direct_message.sender.name)
expect(email).to have_body_text(/#{user_path(direct_message.sender_id)}/)
end
scenario "Sender email" do
sender = create(:user, :level_two)
receiver = create(:user, :level_two)
direct_message = create_direct_message(sender, receiver)
email = unread_emails_for(sender.email).first
expect(email).to have_subject("You have send a new private message")
expect(email).to have_body_text(direct_message.title)
expect(email).to have_body_text(direct_message.body)
expect(email).to have_body_text(direct_message.receiver.name)
end
pending "In the copy sent to the sender, display the receiver's name"
end
context "Proposal notification digest" do
scenario "notifications for proposals that I have supported" do
user = create(:user, email_digest: true)
proposal1 = create(:proposal)
proposal2 = create(:proposal)
proposal3 = create(:proposal)
create(:vote, votable: proposal1, voter: user)
create(:vote, votable: proposal2, voter: user)
reset_mailer
notification1 = create_proposal_notification(proposal1)
notification2 = create_proposal_notification(proposal2)
notification3 = create_proposal_notification(proposal3)
email_digest = EmailDigest.new
email_digest.create
email = open_last_email
expect(email).to have_subject("Proposal notifications in Consul")
expect(email).to deliver_to(user.email)
expect(email).to have_body_text(proposal1.title)
expect(email).to have_body_text(notification1.notifiable.title)
expect(email).to have_body_text(notification1.notifiable.body)
expect(email).to have_body_text(proposal1.author.name)
expect(email).to have_body_text(/#{notification_path(notification1)}/)
expect(email).to have_body_text(/#{proposal_path(proposal1, anchor: 'comments')}/)
expect(email).to have_body_text(/#{proposal_path(proposal1, anchor: 'social-share')}/)
expect(email).to have_body_text(proposal2.title)
expect(email).to have_body_text(notification2.notifiable.title)
expect(email).to have_body_text(notification2.notifiable.body)
expect(email).to have_body_text(/#{notification_path(notification2)}/)
expect(email).to have_body_text(/#{proposal_path(proposal2, anchor: 'comments')}/)
expect(email).to have_body_text(/#{proposal_path(proposal2, anchor: 'social-share')}/)
expect(email).to have_body_text(proposal2.author.name)
expect(email).to_not have_body_text(proposal3.title)
expect(email).to have_body_text(/#{account_path}/)
end
end
end

View File

@@ -149,6 +149,64 @@ feature "Notifications" do
expect(page).to have_css ".notification", count: 0
end
context "Proposal notification" do
scenario "Voters should receive a notification", :js do
author = create(:user)
user1 = create(:user)
user2 = create(:user)
user3 = create(:user)
proposal = create(:proposal, author: author)
create(:vote, voter: user1, votable: proposal, vote_flag: true)
create(:vote, voter: user2, votable: proposal, vote_flag: true)
login_as(author)
visit root_path
visit new_proposal_notification_path(proposal_id: proposal.id)
fill_in 'proposal_notification_title', with: "Thank you for supporting my proposal"
fill_in 'proposal_notification_body', with: "Please share it with others so we can make it happen!"
click_button "Send message"
expect(page).to have_content "Your message has been sent correctly."
logout
login_as user1
visit root_path
find(".icon-notification").click
expect(page).to have_css ".notification", count: 1
expect(page).to have_content "There is one new notification on #{proposal.title}"
expect(page).to have_xpath "//a[@href='#{notification_path(Notification.last)}']"
logout
login_as user2
visit root_path
find(".icon-notification").click
expect(page).to have_css ".notification", count: 1
expect(page).to have_content "There is one new notification on #{proposal.title}"
expect(page).to have_xpath "//a[@href='#{notification_path(Notification.first)}']"
logout
login_as user3
visit root_path
find(".icon-no-notification").click
expect(page).to have_css ".notification", count: 0
end
pending "group notifications for the same proposal"
end
context "mark as read" do
scenario "mark a single notification as read" do

View File

@@ -0,0 +1,109 @@
require 'rails_helper'
feature 'Proposal Notifications' do
scenario "Send a notification" do
author = create(:user)
proposal = create(:proposal, author: author)
login_as(author)
visit root_path
click_link "My activity"
within("#proposal_#{proposal.id}") do
click_link "Send notification"
end
fill_in 'proposal_notification_title', with: "Thank you for supporting my proposal"
fill_in 'proposal_notification_body', with: "Please share it with others so we can make it happen!"
click_button "Send message"
expect(page).to have_content "Your message has been sent correctly."
expect(page).to have_content "Thank you for supporting my proposal"
expect(page).to have_content "Please share it with others so we can make it happen!"
end
scenario "Show notifications" do
proposal = create(:proposal)
notification1 = create(:proposal_notification, proposal: proposal, title: "Hey guys", body: "Just wanted to let you know that...")
notification2 = create(:proposal_notification, proposal: proposal, title: "Another update", body: "We are almost there please share with your peoples!")
visit proposal_path(proposal)
expect(page).to have_content "Hey guys"
expect(page).to have_content "Just wanted to let you know that..."
expect(page).to have_content "Another update"
expect(page).to have_content "We are almost there please share with your peoples!"
end
scenario "Message about receivers" do
author = create(:user)
proposal = create(:proposal, author: author)
7.times { create(:vote, votable: proposal, vote_flag: true) }
login_as(author)
visit new_proposal_notification_path(proposal_id: proposal.id)
expect(page).to have_content "This message will be send to 7 people and it will be visible in the proposal's page"
expect(page).to have_link("the proposal's page", href: proposal_path(proposal, anchor: 'comments'))
end
context "Permissions" do
scenario "Link to send the message" do
user = create(:user)
author = create(:user)
proposal = create(:proposal, author: author)
login_as(author)
visit user_path(author)
within("#proposal_#{proposal.id}") do
expect(page).to have_link "Send notification"
end
login_as(user)
visit user_path(author)
within("#proposal_#{proposal.id}") do
expect(page).to_not have_link "Send message"
end
end
scenario "Accessing form directly" do
user = create(:user)
author = create(:user)
proposal = create(:proposal, author: author)
login_as(user)
visit new_proposal_notification_path(proposal_id: proposal.id)
expect(current_path).to eq(proposals_path)
expect(page).to have_content("You do not have permission to carry out the action")
end
end
scenario "Error messages" do
author = create(:user)
proposal = create(:proposal, author: author)
login_as(author)
visit new_proposal_notification_path(proposal_id: proposal.id)
click_button "Send message"
expect(page).to have_content error_message
end
context "Limits" do
pending "Cannot send more than one notification within established interval"
pending "use timecop to make sure notifications can be sent after time interval"
end
end

View File

@@ -0,0 +1,27 @@
require 'rails_helper'
describe ApplicationHelper do
describe "#author_of?" do
it "should be true if user is the author" do
user = create(:user)
proposal = create(:proposal, author: user)
expect(author_of?(proposal, user)).to eq true
end
it "should be false if user is not the author" do
user = create(:user)
proposal = create(:proposal)
expect(author_of?(proposal, user)).to eq false
end
it "should be false if user or authorable is nil" do
user = create(:user)
proposal = create(:proposal)
expect(author_of?(nil, user)).to eq false
expect(author_of?(proposal, nil)).to eq false
end
end
end

View File

@@ -1,25 +0,0 @@
require 'rails_helper'
describe NotificationsHelper do
describe "#notification_action" do
let(:debate) { create :debate }
let(:debate_comment) { create :comment, commentable: debate }
context "when action was comment on a debate" do
it "returns correct text when someone comments on your debate" do
notification = create :notification, notifiable: debate
expect(notification_action(notification)).to eq "comments_on"
end
end
context "when action was comment on a debate" do
it "returns correct text when someone replies to your comment" do
notification = create :notification, notifiable: debate_comment
expect(notification_action(notification)).to eq "replies_to"
end
end
end
end

View File

@@ -0,0 +1,9 @@
require 'rails_helper'
describe EmailDigest do
describe "create" do
pending "only send unread notifications"
end
end

View File

@@ -37,6 +37,10 @@ describe "Abilities::Common" do
it { should_not be_able_to(:comment_as_administrator, proposal) }
it { should_not be_able_to(:comment_as_moderator, proposal) }
it { should be_able_to(:new, DirectMessage) }
it { should_not be_able_to(:create, DirectMessage) }
it { should_not be_able_to(:show, DirectMessage) }
describe 'flagging content' do
it { should be_able_to(:flag, debate) }
it { should be_able_to(:unflag, debate) }
@@ -85,6 +89,7 @@ describe "Abilities::Common" do
describe "when level 2 verified" do
let(:own_spending_proposal) { create(:spending_proposal, author: user) }
let(:own_direct_message) { create(:direct_message, sender: user) }
before{ user.update(residence_verified_at: Time.now, confirmed_phone: "1") }
it { should be_able_to(:vote, Proposal) }
@@ -93,10 +98,16 @@ describe "Abilities::Common" do
it { should be_able_to(:create, SpendingProposal) }
it { should_not be_able_to(:destroy, create(:spending_proposal)) }
it { should_not be_able_to(:destroy, own_spending_proposal) }
it { should be_able_to(:new, DirectMessage) }
it { should be_able_to(:create, DirectMessage) }
it { should be_able_to(:show, own_direct_message) }
it { should_not be_able_to(:show, create(:direct_message)) }
end
describe "when level 3 verified" do
let(:own_spending_proposal) { create(:spending_proposal, author: user) }
let(:own_direct_message) { create(:direct_message, sender: user) }
before{ user.update(verified_at: Time.now) }
it { should be_able_to(:vote, Proposal) }
@@ -105,5 +116,10 @@ describe "Abilities::Common" do
it { should be_able_to(:create, SpendingProposal) }
it { should_not be_able_to(:destroy, create(:spending_proposal)) }
it { should_not be_able_to(:destroy, own_spending_proposal) }
it { should be_able_to(:new, DirectMessage) }
it { should be_able_to(:create, DirectMessage) }
it { should be_able_to(:show, own_direct_message) }
it { should_not be_able_to(:show, create(:direct_message)) }
end
end

View File

@@ -26,4 +26,6 @@ describe "Abilities::Everyone" do
it { should be_able_to(:index, SpendingProposal) }
it { should_not be_able_to(:create, SpendingProposal) }
end
pending "only authors can access new and create for ProposalNotifications"
end

View File

@@ -0,0 +1,85 @@
require 'rails_helper'
describe DirectMessage do
let(:direct_message) { build(:direct_message) }
before(:each) do
Setting[:direct_message_max_per_day] = 3
end
it "should be valid" do
expect(direct_message).to be_valid
end
it "should not be valid without a title" do
direct_message.title = nil
expect(direct_message).to_not be_valid
end
it "should not be valid without a body" do
direct_message.body = nil
expect(direct_message).to_not be_valid
end
it "should not be valid without an associated sender" do
direct_message.sender = nil
expect(direct_message).to_not be_valid
end
it "should not be valid without an associated receiver" do
direct_message.receiver = nil
expect(direct_message).to_not be_valid
end
describe "maximum number of direct messages per day" do
it "should not be valid if above maximum" do
sender = create(:user)
direct_message1 = create(:direct_message, sender: sender)
direct_message2 = create(:direct_message, sender: sender)
direct_message3 = create(:direct_message, sender: sender)
direct_message4 = build(:direct_message, sender: sender)
expect(direct_message4).to_not be_valid
end
it "should be valid if below maximum" do
sender = create(:user)
direct_message1 = create(:direct_message, sender: sender)
direct_message2 = create(:direct_message, sender: sender)
direct_message3 = build(:direct_message)
expect(direct_message).to be_valid
end
it "should be valid if no direct_messages sent" do
direct_message = build(:direct_message)
expect(direct_message).to be_valid
end
end
describe "scopes" do
describe "today" do
it "should return direct messages created today" do
direct_message1 = create(:direct_message, created_at: Time.zone.now.beginning_of_day + 3.hours)
direct_message2 = create(:direct_message, created_at: Time.zone.now)
direct_message3 = create(:direct_message, created_at: Time.zone.now.end_of_day)
expect(DirectMessage.today.count).to eq 3
end
it "should not return direct messages created another day" do
direct_message1 = create(:direct_message, created_at: 1.day.ago)
direct_message2 = create(:direct_message, created_at: 1.day.from_now)
expect(DirectMessage.today.count).to eq 0
end
end
end
end

View File

@@ -47,4 +47,35 @@ describe Notification do
end
end
describe "#notification_action" do
context "when action was comment on a debate" do
it "returns correct text when someone comments on your debate" do
debate = create(:debate)
notification = create :notification, notifiable: debate
expect(notification.notifiable_action).to eq "comments_on"
end
end
context "when action was comment on a debate" do
it "returns correct text when someone replies to your comment" do
debate = create(:debate)
debate_comment = create :comment, commentable: debate
notification = create :notification, notifiable: debate_comment
expect(notification.notifiable_action).to eq "replies_to"
end
end
context "when action was proposal notification" do
it "returns correct text when the author created a proposal notification" do
proposal_notification = create(:proposal_notification)
notification = create :notification, notifiable: proposal_notification
expect(notification.notifiable_action).to eq "proposal_notification"
end
end
end
end

View File

@@ -0,0 +1,59 @@
require 'rails_helper'
describe ProposalNotification do
let(:notification) { build(:proposal_notification) }
it "should be valid" do
expect(notification).to be_valid
end
it "should not be valid without a title" do
notification.title = nil
expect(notification).to_not be_valid
end
it "should not be valid without a body" do
notification.body = nil
expect(notification).to_not be_valid
end
it "should not be valid without an associated proposal" do
notification.proposal = nil
expect(notification).to_not be_valid
end
describe "minimum interval between notifications" do
before(:each) do
Setting[:proposal_notification_minimum_interval_in_days] = 3
end
it "should not be valid if below minium interval" do
proposal = create(:proposal)
notification1 = create(:proposal_notification, proposal: proposal)
notification2 = build(:proposal_notification, proposal: proposal)
proposal.reload
expect(notification2).to_not be_valid
end
it "should be valid if notifications above minium interval" do
proposal = create(:proposal)
notification1 = create(:proposal_notification, proposal: proposal, created_at: 4.days.ago)
notification2 = build(:proposal_notification, proposal: proposal)
proposal.reload
expect(notification2).to be_valid
end
it "should be valid if no notifications sent" do
notification1 = build(:proposal_notification)
expect(notification1).to be_valid
end
end
end

View File

@@ -83,6 +83,18 @@ describe User do
expect(subject.newsletter).to eq(true)
end
end
describe 'email_digest' do
it 'should be true by default' do
expect(subject.email_digest).to eq(true)
end
end
describe 'email_on_direct_message' do
it 'should be true by default' do
expect(subject.email_on_direct_message).to eq(true)
end
end
end
describe "administrator?" do

View File

@@ -196,4 +196,39 @@ module CommonActions
tag_cloud.tags.map(&:name)
end
def create_proposal_notification(proposal)
login_as(proposal.author)
visit root_path
click_link "My activity"
within("#proposal_#{proposal.id}") do
click_link "Send notification"
end
fill_in 'proposal_notification_title', with: "Thank you for supporting my proposal #{proposal.title}"
fill_in 'proposal_notification_body', with: "Please share it with others so we can make it happen! #{proposal.summary}"
click_button "Send message"
expect(page).to have_content "Your message has been sent correctly."
Notification.last
end
def create_direct_message(sender, receiver)
login_as(sender)
visit user_path(receiver)
click_link "Send private message"
expect(page).to have_content "Send private message to #{receiver.name}"
fill_in 'direct_message_title', with: "Hey #{receiver.name}!"
fill_in 'direct_message_body', with: "How are you doing? This is #{sender.name}"
click_button "Send message"
expect(page).to have_content "You message has been sent successfully."
DirectMessage.last
end
end