diff --git a/Gemfile b/Gemfile index d4a2777a9..7f9579b73 100644 --- a/Gemfile +++ b/Gemfile @@ -59,7 +59,7 @@ end group :development, :test do gem "bullet", '~> 5.5.1' - gem 'byebug', '~> 9.0.6' + gem 'byebug', '~> 9.1.0' gem 'factory_girl_rails', '~> 4.8.0' gem "faker", '~> 1.7.3' gem 'i18n-tasks', '~> 0.9.15' diff --git a/Gemfile.lock b/Gemfile.lock index 72b60628e..b3bdaed90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,7 +71,7 @@ GEM bullet (5.5.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.10.0) - byebug (9.0.6) + byebug (9.1.0) cancancan (1.16.0) capistrano (3.8.2) airbrussh (>= 1.0.0) @@ -494,7 +494,7 @@ DEPENDENCIES ancestry (~> 2.2.2) browser (~> 2.3.0) bullet (~> 5.5.1) - byebug (~> 9.0.6) + byebug (~> 9.1.0) cancancan (~> 1.16.0) capistrano (~> 3.8.1) capistrano-bundler (~> 1.2) diff --git a/app/assets/fonts/icons.eot b/app/assets/fonts/icons.eot index c1afd24fd..5f29191f7 100644 Binary files a/app/assets/fonts/icons.eot and b/app/assets/fonts/icons.eot differ diff --git a/app/assets/fonts/icons.svg b/app/assets/fonts/icons.svg index 68d878942..2b884a912 100644 --- a/app/assets/fonts/icons.svg +++ b/app/assets/fonts/icons.svg @@ -62,4 +62,5 @@ + diff --git a/app/assets/fonts/icons.ttf b/app/assets/fonts/icons.ttf index a9443499b..252457611 100644 Binary files a/app/assets/fonts/icons.ttf and b/app/assets/fonts/icons.ttf differ diff --git a/app/assets/fonts/icons.woff b/app/assets/fonts/icons.woff index 09a6f6dc5..2cf6dc4b7 100644 Binary files a/app/assets/fonts/icons.woff and b/app/assets/fonts/icons.woff differ diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index 33800fed5..a38071a05 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -411,7 +411,7 @@ $maincontent-shadow: 0 0 10px rgba($black, 0.5); $orbit-bullet-background: $medium-gray; $orbit-bullet-background-active: $dark-gray; -$orbit-bullet-diameter: 1.2rem; +$orbit-bullet-diameter: 0.8rem; $orbit-bullet-margin: 0.1rem; $orbit-bullet-margin-top: 0.8rem; $orbit-bullet-margin-bottom: 0.8rem; diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index e55832667..135b9fe12 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -97,10 +97,6 @@ content: '\72'; } -.icon-documents::before { - content: '\68'; -} - .icon-proposals::before { content: '\68'; } @@ -197,10 +193,6 @@ content: '\53'; } -.icon-image::before { - content: '\68'; -} - .icon-notification::before { content: '\6e'; } @@ -264,3 +256,7 @@ .icon-instagram::before { content: '\32'; } + +.icon-image::before { + content: '\33'; +} diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index e68cf3de5..44b450779 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -18,7 +18,8 @@ // 16. Flags // 17. Activity // 18. Banners -// 19. Documents +// 19. Recommended Section Home +// 20. Documents // // 01. Global styles @@ -341,10 +342,30 @@ a { background: $brand; } +.truncate-horizontal-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + -o-text-overflow: ellipsis; + -ms-text-overflow: ellipsis; +} + .align-top { vertical-align: top; } +.aling-middle { + vertical-align: middle; +} + +.table { + display: table; +} + +.table-cell { + display: table-cell; +} + // 02. Header // ---------- @@ -612,7 +633,7 @@ header { text-align: left; @include breakpoint(medium) { - margin-right: $line-height * 1.5; + margin-right: rem-calc(24); } &:hover { @@ -2161,6 +2182,142 @@ table { } } +// 19. Recommended Section Home +// ----------- + +.home-page { + .push { + display: none; + } +} + +.section-recommended { + padding: $line-height * 2 0; + + h2 { + margin-bottom: $line-height * 2; + } + + .debates, + .proposals, + .budget-investments { + + @include breakpoint(medium) { + margin-bottom: 0; + } + + @include breakpoint(small) { + margin-bottom: $line-height; + } + + .button.hollow { + margin-top: rem-calc(15); + } + } + + .card { + + .card-section { + padding: $line-height 0; + max-width: 300px; + margin: 0 auto; + + p { + font-size: rem-calc(15); + text-align: left; + } + } + + .orbit { + height: 300px; + + .orbit-wrapper { + max-height: 250px; + overflow: hidden; + position: relative; + } + + .orbit-bullets { + @include orbit-bullets; + width: 100%; + } + } + } + + .card .orbit .orbit-wrapper .truncate { + background: image-url('truncate.png'); + background-repeat: repeat-x; + bottom: 0; + height: 20px; + position: absolute; + width: 100%; + } + + .debates-inner { + border-top: 4px solid $debates; + } + + .proposals-inner { + border-top: 4px solid $proposals; + } + + .budget-investments-inner { + border-top: 4px solid $budget; + } + + .debates-inner, + .proposals-inner, + .budget-investments-inner { + background: #fff; + max-height: 350px; + + @include breakpoint(small) { + max-height: 400px; + } + + h4 { + margin-top: $line-height; + margin-bottom: 0; + font-size: rem-calc(18); + min-height: 50px; + } + + h5 { + font-size: rem-calc(14); + text-align: left; + } + } + + .carousel-image { + + .card .orbit { + height: 480px; + + .orbit-wrapper { + max-height: 450px; + } + } + + .debates-inner, + .proposals-inner, + .budget-investments-inner { + max-height: 500px; + + @include breakpoint(small) { + max-height: 600px; + } + } + } + + .carousel-image .orbit-wrapper img { + display: block; + + @include breakpoint(small) { + margin: 0 auto; + } + } +} + // 19. Documents .documents-list { @@ -2218,6 +2375,5 @@ table { } } - } } diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss index cc8fe9360..1aa51d1a6 100644 --- a/app/assets/stylesheets/mixins.scss +++ b/app/assets/stylesheets/mixins.scss @@ -1,6 +1,7 @@ // Table of Contents // // 01. Logo +// 02. Orbit bullets // // 01. Logo @@ -31,12 +32,40 @@ } } +// 02. Orbit bullet +// ---------------- +@mixin orbit-bullets { + @include disable-mouse-outline; + position: relative; + margin-top: $orbit-bullet-margin-top; + margin-bottom: $orbit-bullet-margin-bottom; + text-align: center; + button { + width: $orbit-bullet-diameter; + height: $orbit-bullet-diameter; + margin: $orbit-bullet-margin; + + border-radius: 50%; + background-color: $orbit-bullet-background; + + &:hover { + background-color: $orbit-bullet-background-active; + } + + &.is-active { + background-color: $orbit-bullet-background-active; + } + } +} + +// 02. Direct uploads +// ------------------ @mixin direct-uploads { .cached-image { - max-width: 150px; - max-height: 150px; + max-width: rem-calc(150); + max-height: rem-calc(150); } .progress-bar-placeholder { @@ -49,15 +78,23 @@ .document-attachment, .image-attachment { - padding-left:0; + padding-left: 0; - p{ + p { margin-bottom: 0; } } - input.js-document-attachment, - input.js-image-attachment{ - display: none; + + .attachment-errors { + + > .js-image-attachment, + > .js-document-attachment { + display: none; + + ~ .error { + display: inline-block; + } + } } } @@ -96,4 +133,5 @@ .loading-bar.no-transition { transition: none; } + } diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index fbcda0ebb..b1edc246d 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -255,7 +255,6 @@ .icon-debates, .icon-proposals, .icon-budget, - .icon-documents, .icon-image { font-size: rem-calc(50); line-height: $line-height; @@ -267,7 +266,6 @@ } .icon-proposals, - .icon-documents, .icon-image { color: $proposals; } @@ -312,12 +310,10 @@ .budget-investment-new, .proposal-form, .proposal-edit, -.new_poll_question, -.edit_poll_question { +.poll-question-form { @include direct-uploads; } - // 03. Show participation // ---------------------- @@ -358,8 +354,7 @@ width: rem-calc(48); } - .edit-debate, - .edit-proposal { + .edit-debate { margin-bottom: 0; } @@ -661,20 +656,17 @@ .proposals-list .proposal { @include breakpoint(small) { + .no-image { width: 100%; - max-width: 300px; + max-width: rem-calc(300); margin: 0 auto; - } - .no-image::before { - content: ''; - display: block; - padding-top: 100%; - } - - h3 { - font-size: 1.3rem; + &::before { + content: ''; + display: block; + padding-top: 100%; + } } .column:first-child { @@ -683,19 +675,17 @@ } @include breakpoint(medium) { + .panel { - padding: 0 0.75rem 0 0; + padding: 0 $line-height / 2 0 0; .no-image { - height: 245px; - width: 140px; + height: 100%; + min-height: rem-calc(245); + width: rem-calc(140); } } - h3 { - font-size: 1.4rem; - } - .column:first-child { overflow: hidden; } @@ -705,7 +695,7 @@ } .column:last-child:not(:first-child) { - padding-top: 0.75rem; + padding-top: $line-height / 2; } img { @@ -1701,9 +1691,13 @@ } .section-title-divider { - border-bottom: 2px solid $brand; - color: $brand; - margin-bottom: $line-height; + border-bottom: 1px solid #eee; + color: #000; + margin: $line-height 0; + + span { + border-bottom: 1px solid #000; + } } .poll-question { diff --git a/app/controllers/admin/poll/booth_assignments_controller.rb b/app/controllers/admin/poll/booth_assignments_controller.rb index 06eb233bd..8ffa455c8 100644 --- a/app/controllers/admin/poll/booth_assignments_controller.rb +++ b/app/controllers/admin/poll/booth_assignments_controller.rb @@ -15,7 +15,7 @@ class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController end def show - @booth_assignment = @poll.booth_assignments.includes(:total_results, :voters, + @booth_assignment = @poll.booth_assignments.includes(:recounts, :voters, officer_assignments: [officer: [:user]]).find(params[:id]) @voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date} end diff --git a/app/controllers/admin/poll/officer_assignments_controller.rb b/app/controllers/admin/poll/officer_assignments_controller.rb index 7fa120aa6..fd62df8b3 100644 --- a/app/controllers/admin/poll/officer_assignments_controller.rb +++ b/app/controllers/admin/poll/officer_assignments_controller.rb @@ -18,7 +18,7 @@ class Admin::Poll::OfficerAssignmentsController < Admin::Poll::BaseController @officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id]) @officer_assignments = ::Poll::OfficerAssignment. joins(:booth_assignment). - includes(:total_results, booth_assignment: :booth). + includes(:recounts, booth_assignment: :booth). where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id). order(:date) end diff --git a/app/controllers/admin/poll/recounts_controller.rb b/app/controllers/admin/poll/recounts_controller.rb index 6d4a9a442..32c533eec 100644 --- a/app/controllers/admin/poll/recounts_controller.rb +++ b/app/controllers/admin/poll/recounts_controller.rb @@ -3,7 +3,7 @@ class Admin::Poll::RecountsController < Admin::Poll::BaseController def index @booth_assignments = @poll.booth_assignments. - includes(:booth, :total_results, :voters). + includes(:booth, :recounts, :voters). order("poll_booths.name"). page(params[:page]).per(50) end diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb index 4a1098a28..826d90da9 100644 --- a/app/controllers/concerns/commentable_actions.rb +++ b/app/controllers/concerns/commentable_actions.rb @@ -4,17 +4,22 @@ module CommentableActions include Search def index - @resources = @search_terms.present? ? resource_model.search(@search_terms) : resource_model.all - @resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources + @resources = resource_model.all + @resources = @current_order == "recommendations" && current_user.present? ? @resources.recommendations(current_user) : @resources.for_render + @resources = @resources.search(@search_terms) if @search_terms.present? + @resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources @resources = @resources.tagged_with(@tag_filter) if @tag_filter - @resources = @resources.page(params[:page]).for_render.send("sort_by_#{@current_order}") + + @resources = @resources.page(params[:page]).send("sort_by_#{@current_order}") + index_customization if index_customization.present? @tag_cloud = tag_cloud @banners = Banner.with_active set_resource_votes(@resources) + set_resources_instance end diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index b8d6bdf10..cd113d486 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -10,7 +10,7 @@ class DebatesController < ApplicationController invisible_captcha only: [:create, :update], honeypot: :subtitle - has_orders %w{hot_score confidence_score created_at relevance}, only: :index + has_orders ->(c) { Debate.debates_orders(c.current_user) }, only: :index has_orders %w{most_voted newest oldest}, only: :show load_and_authorize_resource diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 99f401f7b..16a82de6a 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -11,7 +11,7 @@ class NotificationsController < ApplicationController def show @notification = current_user.notifications.find(params[:id]) - redirect_to url_for(@notification.linkable_resource) + redirect_to linkable_resource_path(@notification) end def mark_all_as_read @@ -25,4 +25,13 @@ class NotificationsController < ApplicationController @notification.mark_as_read end + def linkable_resource_path(notification) + case notification.linkable_resource.class.name + when "Budget::Investment" + budget_investment_path @notification.linkable_resource.budget, @notification.linkable_resource + else + url_for @notification.linkable_resource + end + end + end diff --git a/app/controllers/officing/results_controller.rb b/app/controllers/officing/results_controller.rb index 3ad7a91ea..f517f081a 100644 --- a/app/controllers/officing/results_controller.rb +++ b/app/controllers/officing/results_controller.rb @@ -26,9 +26,7 @@ class Officing::ResultsController < Officing::BaseController @partial_results = ::Poll::PartialResult.includes(:question). where(booth_assignment_id: index_params[:booth_assignment_id]). where(date: index_params[:date]) - @whites = ::Poll::WhiteResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) - @nulls = ::Poll::NullResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) - @total = ::Poll::TotalResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) + @recounts = ::Poll::Recount.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]) end end @@ -52,14 +50,14 @@ class Officing::ResultsController < Officing::BaseController go_back_to_new if question.blank? results.each_pair do |answer_index, count| - next unless count.present? + next if count.blank? answer = question.valid_answers[answer_index.to_i] go_back_to_new if question.blank? partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, - date: results_params[:date], - question_id: question_id, - answer: answer) + date: results_params[:date], + question_id: question_id, + answer: answer) partial_result.officer_assignment_id = @officer_assignment.id partial_result.amount = count.to_i partial_result.author = current_user @@ -68,45 +66,21 @@ class Officing::ResultsController < Officing::BaseController end end - build_white_results - build_null_results - build_total_results + build_recounts end - def build_white_results - if results_params[:whites].present? - white_result = ::Poll::WhiteResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, - date: results_params[:date]) - white_result.officer_assignment_id = @officer_assignment.id - white_result.amount = results_params[:whites].to_i - white_result.author = current_user - white_result.origin = 'booth' - @results << white_result - end - end - - def build_null_results - if results_params[:nulls].present? - null_result = ::Poll::NullResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, - date: results_params[:date]) - null_result.officer_assignment_id = @officer_assignment.id - null_result.amount = results_params[:nulls].to_i - null_result.author = current_user - null_result.origin = 'booth' - @results << null_result - end - end - - def build_total_results - if results_params[:total].present? - total_result = ::Poll::TotalResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, - date: results_params[:date]) - total_result.officer_assignment_id = @officer_assignment.id - total_result.amount = results_params[:total].to_i - total_result.author = current_user - total_result.origin = 'booth' - @results << total_result + def build_recounts + recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date]) + recount.officer_assignment_id = @officer_assignment.id + recount.author = current_user + recount.origin = 'booth' + [:whites, :nulls, :total].each do |recount_type| + if results_params[recount_type].present? + recount["#{recount_type.to_s.singularize}_amount"] = results_params[recount_type].to_i + end end + @results << recount end def go_back_to_new(alert = nil) diff --git a/app/controllers/officing/voters_controller.rb b/app/controllers/officing/voters_controller.rb index dee1e00bd..a5343f83d 100644 --- a/app/controllers/officing/voters_controller.rb +++ b/app/controllers/officing/voters_controller.rb @@ -12,7 +12,8 @@ class Officing::VotersController < Officing::BaseController @voter = Poll::Voter.new(document_type: @user.document_type, document_number: @user.document_number, user: @user, - poll: @poll) + poll: @poll, + origin: "booth") @voter.save! end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index d1db0e4a2..e86caf922 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -9,7 +9,7 @@ class ProposalsController < ApplicationController invisible_captcha only: [:create, :update], honeypot: :subtitle - has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index + has_orders ->(c) { Proposal.proposals_orders(c.current_user) }, only: :index has_orders %w{most_voted newest oldest}, only: :show load_and_authorize_resource @@ -113,7 +113,7 @@ class ProposalsController < ApplicationController end def load_featured - return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank? + return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank? && @current_order != "recommendations" @featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3) if @featured_proposals.present? set_featured_proposal_votes(@featured_proposals) diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb index d0c650fa3..73b2b3aba 100644 --- a/app/controllers/welcome_controller.rb +++ b/app/controllers/welcome_controller.rb @@ -1,12 +1,10 @@ class WelcomeController < ApplicationController skip_authorization_check + before_action :set_user_recommendations, only: :index, if: :current_user layout "devise", only: [:welcome, :verification] def index - if current_user - redirect_to :proposals - end end def welcome @@ -16,4 +14,11 @@ class WelcomeController < ApplicationController redirect_to verification_path if signed_in? end + private + + def set_user_recommendations + @recommended_debates = Debate.recommendations(current_user).sort_by_recommendations.limit(3) + @recommended_proposals = Proposal.recommendations(current_user).sort_by_recommendations.limit(3) + end + end diff --git a/app/helpers/debates_helper.rb b/app/helpers/debates_helper.rb index 8db989f61..e880f0831 100644 --- a/app/helpers/debates_helper.rb +++ b/app/helpers/debates_helper.rb @@ -4,4 +4,12 @@ module DebatesHelper Debate.all.featured.count > 0 end -end \ No newline at end of file + def empty_recommended_debates_message_text(user) + if user.interests.any? + t('debates.index.recommendations.without_results') + else + t('debates.index.recommendations.without_interests') + end + end + +end diff --git a/app/helpers/poll_recounts_helper.rb b/app/helpers/poll_recounts_helper.rb index 716b71d85..45eefd696 100644 --- a/app/helpers/poll_recounts_helper.rb +++ b/app/helpers/poll_recounts_helper.rb @@ -1,7 +1,7 @@ module PollRecountsHelper def total_recounts_by_booth(booth_assignment) - booth_assignment.total_results.any? ? booth_assignment.total_results.to_a.sum(&:amount) : nil + booth_assignment.recounts.any? ? booth_assignment.recounts.to_a.sum(&:total_amount) : nil end end diff --git a/app/helpers/proposals_helper.rb b/app/helpers/proposals_helper.rb index 77382253d..9d5ac40cc 100644 --- a/app/helpers/proposals_helper.rb +++ b/app/helpers/proposals_helper.rb @@ -32,6 +32,14 @@ module ProposalsHelper Proposal::RETIRE_OPTIONS.collect { |option| [ t("proposals.retire_options.#{option}"), option ] } end + def empty_recommended_proposals_message_text(user) + if user.interests.any? + t('proposals.index.recommendations.without_results') + else + t('proposals.index.recommendations.without_interests') + end + end + def author_of_proposal?(proposal) author_of?(proposal, current_user) end @@ -40,4 +48,4 @@ module ProposalsHelper current_user && proposal.editable_by?(current_user) end -end \ No newline at end of file +end diff --git a/app/helpers/welcome_helper.rb b/app/helpers/welcome_helper.rb new file mode 100644 index 000000000..9f8c8c13e --- /dev/null +++ b/app/helpers/welcome_helper.rb @@ -0,0 +1,62 @@ +module WelcomeHelper + + def active_class(index) + "is-active is-in" if index == 0 + end + + def slide_display(index) + "display: none;" if index > 0 + end + + def recommended_path(recommended) + case recommended.class.name + when "Debate" + debate_path(recommended) + when "Proposal" + proposal_path(recommended) + else + '#' + end + end + + def render_recommendation_image(recommended, image_default) + image_path = calculate_image_path(recommended, image_default) + image_tag(image_path) if image_path.present? + end + + def calculate_image_path(recommended, image_default) + if recommended.try(:image) && recommended.image.present? && recommended.image.attachment.exists? + recommended.image.attachment.send("url", :medium) + elsif image_default.present? + image_default + end + end + + def calculate_carousel_size(debates, proposals, apply_offset) + offset = calculate_offset(debates, proposals, apply_offset) + centered = calculate_centered(debates, proposals) + "#{offset if offset} #{centered if centered}" + end + + def calculate_centered(debates, proposals) + if (debates.blank? && proposals.any?) || + (debates.any? && proposals.blank?) + centered = "medium-centered large-centered" + end + end + + def calculate_offset(debates, proposals, apply_offset) + if (debates.any? && proposals.any?) + if apply_offset + offset = "medium-offset-2 large-offset-2" + else + offset = "end" + end + end + end + + def highlight_background + (feature?("user.recommendations") && current_user) ? "highlight" : "" + end + +end diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 4278d4f8d..793f2cbd8 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -45,7 +45,7 @@ module Abilities can [:read, :update, :valuate, :destroy, :summary], SpendingProposal - can [:index, :read, :new, :create, :update, :destroy, :calculate_winners], Budget + can [:index, :read, :new, :create, :update, :destroy, :calculate_winners, :read_results], Budget can [:read, :create, :update, :destroy], Budget::Group can [:read, :create, :update, :destroy], Budget::Heading can [:hide, :update, :toggle_selection], Budget::Investment diff --git a/app/models/concerns/followable.rb b/app/models/concerns/followable.rb index 84853b8ca..fee1ebd7f 100644 --- a/app/models/concerns/followable.rb +++ b/app/models/concerns/followable.rb @@ -4,6 +4,10 @@ module Followable included do has_many :follows, as: :followable, dependent: :destroy has_many :followers, through: :follows, source: :user + + scope :followed_by_user, -> (user){ + joins(:follows).where("follows.user_id = ?", user.id) + } end def followed_by?(user) diff --git a/app/models/debate.rb b/app/models/debate.rb index 96c723db2..eb424c308 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -37,14 +37,21 @@ class Debate < ActiveRecord::Base scope :sort_by_random, -> { reorder("RANDOM()") } scope :sort_by_relevance, -> { all } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } + scope :sort_by_recommendations, -> { order(cached_votes_total: :desc) } scope :last_week, -> { where("created_at >= ?", 7.days.ago)} scope :featured, -> { where("featured_at is not null")} scope :public_for_api, -> { all } + # Ahoy setup visitable # Ahoy will automatically assign visit_id on create attr_accessor :link_required + def self.recommendations(user) + tagged_with(user.interests, any: true). + where("author_id != ?", user.id) + end + def searchable_values { title => 'A', author.username => 'B', @@ -135,4 +142,9 @@ class Debate < ActiveRecord::Base featured_at.present? end + def self.debates_orders(user) + orders = %w{hot_score confidence_score created_at relevance} + orders << "recommendations" if user.present? + orders + end end diff --git a/app/models/poll.rb b/app/models/poll.rb index 24ffa3a06..624cfc704 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -2,9 +2,7 @@ class Poll < ActiveRecord::Base has_many :booth_assignments, class_name: "Poll::BoothAssignment" has_many :booths, through: :booth_assignments has_many :partial_results, through: :booth_assignments - has_many :white_results, through: :booth_assignments - has_many :null_results, through: :booth_assignments - has_many :total_results, through: :booth_assignments + has_many :recounts, through: :booth_assignments has_many :voters has_many :officer_assignments, through: :booth_assignments has_many :officers, through: :officer_assignments @@ -61,6 +59,10 @@ class Poll < ActiveRecord::Base voters.where(document_number: document_number, document_type: document_type).exists? end + def voted_in_booth?(user) + Poll::Voter.where(poll: self, user: user, origin: "booth").exists? + end + def date_range unless starts_at.present? && ends_at.present? && starts_at <= ends_at errors.add(:starts_at, I18n.t('errors.messages.invalid_date_range')) diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb index a1ff30f0a..01eebd553 100644 --- a/app/models/poll/answer.rb +++ b/app/models/poll/answer.rb @@ -8,12 +8,13 @@ class Poll::Answer < ActiveRecord::Base validates :question, presence: true validates :author, presence: true validates :answer, presence: true - validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }}, + unless: ->(a) { a.question.blank? } scope :by_author, ->(author_id) { where(author_id: author_id) } scope :by_question, ->(question_id) { where(question_id: question_id) } def record_voter_participation - Poll::Voter.create!(user: author, poll: poll) + Poll::Voter.find_or_create_by!(user: author, poll: poll, origin: "web") end end \ No newline at end of file diff --git a/app/models/poll/booth_assignment.rb b/app/models/poll/booth_assignment.rb index 8b4b655ef..8489c3cf0 100644 --- a/app/models/poll/booth_assignment.rb +++ b/app/models/poll/booth_assignment.rb @@ -7,8 +7,6 @@ class Poll has_many :officers, through: :officer_assignments has_many :voters has_many :partial_results - has_many :white_results - has_many :null_results - has_many :total_results + has_many :recounts end end diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb index 8e86d309c..9fcf02890 100644 --- a/app/models/poll/officer_assignment.rb +++ b/app/models/poll/officer_assignment.rb @@ -3,9 +3,7 @@ class Poll belongs_to :officer belongs_to :booth_assignment has_many :partial_results - has_many :white_results - has_many :null_results - has_many :total_results + has_many :recounts has_many :voters validates :officer_id, presence: true diff --git a/app/models/poll/recount.rb b/app/models/poll/recount.rb new file mode 100644 index 000000000..74a6fe937 --- /dev/null +++ b/app/models/poll/recount.rb @@ -0,0 +1,36 @@ +class Poll::Recount < ActiveRecord::Base + + VALID_ORIGINS = %w{web booth letter}.freeze + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :author, presence: true + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :web, -> { where(origin: 'web') } + scope :booth, -> { where(origin: 'booth') } + scope :letter, -> { where(origin: 'letter') } + + scope :by_author, ->(author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + amounts_changed = false + + [:white, :null, :total].each do |amount| + next unless send("#{amount}_amount_changed?") && send("#{amount}_amount_was").present? + self["#{amount}_amount_log"] += ":#{send("#{amount}_amount_was")}" + amounts_changed = true + end + + update_officer_author if amounts_changed + end + + def update_officer_author + self.officer_assignment_id_log += ":#{officer_assignment_id_was}" + self.author_id_log += ":#{author_id_was}" + end +end diff --git a/app/models/poll/voter.rb b/app/models/poll/voter.rb index 760096206..abcae7d25 100644 --- a/app/models/poll/voter.rb +++ b/app/models/poll/voter.rb @@ -1,5 +1,8 @@ class Poll class Voter < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + belongs_to :poll belongs_to :user belongs_to :geozone @@ -10,9 +13,13 @@ class Poll validates :user_id, presence: true validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted } + validates :origin, inclusion: { in: VALID_ORIGINS } before_validation :set_demographic_info, :set_document_info + scope :web, -> { where(origin: 'web') } + scope :booth, -> { where(origin: 'booth') } + def set_demographic_info return if user.blank? diff --git a/app/models/proposal.rb b/app/models/proposal.rb index da0fc109f..1a688ab30 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -58,14 +58,27 @@ class Proposal < ActiveRecord::Base scope :sort_by_relevance, -> { all } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } scope :sort_by_archival_date, -> { archived.sort_by_confidence_score } + scope :sort_by_recommendations, -> { order(cached_votes_up: :desc) } scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago) } scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago) } scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)} scope :retired, -> { where.not(retired_at: nil) } scope :not_retired, -> { where(retired_at: nil) } scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) } + scope :unsuccessful, -> { where("cached_votes_up < ?", Proposal.votes_needed_for_success) } scope :public_for_api, -> { all } + def self.recommendations(user) + tagged_with(user.interests, any: true). + where("author_id != ?", user.id). + unsuccessful. + not_followed_by_user(user) + end + + def self.not_followed_by_user(user) + where.not(id: followed_by_user(user).pluck(:id)) + end + def to_param "#{id}-#{title}".parameterize end @@ -185,6 +198,12 @@ class Proposal < ActiveRecord::Base (voters + followers).uniq end + def self.proposals_orders(user) + orders = %w{hot_score confidence_score created_at relevance archival_date} + orders << "recommendations" if user.present? + orders + end + protected def set_responsible_name diff --git a/app/models/user.rb b/app/models/user.rb index 006d1a09e..80cf75f61 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -88,12 +88,12 @@ class User < ActiveRecord::Base end def debate_votes(debates) - voted = votes.for_debates(debates) + voted = votes.for_debates(Array(debates).map(&:id)) voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } end def proposal_votes(proposals) - voted = votes.for_proposals(proposals) + voted = votes.for_proposals(Array(proposals).map(&:id)) voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } end diff --git a/app/views/admin/poll/officer_assignments/by_officer.html.erb b/app/views/admin/poll/officer_assignments/by_officer.html.erb index 6ea421e2d..cefe161d9 100644 --- a/app/views/admin/poll/officer_assignments/by_officer.html.erb +++ b/app/views/admin/poll/officer_assignments/by_officer.html.erb @@ -43,8 +43,8 @@ <%= l(officer_assignment.date.to_date) %> <%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> - <% if officer_assignment.total_results.any? %> - <%= officer_assignment.total_results.to_a.sum(&:amount) %> + <% if officer_assignment.recounts.any? %> + <%= officer_assignment.recounts.to_a.sum(&:total_amount) %> <% else %> - <% end %> diff --git a/app/views/admin/poll/questions/edit.html.erb b/app/views/admin/poll/questions/edit.html.erb index 48998081a..fffc4a399 100644 --- a/app/views/admin/poll/questions/edit.html.erb +++ b/app/views/admin/poll/questions/edit.html.erb @@ -2,4 +2,6 @@

