diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f8d4bb811..373bb213c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-07-07 21:23:30 +0200 using RuboCop version 0.49.1. +# on 2017-10-17 22:05:23 +0200 using RuboCop version 0.49.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 45 +# Offense count: 40 # Cop supports --auto-correct. # Configuration parameters: EnforcedHashRocketStyle, SupportedHashRocketStyles, EnforcedColonStyle, SupportedColonStyles, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles. # SupportedHashRocketStyles: key, separator, table @@ -14,11 +14,10 @@ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit Layout/AlignHash: Exclude: - - 'app/controllers/officing/results_controller.rb' - 'spec/controllers/legislation/annotations_controller_spec.rb' - 'spec/features/admin/banners_spec.rb' -# Offense count: 50 +# Offense count: 52 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: with_first_parameter, with_fixed_indentation @@ -33,7 +32,7 @@ Layout/ClosingParenthesisIndentation: - 'spec/models/legislation/annotation_spec.rb' - 'spec/rails_helper.rb' -# Offense count: 51 +# Offense count: 37 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: leading, trailing @@ -68,11 +67,10 @@ Layout/EmptyLines: Exclude: - 'app/controllers/admin/budget_investment_milestones_controller.rb' -# Offense count: 2 +# Offense count: 1 # Cop supports --auto-correct. Layout/EmptyLinesAroundMethodBody: Exclude: - - 'app/models/abilities/administrator.rb' - 'lib/graph_ql/api_types_creator.rb' # Offense count: 2 @@ -126,13 +124,13 @@ Layout/IndentationConsistency: - 'spec/models/legislation/draft_version_spec.rb' - 'spec/models/proposal_spec.rb' -# Offense count: 23 +# Offense count: 48 # Cop supports --auto-correct. # Configuration parameters: Width, IgnoredPatterns. Layout/IndentationWidth: Enabled: false -# Offense count: 7 +# Offense count: 6 # Cop supports --auto-correct. Layout/LeadingCommentSpace: Exclude: @@ -140,7 +138,6 @@ Layout/LeadingCommentSpace: - 'app/controllers/budgets/ballot/lines_controller.rb' - 'spec/features/budgets/ballots_spec.rb' - 'spec/features/comments/poll_questions_spec.rb' - - 'spec/features/officing/voters_spec.rb' - 'spec/support/common_actions.rb' # Offense count: 3 @@ -181,7 +178,7 @@ Layout/MultilineMethodCallBraceLayout: - 'spec/models/legislation/annotation_spec.rb' - 'spec/rails_helper.rb' -# Offense count: 71 +# Offense count: 59 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: aligned, indented, indented_relative_to_receiver @@ -201,7 +198,7 @@ Layout/MultilineMethodCallIndentation: - 'spec/models/proposal_spec.rb' - 'spec/models/user_spec.rb' -# Offense count: 8 +# Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: aligned, indented @@ -264,29 +261,11 @@ Lint/LiteralInCondition: Exclude: - 'app/models/budget/investment.rb' -# Offense count: 51 -Lint/ParenthesesAsGroupedExpression: - Exclude: - - 'spec/factories.rb' - - 'spec/features/admin/organizations_spec.rb' - - 'spec/features/budgets/investments_spec.rb' - - 'spec/features/campaigns_spec.rb' - - 'spec/features/debates_spec.rb' - - 'spec/features/management/managed_users_spec.rb' - - 'spec/features/management/proposals_spec.rb' - - 'spec/features/management/spending_proposals_spec.rb' - - 'spec/features/management/users_spec.rb' - - 'spec/features/proposals_spec.rb' - - 'spec/models/debate_spec.rb' - -# Offense count: 13 +# Offense count: 3 # Cop supports --auto-correct. Lint/StringConversionInInterpolation: Exclude: - - 'app/models/poll/null_result.rb' - 'app/models/poll/partial_result.rb' - - 'app/models/poll/white_result.rb' - - 'app/models/poll/total_result.rb' # Offense count: 15 # Cop supports --auto-correct. @@ -311,7 +290,7 @@ Lint/UnusedMethodArgument: - 'app/mailers/mailer.rb' - 'app/models/abilities/everyone.rb' -# Offense count: 278 +# Offense count: 325 Lint/UselessAssignment: Enabled: false @@ -320,35 +299,46 @@ Lint/Void: Exclude: - 'app/controllers/polls_controller.rb' -# Offense count: 74 +# Offense count: 86 Metrics/AbcSize: - Max: 54 + Max: 64 -# Offense count: 454 +# Offense count: 487 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 1071 - -# Offense count: 8 -# Configuration parameters: CountComments. -Metrics/ClassLength: - Max: 256 + Max: 1227 # Offense count: 10 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 262 + +# Offense count: 13 Metrics/CyclomaticComplexity: Max: 10 -# Offense count: 53 +# Offense count: 25 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 248 + +# Offense count: 67 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 49 + Max: 56 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 214 + Max: 242 -# Offense count: 7 +# Offense count: 3 +# Configuration parameters: CountKeywordArgs. +Metrics/ParameterLists: + Max: 7 + +# Offense count: 8 Metrics/PerceivedComplexity: Max: 11 @@ -402,20 +392,20 @@ Rails/HttpPositionalArguments: - 'spec/controllers/pages_controller_spec.rb' - 'spec/controllers/users/registrations_controller_spec.rb' -# Offense count: 20 +# Offense count: 18 Rails/OutputSafety: Exclude: - 'app/controllers/admin/legislation/draft_versions_controller.rb' - 'app/controllers/admin/legislation/processes_controller.rb' - 'app/controllers/admin/legislation/questions_controller.rb' - 'app/controllers/budgets/investments_controller.rb' + - 'app/controllers/direct_uploads_controller.rb' - 'app/controllers/spending_proposals_controller.rb' - 'app/helpers/application_helper.rb' - 'app/helpers/text_with_links_helper.rb' - - 'app/helpers/users_helper.rb' - 'app/helpers/valuation_helper.rb' -# Offense count: 70 +# Offense count: 71 # Configuration parameters: Blacklist. # Blacklist: decrement!, decrement_counter, increment!, increment_counter, toggle!, touch, update_all, update_attribute, update_column, update_columns, update_counters Rails/SkipsModelValidations: @@ -431,7 +421,7 @@ Style/AccessorMethodName: - 'app/controllers/proposals_controller.rb' - 'lib/merged_comment_tree.rb' -# Offense count: 1 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: braces, no_braces, context_dependent @@ -441,7 +431,7 @@ Style/BracesAroundHashParameters: - 'spec/features/budgets/investments_spec.rb' - 'spec/features/proposals_spec.rb' -# Offense count: 119 +# Offense count: 123 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: @@ -454,14 +444,13 @@ Style/ClassVars: - 'app/models/organization.rb' - 'app/models/user.rb' -# Offense count: 12 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'app/controllers/admin/poll/booth_assignments_controller.rb' - - 'app/controllers/admin/poll/officer_assignments_controller.rb' - 'app/controllers/admin/poll/questions_controller.rb' - 'app/controllers/comments_controller.rb' - 'app/controllers/management/spending_proposals_controller.rb' @@ -487,7 +476,7 @@ Style/FileName: Style/GuardClause: Enabled: false -# Offense count: 12 +# Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: @@ -498,19 +487,19 @@ Style/IfUnlessModifier: - 'app/controllers/legislation/annotations_controller.rb' - 'app/controllers/valuation/budget_investments_controller.rb' - 'app/controllers/verification/letter_controller.rb' - - 'app/controllers/welcome_controller.rb' - 'app/helpers/embed_videos_helper.rb' - 'app/mailers/mailer.rb' - 'app/models/proposal.rb' - 'app/models/spending_proposal.rb' -# Offense count: 4 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: line_count_dependent, lambda, literal Style/Lambda: Exclude: - 'app/models/comment.rb' + - 'app/models/concerns/followable.rb' - 'app/models/direct_message.rb' - 'app/models/vote.rb' - 'lib/graph_ql/api_types_creator.rb' @@ -526,17 +515,14 @@ Style/MultilineIfThen: Exclude: - 'app/controllers/management/users_controller.rb' -# Offense count: 15 +# Offense count: 13 # Cop supports --auto-correct. Style/MutableConstant: Exclude: - 'app/models/activity.rb' - 'app/models/budget/reclassified_vote.rb' - 'app/models/legislation/draft_version.rb' - - 'app/models/poll/null_result.rb' - 'app/models/poll/partial_result.rb' - - 'app/models/poll/white_result.rb' - - 'app/models/poll/total_result.rb' - 'app/models/proposal.rb' - 'app/models/signature_sheet.rb' - 'app/models/site_customization/content_block.rb' @@ -552,7 +538,7 @@ Style/MutableConstant: Style/NumericLiterals: MinDigits: 9 -# Offense count: 19 +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. # SupportedStyles: predicate, comparison @@ -580,7 +566,7 @@ Style/ParallelAssignment: - 'lib/active_model/dates.rb' - 'spec/support/common_actions.rb' -# Offense count: 11 +# Offense count: 10 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ @@ -594,7 +580,6 @@ Style/PredicateName: - 'app/helpers/debates_helper.rb' - 'app/models/budget/ballot.rb' - 'app/models/user.rb' - - 'lib/census_api.rb' # Offense count: 4 # Cop supports --auto-correct. @@ -614,13 +599,14 @@ Style/RedundantBegin: - 'app/controllers/graphql_controller.rb' - 'app/models/legislation/annotation.rb' -# Offense count: 3 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: Exclude: - 'app/helpers/embed_videos_helper.rb' + - 'app/models/poll/question/answer/video.rb' - 'spec/customization_engine_spec.rb' # Offense count: 6 @@ -639,22 +625,16 @@ Style/SafeNavigation: Exclude: - 'app/models/signature.rb' -# Offense count: 9 -# Configuration parameters: SupportedStyles. -# SupportedStyles: snake_case, camelCase -Style/VariableName: - EnforcedStyle: snake_case - -# Offense count: 107 +# Offense count: 93 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: snake_case, normalcase, non_integer Style/VariableNumber: Enabled: false -# Offense count: 31 +# Offense count: 34 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles, WordRegex. # SupportedStyles: percent, brackets Style/WordArray: EnforcedStyle: percent - MinSize: 9 + MinSize: 8 diff --git a/app/assets/fonts/icons.eot b/app/assets/fonts/icons.eot index 5564a5c62..53439b281 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 0d94e5751..a0c854397 100644 --- a/app/assets/fonts/icons.svg +++ b/app/assets/fonts/icons.svg @@ -65,4 +65,9 @@ + + + + + diff --git a/app/assets/fonts/icons.ttf b/app/assets/fonts/icons.ttf index 6af0797fb..3bd8e9dec 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 7f8f3c95e..0ac7f8f19 100644 Binary files a/app/assets/fonts/icons.woff and b/app/assets/fonts/icons.woff differ diff --git a/app/assets/images/ballot.gif b/app/assets/images/ballot.gif deleted file mode 100644 index 18cf0717f..000000000 Binary files a/app/assets/images/ballot.gif and /dev/null differ diff --git a/app/assets/images/ballot_tiny.gif b/app/assets/images/ballot_tiny.gif deleted file mode 100644 index 976d37591..000000000 Binary files a/app/assets/images/ballot_tiny.gif and /dev/null differ diff --git a/app/assets/images/custom/example_horizontal.jpg b/app/assets/images/custom/example_horizontal.jpg deleted file mode 100644 index 634e432ac..000000000 Binary files a/app/assets/images/custom/example_horizontal.jpg and /dev/null differ diff --git a/app/assets/images/custom/example_vertical.jpg b/app/assets/images/custom/example_vertical.jpg deleted file mode 100644 index 11743cc18..000000000 Binary files a/app/assets/images/custom/example_vertical.jpg and /dev/null differ diff --git a/app/assets/images/help/help_icon_budgets.png b/app/assets/images/help/help_icon_budgets.png index fc5e3022f..f986a939c 100644 Binary files a/app/assets/images/help/help_icon_budgets.png and b/app/assets/images/help/help_icon_budgets.png differ diff --git a/app/assets/images/help/help_icon_polls.png b/app/assets/images/help/help_icon_polls.png index b7f7cf479..f13363934 100644 Binary files a/app/assets/images/help/help_icon_polls.png and b/app/assets/images/help/help_icon_polls.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index ce9861547..51de9c678 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -15,6 +15,7 @@ //= require jquery-ui/widgets/datepicker //= require jquery-ui/i18n/datepicker-es //= require jquery-ui/widgets/autocomplete +//= require jquery-ui/widgets/sortable //= require jquery-fileupload/basic //= require foundation //= require turbolinks @@ -71,6 +72,7 @@ //= require leaflet //= require map //= require polls +//= require sortable var initialize_modules = function() { App.Comments.initialize(); @@ -110,6 +112,7 @@ var initialize_modules = function() { App.PollsAdmin.initialize(); App.Map.initialize(); App.Polls.initialize(); + App.Sortable.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/polls.js.coffee b/app/assets/javascripts/polls.js.coffee index 5cf792ce2..ac2c759ba 100644 --- a/app/assets/javascripts/polls.js.coffee +++ b/app/assets/javascripts/polls.js.coffee @@ -26,3 +26,19 @@ App.Polls = token_message.html(token_message.html() + "
" + @token + ""); token_message.show() false + + $(".zoom-link").on "click", (event) -> + element = event.target + answer = $(element).closest('div.answer') + + if $(answer).hasClass('medium-6') + $(answer).removeClass("medium-6"); + $(answer).addClass("answer-divider"); + unless $(answer).hasClass('first') + $(answer).insertBefore($(answer).prev('div.answer')); + else + $(answer).addClass("medium-6"); + $(answer).removeClass("answer-divider"); + unless $(answer).hasClass('first') + $(answer).insertAfter($(answer).next('div.answer')); + diff --git a/app/assets/javascripts/prevent_double_submission.js.coffee b/app/assets/javascripts/prevent_double_submission.js.coffee index 2a4ef9c5e..56a099730 100644 --- a/app/assets/javascripts/prevent_double_submission.js.coffee +++ b/app/assets/javascripts/prevent_double_submission.js.coffee @@ -22,11 +22,13 @@ App.PreventDoubleSubmission = initialize: -> $('form').on('submit', (event) -> - buttons = $(this).find(':button, :submit') - App.PreventDoubleSubmission.disable_buttons(buttons) - ).on('ajax:success', -> - buttons = $(this).find(':button, :submit') - App.PreventDoubleSubmission.reset_buttons(buttons) + unless event.target.id == "new_officing_voter" + buttons = $(this).find(':button, :submit') + App.PreventDoubleSubmission.disable_buttons(buttons) + ).on('ajax:success', (event) -> + unless event.target.id == "new_officing_voter" + buttons = $(this).find(':button, :submit') + App.PreventDoubleSubmission.reset_buttons(buttons) ) false diff --git a/app/assets/javascripts/sortable.js.coffee b/app/assets/javascripts/sortable.js.coffee new file mode 100644 index 000000000..1af543f6a --- /dev/null +++ b/app/assets/javascripts/sortable.js.coffee @@ -0,0 +1,9 @@ +App.Sortable = + initialize: -> + $(".sortable").sortable + update: (event, ui) -> + new_order = $(this).sortable('toArray', {attribute: 'data-answer-id'}); + $.ajax + url: $('.sortable').data('js-url'), + data: {ordered_list: new_order}, + type: 'POST' diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 8f6912716..85997aecd 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -605,6 +605,7 @@ table { .callout { height: $line-height * 2; line-height: $line-height * 2; + margin: 0; padding: 0 $line-height / 2; } } diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index aff55c88c..3f93ffa6b 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -18,4 +18,5 @@ @import 'datepicker_overrides'; @import 'jquery-ui/autocomplete'; @import 'autocomplete_overrides'; +@import 'jquery-ui/sortable'; @import 'leaflet'; diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index 96eb10a0d..b9f2735b4 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -268,3 +268,23 @@ .icon-search-minus::before { content: '\35'; } + +.icon-calculator::before { + content: '\36'; +} + +.icon-map-marker::before { + content: '\37'; +} + +.icon-user-plus::before { + content: '\38'; +} + +.icon-file-text-o::before { + content: '\39'; +} + +.icon-file-text::before { + content: '\21'; +} diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index efa0454db..6a22e92b9 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -155,6 +155,10 @@ a { margin-bottom: $line-height; } +.margin-left { + margin-left: $line-height; +} + .margin-right { margin-right: $line-height; } @@ -210,6 +214,8 @@ a { margin-bottom: $line-height / 2; li { + font-size: $base-font-size; + margin-bottom: 0; margin-right: $line-height / 2; @include breakpoint(medium) { @@ -312,13 +318,16 @@ a { } .tabs { - border: { - left: 0; - right: 0; - top: 0; - }; + border-left: 0; + border-right: 0; + border-top: 0; margin-bottom: $line-height; + .tabs-title { + font-size: $base-font-size; + margin-bottom: 0; + } + .tabs-title > a { color: $text-medium; margin-bottom: rem-calc(-1); @@ -368,6 +377,14 @@ a { display: table-cell; } +.off-canvas-content { + box-shadow: none; +} + +.uppercase { + text-transform: uppercase; +} + // 02. Header // ---------- @@ -1131,8 +1148,13 @@ form { color: #ecf00b; font-size: rem-calc(10); position: absolute; - right: 8px; + left: 12px; top: 6px; + + @include breakpoint(medium) { + left: auto; + right: 8px; + } } } @@ -2020,7 +2042,6 @@ table { // ------------ .activity { - margin-bottom: $line-height * 2; .accordion li { margin-bottom: $line-height / 2; @@ -2052,62 +2073,15 @@ table { } } - table { - border: 0; - margin-bottom: 0; - } - - td { - position: relative; - - &:first-child { - padding-left: $line-height * 1.5; - width: 75%; - } - - &::before { - color: $brand; - font-family: "icons" !important; - font-size: rem-calc(24); - left: 4px; - position: absolute; - } - } - - .activity-comments td:first-child::before { - content: 'e'; - top: 18px; - } - - .activity-debates td:first-child::before { - content: 'i'; - top: 14px; - } - - .activity-proposals { - - td:first-child::before { - content: 'h'; - top: 18px; - } - - .retired { - text-decoration: line-through; - } - } - - .activity-investment-projects td:first-child::before, - .activity-ballot td:first-child::before { - content: '\53'; - top: 10px; + .retired { + text-decoration: line-through; } } .public-interests { - margin-top: $line-height; - .column { - padding-left: 0; + li { + margin-right: $line-height / 4; } } diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 7d6a0fff7..81757d78c 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -8,6 +8,7 @@ // 06. Budget // 07. Proposals successful // 08. Polls +// 09. Polls results and stats // // 01. Votes and supports @@ -363,10 +364,6 @@ width: rem-calc(48); } - .edit-debate { - margin-bottom: 0; - } - .debate-info, .proposal-info, .investment-project-info, @@ -1478,33 +1475,6 @@ } } -.featured-proposals-ballot-banner, -.sucessfull-proposals-banner { - background: #2d3e50 image-url('ballot_tiny.gif') no-repeat; - background-position: 75% 0; - position: relative; - - @include breakpoint(medium) { - margin-left: 0 !important; - margin-right: 0 !important; - } - - @include breakpoint(large) { - background: #2d3e50 image-url('ballot.gif') no-repeat; - background-position: 90% 0; - } - - h2, - a:hover h2 { - color: #ffd200 !important; - } - - p { - color: #fff; - } -} - -.sucessfull-proposals-banner, .successful .panel { .icon-successful { @@ -1596,7 +1566,7 @@ height: 100%; &.short { - height: $line-height * 12; + height: rem-calc(300); overflow: hidden; } } @@ -1830,3 +1800,59 @@ } } } + +// 09. Polls results and stats +// --------------------------- + +.polls-results-stats { + + .sidebar { + border-bottom: 1px solid $border; + margin-bottom: $line-height; + + @include breakpoint(medium) { + border-bottom: 0; + border-right: 1px solid $border; + } + + .menu { + padding: 0; + + li a { + color: $link; + line-height: $line-height; + } + } + } + + table { + table-layout: fixed; + + caption { + padding: $line-height / 2 0; + text-align: left; + } + + th { + text-align: left; + + &.win { + background: #009fde; + } + } + + td { + + &.win { + background: #ccedf8; + font-weight: bold; + } + } + } + + .number { + font-size: rem-calc(60); + font-weight: bold; + line-height: rem-calc(60); + } +} diff --git a/app/assets/stylesheets/print.css b/app/assets/stylesheets/print.css index 6b3e5d6f6..58d2bab5e 100644 --- a/app/assets/stylesheets/print.css +++ b/app/assets/stylesheets/print.css @@ -12,7 +12,7 @@ #print_link { display: none !important; } -#responsive-menu { display: none !important; } +#responsive_menu { display: none !important; } .admin-sidebar { display: none !important; } diff --git a/app/controllers/admin/poll/booth_assignments_controller.rb b/app/controllers/admin/poll/booth_assignments_controller.rb index 8ffa455c8..5b9ea9d7b 100644 --- a/app/controllers/admin/poll/booth_assignments_controller.rb +++ b/app/controllers/admin/poll/booth_assignments_controller.rb @@ -18,21 +18,29 @@ class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController @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} + @partial_results = @booth_assignment.partial_results + @recounts = @booth_assignment.recounts end def create - @booth_assignment = ::Poll::BoothAssignment.new(poll_id: booth_assignment_params[:poll_id], - booth_id: booth_assignment_params[:booth_id]) + @poll = Poll.find(booth_assignment_params[:poll_id]) + @booth = Poll::Booth.find(booth_assignment_params[:booth_id]) + @booth_assignment = ::Poll::BoothAssignment.new(poll: @poll, + booth: @booth) if @booth_assignment.save notice = t("admin.poll_booth_assignments.flash.create") else notice = t("admin.poll_booth_assignments.flash.error_create") end - redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + respond_to do |format| + format.js { render layout: false } + end end def destroy + @poll = Poll.find(booth_assignment_params[:poll_id]) + @booth = Poll::Booth.find(booth_assignment_params[:booth_id]) @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) if @booth_assignment.destroy @@ -40,7 +48,14 @@ class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController else notice = t("admin.poll_booth_assignments.flash.error_destroy") end - redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + respond_to do |format| + format.js { render layout: false } + end + end + + def manage + @booths = ::Poll::Booth.all + @poll = Poll.find(params[:poll_id]) end private diff --git a/app/controllers/admin/poll/officer_assignments_controller.rb b/app/controllers/admin/poll/officer_assignments_controller.rb index fd62df8b3..7532362a7 100644 --- a/app/controllers/admin/poll/officer_assignments_controller.rb +++ b/app/controllers/admin/poll/officer_assignments_controller.rb @@ -25,7 +25,9 @@ class Admin::Poll::OfficerAssignmentsController < Admin::Poll::BaseController def search_officers load_search - @officers = User.joins(:poll_officer).search(@search).order(username: :asc) + + poll_officers = User.where(id: @poll.officers.pluck(:user_id)) + @officers = poll_officers.search(@search).order(username: :asc) respond_to do |format| format.js diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb index cce880ed4..90a31192c 100644 --- a/app/controllers/admin/poll/polls_controller.rb +++ b/app/controllers/admin/poll/polls_controller.rb @@ -47,6 +47,10 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController redirect_to admin_poll_path(@poll), notice: notice end + def booth_assignments + @polls = Poll.current_or_incoming + end + private def load_geozones @@ -54,9 +58,10 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController end def poll_params - params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, :summary, :description, - geozone_ids: [], - image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]) + params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, + :summary, :description, :results_enabled, :stats_enabled, + geozone_ids: [], + image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]) end def search_params diff --git a/app/controllers/admin/poll/questions/answers/images_controller.rb b/app/controllers/admin/poll/questions/answers/images_controller.rb index 1bf30907d..030e177a1 100644 --- a/app/controllers/admin/poll/questions/answers/images_controller.rb +++ b/app/controllers/admin/poll/questions/answers/images_controller.rb @@ -5,7 +5,7 @@ class Admin::Poll::Questions::Answers::ImagesController < Admin::Poll::BaseContr end def new - @answer = ::Poll::Question::Answer.find(params[:answer_id]) + @answer = ::Poll::Question::Answer.find(params[:answer_id]) end def create diff --git a/app/controllers/admin/poll/questions/answers/videos_controller.rb b/app/controllers/admin/poll/questions/answers/videos_controller.rb index a231c1a20..e9262b6dd 100644 --- a/app/controllers/admin/poll/questions/answers/videos_controller.rb +++ b/app/controllers/admin/poll/questions/answers/videos_controller.rb @@ -33,11 +33,11 @@ class Admin::Poll::Questions::Answers::VideosController < Admin::Poll::BaseContr end def destroy - if @video.destroy - notice = t("flash.actions.destroy.poll_question_answer_video") - else - notice = t("flash.actions.destroy.error") - end + notice = if @video.destroy + t("flash.actions.destroy.poll_question_answer_video") + else + t("flash.actions.destroy.error") + end redirect_to :back, notice: notice end diff --git a/app/controllers/admin/poll/questions/answers_controller.rb b/app/controllers/admin/poll/questions/answers_controller.rb index 51c44dd94..bef176a22 100644 --- a/app/controllers/admin/poll/questions/answers_controller.rb +++ b/app/controllers/admin/poll/questions/answers_controller.rb @@ -39,6 +39,11 @@ class Admin::Poll::Questions::AnswersController < Admin::Poll::BaseController render 'admin/poll/questions/answers/documents' end + def order_answers + ::Poll::Question::Answer.order_answers(params[:ordered_list]) + render nothing: true + end + private def answer_params diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb index 5cf587735..64ecf4009 100644 --- a/app/controllers/admin/poll/questions_controller.rb +++ b/app/controllers/admin/poll/questions_controller.rb @@ -15,7 +15,6 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController def new @polls = Poll.all - @question.valid_answers = I18n.t('poll_questions.default_valid_answers') proposal = Proposal.find(params[:proposal_id]) if params[:proposal_id].present? @question.copy_attributes_from_proposal(proposal) end @@ -56,7 +55,7 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController private def question_params - params.require(:poll_question).permit(:poll_id, :title, :question, :proposal_id, :valid_answers, :video_url) + params.require(:poll_question).permit(:poll_id, :title, :question, :proposal_id, :video_url) end def search_params diff --git a/app/controllers/admin/poll/shifts_controller.rb b/app/controllers/admin/poll/shifts_controller.rb index 1f60b2b4e..1261f2951 100644 --- a/app/controllers/admin/poll/shifts_controller.rb +++ b/app/controllers/admin/poll/shifts_controller.rb @@ -6,6 +6,8 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController def new load_shifts @shift = ::Poll::Shift.new + @voting_polls = @booth.polls.current_or_incoming + @recount_polls = @booth.polls.current_or_recounting_or_incoming end def create @@ -49,7 +51,7 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController end def shift_params - shift_params = params.require(:shift).permit(:booth_id, :officer_id, :task, date:[:vote_collection_date, :recount_scrutiny_date]) + shift_params = params.require(:shift).permit(:booth_id, :officer_id, :task, date: [:vote_collection_date, :recount_scrutiny_date]) shift_params.merge(date: shift_params[:date]["#{shift_params[:task]}_date".to_sym]) end end diff --git a/app/controllers/direct_uploads_controller.rb b/app/controllers/direct_uploads_controller.rb index ade7d631f..a067a5d04 100644 --- a/app/controllers/direct_uploads_controller.rb +++ b/app/controllers/direct_uploads_controller.rb @@ -18,8 +18,7 @@ class DirectUploadsController < ApplicationController render json: { cached_attachment: @direct_upload.relation.cached_attachment, filename: @direct_upload.relation.attachment.original_filename, destroy_link: render_destroy_upload_link(@direct_upload).html_safe, - attachment_url: @direct_upload.relation.attachment.url - } + attachment_url: @direct_upload.relation.attachment.url} else @direct_upload.destroy_attachment render json: { errors: @direct_upload.errors[:attachment].join(", ") }, @@ -28,7 +27,7 @@ class DirectUploadsController < ApplicationController end def destroy - @direct_upload = DirectUpload.new(direct_upload_params.merge(user: current_user) ) + @direct_upload = DirectUpload.new(direct_upload_params.merge(user: current_user)) @direct_upload.relation.set_attachment_from_cached_attachment if @direct_upload.destroy_attachment diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 16a82de6a..7d833f6a1 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -29,6 +29,8 @@ class NotificationsController < ApplicationController case notification.linkable_resource.class.name when "Budget::Investment" budget_investment_path @notification.linkable_resource.budget, @notification.linkable_resource + when "Topic" + community_topic_path @notification.linkable_resource.community, @notification.linkable_resource else url_for @notification.linkable_resource end diff --git a/app/controllers/officing/base_controller.rb b/app/controllers/officing/base_controller.rb index 97ef23d30..07cf4cfa5 100644 --- a/app/controllers/officing/base_controller.rb +++ b/app/controllers/officing/base_controller.rb @@ -7,6 +7,6 @@ class Officing::BaseController < ApplicationController skip_authorization_check def verify_officer - raise CanCan::AccessDenied unless current_user.try(:poll_officer?) || current_user.try(:administrator?) + raise CanCan::AccessDenied unless current_user.try(:poll_officer?) end end \ No newline at end of file diff --git a/app/controllers/officing/results_controller.rb b/app/controllers/officing/results_controller.rb index 23ef0b038..39adcc7ed 100644 --- a/app/controllers/officing/results_controller.rb +++ b/app/controllers/officing/results_controller.rb @@ -5,7 +5,7 @@ class Officing::ResultsController < Officing::BaseController before_action :load_partial_results, only: :new before_action :load_officer_assignment, only: :create - before_action :check_booth_and_date, only: :create + before_action :check_officer_assignment, only: :create before_action :build_results, only: :create def new @@ -32,13 +32,9 @@ class Officing::ResultsController < Officing::BaseController private - def check_booth_and_date + def check_officer_assignment if @officer_assignment.blank? go_back_to_new(t("officing.results.flash.error_wrong_booth")) - elsif results_params[:date].blank? || - Date.parse(results_params[:date]) < @poll.starts_at.to_date || - Date.parse(results_params[:date]) > @poll.ends_at.to_date - go_back_to_new(t("officing.results.flash.error_wrong_date")) end end @@ -51,11 +47,11 @@ class Officing::ResultsController < Officing::BaseController results.each_pair do |answer_index, count| next if count.blank? - answer = question.valid_answers[answer_index.to_i] + answer = question.question_answers.where(given_order: answer_index.to_i + 1).first.title 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], + date: Date.current, question_id: question_id, answer: answer) partial_result.officer_assignment_id = @officer_assignment.id @@ -71,7 +67,7 @@ class Officing::ResultsController < Officing::BaseController def build_recounts recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, - date: results_params[:date]) + date: Date.current) recount.officer_assignment_id = @officer_assignment.id recount.author = current_user recount.origin = 'booth' @@ -84,7 +80,7 @@ class Officing::ResultsController < Officing::BaseController end def go_back_to_new(alert = nil) - params[:d] = results_params[:date] + params[:d] = Date.current params[:oa] = results_params[:officer_assignment_id] flash.now[:alert] = (alert || t("officing.results.flash.error_create")) load_officer_assignments @@ -108,7 +104,7 @@ class Officing::ResultsController < Officing::BaseController final. where(id: current_user.poll_officer.officer_assignment_ids). where("poll_booth_assignments.poll_id = ?", @poll.id). - order(date: :asc) + where(date: Date.current) end def load_partial_results @@ -119,7 +115,7 @@ class Officing::ResultsController < Officing::BaseController end def results_params - params.permit(:officer_assignment_id, :date, :questions, :whites, :nulls, :total) + params.permit(:officer_assignment_id, :questions, :whites, :nulls, :total) end def index_params diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb index 407e6d984..e1fc73805 100644 --- a/app/controllers/polls/questions_controller.rb +++ b/app/controllers/polls/questions_controller.rb @@ -13,6 +13,9 @@ class Polls::QuestionsController < ApplicationController answer.touch if answer.persisted? answer.save! answer.record_voter_participation(token) + @question.question_answers.where(question_id: @question).each do |answer| + answer.set_most_voted + end @answers_by_question_id = { @question.id => params[:answer] } end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb index 064aa130f..708f68abb 100644 --- a/app/controllers/polls_controller.rb +++ b/app/controllers/polls_controller.rb @@ -1,10 +1,10 @@ class PollsController < ApplicationController - include PollsHelper load_and_authorize_resource has_filters %w{current expired incoming} + has_orders %w{most_voted newest oldest}, only: :show ::Poll::Answer # trigger autoload @@ -15,11 +15,23 @@ class PollsController < ApplicationController def show @questions = @poll.questions.for_render.sort_for_list @token = poll_voter_token(@poll, current_user) + @poll_questions_answers = Poll::Question::Answer.where(question: @poll.questions).where.not(description: "").order(:given_order) + @answers_by_question_id = {} poll_answers = ::Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id)) poll_answers.each do |answer| @answers_by_question_id[answer.question_id] = answer.answer end + + @commentable = @poll + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + end + + def stats + @stats = Poll::Stats.new(@poll).generate + end + + def results end end diff --git a/app/helpers/documentables_helper.rb b/app/helpers/documentables_helper.rb index 8d9f85578..3fe56abd2 100644 --- a/app/helpers/documentables_helper.rb +++ b/app/helpers/documentables_helper.rb @@ -24,8 +24,8 @@ module DocumentablesHelper def documentable_humanized_accepted_content_types(documentable_class) documentable_class.accepted_content_types - .collect{ |content_type| content_type.split("/").last } - .join(", ") + .collect{ |content_type| content_type.split("/").last } + .join(", ") end def documentables_note(documentable) diff --git a/app/helpers/documents_helper.rb b/app/helpers/documents_helper.rb index d7e8d1dea..70eb6a27f 100644 --- a/app/helpers/documents_helper.rb +++ b/app/helpers/documents_helper.rb @@ -33,7 +33,7 @@ module DocumentsHelper def render_attachment(builder, document) klass = document.errors[:attachment].any? ? "error" : "" - klass = document.persisted? || document.cached_attachment.present? ? " hide" : "" + klass = document.persisted? || document.cached_attachment.present? ? " hide" : "" html = builder.label :attachment, t("documents.form.attachment_label"), class: "button hollow #{klass}" diff --git a/app/helpers/flags_helper.rb b/app/helpers/flags_helper.rb index b5ba67f41..9983b34ae 100644 --- a/app/helpers/flags_helper.rb +++ b/app/helpers/flags_helper.rb @@ -12,7 +12,7 @@ module FlagsHelper def flagged?(flaggable) if flaggable.is_a? Comment - @comment_flags[flaggable.id] + @comment_flags[flaggable.id] unless flaggable.commentable_type == "Poll" else Flag.flagged?(current_user, flaggable) end diff --git a/app/helpers/imageables_helper.rb b/app/helpers/imageables_helper.rb index b1c8059ce..8cae1b989 100644 --- a/app/helpers/imageables_helper.rb +++ b/app/helpers/imageables_helper.rb @@ -9,10 +9,10 @@ module ImageablesHelper end def imageable_max_file_size - bytesToMeg(Image::MAX_IMAGE_SIZE) + bytes_to_megabytes(Image::MAX_IMAGE_SIZE) end - def bytesToMeg(bytes) + def bytes_to_megabytes(bytes) bytes / Numeric::MEGABYTE end @@ -22,19 +22,19 @@ module ImageablesHelper def imageable_accepted_content_types_extensions Image::ACCEPTED_CONTENT_TYPE - .collect{ |content_type| ".#{content_type.split("/").last}" } - .join(",") + .collect{ |content_type| ".#{content_type.split('/').last}" } + .join(",") end def imageable_humanized_accepted_content_types Image::ACCEPTED_CONTENT_TYPE - .collect{ |content_type| content_type.split("/").last } - .join(", ") + .collect{ |content_type| content_type.split("/").last } + .join(", ") end - def imageables_note(imageable) + def imageables_note(_imageable) t "images.form.note", accepted_content_types: imageable_humanized_accepted_content_types, max_file_size: imageable_max_file_size end -end \ No newline at end of file +end diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb index 4e2bdff0e..208ccf5ab 100644 --- a/app/helpers/images_helper.rb +++ b/app/helpers/images_helper.rb @@ -22,7 +22,7 @@ module ImagesHelper image.errors[:attachment].join(', ') if image.errors.key?(:attachment) end - def image_bytesToMeg(bytes) + def image_bytes_to_megabytes(bytes) bytes / Numeric::MEGABYTE end diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb index dd4018be7..0d5b0a605 100644 --- a/app/helpers/polls_helper.rb +++ b/app/helpers/polls_helper.rb @@ -44,7 +44,7 @@ module PollsHelper def poll_voter_token(poll, user) Poll::Voter.where(poll: poll, user: user, origin: "web").first&.token || '' end - + def voted_before_sign_in(question) question.answers.where(author: current_user).any? { |vote| current_user.current_sign_in_at >= vote.updated_at } end diff --git a/app/helpers/shifts_helper.rb b/app/helpers/shifts_helper.rb index dc9f97a91..7ea17baf2 100644 --- a/app/helpers/shifts_helper.rb +++ b/app/helpers/shifts_helper.rb @@ -1,19 +1,30 @@ module ShiftsHelper - def shift_vote_collection_dates(polls) - date_options((start_date(polls)..end_date(polls))) + def shift_vote_collection_dates(booth, polls) + return [] if polls.blank? + date_options((start_date(polls)..end_date(polls)), Poll::Shift.tasks[:vote_collection], booth) end - def shift_recount_scrutiny_dates(polls) - date_options(polls.map(&:ends_at).map(&:to_date).sort.inject([]) { |total, date| total << (date..date + 1.week).to_a }.flatten.uniq) + def shift_recount_scrutiny_dates(booth, polls) + return [] if polls.blank? + dates = polls.map(&:ends_at).map(&:to_date).sort.inject([]) do |total, date| + initial_date = date < Date.current ? Date.current : date + total << (initial_date..date + Poll::RECOUNT_DURATION).to_a + end + date_options(dates.flatten.uniq, Poll::Shift.tasks[:recount_scrutiny], booth) end - def date_options(dates) - dates.map { |date| [l(date, format: :long), l(date)] } + def date_options(dates, task_id, booth) + valid_dates(dates, task_id, booth).map { |date| [l(date, format: :long), l(date)] } + end + + def valid_dates(dates, task_id, booth) + dates.reject { |date| officer_shifts(task_id, booth).include?(date) } end def start_date(polls) - polls.map(&:starts_at).min.to_date + start_date = polls.map(&:starts_at).min.to_date + start_date < Date.current ? Date.current : start_date end def end_date(polls) @@ -24,4 +35,9 @@ module ShiftsHelper officers.collect { |officer| [officer.name, officer.id] } end + private + + def officer_shifts(task_id, booth) + @officer.shifts.where(task: task_id, booth: booth).map(&:date) + end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 111a80267..da340bb46 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -52,12 +52,8 @@ module UsersHelper current_user && current_user.manager? end - def current_poll_officer? - current_user && current_user.poll_officer? - end - def show_admin_menu? - current_administrator? || current_moderator? || current_valuator? || current_manager? || current_poll_officer? + current_administrator? || current_moderator? || current_valuator? || current_manager? end def interests_title_text(user) diff --git a/app/helpers/welcome_helper.rb b/app/helpers/welcome_helper.rb index 9f8c8c13e..0b483468f 100644 --- a/app/helpers/welcome_helper.rb +++ b/app/helpers/welcome_helper.rb @@ -1,11 +1,11 @@ module WelcomeHelper def active_class(index) - "is-active is-in" if index == 0 + "is-active is-in" if index.zero? end def slide_display(index) - "display: none;" if index > 0 + "display: none;" if index.positive? end def recommended_path(recommended) @@ -46,17 +46,13 @@ module WelcomeHelper 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 + if debates.any? && proposals.any? + offset = if apply_offset + "medium-offset-2 large-offset-2" + else + "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 795402003..951f06249 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -62,10 +62,10 @@ module Abilities can [:index, :create, :edit, :update, :destroy], Geozone - can [:read, :create, :update, :destroy, :add_question, :search_booths, :search_officers], Poll + can [:read, :create, :update, :destroy, :add_question, :search_booths, :search_officers, :booth_assignments, :results, :stats], Poll can [:read, :create, :update, :destroy, :available], Poll::Booth can [:search, :create, :index, :destroy], ::Poll::Officer - can [:create, :destroy], ::Poll::BoothAssignment + can [:create, :destroy, :manage], ::Poll::BoothAssignment can [:create, :destroy], ::Poll::OfficerAssignment can [:read, :create, :update], Poll::Question can :destroy, Poll::Question # , comments_count: 0, votes_up: 0 diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index c97bf0d94..258e030bd 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -7,6 +7,12 @@ module Abilities can [:read, :map, :summary, :share], Proposal can :read, Comment can :read, Poll + can :results, Poll do |poll| + poll.expired? && poll.results_enabled? + end + can :stats, Poll do |poll| + poll.expired? && poll.stats_enabled? + end can :read, Poll::Question can [:read, :welcome], Budget can :read, SpendingProposal diff --git a/app/models/comment.rb b/app/models/comment.rb index a0cb343d7..8e3024d06 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -3,7 +3,7 @@ class Comment < ActiveRecord::Base include HasPublicAuthor include Graphqlable - COMMENTABLE_TYPES = %w(Debate Proposal Budget::Investment Poll::Question Legislation::Question Legislation::Annotation Topic Legislation::Proposal).freeze + COMMENTABLE_TYPES = %w(Debate Proposal Budget::Investment Poll::Question Legislation::Question Legislation::Annotation Topic Legislation::Proposal Poll).freeze acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases diff --git a/app/models/comment_notifier.rb b/app/models/comment_notifier.rb index 68b350e6b..600009a23 100644 --- a/app/models/comment_notifier.rb +++ b/app/models/comment_notifier.rb @@ -22,6 +22,7 @@ class CommentNotifier end def email_on_comment? + return false if @comment.commentable.is_a?(Poll) commentable_author = @comment.commentable.author commentable_author != @author && commentable_author.email_on_comment? end diff --git a/app/models/concerns/followable.rb b/app/models/concerns/followable.rb index fee1ebd7f..d635cadda 100644 --- a/app/models/concerns/followable.rb +++ b/app/models/concerns/followable.rb @@ -5,7 +5,7 @@ module Followable has_many :follows, as: :followable, dependent: :destroy has_many :followers, through: :follows, source: :user - scope :followed_by_user, -> (user){ + scope :followed_by_user, ->(user){ joins(:follows).where("follows.user_id = ?", user.id) } end diff --git a/app/models/debate.rb b/app/models/debate.rb index eb424c308..65c06345d 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -48,8 +48,8 @@ class Debate < ActiveRecord::Base attr_accessor :link_required def self.recommendations(user) - tagged_with(user.interests, any: true). - where("author_id != ?", user.id) + tagged_with(user.interests, any: true) + .where("author_id != ?", user.id) end def searchable_values diff --git a/app/models/direct_upload.rb b/app/models/direct_upload.rb index e597f9f12..6681aa7be 100644 --- a/app/models/direct_upload.rb +++ b/app/models/direct_upload.rb @@ -7,7 +7,7 @@ class DirectUpload :relation, :resource_relation, :attachment, :cached_attachment, :user - validates_presence_of :attachment, :resource_type, :resource_relation, :user + validates :attachment, :resource_type, :resource_relation, :user, presence: true validate :parent_resource_attachment_validations, if: -> { attachment.present? && resource_type.present? && resource_relation.present? && user.present? } @@ -19,15 +19,15 @@ class DirectUpload if @resource_type.present? && @resource_relation.present? && (@attachment.present? || @cached_attachment.present?) @resource = @resource_type.constantize.find_or_initialize_by(id: @resource_id) - #Refactor - if @resource.respond_to?(:images) && - ((@attachment.present? && !@attachment.content_type.match(/pdf/)) || @cached_attachment.present?) - @relation = @resource.images.send("build", relation_attributtes) - elsif @resource.class.reflections[@resource_relation].macro == :has_one - @relation = @resource.send("build_#{resource_relation}", relation_attributtes) - else - @relation = @resource.send(@resource_relation).build(relation_attributtes) - end + # Refactor + @relation = if @resource.respond_to?(:images) && + ((@attachment.present? && !@attachment.content_type.match(/pdf/)) || @cached_attachment.present?) + @resource.images.send("build", relation_attributtes) + elsif @resource.class.reflections[@resource_relation].macro == :has_one + @resource.send("build_#{resource_relation}", relation_attributtes) + else + @resource.send(@resource_relation).build(relation_attributtes) + end @relation.user = user end @@ -50,7 +50,7 @@ class DirectUpload def parent_resource_attachment_validations @relation.valid? - if @relation.errors.has_key? :attachment + if @relation.errors.key? :attachment errors[:attachment] = @relation.errors[:attachment] end end diff --git a/app/models/document.rb b/app/models/document.rb index f680bc04e..8843bbed9 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -44,7 +44,7 @@ class Document < ActiveRecord::Base attachment.instance.prefix(attachment, style) end - def prefix(attachment, style) + def prefix(attachment, _style) if !attachment.instance.persisted? "cached_attachments/user/#{attachment.instance.user_id}" else diff --git a/app/models/image.rb b/app/models/image.rb index 84b38dd4a..99061f6f1 100644 --- a/app/models/image.rb +++ b/app/models/image.rb @@ -5,7 +5,7 @@ class Image < ActiveRecord::Base TITLE_LEGHT_RANGE = 4..80 MIN_SIZE = 475 MAX_IMAGE_SIZE = 1.megabyte - ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg) + ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg).freeze has_attached_file :attachment, styles: { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" }, url: "/system/:class/:prefix/:style/:hash.:extension", @@ -52,7 +52,7 @@ class Image < ActiveRecord::Base attachment.instance.prefix(attachment, style) end - def prefix(attachment, style) + def prefix(attachment, _style) if !attachment.instance.persisted? "cached_attachments/user/#{attachment.instance.user_id}" else @@ -103,8 +103,8 @@ class Image < ActiveRecord::Base def remove_cached_attachment image = Image.new(imageable: imageable, - cached_attachment: cached_attachment, - user: user) + cached_attachment: cached_attachment, + user: user) image.set_attachment_from_cached_attachment image.attachment.destroy end diff --git a/app/models/poll.rb b/app/models/poll.rb index 84349bf0b..27125d8a1 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -1,5 +1,9 @@ class Poll < ActiveRecord::Base include Imageable + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + RECOUNT_DURATION = 1.week has_many :booth_assignments, class_name: "Poll::BoothAssignment" has_many :booths, through: :booth_assignments @@ -9,8 +13,10 @@ class Poll < ActiveRecord::Base has_many :officer_assignments, through: :booth_assignments has_many :officers, through: :officer_assignments has_many :questions + has_many :comments, as: :commentable has_and_belongs_to_many :geozones + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' validates :name, presence: true @@ -19,11 +25,16 @@ class Poll < ActiveRecord::Base scope :current, -> { where('starts_at <= ? and ? <= ends_at', Date.current.beginning_of_day, Date.current.beginning_of_day) } scope :incoming, -> { where('? < starts_at', Date.current.beginning_of_day) } scope :expired, -> { where('ends_at < ?', Date.current.beginning_of_day) } + scope :recounting, -> { Poll.where(ends_at: (Date.current.beginning_of_day - RECOUNT_DURATION)..Date.current.beginning_of_day) } scope :published, -> { where('published = ?', true) } scope :by_geozone_id, ->(geozone_id) { where(geozones: {id: geozone_id}.joins(:geozones)) } scope :sort_for_list, -> { order(:geozone_restricted, :starts_at, :name) } + def title + name + end + def current?(timestamp = Date.current.beginning_of_day) starts_at <= timestamp && timestamp <= ends_at end @@ -40,6 +51,10 @@ class Poll < ActiveRecord::Base current + incoming end + def self.current_or_recounting_or_incoming + current + recounting + incoming + end + def answerable_by?(user) user.present? && user.level_two_or_three_verified? && @@ -65,6 +80,10 @@ class Poll < ActiveRecord::Base Poll::Voter.where(poll: self, user: user, origin: "booth").exists? end + def voted_in_web?(user) + Poll::Voter.where(poll: self, user: user, origin: "web").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 4484060dd..78122188a 100644 --- a/app/models/poll/answer.rb +++ b/app/models/poll/answer.rb @@ -9,9 +9,8 @@ class Poll::Answer < ActiveRecord::Base validates :author, presence: true validates :answer, presence: true - # temporary skipping validation, review when removing valid_answers - # validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }}, - # unless: ->(a) { a.question.blank? } + validates :answer, inclusion: { in: ->(a) { a.question.question_answers.pluck(:title) }}, + 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) } diff --git a/app/models/poll/booth.rb b/app/models/poll/booth.rb index 04f9d3dea..b9cba45b1 100644 --- a/app/models/poll/booth.rb +++ b/app/models/poll/booth.rb @@ -12,8 +12,11 @@ class Poll end def self.available - where(polls: { id: Poll.current_or_incoming }).includes(:polls) + where(polls: { id: Poll.current_or_recounting_or_incoming }).includes(:polls) end + def assignment_on_poll(poll) + booth_assignments.where(poll: poll).first + end end -end \ No newline at end of file +end diff --git a/app/models/poll/null_result.rb b/app/models/poll/null_result.rb deleted file mode 100644 index d10fe3cb0..000000000 --- a/app/models/poll/null_result.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Poll::NullResult < ActiveRecord::Base - - VALID_ORIGINS = %w{web booth} - - 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 :by_author, ->(author_id) { where(author_id: author_id) } - - before_save :update_logs - - def update_logs - if amount_changed? && amount_was.present? - self.amount_log += ":#{amount_was.to_s}" - self.officer_assignment_id_log += ":#{officer_assignment_id_was.to_s}" - self.author_id_log += ":#{author_id_was.to_s}" - end - end -end diff --git a/app/models/poll/partial_result.rb b/app/models/poll/partial_result.rb index 12b16aa3a..0550650a5 100644 --- a/app/models/poll/partial_result.rb +++ b/app/models/poll/partial_result.rb @@ -10,8 +10,9 @@ class Poll::PartialResult < ActiveRecord::Base validates :question, presence: true validates :author, presence: true validates :answer, presence: true - validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} - validates :origin, inclusion: {in: VALID_ORIGINS} + validates :answer, inclusion: { in: ->(a) { a.question.question_answers.pluck(:title) }}, + unless: ->(a) { a.question.blank? } + validates :origin, inclusion: { in: VALID_ORIGINS } scope :by_author, ->(author_id) { where(author_id: author_id) } scope :by_question, ->(question_id) { where(question_id: question_id) } diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb index e6ef482a9..6be729757 100644 --- a/app/models/poll/question.rb +++ b/app/models/poll/question.rb @@ -10,7 +10,7 @@ class Poll::Question < ActiveRecord::Base has_many :comments, as: :commentable has_many :answers, class_name: 'Poll::Answer' - has_many :question_answers, class_name: 'Poll::Question::Answer' + has_many :question_answers, -> { order 'given_order asc' }, class_name: 'Poll::Question::Answer' has_many :partial_results belongs_to :proposal @@ -39,17 +39,12 @@ class Poll::Question < ActiveRecord::Base author_visible_name => 'C' } end - def valid_answers - (super.try(:split, ',').compact || []).map(&:strip) - end - def copy_attributes_from_proposal(proposal) if proposal.present? self.author = proposal.author self.author_visible_name = proposal.author.name self.proposal_id = proposal.id self.title = proposal.title - self.valid_answers = I18n.t('poll_questions.default_valid_answers') end end @@ -60,4 +55,8 @@ class Poll::Question < ActiveRecord::Base where(poll_id: Poll.answerable_by(user).pluck(:id)) end + def answers_total_votes + question_answers.map { |a| Poll::Answer.where(question_id: self, answer: a.title).count }.sum + end + end diff --git a/app/models/poll/question/answer.rb b/app/models/poll/question/answer.rb index b3324e57f..dbf2f2139 100644 --- a/app/models/poll/question/answer.rb +++ b/app/models/poll/question/answer.rb @@ -1,5 +1,5 @@ class Poll::Question::Answer < ActiveRecord::Base - include Galleryable + include Galleryable include Documentable documentable max_documents_allowed: 3, max_file_size: 3.megabytes, @@ -10,8 +10,45 @@ class Poll::Question::Answer < ActiveRecord::Base has_many :videos, class_name: 'Poll::Question::Answer::Video' validates :title, presence: true + validates :given_order, presence: true, uniqueness: { scope: :question_id } + + before_validation :set_order, on: :create def description super.try :html_safe end + + def self.order_answers(ordered_array) + ordered_array.each_with_index do |answer_id, order| + find(answer_id).update_attribute(:given_order, (order + 1)) + end + end + + def set_order + self.given_order = self.class.last_position(question_id) + 1 + end + + def self.last_position(question_id) + where(question_id: question_id).maximum('given_order') || 0 + end + + def total_votes + Poll::Answer.where(question_id: question, answer: title).count + end + + def most_voted? + self.most_voted + end + + def total_votes_percentage + question.answers_total_votes == 0 ? 0 : (total_votes * 100) / question.answers_total_votes + end + + def set_most_voted + answers = question.question_answers + .map { |a| Poll::Answer.where(question_id: a.question, answer: a.title).count } + is_most_voted = !answers.any?{ |a| a > self.total_votes } + + self.update(most_voted: is_most_voted) + end end diff --git a/app/models/poll/stats.rb b/app/models/poll/stats.rb new file mode 100644 index 000000000..ac45a2aae --- /dev/null +++ b/app/models/poll/stats.rb @@ -0,0 +1,143 @@ +class Poll + class Stats + + def initialize(poll) + @poll = poll + end + + def generate + stats = %w[total_participants total_participants_web total_web_valid total_web_white total_web_null + total_participants_booth total_booth_valid total_booth_white total_booth_null + total_valid_votes total_white_votes total_null_votes valid_percentage_web valid_percentage_booth + total_valid_percentage white_percentage_web white_percentage_booth total_white_percentage + null_percentage_web null_percentage_booth total_null_percentage total_participants_web_percentage + total_participants_booth_percentage] + stats.map { |stat_name| [stat_name.to_sym, send(stat_name)] }.to_h + end + + private + + def total_participants + stats_cache('total_participants') { total_participants_web + total_participants_booth } + end + + def total_participants_web + stats_cache('total_participants_web') { total_web_valid + total_web_white + total_web_null } + end + + def total_participants_web_percentage + stats_cache('total_participants_web_percentage') { + (total_participants) == 0 ? 0 : total_participants_web * 100 / total_participants + } + end + + def total_participants_booth + stats_cache('total_participants_booth') { voters.where(origin: 'booth').count } + end + + def total_participants_booth_percentage + stats_cache('total_participants_booth_percentage') { + (total_participants) == 0 ? 0 : total_participants_booth * 100 / total_participants.to_f + } + end + + def total_web_valid + stats_cache('total_web_valid') { voters.where(origin: 'web').count } + end + + def valid_percentage_web + stats_cache('valid_percentage_web') { + (total_valid_votes) == 0 ? 0 : total_web_valid * 100 / total_valid_votes.to_f + } + end + + def total_web_white + stats_cache('total_web_white') { 0 } + end + + def white_percentage_web + stats_cache('white_percentage_web') { 0 } + end + + def total_web_null + stats_cache('total_web_null') { 0 } + end + + def null_percentage_web + stats_cache('null_percentage_web') { 0 } + end + + def total_booth_valid + stats_cache('total_booth_valid') { recounts.sum(:total_amount) } + end + + def valid_percentage_booth + stats_cache('valid_percentage_booth') { + (total_valid_votes) == 0 ? 0 : total_booth_valid * 100 / total_valid_votes.to_f + } + end + + def total_booth_white + stats_cache('total_booth_white') { recounts.sum(:white_amount) } + end + + def white_percentage_booth + stats_cache('white_percentage_booth') { + (total_white_votes) == 0 ? 0 : total_booth_white * 100 / total_white_votes.to_f + } + end + + def total_booth_null + stats_cache('total_booth_null') { recounts.sum(:null_amount) } + end + + def null_percentage_booth + stats_cache('null_percentage_booth') { + (total_null_votes == 0) ? 0 : total_booth_null * 100 / total_null_votes.to_f + } + end + + def total_valid_votes + stats_cache('total_valid_votes') { total_web_valid + total_booth_valid } + end + + def total_valid_percentage + stats_cache('total_valid_percentage'){ + (total_participants) == 0 ? 0 : total_valid_votes * 100 / total_participants.to_f + } + end + + def total_white_votes + stats_cache('total_white_votes') { total_web_white + total_booth_white } + end + + def total_white_percentage + stats_cache('total_white_percentage') { + (total_participants) == 0 ? 0 : total_white_votes * 100 / total_participants.to_f + } + end + + def total_null_votes + stats_cache('total_null_votes') { total_web_null + total_booth_null } + end + + def total_null_percentage + stats_cache('total_null_percentage') { + (total_participants) == 0 ? 0 : total_null_votes * 100 / total_participants.to_f + } + end + + def voters + stats_cache('voters') { @poll.voters } + end + + def recounts + stats_cache('recounts') { @poll.recounts } + end + + def stats_cache(key, &block) + Rails.cache.fetch("polls_stats/#{@poll.id}/#{key}", &block) + end + + end +end diff --git a/app/models/poll/total_result.rb b/app/models/poll/total_result.rb deleted file mode 100644 index 2df01929e..000000000 --- a/app/models/poll/total_result.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Poll::TotalResult < ActiveRecord::Base - - VALID_ORIGINS = %w{web booth} - - 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 :by_author, ->(author_id) { where(author_id: author_id) } - - before_save :update_logs - - def update_logs - if amount_changed? && amount_was.present? - self.amount_log += ":#{amount_was.to_s}" - self.officer_assignment_id_log += ":#{officer_assignment_id_was.to_s}" - self.author_id_log += ":#{author_id_was.to_s}" - end - end -end diff --git a/app/models/poll/voter.rb b/app/models/poll/voter.rb index bc0118a88..778a5d88a 100644 --- a/app/models/poll/voter.rb +++ b/app/models/poll/voter.rb @@ -1,7 +1,7 @@ class Poll class Voter < ActiveRecord::Base - VALID_ORIGINS = %w{ web booth } + VALID_ORIGINS = %w{web booth}.freeze belongs_to :poll belongs_to :user diff --git a/app/models/poll/white_result.rb b/app/models/poll/white_result.rb deleted file mode 100644 index a4a4e5a4d..000000000 --- a/app/models/poll/white_result.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Poll::WhiteResult < ActiveRecord::Base - - VALID_ORIGINS = %w{web booth} - - 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 :by_author, ->(author_id) { where(author_id: author_id) } - - before_save :update_logs - - def update_logs - if amount_changed? && amount_was.present? - self.amount_log += ":#{amount_was.to_s}" - self.officer_assignment_id_log += ":#{officer_assignment_id_was.to_s}" - self.author_id_log += ":#{author_id_was.to_s}" - end - end -end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 1a688ab30..3b0696d26 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -69,10 +69,10 @@ class Proposal < ActiveRecord::Base 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) + 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) diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index f0350b30f..60d062bcc 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -60,7 +60,8 @@ <%= t("admin.menu.title_polls") %>