<%= t("admin.questions.edit.title") %>

-<%= render "form", form_url: admin_question_path(@question) %> \ No newline at end of file +
+ <%= render "form", form_url: admin_question_path(@question) %> +
diff --git a/app/views/admin/poll/questions/new.html.erb b/app/views/admin/poll/questions/new.html.erb index 844195920..c91b78016 100644 --- a/app/views/admin/poll/questions/new.html.erb +++ b/app/views/admin/poll/questions/new.html.erb @@ -2,4 +2,6 @@

<%= t("admin.questions.new.title") %>

-<%= render "form", form_url: admin_questions_path %> \ No newline at end of file +
+ <%= render "form", form_url: admin_questions_path %> +
diff --git a/app/views/admin/poll/results/index.html.erb b/app/views/admin/poll/results/index.html.erb index 64a93aa10..c65b3ead7 100644 --- a/app/views/admin/poll/results/index.html.erb +++ b/app/views/admin/poll/results/index.html.erb @@ -18,9 +18,9 @@ - <%= @poll.white_results.sum(:amount) %> - <%= @poll.null_results.sum(:amount) %> - <%= @poll.total_results.sum(:amount) %> + <%= @poll.recounts.sum(:white_amount) %> + <%= @poll.recounts.sum(:null_amount) %> + <%= @poll.recounts.sum(:total_amount) %> diff --git a/app/views/budgets/investments/_investment.html.erb b/app/views/budgets/investments/_investment.html.erb index e663229ad..2c6269a3f 100644 --- a/app/views/budgets/investments/_investment.html.erb +++ b/app/views/budgets/investments/_investment.html.erb @@ -1,13 +1,15 @@
-
+
- <% if investment.image.present? %> - <%= image_tag investment.image_url(:thumb), alt: investment.image.title %> - <% else %> -
- <% end %> +
+ <% if investment.image.present? %> + <%= image_tag investment.image_url(:thumb), alt: investment.image.title %> + <% else %> +
+ <% end %> +
@@ -53,7 +55,7 @@ <% if investment.should_show_votes? %>
+ class="small-12 medium-3 column text-center" data-equalizer-watch> <%= render partial: '/budgets/investments/votes', locals: { investment: investment, investment_votes: investment_votes, @@ -62,7 +64,7 @@
<% elsif investment.should_show_vote_count? %>
+ class="small-12 medium-3 column text-center" data-equalizer-watch>
<%= t("budgets.investments.investment.supports", @@ -72,7 +74,7 @@
<% elsif investment.should_show_ballots? %>
+ class="small-12 medium-3 column text-center" data-equalizer-watch> <%= render partial: '/budgets/investments/ballot', locals: { investment: investment, investment_ids: investment_ids, @@ -81,11 +83,13 @@
<% elsif investment.should_show_price? %>
+ class="supports small-12 medium-3 column text-center" data-equalizer-watch>

<%= investment.formatted_price %>

+ <% else %> +
<% end %> <% end %> diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index 814488d9d..99e9cae3c 100644 --- a/app/views/budgets/show.html.erb +++ b/app/views/budgets/show.html.erb @@ -36,7 +36,7 @@ <% end %> <% end %> - <% if @budget.finished? %> + <% if @budget.finished? || (@budget.balloting? && can?(:read_results, @budget)) %> <%= link_to t("budgets.show.see_results"), budget_results_path(@budget, heading_id: @budget.headings.first), class: "button margin-top expanded" %> diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb index 01442c3e4..f06f43923 100644 --- a/app/views/debates/index.html.erb +++ b/app/views/debates/index.html.erb @@ -54,7 +54,11 @@ <%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button expanded' %>
- <%= render @debates %> + <% if @debates.any? || current_user.blank? %> + <%= render @debates %> + <% else %> + <%= empty_recommended_debates_message_text(current_user) %> + <% end %> <%= paginate @debates %> <% unless @search_terms || @advanced_search_terms || @tag_filter %> diff --git a/app/views/documents/_nested_documents.html.erb b/app/views/documents/_nested_documents.html.erb index ecbe46ce7..04fd4e8a2 100644 --- a/app/views/documents/_nested_documents.html.erb +++ b/app/views/documents/_nested_documents.html.erb @@ -20,7 +20,4 @@
"> <%= t "documents.max_documents_allowed_reached_html" %>
- -
- -
\ No newline at end of file +
diff --git a/app/views/images/_image_fields.html.erb b/app/views/images/_image_fields.html.erb index 10a4cf898..092402706 100644 --- a/app/views/images/_image_fields.html.erb +++ b/app/views/images/_image_fields.html.erb @@ -28,4 +28,5 @@
+
diff --git a/app/views/images/_nested_image.html.erb b/app/views/images/_nested_image.html.erb index a981dd190..2f65b8760 100644 --- a/app/views/images/_nested_image.html.erb +++ b/app/views/images/_nested_image.html.erb @@ -1,12 +1,10 @@ -
- <%= f.label :image, t("images.form.title") %> -

<%= imageables_note(imageable) %>

+<%= f.label :image, t("images.form.title") %> +

<%= imageables_note(imageable) %>

-
- <%= f.fields_for :image do |image_builder| %> - <%= render 'images/image_fields', f: image_builder, imageable: imageable %> - <% end %> -
+
+ <%= f.fields_for :image do |image_builder| %> + <%= render 'images/image_fields', f: image_builder, imageable: imageable %> + <% end %>
<%= link_to_add_association t('images.form.add_new_image'), f, :image, @@ -21,5 +19,3 @@ association_insertion_node: "#nested-image", association_insertion_method: "append" } %> - -
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index f88a4f136..4ed17ed3d 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -23,7 +23,7 @@ <%= setting['per_page_code_head'].try(:html_safe) %> - + <%= setting['per_page_code_body'].try(:html_safe) %>

<%= setting['org_name'] %>

diff --git a/app/views/officing/results/index.html.erb b/app/views/officing/results/index.html.erb index 3b14e3c97..924d7fc13 100644 --- a/app/views/officing/results/index.html.erb +++ b/app/views/officing/results/index.html.erb @@ -21,9 +21,9 @@ - <%= @whites %> - <%= @nulls %> - <%= @total %> + <%= @recounts.sum(:white_amount) %> + <%= @recounts.sum(:null_amount) %> + <%= @recounts.sum(:total_amount) %> diff --git a/app/views/polls/_poll_group.html.erb b/app/views/polls/_poll_group.html.erb index e3e5c5976..9372aee17 100644 --- a/app/views/polls/_poll_group.html.erb +++ b/app/views/polls/_poll_group.html.erb @@ -33,23 +33,30 @@ <%= t("polls.index.cant_answer") %>
<% end %> -
-
-
<%= poll_dates(poll) %>
+
+
+ +
 
+ +
+
+
<% if poll.questions.count == 1 %> <% poll.questions.each do |question| %> -

<%= link_to question.title, poll %>

+

<%= link_to question.title, poll %>

+ <%= poll_dates(poll) %> <% end %> <% else %> -

<%= link_to poll.name, poll %>

-
    +

    <%= link_to poll.name, poll %>

    + <%= poll_dates(poll) %> +
      <% poll.questions.each do |question| %>
    • <%= link_to question.title, question_path(question) %>
    • <% end %>
    <% end %> <% if poll.geozones.any? %> -

    +

    <%= t("polls.index.geozone_info") %>

    <% end %> @@ -59,16 +66,18 @@ <% end %>
-
- <%= link_to poll, class: "button expanded" do %> - <% if poll.expired? %> - <%= t("polls.index.participate_button_expired") %> - <% elsif poll.incoming? %> - <%= t("polls.index.participate_button_incoming") %> - <% else %> - <%= t("polls.index.participate_button") %> +
+
+ <%= link_to poll, class: "button hollow expanded" do %> + <% if poll.expired? %> + <%= t("polls.index.participate_button_expired") %> + <% elsif poll.incoming? %> + <%= t("polls.index.participate_button_incoming") %> + <% else %> + <%= t("polls.index.participate_button") %> + <% end %> <% end %> - <% end %> +
diff --git a/app/views/polls/index.html.erb b/app/views/polls/index.html.erb index 871b04953..6877cd31d 100644 --- a/app/views/polls/index.html.erb +++ b/app/views/polls/index.html.erb @@ -6,18 +6,22 @@ <%= render "shared/section_header", i18n_namespace: "polls.index.section_header", image: "polls" %>
-
+
<%= render 'shared/filter_subnav', i18n_namespace: "polls.index" %> <% polls_by_geozone_restriction = @polls.group_by(&:geozone_restricted) %> <% if polls_by_geozone_restriction[false].present? %> -

<%= t("polls.index.no_geozone_restricted") %>

+

+ <%= t("polls.index.no_geozone_restricted") %> +

<%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[false]} %> <% end %> <% if polls_by_geozone_restriction[true].present? %> -

<%= t("polls.index.geozone_restricted") %>

+

+ <%= t("polls.index.geozone_restricted") %> +

<%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[true]} %> <% end %> diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb index 02ab70525..6895397de 100644 --- a/app/views/polls/show.html.erb +++ b/app/views/polls/show.html.erb @@ -31,8 +31,14 @@
- <% @questions.each do |question| %> - <%= render 'polls/questions/question', question: question %> + <% if @poll.voted_in_booth?(current_user) %> +
+ <%= t("polls.show.already_voted_in_booth") %> +
+ <% else %> + <% @questions.each do |question| %> + <%= render 'polls/questions/question', question: question %> + <% end %> <% end %>
diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb index eb02ea316..be5ac8e17 100644 --- a/app/views/proposals/_proposal.html.erb +++ b/app/views/proposals/_proposal.html.erb @@ -3,14 +3,16 @@ data-type="proposal">
-
+
- <% if proposal.image.present? %> - <%= image_tag proposal.image_url(:thumb), alt: proposal.image.title %> - <% else %> -
- <% end %> +
+ <% if proposal.image.present? %> + <%= image_tag proposal.image_url(:thumb), alt: proposal.image.title %> + <% else %> +
+ <% end %> +
@@ -58,7 +60,7 @@
-
+
<% if proposal.successful? %>
diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index a2b2901ba..b5710dfc7 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -14,13 +14,13 @@
<% if @search_terms || @advanced_search_terms %> -

<%= t("shared.search_results") %>

-

- <%= page_entries_info @proposals %> - <% if !@advanced_search_terms %> - <%= t("proposals.index.search_results_html", count: @proposals.size, search_term: @search_terms) %> - <% end %> -

+

<%= t("shared.search_results") %>

+

+ <%= page_entries_info @proposals %> + <% if !@advanced_search_terms %> + <%= t("proposals.index.search_results_html", count: @proposals.size, search_term: @search_terms) %> + <% end %> +

<% elsif @tag_filter %>

<%= t("shared.search_results") %>

@@ -68,7 +68,11 @@ <% end %>

- <%= render partial: 'proposals/proposal', collection: @proposals %> + <% if @proposals.any? || current_user.blank? %> + <%= render partial: 'proposals/proposal', collection: @proposals %> + <% else %> + <%= empty_recommended_proposals_message_text(current_user) %> + <% end %> <%= paginate @proposals %> <% unless @search_terms || @advanced_search_terms || @tag_filter %> diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index e25bdf9f7..ade0972d8 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -117,6 +117,13 @@

<%= t("proposals.show.author") %>

+ <% if current_editable?(@proposal) %> + <%= link_to edit_proposal_path(@proposal), class: 'button hollow expanded' do %> + + <%= t("proposals.show.edit_proposal_link") %> + <% end %> + <% end %> + <% if author_of_proposal?(@proposal) %> <%= link_to new_proposal_notification_path(proposal_id: @proposal.id), class: 'button hollow expanded' do %> @@ -128,20 +135,13 @@ <% if can_destroy_image?(@proposal) %> <%= link_to image_path(@proposal.image, from: request.url), method: :delete, - class: 'button hollow expanded', + class: 'button hollow alert expanded', data: { confirm: t('images.actions.destroy.confirm') } do %> - + <%= t("images.remove_image") %> <% end %> <% end %> - <% if current_editable?(@proposal) %> - <%= link_to edit_proposal_path(@proposal), class: 'edit-proposal button hollow expanded' do %> - - <%= t("proposals.show.edit_proposal_link") %> - <% end %> - <% end %> -
<% end %> diff --git a/app/views/welcome/_recommended.html.erb b/app/views/welcome/_recommended.html.erb new file mode 100644 index 000000000..67251e749 --- /dev/null +++ b/app/views/welcome/_recommended.html.erb @@ -0,0 +1,31 @@ + diff --git a/app/views/welcome/_recommended_carousel.html.erb b/app/views/welcome/_recommended_carousel.html.erb new file mode 100644 index 000000000..895618ace --- /dev/null +++ b/app/views/welcome/_recommended_carousel.html.erb @@ -0,0 +1,42 @@ + diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb index b8c361fed..0ea0f748d 100644 --- a/app/views/welcome/index.html.erb +++ b/app/views/welcome/index.html.erb @@ -1,3 +1,5 @@ +<% content_for :body_class, "home-page" %> + <% content_for :canonical do %> <%= render "shared/canonical", href: root_url %> <% end %> @@ -16,37 +18,35 @@
+<% if feature?("user.recommendations") && (@recommended_debates.present? || @recommended_proposals.present?) %> + <%= render "recommended", + recommended_debates: @recommended_debates, + recommended_proposals: @recommended_proposals %> +<% end %> + <% cache [locale_and_user_status, @featured_debates, @featured_proposals, 'featured'] do %>
-
-
-

- <%= t("welcome.debates.title") %>
- <%= t("welcome.debates.description") %> -

-
-
-

- <%= t("welcome.proposal.title") %>
- <%= t("welcome.proposal.description") %> -

+
+
+
+

<%= t("welcome.debates.title") %>

+

<%= t("welcome.debates.description") %>

+
+
+

<%= t("welcome.proposal.title") %>

+

<%= t("welcome.proposal.description") %>

+
+
+

<%= t("welcome.decide.title") %>

+

<%= t("welcome.decide.description") %>

+
+
+

<%= t("welcome.do.title") %>

+

<%= t("welcome.do.description") %>

+
-
-
-

- <%= t("welcome.decide.title") %>
- <%= t("welcome.decide.description") %> -

-
-
-

- <%= t("welcome.do.title") %>
- <%= t("welcome.do.description") %> -

-
-
<% end %> diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index 290e1a278..67d8eef2c 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -109,6 +109,10 @@ en: hot_score: most active most_commented: most commented relevance: relevance + recommendations: recommendations + recommendations: + without_results: There are not debates related to your interests + without_interests: Follow proposals so we can give you recommendations search_form: button: Search placeholder: Search debates... @@ -348,6 +352,10 @@ en: most_commented: most commented relevance: relevance archival_date: Archived + recommendations: recommendations + recommendations: + without_results: There are not proposals related to your interests + without_interests: Follow proposals so we can give you recommendations retired_proposals: Retired proposals retired_proposals_link: "Proposals retired by the author" retired_links: @@ -473,6 +481,7 @@ en: help_text_1: "Voting takes place when a citizen proposal supports reaches 1% of the census with voting rights. Voting can also include questions that the City Council ask to the citizens decision." help_text_2: "To participate in the next vote you have to sign up on %{org} and verify your account. All registered voters in the city over 16 years old can vote. The results of all votes are binding on the government." show: + already_voted_in_booth: "You have already participated in a booth for this poll." dates_title: "Participation dates" cant_answer_not_logged_in: "You must %{signin} or %{signup} to participate." signin: Sign in @@ -753,6 +762,16 @@ en: proposal: description: Open space for citizen proposals about the kind of city we want to live in. title: You propose + recommended: + title: Recommendations that may interest you + debates: + title: Recommended debates + btn_text_link: All recommended debates + proposals: + title: Recommended proposals + btn_text_link: All recommended proposals + budget_investments: + title: Recommended investments verification: i_dont_have_an_account: I don't have an account i_have_an_account: I already have an account diff --git a/config/locales/en/settings.yml b/config/locales/en/settings.yml index eaf610f4d..44e767a6e 100644 --- a/config/locales/en/settings.yml +++ b/config/locales/en/settings.yml @@ -39,6 +39,8 @@ en: spending_proposal_features: voting_allowed: Voting on investment projects legislation: Legislation + user: + recommendations: Recommendeds community: Community on proposals and investments map: Proposals and budget investments geolocation map_latitude: Latitude diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index 722c0dabb..aaa59a900 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -109,6 +109,10 @@ es: hot_score: Más activos hoy most_commented: Más comentados relevance: Más relevantes + recommendations: Recomendaciones + recommendations: + without_results: No existen debates relacionados con tus intereses + without_interests: Sigue propuestas para que podamos darte recomendaciones search_form: button: Buscar placeholder: Buscar debates... @@ -348,6 +352,10 @@ es: most_commented: Más comentadas relevance: Más relevantes archival_date: Archivadas + recommendations: Recomendaciones + recommendations: + without_results: No existen propuestas relacionadas con tus intereses + without_interests: Sigue propuestas para que podamos darte recomendaciones retired_proposals: Propuestas retiradas retired_proposals_link: "Propuestas retiradas por sus autores" retired_links: @@ -394,7 +402,7 @@ es: proposal: created: "¡Has creado una propuesta!" share: - guide: "Compártela para que la gente empieze a apoyarla." + guide: "Compártela para que la gente empiece a apoyarla." edit: "Antes de que se publique podrás modificar el texto a tu gusto." view_proposal: "Ahora no, ir a mi propuesta" improve_info: "Mejora tu campaña y consigue más apoyos" @@ -473,6 +481,7 @@ es: help_text_1: "Las votaciones se convocan cuando una propuesta ciudadana alcanza el 1% de apoyos del censo con derecho a voto. En las votaciones también se pueden incluir cuestiones que el Ayuntamiento somete a decisión directa de la ciudadanía." help_text_2: "Para participar en la próxima votación tienes que registrarte en %{org} y verificar tu cuenta. Pueden votar todas las personas empadronadas en la ciudad mayores de 16 años. Los resultados de todas las votaciones serán vinculantes para el gobierno." show: + already_voted_in_booth: "Ya has participado en esta votación en una urna." dates_title: "Fechas de participación" cant_answer_not_logged_in: "Necesitas %{signin} o %{signup} para participar." signin: iniciar sesión @@ -753,6 +762,16 @@ es: proposal: description: Espacio abierto para propuestas ciudadanas sobre el tipo de ciudad en el que queremos vivir. title: Propones + recommended: + title: Recomendaciones que te pueden interesar + debates: + title: Debates recomendados + btn_text_link: Todos los debates recomendados + proposals: + title: Propuestas recomendadas + btn_text_link: Todas las propuestas recomendadas + budget_investments: + title: Presupuestos recomendados verification: i_dont_have_an_account: No tengo cuenta, quiero crear una y verificarla i_have_an_account: Ya tengo una cuenta que quiero verificar diff --git a/config/locales/es/settings.yml b/config/locales/es/settings.yml index b23c6e688..fcf2eb1db 100644 --- a/config/locales/es/settings.yml +++ b/config/locales/es/settings.yml @@ -39,6 +39,8 @@ es: spending_proposal_features: voting_allowed: Votaciones sobre propuestas de inversión legislation: Legislación + user: + recommendations: Recomendaciones community: Comunidad en propuestas y proyectos de inversión map: Geolocalización de propuestas y proyectos de inversión map_latitude: Latitud diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index fcd86c44a..b151a45cf 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -36,6 +36,7 @@ Setting.create(key: 'feature.facebook_login', value: "true") Setting.create(key: 'feature.google_login', value: "true") Setting.create(key: 'feature.signature_sheets', value: "true") Setting.create(key: 'feature.legislation', value: "true") +Setting.create(key: 'feature.user.recommendations', value: "true") Setting.create(key: 'feature.community', value: "true") Setting.create(key: 'feature.map', value: "true") Setting.create(key: 'per_page_code_head', value: "") diff --git a/db/migrate/20171002121658_add_origin_to_poll_voters.rb b/db/migrate/20171002121658_add_origin_to_poll_voters.rb new file mode 100644 index 000000000..845c1b774 --- /dev/null +++ b/db/migrate/20171002121658_add_origin_to_poll_voters.rb @@ -0,0 +1,5 @@ +class AddOriginToPollVoters < ActiveRecord::Migration + def change + add_column :poll_voters, :origin, :string + end +end diff --git a/db/migrate/20171002122312_create_poll_recount.rb b/db/migrate/20171002122312_create_poll_recount.rb new file mode 100644 index 000000000..807f1c84e --- /dev/null +++ b/db/migrate/20171002122312_create_poll_recount.rb @@ -0,0 +1,24 @@ +class CreatePollRecount < ActiveRecord::Migration + def change + create_table :poll_recounts do |t| + t.integer :author_id + t.string :origin + t.date :date + t.integer :booth_assignment_id + t.integer :officer_assignment_id + t.text :officer_assignment_id_log, default: "" + t.text :author_id_log, default: "" + t.integer :white_amount + t.text :white_amount_log, default: "" + t.integer :null_amount + t.text :null_amount_log, default: "" + t.integer :total_amount + t.text :total_amount_log, default: "" + end + + add_index :poll_recounts, :booth_assignment_id + add_index :poll_recounts, :officer_assignment_id + add_foreign_key :poll_recounts, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_recounts, :poll_officer_assignments, column: :officer_assignment_id + end +end diff --git a/db/migrate/20171002191347_add_default_to_recount_amounts.rb b/db/migrate/20171002191347_add_default_to_recount_amounts.rb new file mode 100644 index 000000000..b90e86aae --- /dev/null +++ b/db/migrate/20171002191347_add_default_to_recount_amounts.rb @@ -0,0 +1,7 @@ +class AddDefaultToRecountAmounts < ActiveRecord::Migration + def change + change_column_default :poll_recounts, :white_amount, 0 + change_column_default :poll_recounts, :null_amount, 0 + change_column_default :poll_recounts, :total_amount, 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index f6ff7447d..5c5b99d11 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171002103314) do +ActiveRecord::Schema.define(version: 20171002191347) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -690,6 +690,25 @@ ActiveRecord::Schema.define(version: 20171002103314) do add_index "poll_questions", ["proposal_id"], name: "index_poll_questions_on_proposal_id", using: :btree add_index "poll_questions", ["tsv"], name: "index_poll_questions_on_tsv", using: :gin + create_table "poll_recounts", force: :cascade do |t| + t.integer "author_id" + t.string "origin" + t.date "date" + t.integer "booth_assignment_id" + t.integer "officer_assignment_id" + t.text "officer_assignment_id_log", default: "" + t.text "author_id_log", default: "" + t.integer "white_amount", default: 0 + t.text "white_amount_log", default: "" + t.integer "null_amount", default: 0 + t.text "null_amount_log", default: "" + t.integer "total_amount", default: 0 + t.text "total_amount_log", default: "" + end + + add_index "poll_recounts", ["booth_assignment_id"], name: "index_poll_recounts_on_booth_assignment_id", using: :btree + add_index "poll_recounts", ["officer_assignment_id"], name: "index_poll_recounts_on_officer_assignment_id", using: :btree + create_table "poll_shifts", force: :cascade do |t| t.integer "booth_id" t.integer "officer_id" @@ -734,6 +753,7 @@ ActiveRecord::Schema.define(version: 20171002103314) do t.integer "answer_id" t.integer "officer_assignment_id" t.integer "user_id" + t.string "origin" end add_index "poll_voters", ["booth_assignment_id"], name: "index_poll_voters_on_booth_assignment_id", using: :btree @@ -1127,6 +1147,8 @@ ActiveRecord::Schema.define(version: 20171002103314) do add_foreign_key "poll_questions", "polls" add_foreign_key "poll_questions", "proposals" add_foreign_key "poll_questions", "users", column: "author_id" + add_foreign_key "poll_recounts", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_recounts", "poll_officer_assignments", column: "officer_assignment_id" add_foreign_key "poll_voters", "polls" add_foreign_key "poll_white_results", "poll_booth_assignments", column: "booth_assignment_id" add_foreign_key "poll_white_results", "poll_officer_assignments", column: "officer_assignment_id" diff --git a/db/seeds.rb b/db/seeds.rb index ee444bd91..fcfa6340c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -79,6 +79,7 @@ Setting['feature.public_stats'] = true Setting['feature.budgets'] = true Setting['feature.signature_sheets'] = true Setting['feature.legislation'] = true +Setting['feature.user.recommendations'] = true Setting['feature.community'] = true Setting['feature.map'] = nil diff --git a/spec/factories.rb b/spec/factories.rb index b0a287aea..c21c35b4e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -52,6 +52,12 @@ FactoryGirl.define do trait :verified do verified_at Time.current end + + trait :in_census do + document_number "12345678Z" + document_type "1" + verified_at Time.current + end end factory :identity do @@ -525,6 +531,7 @@ FactoryGirl.define do factory :poll_voter, class: 'Poll::Voter' do poll association :user, :level_two + origin "web" trait :from_booth do association :booth_assignment, factory: :poll_booth_assignment @@ -569,6 +576,11 @@ FactoryGirl.define do origin { 'web' } end + factory :poll_recount, class: 'Poll::Recount' do + association :author, factory: :user + origin { 'web' } + end + factory :officing_residence, class: 'Officing::Residence' do user association :officer, factory: :poll_officer diff --git a/spec/features/admin/poll/polls_spec.rb b/spec/features/admin/poll/polls_spec.rb index 6eb64f6e1..8d30dc5eb 100644 --- a/spec/features/admin/poll/polls_spec.rb +++ b/spec/features/admin/poll/polls_spec.rb @@ -249,18 +249,18 @@ feature 'Admin polls' do booth_assignment_final_recounted = create(:poll_booth_assignment, poll: poll) 3.times do |i| - create(:poll_total_result, + create(:poll_recount, booth_assignment: booth_assignment, date: poll.starts_at + i.days, - amount: 21) + total_amount: 21) end 2.times { create(:poll_voter, booth_assignment: booth_assignment_final_recounted) } - create(:poll_total_result, + create(:poll_recount, booth_assignment: booth_assignment_final_recounted, date: poll.ends_at, - amount: 55555) + total_amount: 55555) visit admin_poll_path(poll) @@ -318,12 +318,10 @@ feature 'Admin polls' do answer: 'Tomorrow', amount: 5) end - create(:poll_white_result, + create(:poll_recount, booth_assignment: booth_assignment_1, - amount: 21) - create(:poll_null_result, - booth_assignment: booth_assignment_3, - amount: 44) + white_amount: 21, + null_amount: 44) visit admin_poll_path(poll) diff --git a/spec/features/admin_spec.rb b/spec/features/admin_spec.rb index 9ea2c2390..f2374f183 100644 --- a/spec/features/admin_spec.rb +++ b/spec/features/admin_spec.rb @@ -12,7 +12,7 @@ feature 'Admin' do visit admin_root_path expect(current_path).not_to eq(admin_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -22,7 +22,7 @@ feature 'Admin' do visit admin_root_path expect(current_path).not_to eq(admin_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -32,7 +32,7 @@ feature 'Admin' do visit admin_root_path expect(current_path).not_to eq(admin_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -42,7 +42,7 @@ feature 'Admin' do visit admin_root_path expect(current_path).not_to eq(admin_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -52,7 +52,7 @@ feature 'Admin' do visit admin_root_path expect(current_path).not_to eq(admin_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end diff --git a/spec/features/debates_spec.rb b/spec/features/debates_spec.rb index 61210df6e..3d7c6acf8 100644 --- a/spec/features/debates_spec.rb +++ b/spec/features/debates_spec.rb @@ -219,7 +219,7 @@ feature 'Debates' do visit edit_debate_path(debate) expect(current_path).not_to eq(edit_debate_path(debate)) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to carry out the action 'edit' on debate." end @@ -234,7 +234,7 @@ feature 'Debates' do visit edit_debate_path(debate) expect(current_path).not_to eq(edit_debate_path(debate)) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content 'You do not have permission to' end @@ -351,6 +351,69 @@ feature 'Debates' do expect(current_url).to include('order=created_at') expect(current_url).to include('page=1') end + + context 'Recommendations' do + + background do + Setting['feature.user.recommendations'] = true + create(:debate, title: 'Best', cached_votes_total: 10, tag_list: "Sport") + create(:debate, title: 'Medium', cached_votes_total: 5, tag_list: "Sport") + create(:debate, title: 'Worst', cached_votes_total: 1, tag_list: "Sport") + end + + after do + Setting['feature.user.recommendations'] = nil + end + + scenario 'Debates can not ordered by recommendations when there is not an user logged', :js do + visit debates_path + + expect(page).not_to have_selector('a', text: 'recommendations') + end + + scenario 'Should display text when there are not recommendeds results', :js do + user = create(:user) + proposal = create(:proposal, tag_list: "Distinct_to_sport") + create(:follow, followable: proposal, user: user) + login_as(user) + visit debates_path + + click_link 'recommendations' + + expect(page).to have_content "There are not debates related to your interests" + end + + scenario 'Should display text when user has not related interests', :js do + user = create(:user) + login_as(user) + visit debates_path + + click_link 'recommendations' + + expect(page).to have_content "Follow proposals so we can give you recommendations" + end + + scenario 'Debates are ordered by recommendations when there is a user logged', :js do + proposal = create(:proposal, tag_list: "Sport" ) + user = create(:user) + create(:follow, followable: proposal, user: user) + login_as(user) + + visit debates_path + + click_link 'recommendations' + + expect(page).to have_selector('a.active', text: 'recommendations') + + within '#debates' do + expect('Best').to appear_before('Medium') + expect('Medium').to appear_before('Worst') + end + + expect(current_url).to include('order=recommendations') + expect(current_url).to include('page=1') + end + end end context "Search" do @@ -759,6 +822,32 @@ feature 'Debates' do end end + scenario "Reorder by recommendations results maintaing search", :js do + Setting['feature.user.recommendations'] = true + user = create(:user) + login_as(user) + debate1 = create(:debate, title: "Show you got", cached_votes_total: 10, tag_list: "Sport") + debate2 = create(:debate, title: "Show what you got", cached_votes_total: 1, tag_list: "Sport") + debate3 = create(:debate, title: "Do not display with same tag", cached_votes_total: 100, tag_list: "Sport") + debate4 = create(:debate, title: "Do not display", cached_votes_total: 1) + proposal1 = create(:proposal, tag_list: "Sport") + create(:follow, followable: proposal1, user: user) + + visit debates_path + fill_in "search", with: "Show you got" + click_button "Search" + click_link 'recommendations' + expect(page).to have_selector("a.active", text: "recommendations") + + within("#debates") do + expect(all(".debate")[0].text).to match "Show you got" + expect(all(".debate")[1].text).to match "Show what you got" + expect(page).to_not have_content "Do not display with same tag" + expect(page).to_not have_content "Do not display" + end + Setting['feature.user.recommendations'] = nil + end + scenario 'After a search do not show featured debates' do featured_debates = create_featured_debates debate = create(:debate, title: "Abcdefghi") diff --git a/spec/features/home_spec.rb b/spec/features/home_spec.rb index 4deed36bb..cb5349814 100644 --- a/spec/features/home_spec.rb +++ b/spec/features/home_spec.rb @@ -3,20 +3,138 @@ require 'rails_helper' feature "Home" do feature "For not logged users" do + scenario 'Welcome message' do visit root_path expect(page).to have_content "Love the city, and it will become a city you love" end + + scenario 'Not display recommended section' do + debate = create(:debate) + + visit root_path + + expect(page).not_to have_content "Recommendations that may interest you" + end + end feature "For signed in users" do - scenario 'Redirect to proposals' do - login_as(create(:user)) - visit root_path - expect(current_path).to eq proposals_path + feature "Recommended" do + + background do + Setting['feature.user.recommendations'] = true + user = create(:user) + proposal = create(:proposal, tag_list: "Sport" ) + create(:follow, followable: proposal, user: user) + login_as(user) + end + + after do + Setting['feature.user.recommendations'] = nil + end + + scenario 'Display recommended section' do + debate = create(:debate, tag_list: "Sport") + visit root_path + expect(page).to have_content "Recommendations that may interest you" + end + + scenario 'Display recommended section when feature flag recommended is active' do + debate = create(:debate, tag_list: "Sport") + visit root_path + expect(page).to have_content "Recommendations that may interest you" + end + + scenario 'Not display recommended section when feature flag recommended is not active' do + debate = create(:debate, tag_list: "Sport") + Setting['feature.user.recommendations'] = false + + visit root_path + + expect(page).not_to have_content "Recommendations that may interest you" + end + + scenario 'Display debates' do + debate = create(:debate, tag_list: "Sport") + + visit root_path + + expect(page).to have_content debate.title + expect(page).to have_content debate.description + end + + scenario 'Display all recommended debates link' do + debate = create(:debate, tag_list: "Sport") + visit root_path + expect(page).to have_link("All recommended debates", href: debates_path(order: "recommendations")) + end + + scenario 'Display proposal' do + proposal = create(:proposal, tag_list: "Sport") + + visit root_path + + expect(page).to have_content proposal.title + expect(page).to have_content proposal.description + end + + scenario 'Display all recommended proposals link' do + debate = create(:proposal, tag_list: "Sport") + visit root_path + expect(page).to have_link("All recommended proposals", href: proposals_path(order: "recommendations")) + end + + scenario 'Display orbit carrousel' do + create_list(:debate, 3, tag_list: "Sport") + + visit root_path + + expect(page).to have_selector('li[data-slide="0"]') + expect(page).to have_selector('li[data-slide="1"]', visible: false) + expect(page).to have_selector('li[data-slide="2"]', visible: false) + end + + scenario 'Display recommended show when click on carousel' do + debate = create(:debate, tag_list: "Sport") + + visit root_path + click_on debate.title + + expect(current_path).to eq debate_path(debate) + end + + scenario 'Do not display recommended section when there are not debates and proposals' do + visit root_path + expect(page).not_to have_content "Recommendations that may interest you" + end + + feature 'Carousel size' do + + scenario 'Display debates centered when there are no proposals' do + debate = create(:debate, tag_list: "Sport") + visit root_path + expect(page).to have_selector('.medium-centered.large-centered') + end + + scenario 'Correct display debates and proposals' do + proposal = create(:proposal, tag_list: "Sport") + debates = create(:debate, tag_list: "Sport") + + visit root_path + + expect(page).to have_selector('.debates.medium-offset-2.large-offset-2') + expect(page).not_to have_selector('.proposals.medium-offset-2.large-offset-2') + expect(page).not_to have_selector('.debates.end') + expect(page).to have_selector('.proposals.end') + expect(page).not_to have_selector('.medium-centered.large-centered') + end + + end end + end feature 'IE alert' do diff --git a/spec/features/moderation_spec.rb b/spec/features/moderation_spec.rb index fedcb105d..ad4042425 100644 --- a/spec/features/moderation_spec.rb +++ b/spec/features/moderation_spec.rb @@ -11,7 +11,7 @@ feature 'Moderation' do visit moderation_root_path expect(current_path).not_to eq(moderation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -25,7 +25,7 @@ feature 'Moderation' do visit moderation_root_path expect(current_path).not_to eq(moderation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -39,7 +39,7 @@ feature 'Moderation' do visit moderation_root_path expect(current_path).not_to eq(moderation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -53,7 +53,7 @@ feature 'Moderation' do visit moderation_root_path expect(current_path).not_to eq(moderation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end diff --git a/spec/features/notifications_spec.rb b/spec/features/notifications_spec.rb index 7b044e18a..9925b0d56 100644 --- a/spec/features/notifications_spec.rb +++ b/spec/features/notifications_spec.rb @@ -15,16 +15,7 @@ feature "Notifications" do let(:legislation_annotation) { create(:legislation_annotation, author: author) } scenario "User commented on my debate", :js do - login_as user - visit debate_path debate - - fill_in "comment-body-debate_#{debate.id}", with: "I commented on your debate" - click_button "Publish comment" - within "#comments" do - expect(page).to have_content "I commented on your debate" - end - - logout + create(:notification, notifiable: debate, user: author) login_as author visit root_path @@ -37,17 +28,7 @@ feature "Notifications" do end scenario "User commented on my legislation question", :js do - verified_user = create(:user, :level_two) - login_as verified_user - visit legislation_process_question_path legislation_question.process, legislation_question - - fill_in "comment-body-legislation_question_#{legislation_question.id}", with: "I answered your question" - click_button "Publish answer" - within "#comments" do - expect(page).to have_content "I answered your question" - end - - logout + create(:notification, notifiable: legislation_question, user: administrator) login_as administrator visit root_path @@ -82,6 +63,7 @@ feature "Notifications" do logout login_as author visit root_path + visit root_path find(".icon-notification").click @@ -107,8 +89,10 @@ feature "Notifications" do end logout + login_as author visit root_path + visit root_path find(".icon-notification").click @@ -137,6 +121,7 @@ feature "Notifications" do login_as author visit root_path + visit root_path find(".icon-notification").click @@ -208,6 +193,7 @@ feature "Notifications" do logout login_as user1 visit root_path + visit root_path find(".icon-notification").click @@ -219,6 +205,7 @@ feature "Notifications" do logout login_as user2 visit root_path + visit root_path find(".icon-notification").click @@ -230,6 +217,7 @@ feature "Notifications" do logout login_as user3 visit root_path + visit root_path find(".icon-no-notification").click diff --git a/spec/features/officing/results_spec.rb b/spec/features/officing/results_spec.rb index 835903ab9..f7e1ad8ef 100644 --- a/spec/features/officing/results_spec.rb +++ b/spec/features/officing/results_spec.rb @@ -127,21 +127,13 @@ feature 'Officing Results' do date: @poll.ends_at, question: @question_1, amount: 33) - white_result = create(:poll_white_result, + poll_recount = create(:poll_recount, officer_assignment: @officer_assignment, booth_assignment: @officer_assignment.booth_assignment, date: @poll.ends_at, - amount: 21) - null_result = create(:poll_null_result, - officer_assignment: @officer_assignment, - booth_assignment: @officer_assignment.booth_assignment, - date: @poll.ends_at, - amount: 44) - total_result = create(:poll_total_result, - officer_assignment: @officer_assignment, - booth_assignment: @officer_assignment.booth_assignment, - date: @poll.ends_at, - amount: 66) + white_amount: 21, + null_amount: 44, + total_amount: 66) visit officing_poll_results_path(@poll, date: I18n.l(@poll.ends_at.to_date), diff --git a/spec/features/officing_spec.rb b/spec/features/officing_spec.rb index d4cb416a1..aff3eac4e 100644 --- a/spec/features/officing_spec.rb +++ b/spec/features/officing_spec.rb @@ -11,7 +11,7 @@ feature 'Poll Officing' do visit officing_root_path expect(current_path).not_to eq(officing_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -24,7 +24,7 @@ feature 'Poll Officing' do visit officing_root_path expect(current_path).not_to eq(officing_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -37,7 +37,7 @@ feature 'Poll Officing' do visit officing_root_path expect(current_path).not_to eq(officing_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -50,7 +50,7 @@ feature 'Poll Officing' do visit officing_root_path expect(current_path).not_to eq(officing_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end diff --git a/spec/features/polls/polls_spec.rb b/spec/features/polls/polls_spec.rb index 047c71146..8a90fb4a8 100644 --- a/spec/features/polls/polls_spec.rb +++ b/spec/features/polls/polls_spec.rb @@ -184,6 +184,7 @@ feature 'Polls' do poll.geozones << geozone create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') user = create(:user, :level_two, geozone: geozone) + login_as user visit poll_path(poll) @@ -193,5 +194,25 @@ feature 'Polls' do expect(page).to have_link('Chewbacca') end + scenario 'Level 2 users changing answer', :js do + poll.update(geozone_restricted: true) + poll.geozones << geozone + create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + user = create(:user, :level_two, geozone: geozone) + + login_as user + visit poll_path(poll) + + click_link 'Han Solo' + + expect(page).to_not have_link('Han Solo') + expect(page).to have_link('Chewbacca') + + click_link 'Chewbacca' + + expect(page).to_not have_link('Chewbacca') + expect(page).to have_link('Han Solo') + end + end end diff --git a/spec/features/polls/voter_spec.rb b/spec/features/polls/voter_spec.rb new file mode 100644 index 000000000..08bc6f963 --- /dev/null +++ b/spec/features/polls/voter_spec.rb @@ -0,0 +1,94 @@ +require 'rails_helper' + +feature "Voter" do + + context "Origin" do + + scenario "Voting via web", :js do + poll = create(:poll) + question = create(:poll_question, poll: poll, valid_answers: 'Yes, No') + user = create(:user, :level_two) + + login_as user + visit question_path(question) + + click_link 'Answer this question' + click_link 'Yes' + + expect(page).to_not have_link('Yes') + expect(Poll::Voter.count).to eq(1) + expect(Poll::Voter.first.origin).to eq("web") + end + + scenario "Voting in booth", :js do + user = create(:user, :in_census) + create(:geozone, :in_census) + + poll = create(:poll) + officer = create(:poll_officer) + + ba = create(:poll_booth_assignment, poll: poll) + create(:poll_officer_assignment, officer: officer, booth_assignment: ba) + + login_through_form_as_officer(officer.user) + + visit new_officing_residence_path + officing_verify_residence + + expect(page).to have_content poll.name + + first(:button, "Confirm vote").click + expect(page).to have_content "Vote introduced!" + + expect(Poll::Voter.count).to eq(1) + expect(Poll::Voter.first.origin).to eq("booth") + end + + context "Trying to vote the same poll in booth and web" do + + let(:poll) { create(:poll) } + let(:question) { create(:poll_question, poll: poll, valid_answers: 'Yes, No') } + let!(:user) { create(:user, :in_census) } + + let(:officer) { create(:poll_officer) } + let(:ba) { create(:poll_booth_assignment, poll: poll) } + let!(:oa) { create(:poll_officer_assignment, officer: officer, booth_assignment: ba) } + + scenario "Trying to vote in web and then in booth", :js do + login_as user + vote_for_poll_via_web + + click_link "Sign out" + + login_through_form_as_officer(officer.user) + + visit new_officing_residence_path + officing_verify_residence + + expect(page).to have_content poll.name + expect(page).to_not have_button "Confirm vote" + expect(page).to have_content "Has already participated in this poll" + end + + scenario "Trying to vote in booth and then in web", :js do + login_through_form_as_officer(officer.user) + + vote_for_poll_via_booth + + visit root_path + click_link "Sign out" + + login_as user + visit question_path(question) + + click_link 'Answer this question' + + expect(page).to_not have_link('Yes') + expect(page).to have_content "You have already participated in a booth for this poll." + expect(Poll::Voter.count).to eq(1) + end + end + + end + +end \ No newline at end of file diff --git a/spec/features/proposal_notifications_spec.rb b/spec/features/proposal_notifications_spec.rb index 4756a1903..1e93f7e13 100644 --- a/spec/features/proposal_notifications_spec.rb +++ b/spec/features/proposal_notifications_spec.rb @@ -189,7 +189,7 @@ feature 'Proposal Notifications' do login_as(user) visit new_proposal_notification_path(proposal_id: proposal.id) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content("You do not have permission to carry out the action") end diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index f0e50c7eb..81f7fb998 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -556,7 +556,7 @@ feature 'Proposals' do visit edit_proposal_path(proposal) expect(current_path).not_to eq(edit_proposal_path(proposal)) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content 'You do not have permission' end @@ -571,7 +571,7 @@ feature 'Proposals' do visit edit_proposal_path(proposal) expect(current_path).not_to eq(edit_proposal_path(proposal)) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content 'You do not have permission' Setting["max_votes_for_proposal_edit"] = 1000 end @@ -663,6 +663,69 @@ feature 'Proposals' do expect(current_url).to include('order=created_at') expect(current_url).to include('page=1') end + + context 'Recommendations' do + + before do + Setting['feature.user.recommendations'] = true + create(:proposal, title: 'Best', cached_votes_up: 10, tag_list: "Sport") + create(:proposal, title: 'Medium', cached_votes_up: 5, tag_list: "Sport") + create(:proposal, title: 'Worst', cached_votes_up: 1, tag_list: "Sport") + end + + after do + Setting['feature.user.recommendations'] = nil + end + + scenario 'Proposals can not ordered by recommendations when there is not an user logged', :js do + visit proposals_path + + expect(page).not_to have_selector('a', text: 'recommendations') + end + + scenario 'Should display text when there are not recommendeds results', :js do + user = create(:user) + proposal = create(:proposal, tag_list: "Distinct_to_sport") + create(:follow, followable: proposal, user: user) + login_as(user) + visit proposals_path + + click_link 'recommendations' + + expect(page).to have_content "There are not proposals related to your interests" + end + + scenario 'Should display text when user has not related interests', :js do + user = create(:user) + login_as(user) + visit proposals_path + + click_link 'recommendations' + + expect(page).to have_content "Follow proposals so we can give you recommendations" + end + + scenario 'Proposals are ordered by recommendations when there is an user logged', :js do + user = create(:user) + proposal = create(:proposal, tag_list: "Sport") + create(:follow, followable: proposal, user: user) + login_as(user) + + visit proposals_path + + click_link 'recommendations' + + expect(page).to have_selector('a.active', text: 'recommendations') + + within '#proposals-list' do + expect('Best').to appear_before('Medium') + expect('Medium').to appear_before('Worst') + end + + expect(current_url).to include('order=recommendations') + expect(current_url).to include('page=1') + end + end end feature 'Archived proposals' do @@ -1198,6 +1261,32 @@ feature 'Proposals' do end end + scenario "Reorder by recommendations results maintaing search", :js do + Setting['feature.user.recommendations'] = true + user = create(:user) + login_as(user) + proposal1 = create(:proposal, title: "Show you got", cached_votes_up: 10, tag_list: "Sport") + proposal2 = create(:proposal, title: "Show what you got", cached_votes_up: 1, tag_list: "Sport") + proposal3 = create(:proposal, title: "Do not display with same tag", cached_votes_up: 100, tag_list: "Sport") + proposal4 = create(:proposal, title: "Do not display", cached_votes_up: 1) + proposal5 = create(:proposal, tag_list: "Sport") + create(:follow, followable: proposal5, user: user) + + visit proposals_path + fill_in "search", with: "Show you got" + click_button "Search" + click_link 'recommendations' + expect(page).to have_selector("a.active", text: "recommendations") + + within("#proposals") do + expect(all(".proposal")[0].text).to match "Show you got" + expect(all(".proposal")[1].text).to match "Show what you got" + expect(page).to_not have_content "Do not display with same tag" + expect(page).to_not have_content "Do not display" + end + Setting['feature.user.recommendations'] = nil + end + scenario 'After a search do not show featured proposals' do featured_proposals = create_featured_proposals proposal = create(:proposal, title: "Abcdefghi") diff --git a/spec/features/valuation_spec.rb b/spec/features/valuation_spec.rb index 5b6cc961c..9e74d9341 100644 --- a/spec/features/valuation_spec.rb +++ b/spec/features/valuation_spec.rb @@ -21,7 +21,7 @@ feature 'Valuation' do visit valuation_root_path expect(current_path).not_to eq(valuation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -34,7 +34,7 @@ feature 'Valuation' do visit valuation_root_path expect(current_path).not_to eq(valuation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -47,7 +47,7 @@ feature 'Valuation' do visit valuation_root_path expect(current_path).not_to eq(valuation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end @@ -60,7 +60,7 @@ feature 'Valuation' do visit valuation_root_path expect(current_path).not_to eq(valuation_root_path) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) expect(page).to have_content "You do not have permission to access this page" end diff --git a/spec/features/welcome_spec.rb b/spec/features/welcome_spec.rb index ce06befcc..245a9923e 100644 --- a/spec/features/welcome_spec.rb +++ b/spec/features/welcome_spec.rb @@ -33,7 +33,7 @@ feature "Welcome screen" do login_through_form_as(user) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) end scenario 'is not shown to organizations' do @@ -41,7 +41,7 @@ feature "Welcome screen" do login_through_form_as(organization.user) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) end scenario 'it is not shown to level-2 users' do @@ -49,7 +49,7 @@ feature "Welcome screen" do login_through_form_as(user) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) end scenario 'it is not shown to level-3 users' do @@ -57,7 +57,7 @@ feature "Welcome screen" do login_through_form_as(user) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) end scenario 'is not shown to administrators' do @@ -65,7 +65,7 @@ feature "Welcome screen" do login_through_form_as(administrator.user) - expect(current_path).to eq(proposals_path) + expect(current_path).to eq(root_path) end end diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index 5a6c4b734..0934c7cfe 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -72,6 +72,7 @@ describe "Abilities::Administrator" do it { should be_able_to(:create, Budget) } it { should be_able_to(:update, Budget) } + it { should be_able_to(:read_results, Budget) } it { should be_able_to(:create, Budget::ValuatorAssignment) } diff --git a/spec/models/abilities/everyone_spec.rb b/spec/models/abilities/everyone_spec.rb index fcfff4e42..21568c999 100644 --- a/spec/models/abilities/everyone_spec.rb +++ b/spec/models/abilities/everyone_spec.rb @@ -8,6 +8,9 @@ describe "Abilities::Everyone" do let(:debate) { create(:debate) } let(:proposal) { create(:proposal) } + let(:reviewing_ballot_budget) { create(:budget, phase: 'reviewing_ballots') } + let(:finished_budget) { create(:budget, phase: 'finished') } + it { should be_able_to(:index, Debate) } it { should be_able_to(:show, debate) } it { should_not be_able_to(:edit, Debate) } @@ -28,4 +31,7 @@ describe "Abilities::Everyone" do it { should_not be_able_to(:create, SpendingProposal) } it { should be_able_to(:index, Budget) } -end \ No newline at end of file + + it { should be_able_to(:read_results, finished_budget) } + it { should_not be_able_to(:read_results, reviewing_ballot_budget) } +end diff --git a/spec/models/debate_spec.rb b/spec/models/debate_spec.rb index 5ab801cd0..64dbf729f 100644 --- a/spec/models/debate_spec.rb +++ b/spec/models/debate_spec.rb @@ -714,4 +714,53 @@ describe Debate do end end + describe "#recommendations" do + + let(:user) { create(:user) } + + it "Should not return any debates when user has not interests" do + create(:debate) + + expect(Debate.recommendations(user).size).to eq 0 + end + + it "Should return debates ordered by cached_votes_total" do + debate1 = create(:debate, cached_votes_total: 1, tag_list: "Sport" ) + debate2 = create(:debate, cached_votes_total: 5, tag_list: "Sport" ) + debate3 = create(:debate, cached_votes_total: 10, tag_list: "Sport" ) + proposal = create(:proposal, tag_list: "Sport" ) + create(:follow, followable: proposal, user: user) + + result = Debate.recommendations(user).sort_by_recommendations + + expect(result.first).to eq debate3 + expect(result.second).to eq debate2 + expect(result.third).to eq debate1 + end + + it "Should return debates related with user interests" do + debate1 = create(:debate, tag_list: "Sport") + debate2 = create(:debate, tag_list: "Politics") + proposal1 = create(:proposal, tag_list: "Sport") + create(:follow, followable: proposal1, user: user) + + result = Debate.recommendations(user) + + expect(result.size).to eq 1 + expect(result).to eq [debate1] + end + + it "Should not return debates when user is the author" do + debate1 = create(:debate, author: user, tag_list: "Sport") + debate2 = create(:debate, tag_list: "Sport") + proposal = create(:proposal, tag_list: "Sport" ) + create(:follow, followable: proposal, user: user) + + result = Debate.recommendations(user) + + expect(result.size).to eq 1 + expect(result).to eq [debate2] + end + + end end diff --git a/spec/models/poll/answer_spec.rb b/spec/models/poll/answer_spec.rb index 10ccafbb6..d66cdc18c 100644 --- a/spec/models/poll/answer_spec.rb +++ b/spec/models/poll/answer_spec.rb @@ -3,28 +3,71 @@ require 'rails_helper' describe Poll::Answer do describe "validations" do - it "validates that the answers are included in the Poll::Question's list" do - q = create(:poll_question, valid_answers: 'One, Two, Three') - expect(build(:poll_answer, question: q, answer: 'One')).to be_valid - expect(build(:poll_answer, question: q, answer: 'Two')).to be_valid - expect(build(:poll_answer, question: q, answer: 'Three')).to be_valid - expect(build(:poll_answer, question: q, answer: 'Four')).to_not be_valid + let(:answer) { build(:poll_answer) } + + it "should be valid" do + expect(answer).to be_valid + end + + it "should not be valid wihout a question" do + answer.question = nil + expect(answer).to_not be_valid + end + + it "should not be valid without an author" do + answer.author = nil + expect(answer).to_not be_valid + end + + it "should not be valid without an answer" do + answer.answer = nil + expect(answer).to_not be_valid + end + + it "should be valid for answers included in the Poll::Question's list" do + question = create(:poll_question, valid_answers: 'One, Two, Three') + expect(build(:poll_answer, question: question, answer: 'One')).to be_valid + expect(build(:poll_answer, question: question, answer: 'Two')).to be_valid + expect(build(:poll_answer, question: question, answer: 'Three')).to be_valid + + expect(build(:poll_answer, question: question, answer: 'Four')).to_not be_valid end end describe "#record_voter_participation" do + + let(:author) { create(:user, :level_two) } + let(:poll) { create(:poll) } + let(:question) { create(:poll_question, poll: poll, valid_answers: "Yes, No") } + it "creates a poll_voter with user and poll data" do - answer = create(:poll_answer) + answer = create(:poll_answer, question: question, author: author, answer: "Yes") expect(answer.poll.voters).to be_blank answer.record_voter_participation - expect(answer.poll.reload.voters.size).to eq(1) - voter = answer.poll.voters.first + expect(poll.reload.voters.size).to eq(1) + voter = poll.voters.first expect(voter.document_number).to eq(answer.author.document_number) expect(voter.poll_id).to eq(answer.poll.id) end + + it "updates a poll_voter with user and poll data" do + answer = create(:poll_answer, question: question, author: author, answer: "Yes") + answer.record_voter_participation + + expect(poll.reload.voters.size).to eq(1) + + answer = create(:poll_answer, question: question, author: author, answer: "No") + answer.record_voter_participation + + expect(poll.reload.voters.size).to eq(1) + + voter = poll.voters.first + expect(voter.document_number).to eq(answer.author.document_number) + expect(voter.poll_id).to eq(answer.poll.id) + end end end diff --git a/spec/models/poll/poll_spec.rb b/spec/models/poll/poll_spec.rb index ac0cc44ea..5083d7439 100644 --- a/spec/models/poll/poll_spec.rb +++ b/spec/models/poll/poll_spec.rb @@ -138,4 +138,33 @@ describe :poll do end end end + + describe "#voted_in_booth?" do + + it "returns true if the user has already voted in booth" do + user = create(:user, :level_two) + poll = create(:poll) + + create(:poll_voter, poll: poll, user: user, origin: "booth") + + expect(poll.voted_in_booth?(user)).to be + end + + it "returns false if the user has not already voted in a booth" do + user = create(:user, :level_two) + poll = create(:poll) + + expect(poll.voted_in_booth?(user)).to_not be + end + + it "returns false if the user has voted in web" do + user = create(:user, :level_two) + poll = create(:poll) + + create(:poll_voter, poll: poll, user: user, origin: "web") + + expect(poll.voted_in_booth?(user)).to_not be + end + + end end diff --git a/spec/models/poll/recount_spec.rb b/spec/models/poll/recount_spec.rb new file mode 100644 index 000000000..2a5ed16a7 --- /dev/null +++ b/spec/models/poll/recount_spec.rb @@ -0,0 +1,104 @@ +require 'rails_helper' + +describe Poll::Recount do + + describe "logging changes" do + let(:author) { create(:user) } + let(:officer_assignment) { create(:poll_officer_assignment) } + let(:poll_recount) { create(:poll_recount, author: author, officer_assignment: officer_assignment) } + + it "should update white_amount_log if white_amount changes" do + poll_recount.white_amount = 33 + + expect(poll_recount.white_amount_log).to eq("") + + poll_recount.white_amount = 33 + poll_recount.save + poll_recount.white_amount = 32 + poll_recount.save + poll_recount.white_amount = 34 + poll_recount.save + + expect(poll_recount.white_amount_log).to eq(":0:33:32") + end + + it "should update null_amount_log if null_amount changes" do + poll_recount.null_amount = 33 + + expect(poll_recount.null_amount_log).to eq("") + + poll_recount.null_amount = 33 + poll_recount.save + poll_recount.null_amount = 32 + poll_recount.save + poll_recount.null_amount = 34 + poll_recount.save + + expect(poll_recount.null_amount_log).to eq(":0:33:32") + end + + it "should update total_amount_log if total_amount changes" do + poll_recount.total_amount = 33 + + expect(poll_recount.total_amount_log).to eq("") + + poll_recount.total_amount = 33 + poll_recount.save + poll_recount.total_amount = 32 + poll_recount.save + poll_recount.total_amount = 34 + poll_recount.save + + expect(poll_recount.total_amount_log).to eq(":0:33:32") + end + + it "should update officer_assignment_id_log if amount changes" do + poll_recount.white_amount = 33 + + expect(poll_recount.white_amount_log).to eq("") + expect(poll_recount.officer_assignment_id_log).to eq("") + + poll_recount.white_amount = 33 + poll_recount.officer_assignment = create(:poll_officer_assignment, id: 101) + poll_recount.save + + poll_recount.white_amount = 32 + poll_recount.officer_assignment = create(:poll_officer_assignment, id: 102) + poll_recount.save + + poll_recount.white_amount = 34 + poll_recount.officer_assignment = create(:poll_officer_assignment, id: 103) + poll_recount.save + + expect(poll_recount.white_amount_log).to eq(":0:33:32") + expect(poll_recount.officer_assignment_id_log).to eq(":#{officer_assignment.id}:101:102") + end + + it "should update author_id if amount changes" do + poll_recount.white_amount = 33 + + expect(poll_recount.white_amount_log).to eq("") + expect(poll_recount.author_id_log).to eq("") + + first_author = create(:poll_officer).user + second_author = create(:poll_officer).user + third_author = create(:poll_officer).user + + poll_recount.white_amount = 33 + poll_recount.author_id = first_author.id + poll_recount.save! + + poll_recount.white_amount = 32 + poll_recount.author_id = second_author.id + poll_recount.save! + + poll_recount.white_amount = 34 + poll_recount.author_id = third_author.id + poll_recount.save! + + expect(poll_recount.white_amount_log).to eq(":0:33:32") + expect(poll_recount.author_id_log).to eq(":#{author.id}:#{first_author.id}:#{second_author.id}") + end + end + +end diff --git a/spec/models/poll/voter_spec.rb b/spec/models/poll/voter_spec.rb index c1c248550..f306248dc 100644 --- a/spec/models/poll/voter_spec.rb +++ b/spec/models/poll/voter_spec.rb @@ -83,6 +83,64 @@ describe :voter do expect(voter.errors.messages[:document_number]).to eq(["User has already voted"]) end + context "origin" do + + it "should not be valid without an origin" do + voter.origin = nil + expect(voter).to_not be_valid + end + + it "should not be valid without a valid origin" do + voter.origin = "invalid_origin" + expect(voter).to_not be_valid + end + + it "should be valid with a booth origin" do + voter.origin = "booth" + expect(voter).to be_valid + end + + it "should be valid with a web origin" do + voter.origin = "web" + expect(voter).to be_valid + end + + end + + end + + describe "scopes" do + + describe "#web" do + it "returns voters with a web origin" do + voter1 = create(:poll_voter, origin: "web") + voter2 = create(:poll_voter, origin: "web") + voter3 = create(:poll_voter, origin: "booth") + + web_voters = Poll::Voter.web + + expect(web_voters.count).to eq(2) + expect(web_voters).to include(voter1) + expect(web_voters).to include(voter2) + expect(web_voters).to_not include(voter3) + end + end + + describe "#booth" do + it "returns voters with a booth origin" do + voter1 = create(:poll_voter, origin: "booth") + voter2 = create(:poll_voter, origin: "booth") + voter3 = create(:poll_voter, origin: "web") + + booth_voters = Poll::Voter.booth + + expect(booth_voters.count).to eq(2) + expect(booth_voters).to include(voter1) + expect(booth_voters).to include(voter2) + expect(booth_voters).to_not include(voter3) + end + end + end describe "save" do diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index 83c19edde..588bd4057 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -891,4 +891,63 @@ describe Proposal do end end + + describe "#recommendations" do + + let(:user) { create(:user) } + + it "Should not return any proposals when user has not interests" do + create(:proposal) + + expect(Proposal.recommendations(user).size).to eq 0 + end + + it "Should return proposals ordered by cached_votes_up" do + proposal1 = create(:proposal, cached_votes_up: 1, tag_list: "Sport" ) + proposal2 = create(:proposal, cached_votes_up: 5, tag_list: "Sport" ) + proposal3 = create(:proposal, cached_votes_up: 10, tag_list: "Sport" ) + proposal4 = create(:proposal, tag_list: "Sport" ) + create(:follow, followable: proposal4, user: user) + + result = Proposal.recommendations(user).sort_by_recommendations + + expect(result.first).to eq proposal3 + expect(result.second).to eq proposal2 + expect(result.third).to eq proposal1 + end + + it "Should return proposals related with user interests" do + proposal1 = create(:proposal, tag_list: "Sport") + proposal2 = create(:proposal, tag_list: "Sport") + proposal3 = create(:proposal, tag_list: "Politics") + create(:follow, followable: proposal1, user: user) + + result = Proposal.recommendations(user) + + expect(result.size).to eq 1 + expect(result).to eq [proposal2] + end + + it "Should not return proposals when user is follower" do + proposal1 = create(:proposal, tag_list: "Sport") + create(:follow, followable: proposal1, user: user) + + result = Proposal.recommendations(user) + + expect(result.size).to eq 0 + end + + it "Should not return proposals when user is the author" do + proposal1 = create(:proposal, author: user, tag_list: "Sport") + proposal2 = create(:proposal, tag_list: "Sport") + proposal3 = create(:proposal, tag_list: "Sport") + create(:follow, followable: proposal3, user: user) + + result = Proposal.recommendations(user) + + expect(result.size).to eq 1 + expect(result).to eq [proposal2] + end + + end end diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index 8b8470293..97f05294c 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -24,6 +24,17 @@ module CommonActions click_button 'Enter' end + def login_through_form_as_officer(user) + visit root_path + click_link 'Sign in' + + fill_in 'user_login', with: user.email + fill_in 'user_password', with: user.password + + click_button 'Enter' + visit new_officing_residence_path + end + def login_as_authenticated_manager expected_response = {login: login, user_key: user_key, date: date}.with_indifferent_access login, user_key, date = "JJB042", "31415926", Time.current.strftime("%Y%m%d%H%M%S") @@ -287,4 +298,26 @@ module CommonActions end end + def vote_for_poll_via_web + visit question_path(question) + + click_link 'Answer this question' + click_link 'Yes' + + expect(page).to_not have_link('Yes') + expect(Poll::Voter.count).to eq(1) + end + + def vote_for_poll_via_booth + visit new_officing_residence_path + officing_verify_residence + + expect(page).to have_content poll.name + + first(:button, "Confirm vote").click + expect(page).to have_content "Vote introduced!" + + expect(Poll::Voter.count).to eq(1) + end + end diff --git a/spec/views/welcome/index.html.erb_spec.rb b/spec/views/welcome/index.html.erb_spec.rb new file mode 100644 index 000000000..d8738f42b --- /dev/null +++ b/spec/views/welcome/index.html.erb_spec.rb @@ -0,0 +1,43 @@ +require "rails_helper" + +RSpec.describe "welcome/index" do + + it 'Display images on orbit carrousel when we have defined image_default' do + debate = create(:debate) + + render template: "welcome/_recommended_carousel.html.erb", + locals: { key: "debates", + recommendeds: [debate], + image_field: nil, + image_version: nil, + image_default: "https://dummyimage.com/600x400/000/fff", + carousel_size: "medium-6 large-6 medium-centered large-centered", + btn_text_link: t("welcome.recommended.debates.btn_text_link"), + btn_path_link: debates_path(order: "recommendations")} + + within 'li[data-slide="0"] .card' do + expect(page).to have_selector("img") + end + + end + + it 'Not display images on orbit carrousel when we have not defined image_default' do + debate = create(:debate) + + render template: "welcome/_recommended_carousel.html.erb", + locals: { key: "debates", + recommendeds: [debate], + image_field: nil, + image_version: nil, + image_default: nil, + carousel_size: "medium-6 large-6 medium-centered large-centered", + btn_text_link: t("welcome.recommended.debates.btn_text_link"), + btn_path_link: debates_path(order: "recommendations")} + + within 'li[data-slide="0"] .card' do + expect(page).not_to have_selector("img") + end + + end + +end