diff --git a/.gitignore b/.gitignore index 31491de9d..57284ee60 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,3 @@ public/sitemap.xml public/system/ /public/ckeditor_assets/ - diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d5448da..180257734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,77 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) +## [1.0.0](https://github.com/consul/consul/tree/1.0.0) (2019-06-10) +[Full Changelog](https://github.com/consul/consul/compare/1.0.0-beta...1.0.0) + +### Added +- **Accounts:** Add description field to administrator users like evaluators description [\#3389](https://github.com/consul/consul/pull/3389) +- **Admin:** Add document uploads from admin section [\#3466](https://github.com/consul/consul/pull/3466) +- **Admin:** Images and documents settings [\#3585](https://github.com/consul/consul/pull/3585) +- **Budgets:** notify by email new evaluation comments [\#3413](https://github.com/consul/consul/pull/3413) +- **Installation:** Add deploy-secrets.yml.example file [\#3516](https://github.com/consul/consul/pull/3516) +- **Installation:** Add new settings automatically on every deployment [\#3576](https://github.com/consul/consul/pull/3576) +- **Installation:** Add task to upgrade to a new release [\#3590](https://github.com/consul/consul/pull/3590) +- **Legislations:** Create Legislation::PeopleProposal model [\#3591](https://github.com/consul/consul/pull/3591) +- **Translations:** Update translations from Crowdin [\#3378](https://github.com/consul/consul/pull/3378) +- **Translations:** Admin basic customization texts [\#3488](https://github.com/consul/consul/pull/3488) +- **Translations:** Add Bosnian, Croatian, Czech, Danish, Greek, and Turkish locales [\#3571](https://github.com/consul/consul/pull/3571) +- **Newsletters:** Proposals authors user segment [\#3507](https://github.com/consul/consul/pull/3507) +- **Polls:** Add slug to polls [\#3504](https://github.com/consul/consul/pull/3504) +- **Statistics:** Add budget stats [\#3438](https://github.com/consul/consul/pull/3438) +- **Statistics:** Add admin budget stats [\#3499](https://github.com/consul/consul/pull/3499) +- **Statistics:** Add options to show advanced stats [\#3520](https://github.com/consul/consul/pull/3520) + +### Changed +- **Accounts:** Change devise configuration [\#3561](https://github.com/consul/consul/pull/3561) +- **Admin:** Show count of votes associated to verified signatures [\#2616](https://github.com/consul/consul/pull/2616) +- **Budgets:** Don't destroy budgets with an associated poll [\#3492](https://github.com/consul/consul/pull/3492) +- **Budgets:** Add task to regenerate ballot\_lines\_count cache [\#3563](https://github.com/consul/consul/pull/3563) +- **Dashboard:** Hide polls created by users from proposals dashboard on admin poll index [\#3572](https://github.com/consul/consul/pull/3572) +- **Dashboard:** Allow users to delete dashboard polls [\#3574](https://github.com/consul/consul/pull/3574) +- **Maintenance:** Add Rails 5.1 compatibility [\#3562](https://github.com/consul/consul/pull/3562) +- **Maintenance:** Update migrations and schema file [\#3598](https://github.com/consul/consul/pull/3598) +- **Maintenance-Refactoring:** Refactor admin/debates and admin/comments to hidden [\#3376](https://github.com/consul/consul/pull/3376) +- **Maintenance-Refactoring:** Simplify stats caching [\#3510](https://github.com/consul/consul/pull/3510) +- **Maintenance-Refactoring:** Refactor gender and age stats methods [\#3511](https://github.com/consul/consul/pull/3511) +- **Maintenance-Refactoring:** Simplify link to poll [\#3519](https://github.com/consul/consul/pull/3519) +- **Maintenance-Refactoring:** Extract partial with mobile sticky content [\#3577](https://github.com/consul/consul/pull/3577) +- **Maintenance-Refactoring:** Use find instead of find by [\#3580](https://github.com/consul/consul/pull/3580) +- **Maintenance-Rubocop:** Allow lines to be 110 characters long by Rubocop [\#3529](https://github.com/consul/consul/pull/3529) +- **Maintenance-Seeds:** Simplify settings seeds [\#3564](https://github.com/consul/consul/pull/3564) +- **Polls:** Display all polls for current booth [\#3361](https://github.com/consul/consul/pull/3361) +- **Polls:** Allow delete polls with associated questions and answers [\#3476](https://github.com/consul/consul/pull/3476) +- **Polls:** Remove redirect for poll officers [\#3506](https://github.com/consul/consul/pull/3506) +- **Polls:** Remove token on views [\#3539](https://github.com/consul/consul/pull/3539) +- **Proposals:** Remove question and external_url fields from proposals and legislation proposals [\#3397](https://github.com/consul/consul/pull/3397) +- **Proposals:** Proposals support on mobile [\#3515](https://github.com/consul/consul/pull/3515) +- **Proposals:** Make proposals to be selected by administrators [\#3567](https://github.com/consul/consul/pull/3567) +- **Statistics:** Improve poll stats [\#3503](https://github.com/consul/consul/pull/3503) +- **Statistics:** Change stats layout [\#3512](https://github.com/consul/consul/pull/3512) +- **UX/UI:** Improve help texts on Admin UI [\#3508](https://github.com/consul/consul/pull/3508) +- **UX/UI:** Users menu [\#3509](https://github.com/consul/consul/pull/3509) +- **UX/UI:** Add help texs, links and new message section to improve UX [\#3573](https://github.com/consul/consul/pull/3573) + +### Fixed +- **Budgets:** Don't show links to disabled budget results [\#3592](https://github.com/consul/consul/pull/3592) +- **Legislations:** Fix order in annotation comments with same score [\#3565](https://github.com/consul/consul/pull/3565) +- **Maintenance:** Fix obsolete `respond\_with\_bip` usage [\#3483](https://github.com/consul/consul/pull/3483) +- **Maintenance:** Remove Rspec deprecation warning [\#3530](https://github.com/consul/consul/pull/3530) +- **Maintenance:** Fix column order in schema file [\#3533](https://github.com/consul/consul/pull/3533) +- **Maintenance:** Fix indentation in schema file [\#3595](https://github.com/consul/consul/pull/3595) +- **Maintenance-Specs:** Fix typo in budget executions spec [\#3486](https://github.com/consul/consul/pull/3486) +- **Maintenance-Specs:** Remove unused \(and flaky\) card code and its spec [\#3487](https://github.com/consul/consul/pull/3487) +- **Maintenance-Specs:** Resize Capybara window back to its original size [\#3534](https://github.com/consul/consul/pull/3534) +- **Maintenance-Specs:** Check the comment is present after commenting [\#3596](https://github.com/consul/consul/pull/3596) +- **Maintenance-Specs:** Reset globalize fallbacks before every test [\#3601](https://github.com/consul/consul/pull/3601) +- **Multi-language:** Avoid duplicate records using translations [\#3581](https://github.com/consul/consul/pull/3581) +- **Polls:** Fix valid votes labels [\#3570](https://github.com/consul/consul/pull/3570) +- **Polls:** Show name and email for deleted poll officer's user account [\#3587](https://github.com/consul/consul/pull/3587) +- **UX/UI:** Always use map image from admin site customization images [\#3472](https://github.com/consul/consul/pull/3472) + +### Removed +- **Maintenance-Deprecated:** Delete spending proposals [\#3569](https://github.com/consul/consul/pull/3569) + ## [1.0.0-beta](https://github.com/consul/consul/compare/v0.19...1.0.0-beta) (2019-04-29) ### Added @@ -782,7 +853,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Rails 4.2.6 - Ruby 2.2.3 -[Unreleased]: https://github.com/consul/consul/compare/1.0.0-beta...consul:master +[Unreleased]: https://github.com/consul/consul/compare/1.0.0...consul:master +[1.0.0]: https://github.com/consul/consul/compare/1.0.0-beta...1.0.0 [1.0.0-beta]: https://github.com/consul/consul/compare/v0.19...1.0.0-beta [0.19.0]: https://github.com/consul/consul/compare/v0.18...v.019 [0.18.0]: https://github.com/consul/consul/compare/v0.17...v.018 diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 9c95f400f..e42bb0362 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -414,6 +414,15 @@ code { word-break: break-all; } +.content-type { + white-space: nowrap; + padding-right: $line-height; + + label { + margin-left: 0 !important; + } +} + // 02. Sidebar // ----------- diff --git a/app/controllers/admin/administrators_controller.rb b/app/controllers/admin/administrators_controller.rb index 3467ee0c0..5e423be81 100644 --- a/app/controllers/admin/administrators_controller.rb +++ b/app/controllers/admin/administrators_controller.rb @@ -28,4 +28,22 @@ class Admin::AdministratorsController < Admin::BaseController redirect_to admin_administrators_path end + + def edit + end + + def update + if @administrator.update(update_administrator_params) + notice = t("admin.administrators.form.updated") + redirect_to admin_administrators_path, notice: notice + else + render :edit + end + end + + private + + def update_administrator_params + params.require(:administrator).permit(:description) + end end diff --git a/app/controllers/admin/budget_groups_controller.rb b/app/controllers/admin/budget_groups_controller.rb index aa87e2373..8e44b2101 100644 --- a/app/controllers/admin/budget_groups_controller.rb +++ b/app/controllers/admin/budget_groups_controller.rb @@ -46,11 +46,11 @@ class Admin::BudgetGroupsController < Admin::BaseController private def load_budget - @budget = Budget.includes(:groups).find(params[:budget_id]) + @budget = Budget.find_by_slug_or_id! params[:budget_id] end def load_group - @group = @budget.groups.find(params[:id]) + @group = @budget.groups.find_by_slug_or_id! params[:id] end def groups_index diff --git a/app/controllers/admin/budget_headings_controller.rb b/app/controllers/admin/budget_headings_controller.rb index 46e46bc62..659bf6760 100644 --- a/app/controllers/admin/budget_headings_controller.rb +++ b/app/controllers/admin/budget_headings_controller.rb @@ -47,15 +47,15 @@ class Admin::BudgetHeadingsController < Admin::BaseController private def load_budget - @budget = Budget.includes(:groups).find(params[:budget_id]) + @budget = Budget.find_by_slug_or_id! params[:budget_id] end def load_group - @group = @budget.groups.find(params[:group_id]) + @group = @budget.groups.find_by_slug_or_id! params[:group_id] end def load_heading - @heading = @group.headings.find(params[:id]) + @heading = @group.headings.find_by_slug_or_id! params[:id] end def headings_index diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index 0ab4a4765..d036e8006 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -83,11 +83,11 @@ class Admin::BudgetInvestmentsController < Admin::BaseController params.require(:budget_investment) .permit(:title, :description, :external_url, :heading_id, :administrator_id, :tag_list, :valuation_tag_list, :incompatible, :visible_to_valuators, :selected, - valuator_ids: [], valuator_group_ids: []) + :milestone_tag_list, valuator_ids: [], valuator_group_ids: []) end def load_budget - @budget = Budget.includes(:groups).find(params[:budget_id]) + @budget = Budget.find_by_slug_or_id! params[:budget_id] end def load_investment diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb index 771a8c91d..fa9ad6752 100644 --- a/app/controllers/admin/budgets_controller.rb +++ b/app/controllers/admin/budgets_controller.rb @@ -6,6 +6,7 @@ class Admin::BudgetsController < Admin::BaseController has_filters %w{open finished}, only: :index + before_action :load_budget, except: [:index, :new, :create] load_and_authorize_resource def index @@ -66,4 +67,8 @@ class Admin::BudgetsController < Admin::BaseController params.require(:budget).permit(*valid_attributes, *report_attributes, translation_params(Budget)) end + def load_budget + @budget = Budget.find_by_slug_or_id! params[:id] + end + end diff --git a/app/controllers/admin/comments_controller.rb b/app/controllers/admin/hidden_comments_controller.rb similarity index 92% rename from app/controllers/admin/comments_controller.rb rename to app/controllers/admin/hidden_comments_controller.rb index ef815470d..c3e3bdea9 100644 --- a/app/controllers/admin/comments_controller.rb +++ b/app/controllers/admin/hidden_comments_controller.rb @@ -1,4 +1,4 @@ -class Admin::CommentsController < Admin::BaseController +class Admin::HiddenCommentsController < Admin::BaseController has_filters %w{without_confirmed_hide all with_confirmed_hide} before_action :load_comment, only: [:confirm_hide, :restore] diff --git a/app/controllers/admin/debates_controller.rb b/app/controllers/admin/hidden_debates_controller.rb similarity index 91% rename from app/controllers/admin/debates_controller.rb rename to app/controllers/admin/hidden_debates_controller.rb index bbe4a06d7..77d30b488 100644 --- a/app/controllers/admin/debates_controller.rb +++ b/app/controllers/admin/hidden_debates_controller.rb @@ -1,4 +1,4 @@ -class Admin::DebatesController < Admin::BaseController +class Admin::HiddenDebatesController < Admin::BaseController include FeatureFlags feature_flag :debates @@ -29,4 +29,4 @@ class Admin::DebatesController < Admin::BaseController @debate = Debate.with_hidden.find(params[:id]) end -end \ No newline at end of file +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 34fa22704..f24b6c63e 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -14,6 +14,7 @@ class Admin::SettingsController < Admin::BaseController @participation_processes_settings = all_settings["process"] @map_configuration_settings = all_settings["map"] @proposals_settings = all_settings["proposals"] + @uploads_settings = all_settings["uploads"] end def update @@ -29,10 +30,24 @@ class Admin::SettingsController < Admin::BaseController redirect_to admin_settings_path, notice: t("admin.settings.index.map.flash.update") end + def update_content_types + setting = Setting.find(params[:id]) + group = setting.content_type_group + mime_type_values = content_type_params.keys.map do |content_type| + Setting.mime_types[group][content_type] + end + setting.update value: mime_type_values.join(" ") + redirect_to admin_settings_path, notice: t("admin.settings.flash.updated") + end + private def settings_params params.require(:setting).permit(:value) end + def content_type_params + params.permit(:jpg, :png, :gif, :pdf, :doc, :docx, :xls, :xlsx, :csv, :zip) + end + end diff --git a/app/controllers/admin/system_emails_controller.rb b/app/controllers/admin/system_emails_controller.rb index 689425569..f70c4bd53 100644 --- a/app/controllers/admin/system_emails_controller.rb +++ b/app/controllers/admin/system_emails_controller.rb @@ -14,7 +14,8 @@ class Admin::SystemEmailsController < Admin::BaseController direct_message_for_receiver: %w[view edit_info], direct_message_for_sender: %w[view edit_info], email_verification: %w[view edit_info], - user_invite: %w[view edit_info] + user_invite: %w[view edit_info], + evaluation_comment: %w[view edit_info] } end @@ -34,6 +35,8 @@ class Admin::SystemEmailsController < Admin::BaseController load_sample_user when "user_invite" @subject = t("mailers.user_invite.subject", org_name: Setting["org_name"]) + when "evaluation_comment" + load_sample_valuation_comment end end @@ -97,6 +100,17 @@ class Admin::SystemEmailsController < Admin::BaseController end end + def load_sample_valuation_comment + comment = Comment.where(commentable_type: "Budget::Investment").last + if comment + @email = EvaluationCommentEmail.new(comment) + @email_to = @email.to.first + else + redirect_to admin_system_emails_path, + alert: t("admin.system_emails.alert.no_evaluation_comments") + end + end + def load_sample_user @user = User.last @token = @user.email_verification_token || SecureRandom.hex diff --git a/app/controllers/budgets/ballot/lines_controller.rb b/app/controllers/budgets/ballot/lines_controller.rb index c2e617e08..5d882c51b 100644 --- a/app/controllers/budgets/ballot/lines_controller.rb +++ b/app/controllers/budgets/ballot/lines_controller.rb @@ -37,7 +37,7 @@ module Budgets end def load_budget - @budget = Budget.find(params[:budget_id]) + @budget = Budget.find_by_slug_or_id! params[:budget_id] end def load_ballot diff --git a/app/controllers/budgets/ballots_controller.rb b/app/controllers/budgets/ballots_controller.rb index b5b63b4aa..91286e2ec 100644 --- a/app/controllers/budgets/ballots_controller.rb +++ b/app/controllers/budgets/ballots_controller.rb @@ -1,6 +1,7 @@ module Budgets class BallotsController < ApplicationController before_action :authenticate_user! + before_action :load_budget load_and_authorize_resource :budget before_action :load_ballot after_action :store_referer, only: [:show] @@ -13,6 +14,10 @@ module Budgets private + def load_budget + @budget = Budget.find_by_slug_or_id! params[:budget_id] + end + def load_ballot query = Budget::Ballot.where(user: current_user, budget: @budget) @ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize diff --git a/app/controllers/budgets/executions_controller.rb b/app/controllers/budgets/executions_controller.rb index 0d4c25134..63f74717e 100644 --- a/app/controllers/budgets/executions_controller.rb +++ b/app/controllers/budgets/executions_controller.rb @@ -12,15 +12,15 @@ module Budgets private def investments_by_heading + base = @budget.investments.winners + base = base.joins(milestones: :translations).includes(:milestones) + base = base.tagged_with(params[:milestone_tag]) if params[:milestone_tag].present? + if params[:status].present? - @budget.investments.winners - .with_milestone_status_id(params[:status]) - .uniq - .group_by(&:heading) + base = base.with_milestone_status_id(params[:status]) + base.uniq.group_by(&:heading) else - @budget.investments.winners - .joins(milestones: :translations) - .distinct.group_by(&:heading) + base.distinct.group_by(&:heading) end end diff --git a/app/controllers/budgets/groups_controller.rb b/app/controllers/budgets/groups_controller.rb index f298b5a93..e309a3c8c 100644 --- a/app/controllers/budgets/groups_controller.rb +++ b/app/controllers/budgets/groups_controller.rb @@ -1,5 +1,7 @@ module Budgets class GroupsController < ApplicationController + before_action :load_budget + before_action :load_group load_and_authorize_resource :budget load_and_authorize_resource :group, class: "Budget::Group" @@ -9,5 +11,14 @@ module Budgets def show end + private + + def load_budget + @budget = Budget.find_by_slug_or_id! params[:budget_id] + end + + def load_group + @group = @budget.groups.find_by_slug_or_id! params[:id] + end end end \ No newline at end of file diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index cc762cff5..1aea05085 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -10,6 +10,7 @@ module Budgets PER_PAGE = 10 before_action :authenticate_user!, except: [:index, :show, :json_data] + before_action :load_budget, except: :json_data load_and_authorize_resource :budget, except: :json_data load_and_authorize_resource :investment, through: :budget, class: "Budget::Investment", @@ -136,7 +137,7 @@ module Budgets def load_heading if params[:heading_id].present? - @heading = @budget.headings.find(params[:heading_id]) + @heading = @budget.headings.find_by_slug_or_id! params[:heading_id] @assigned_heading = @ballot.try(:heading_for_group, @heading.try(:group)) load_map end @@ -154,6 +155,10 @@ module Budgets TagCloud.new(Budget::Investment, params[:search]) end + def load_budget + @budget = Budget.find_by_slug_or_id! params[:budget_id] + end + def set_view @view = (params[:view] == "minimal") ? "minimal" : "default" end diff --git a/app/controllers/budgets/results_controller.rb b/app/controllers/budgets/results_controller.rb index e8e80a234..f8d54c2a2 100644 --- a/app/controllers/budgets/results_controller.rb +++ b/app/controllers/budgets/results_controller.rb @@ -14,15 +14,14 @@ module Budgets private def load_budget - @budget = Budget.find_by(id: params[:budget_id]) + @budget = Budget.find_by_slug_or_id(params[:budget_id]) || Budget.first end def load_heading - @heading = if params[:heading_id].present? - @budget.headings.find(params[:heading_id]) - else - @budget.headings.first - end + if @budget.present? + headings = @budget.headings + @heading = headings.find_by_slug_or_id(params[:heading_id]) || headings.first + end end end diff --git a/app/controllers/budgets/stats_controller.rb b/app/controllers/budgets/stats_controller.rb index d8ac1e267..964974125 100644 --- a/app/controllers/budgets/stats_controller.rb +++ b/app/controllers/budgets/stats_controller.rb @@ -13,7 +13,7 @@ module Budgets private def load_budget - @budget = Budget.find_by(slug: params[:budget_id]) || Budget.find_by(id: params[:budget_id]) + @budget = Budget.find_by_slug_or_id! params[:budget_id] end end diff --git a/app/controllers/budgets_controller.rb b/app/controllers/budgets_controller.rb index c1f9e03ef..19f1e69be 100644 --- a/app/controllers/budgets_controller.rb +++ b/app/controllers/budgets_controller.rb @@ -3,6 +3,7 @@ class BudgetsController < ApplicationController include BudgetsHelper feature_flag :budgets + before_action :load_budget, only: :show load_and_authorize_resource before_action :set_default_budget_filter, only: :show has_filters %w[not_unfeasible feasible unfeasible unselected selected winners], only: :show @@ -19,4 +20,10 @@ class BudgetsController < ApplicationController @banners = Banner.in_section("budgets").with_active end + private + + def load_budget + @budget = Budget.find_by_slug_or_id! params[:id] + end + end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 47434ba16..18ee03e09 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -14,6 +14,7 @@ class CommentsController < ApplicationController if @comment.save CommentNotifier.new(comment: @comment).process add_notification @comment + EvaluationCommentNotifier.new(comment: @comment).process if send_evaluation_notification? else render :new end @@ -107,4 +108,7 @@ class CommentsController < ApplicationController end end + def send_evaluation_notification? + @comment.valuation && Setting["feature.valuation_comment_notification"] + end end diff --git a/app/controllers/installation_controller.rb b/app/controllers/installation_controller.rb index 8d9f7293e..79c244e5e 100644 --- a/app/controllers/installation_controller.rb +++ b/app/controllers/installation_controller.rb @@ -12,7 +12,7 @@ class InstallationController < ApplicationController def consul_installation_details { - release: "1.0.0-beta" + release: "1.0.0" }.merge(features: settings_feature_flags) end diff --git a/app/controllers/management/budgets/investments_controller.rb b/app/controllers/management/budgets/investments_controller.rb index 0b30820a2..79a65cf15 100644 --- a/app/controllers/management/budgets/investments_controller.rb +++ b/app/controllers/management/budgets/investments_controller.rb @@ -1,4 +1,5 @@ class Management::Budgets::InvestmentsController < Management::BaseController + before_action :load_budget load_resource :budget load_resource :investment, through: :budget, class: "Budget::Investment" @@ -60,6 +61,10 @@ class Management::Budgets::InvestmentsController < Management::BaseController check_verified_user t("management.budget_investments.alert.unverified_user") end + def load_budget + @budget = Budget.find_by_slug_or_id! params[:budget_id] + end + def load_categories @categories = ActsAsTaggableOn::Tag.category.order(:name) end diff --git a/app/controllers/officing/base_controller.rb b/app/controllers/officing/base_controller.rb index 96f185d24..42b85860b 100644 --- a/app/controllers/officing/base_controller.rb +++ b/app/controllers/officing/base_controller.rb @@ -45,5 +45,4 @@ class Officing::BaseController < ApplicationController def current_booth Poll::Booth.where(id: session[:booth_id]).first end - end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb index 60e1e4eff..f322379ce 100644 --- a/app/controllers/polls_controller.rb +++ b/app/controllers/polls_controller.rb @@ -12,8 +12,9 @@ class PollsController < ApplicationController ::Poll::Answer # trigger autoload def index - @polls = @polls.not_budget.public_polls.send(@current_filter).includes(:geozones) - .sort_for_list.page(params[:page]) + @polls = Kaminari.paginate_array( + @polls.public_polls.not_budget.send(@current_filter).includes(:geozones).sort_for_list + ).page(params[:page]) end def show diff --git a/app/controllers/valuation/budget_investments_controller.rb b/app/controllers/valuation/budget_investments_controller.rb index aa29120f1..66398f14f 100644 --- a/app/controllers/valuation/budget_investments_controller.rb +++ b/app/controllers/valuation/budget_investments_controller.rb @@ -65,7 +65,7 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController end def load_budget - @budget = Budget.find(params[:budget_id]) + @budget = Budget.find_by_slug_or_id! params[:budget_id] end def load_investment @@ -75,9 +75,7 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController def heading_filters investments = @budget.investments.by_valuator(current_user.valuator.try(:id)) .visible_to_valuators.distinct - investment_headings = Budget::Heading.joins(:translations) - .where(id: investments.pluck(:heading_id).uniq) - .order(name: :asc) + investment_headings = Budget::Heading.where(id: investments.pluck(:heading_id)).sort_by(&:name) all_headings_filter = [ { diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index e0dc3834f..ac42f01f7 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -25,7 +25,7 @@ module AdminHelper end def moderated_sections - ["hidden_proposals", "debates", "comments", "hidden_users", "activity", + ["hidden_proposals", "hidden_debates", "hidden_comments", "hidden_users", "activity", "hidden_budget_investments"] end @@ -77,7 +77,9 @@ module AdminHelper end def admin_select_options - Administrator.all.order("users.username asc").includes(:user).collect { |v| [ v.name, v.id ] } + Administrator.with_user + .collect { |v| [ v.description_or_name, v.id ] } + .sort_by { |a| a[0] } end def admin_submit_action(resource) diff --git a/app/helpers/budget_executions_helper.rb b/app/helpers/budget_executions_helper.rb index 90d0744b0..d8f20936f 100644 --- a/app/helpers/budget_executions_helper.rb +++ b/app/helpers/budget_executions_helper.rb @@ -4,6 +4,12 @@ module BudgetExecutionsHelper @budget.investments.winners.with_milestone_status_id(status).count end + def options_for_milestone_tags + @budget.milestone_tags.map do |tag| + ["#{tag} (#{@budget.investments.winners.tagged_with(tag).count})", tag] + end + end + def first_milestone_with_image(investment) investment.milestones.order_by_publication_date .select{ |milestone| milestone.image.present? }.last diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb index d8425c217..ea2a327f7 100644 --- a/app/helpers/budgets_helper.rb +++ b/app/helpers/budgets_helper.rb @@ -105,4 +105,18 @@ module BudgetsHelper ends_at: balloting_phase.ends_at }), method: :post end + + def budget_subnav_items_for(budget) + { + results: t("budgets.results.link"), + stats: t("stats.budgets.link"), + executions: t("budgets.executions.link") + }.select { |section, _| can?(:"read_#{section}", budget) }.map do |section, text| + { + text: text, + url: send("budget_#{section}_path", budget), + active: controller_name == section.to_s + } + end + end end diff --git a/app/helpers/documentables_helper.rb b/app/helpers/documentables_helper.rb index ef1a2d6b9..98ce55b59 100644 --- a/app/helpers/documentables_helper.rb +++ b/app/helpers/documentables_helper.rb @@ -17,15 +17,11 @@ module DocumentablesHelper end def accepted_content_types_extensions(documentable_class) - documentable_class.accepted_content_types - .collect{ |content_type| ".#{content_type.split("/").last}" } - .join(",") + Setting.accepted_content_types_for("documents").map { |content_type| ".#{content_type}" }.join(",") end def documentable_humanized_accepted_content_types(documentable_class) - documentable_class.accepted_content_types - .collect{ |content_type| content_type.split("/").last } - .join(", ") + Setting.accepted_content_types_for("documents").join(", ") end def documentables_note(documentable) diff --git a/app/helpers/imageables_helper.rb b/app/helpers/imageables_helper.rb index 1a85362d8..7c8d87298 100644 --- a/app/helpers/imageables_helper.rb +++ b/app/helpers/imageables_helper.rb @@ -9,7 +9,7 @@ module ImageablesHelper end def imageable_max_file_size - bytes_to_megabytes(Image::MAX_IMAGE_SIZE) + bytes_to_megabytes(Setting["uploads.images.max_size"].to_i.megabytes) end def bytes_to_megabytes(bytes) @@ -17,19 +17,21 @@ module ImageablesHelper end def imageable_accepted_content_types - Image::ACCEPTED_CONTENT_TYPE + Setting["uploads.images.content_types"]&.split(" ") || [ "image/jpeg" ] end def imageable_accepted_content_types_extensions - Image::ACCEPTED_CONTENT_TYPE - .collect{ |content_type| ".#{content_type.split("/").last}" } - .join(",") + Setting.accepted_content_types_for("images").map do |content_type| + if content_type == "jpg" + ".jpg,.jpeg" + else + ".#{content_type}" + end + end.join(",") end def imageable_humanized_accepted_content_types - Image::ACCEPTED_CONTENT_TYPE - .collect{ |content_type| content_type.split("/").last } - .join(", ") + Setting.accepted_content_types_for("images").join(", ") end def imageables_note(_imageable) diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb index c3bda48b9..1f6f4d3a6 100644 --- a/app/helpers/mailer_helper.rb +++ b/app/helpers/mailer_helper.rb @@ -8,4 +8,16 @@ module MailerHelper return budget_investment_url(commentable.budget_id, commentable) if commentable.is_a?(Budget::Investment) end + def valuation_comments_url(commentable) + admin_budget_budget_investment_url(commentable.budget, commentable, anchor: "comments") + end + + def valuation_comments_link(commentable) + link_to( + commentable.title, + valuation_comments_url(@email.commentable), + target: :blank, + style: "color: #2895F1; text-decoration:none;" + ) + end end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index d8fa120a4..9defb0f15 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -120,6 +120,13 @@ class Mailer < ApplicationMailer mail(to: @email_to, from: @newsletter.from, subject: @newsletter.subject) end + def evaluation_comment(comment, to) + @email = EvaluationCommentEmail.new(comment) + @email_to = to + + mail(to: @email_to.email, subject: @email.subject) if @email.can_be_sent? + end + private def with_user(user, &block) diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index c37e06f65..c0e723fff 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -51,7 +51,7 @@ module Abilities can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment, Legislation::Question, Legislation::Proposal, Legislation::Annotation, Topic] - can [:search, :create, :index, :destroy], ::Administrator + can [:search, :create, :index, :destroy, :edit, :update], ::Administrator can [:search, :create, :index, :destroy], ::Moderator can [:search, :show, :edit, :update, :create, :index, :destroy, :summary], ::Valuator can [:search, :create, :index, :destroy], ::Manager diff --git a/app/models/administrator.rb b/app/models/administrator.rb index b8b6cc1c0..1c5cb027a 100644 --- a/app/models/administrator.rb +++ b/app/models/administrator.rb @@ -3,4 +3,14 @@ class Administrator < ApplicationRecord delegate :name, :email, :name_and_email, to: :user validates :user_id, presence: true, uniqueness: true + + scope :with_user, -> { includes(:user) } + + def description_or_name + description.presence || name + end + + def description_or_name_and_email + "#{description_or_name} (#{email})" + end end diff --git a/app/models/budget.rb b/app/models/budget.rb index b31f08ed5..3797be797 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -195,6 +195,10 @@ class Budget < ApplicationRecord investments.winners.any? end + def milestone_tags + investments.winners.map(&:milestone_tag_list).flatten.uniq.sort + end + private def sanitize_descriptions diff --git a/app/models/budget/group.rb b/app/models/budget/group.rb index d77c840c0..43d28a0ae 100644 --- a/app/models/budget/group.rb +++ b/app/models/budget/group.rb @@ -28,7 +28,9 @@ class Budget validates :budget_id, presence: true validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/ - scope :sort_by_name, -> { joins(:translations).order(:name) } + def self.sort_by_name + all.sort_by(&:name) + end def single_heading_group? headings.count == 1 diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index 0b94aac67..7b33c4e54 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -40,8 +40,7 @@ class Budget delegate :budget, :budget_id, to: :group, allow_nil: true - scope :i18n, -> { joins(:translations) } - scope :allow_custom_content, -> { i18n.where(allow_custom_content: true).order("budget_heading_translations.name") } + scope :allow_custom_content, -> { where(allow_custom_content: true).sort_by(&:name) } def self.sort_by_name all.sort do |heading, other_heading| diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 2eaf801c1..d078437bb 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -13,9 +13,6 @@ class Budget include Imageable include Mappable include Documentable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] acts_as_votable acts_as_paranoid column: :hidden_at @@ -113,7 +110,7 @@ class Budget end def self.scoped_filter(params, current_filter) - budget = Budget.find_by(slug: params[:budget_id]) || Budget.find_by(id: params[:budget_id]) + budget = Budget.find_by_slug_or_id params[:budget_id] results = Investment.by_budget(budget) results = results.where("cached_votes_up + physical_votes >= ?", @@ -377,6 +374,12 @@ class Budget milestones.published.with_status.order_by_publication_date.last&.status_id end + def admin_and_valuator_users_associated + valuator_users = (valuator_groups.map(&:valuators) + valuators).flatten + all_users = valuator_users << administrator + all_users.compact.uniq + end + private def set_denormalized_ids diff --git a/app/models/comment.rb b/app/models/comment.rb index 76e314c23..9323f9f17 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -4,8 +4,9 @@ class Comment < ApplicationRecord include Graphqlable include Notifiable - COMMENTABLE_TYPES = %w(Debate Proposal Budget::Investment Poll Topic Legislation::Question - Legislation::Annotation Legislation::Proposal).freeze + COMMENTABLE_TYPES = %w[Debate Proposal Budget::Investment Poll Topic + Legislation::Question Legislation::Annotation + Legislation::Proposal Legislation::PeopleProposal].freeze acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases diff --git a/app/models/concerns/documentable.rb b/app/models/concerns/documentable.rb index 36e91ece7..9f49d9af9 100644 --- a/app/models/concerns/documentable.rb +++ b/app/models/concerns/documentable.rb @@ -7,16 +7,17 @@ module Documentable end module ClassMethods - attr_reader :max_documents_allowed, :max_file_size, :accepted_content_types - - private - - def documentable(options = {}) - @max_documents_allowed = options[:max_documents_allowed] - @max_file_size = options[:max_file_size] - @accepted_content_types = options[:accepted_content_types] + def max_documents_allowed + Setting["uploads.documents.max_amount"].to_i end + def max_file_size + Setting["uploads.documents.max_size"].to_i.megabytes + end + + def accepted_content_types + Setting["uploads.documents.content_types"]&.split(" ") || [ "application/pdf" ] + end end end diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb index 66de28d6c..0ed1b47f5 100644 --- a/app/models/concerns/milestoneable.rb +++ b/app/models/concerns/milestoneable.rb @@ -8,6 +8,8 @@ module Milestoneable has_many :progress_bars, as: :progressable + acts_as_taggable_on :milestone_tags + def primary_progress_bar progress_bars.primary.first end diff --git a/app/models/concerns/sluggable.rb b/app/models/concerns/sluggable.rb index 8fb308d22..bdcdff856 100644 --- a/app/models/concerns/sluggable.rb +++ b/app/models/concerns/sluggable.rb @@ -7,6 +7,10 @@ module Sluggable def self.find_by_slug_or_id(slug_or_id) find_by_slug(slug_or_id) || find_by_id(slug_or_id) end + + def self.find_by_slug_or_id!(slug_or_id) + find_by_slug(slug_or_id) || find(slug_or_id) + end end def generate_slug diff --git a/app/models/dashboard/action.rb b/app/models/dashboard/action.rb index afe61c075..4dea153a7 100644 --- a/app/models/dashboard/action.rb +++ b/app/models/dashboard/action.rb @@ -1,13 +1,5 @@ class Dashboard::Action < ApplicationRecord include Documentable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf", - "image/jpeg", - "image/jpg", - "image/png", - "application/zip" ] - include Linkable acts_as_paranoid column: :hidden_at diff --git a/app/models/evaluation_comment_notifier.rb b/app/models/evaluation_comment_notifier.rb new file mode 100644 index 000000000..e1231298c --- /dev/null +++ b/app/models/evaluation_comment_notifier.rb @@ -0,0 +1,16 @@ +class EvaluationCommentNotifier + def initialize(args = {}) + @comment = args.fetch(:comment) + end + + def process + send_evaluation_comment_email + end + + private + def send_evaluation_comment_email + EvaluationCommentEmail.new(@comment).to.each do |to| + Mailer.evaluation_comment(@comment, to).deliver_later + end + end +end diff --git a/app/models/image.rb b/app/models/image.rb index eefcd3926..6929f5f96 100644 --- a/app/models/image.rb +++ b/app/models/image.rb @@ -2,12 +2,11 @@ class Image < ApplicationRecord include ImagesHelper include ImageablesHelper - TITLE_LENGTH_RANGE = 4..80 - MIN_SIZE = 475 - MAX_IMAGE_SIZE = 1.megabyte - ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg).freeze - - has_attached_file :attachment, styles: { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" }, + has_attached_file :attachment, styles: { + large: "x#{Setting["uploads.images.min_height"]}", + medium: "300x300#", + thumb: "140x245#" + }, url: "/system/:class/:prefix/:style/:hash.:extension", hash_data: ":class/:style", use_timestamp: false, @@ -23,7 +22,8 @@ class Image < ApplicationRecord validate :attachment_presence validate :validate_attachment_content_type, if: -> { attachment.present? } validate :validate_attachment_size, if: -> { attachment.present? } - validates :title, presence: true, length: { in: TITLE_LENGTH_RANGE } + validates :title, presence: true + validate :validate_title_length validates :user_id, presence: true validates :imageable_id, presence: true, if: -> { persisted? } validates :imageable_type, presence: true, if: -> { persisted? } @@ -71,20 +71,38 @@ class Image < ApplicationRecord return true if imageable_class == Widget::Card dimensions = Paperclip::Geometry.from_file(attachment.queued_for_write[:original].path) - errors.add(:attachment, :min_image_width, required_min_width: MIN_SIZE) if dimensions.width < MIN_SIZE - errors.add(:attachment, :min_image_height, required_min_height: MIN_SIZE) if dimensions.height < MIN_SIZE + min_width = Setting["uploads.images.min_width"].to_i + min_height = Setting["uploads.images.min_height"].to_i + errors.add(:attachment, :min_image_width, required_min_width: min_width) if dimensions.width < min_width + errors.add(:attachment, :min_image_height, required_min_height: min_height) if dimensions.height < min_height end end def validate_attachment_size if imageable_class && - attachment_file_size > 1.megabytes + attachment_file_size > Setting["uploads.images.max_size"].to_i.megabytes errors.add(:attachment, I18n.t("images.errors.messages.in_between", min: "0 Bytes", max: "#{imageable_max_file_size} MB")) end end + def validate_title_length + if title.present? + + title_min_length = Setting["uploads.images.title.min_length"].to_i + title_max_length = Setting["uploads.images.title.max_length"].to_i + + if title.size < title_min_length + errors.add(:title, I18n.t("errors.messages.too_short", count: title_min_length)) + end + + if title.size > title_max_length + errors.add(:title, I18n.t("errors.messages.too_long", count: title_max_length)) + end + end + end + def validate_attachment_content_type if imageable_class && !attachment_of_valid_content_type? message = I18n.t("images.errors.messages.wrong_content_type", diff --git a/app/models/legislation/people_proposal.rb b/app/models/legislation/people_proposal.rb new file mode 100644 index 000000000..ed57bf352 --- /dev/null +++ b/app/models/legislation/people_proposal.rb @@ -0,0 +1,152 @@ +class Legislation::PeopleProposal < ApplicationRecord + include ActsAsParanoidAliases + include Flaggable + include Taggable + include Conflictable + include Measurable + include Sanitizable + include Searchable + include Filterable + include Followable + include Communitable + include Documentable + include Notifiable + include Imageable + include Randomizable + + accepts_nested_attributes_for :documents, allow_destroy: true + + acts_as_votable + acts_as_paranoid column: :hidden_at + + belongs_to :process, class_name: "Legislation::Process", foreign_key: "legislation_process_id" + belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id" + has_many :comments, as: :commentable + + validates :title, presence: true + validates :summary, presence: true + validates :author, presence: true + validates :process, presence: true + + validates :title, length: { in: 4..Legislation::PeopleProposal.title_max_length } + validates :description, length: { maximum: Legislation::PeopleProposal.description_max_length } + + validates :terms_of_service, acceptance: { allow_nil: false }, on: :create + + before_validation :set_responsible_name + + before_save :calculate_hot_score, :calculate_confidence_score + + scope :for_render, -> { includes(:tags) } + scope :sort_by_hot_score, -> { reorder(hot_score: :desc) } + scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc) } + scope :sort_by_created_at, -> { reorder(created_at: :desc) } + scope :sort_by_most_commented, -> { reorder(comments_count: :desc) } + scope :sort_by_title, -> { reorder(title: :asc) } + scope :sort_by_id, -> { reorder(id: :asc) } + scope :sort_by_supports, -> { reorder(cached_votes_score: :desc) } + scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } + scope :last_week, -> { where("people_proposals.created_at >= ?", 7.days.ago)} + scope :validated, -> { where(validated: true) } + scope :selected, -> { where(selected: true) } + scope :winners, -> { selected.sort_by_confidence_score } + + def to_param + "#{id}-#{title}".parameterize + end + + def searchable_values + { title => "A", + author.username => "B", + tag_list.join(" ") => "B", + summary => "C", + description => "D"} + end + + def self.search(terms) + by_code = search_by_code(terms.strip) + by_code.present? ? by_code : pg_search(terms) + end + + def self.search_by_code(terms) + matched_code = match_code(terms) + results = where(id: matched_code[1]) if matched_code + return results if results.present? && results.first.code == terms + end + + def self.match_code(terms) + /\A#{Setting["proposal_code_prefix"]}-\d\d\d\d-\d\d-(\d*)\z/.match(terms) + end + + def likes + cached_votes_up + end + + def dislikes + cached_votes_down + end + + def total_votes + cached_votes_total + end + + def votes_score + cached_votes_score + end + + def voters + User.active.where(id: votes_for.voters) + end + + def editable? + total_votes <= Setting["max_votes_for_people_proposal_edit"].to_i + end + + def editable_by?(user) + author_id == user.id && editable? + end + + def votable_by?(user) + user && user.level_two_or_three_verified? + end + + def register_vote(user, vote_value) + vote_by(voter: user, vote: vote_value) if votable_by?(user) + end + + def code + "#{Setting["proposal_code_prefix"]}-#{created_at.strftime("%Y-%m")}-#{id}" + end + + def after_commented + save # update cache when it has a new comment + end + + def calculate_hot_score + self.hot_score = ScoreCalculator.hot_score(self) + end + + def calculate_confidence_score + self.confidence_score = ScoreCalculator.confidence_score(total_votes, total_votes) + end + + def after_hide + tags.each{ |t| t.decrement_custom_counter_for("LegislationPeopleProposal") } + end + + def after_restore + tags.each{ |t| t.increment_custom_counter_for("LegislationPeopleProposal") } + end + + def contact_info + [phone, email, website].compact + end + + protected + + def set_responsible_name + if author && author.document_number? + self.responsible_name = author.document_number + end + end +end diff --git a/app/models/legislation/process.rb b/app/models/legislation/process.rb index 60d103d49..47b7103aa 100644 --- a/app/models/legislation/process.rb +++ b/app/models/legislation/process.rb @@ -4,9 +4,6 @@ class Legislation::Process < ApplicationRecord include Milestoneable include Imageable include Documentable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] acts_as_paranoid column: :hidden_at acts_as_taggable_on :customs @@ -20,7 +17,8 @@ class Legislation::Process < ApplicationRecord include Globalizable PHASES_AND_PUBLICATIONS = %i[homepage_phase draft_phase debate_phase allegations_phase - proposals_phase draft_publication result_publication].freeze + proposals_phase people_proposals_phase draft_publication + result_publication].freeze CSS_HEX_COLOR = /\A#?(?:[A-F0-9]{3}){1,2}\z/i @@ -34,6 +32,8 @@ class Legislation::Process < ApplicationRecord foreign_key: "legislation_process_id", dependent: :destroy has_many :proposals, -> { order(:id) }, class_name: "Legislation::Proposal", foreign_key: "legislation_process_id", dependent: :destroy + has_many :people_proposals, -> { order(:id) }, class_name: "Legislation::PeopleProposal", + foreign_key: "legislation_process_id", dependent: :destroy validates_translation :title, presence: true validates :start_date, presence: true @@ -45,6 +45,8 @@ class Legislation::Process < ApplicationRecord validates :allegations_start_date, presence: true, if: :allegations_end_date? validates :allegations_end_date, presence: true, if: :allegations_start_date? validates :proposals_phase_end_date, presence: true, if: :proposals_phase_start_date? + validates :people_proposals_phase_end_date, presence: true, + if: :people_proposals_phase_start_date? validate :valid_date_ranges validates :background_color, format: { allow_blank: true, with: CSS_HEX_COLOR } validates :font_color, format: { allow_blank: true, with: CSS_HEX_COLOR } @@ -84,6 +86,11 @@ class Legislation::Process < ApplicationRecord proposals_phase_end_date, proposals_phase_enabled) end + def people_proposals_phase + Legislation::Process::Phase.new(people_proposals_phase_start_date, + people_proposals_phase_end_date, people_proposals_phase_enabled) + end + def draft_publication Legislation::Process::Publication.new(draft_publication_date, draft_publication_enabled) end diff --git a/app/models/legislation/proposal.rb b/app/models/legislation/proposal.rb index d6fdf5eb3..879eeb27d 100644 --- a/app/models/legislation/proposal.rb +++ b/app/models/legislation/proposal.rb @@ -14,9 +14,6 @@ class Legislation::Proposal < ApplicationRecord include Imageable include Randomizable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] accepts_nested_attributes_for :documents, allow_destroy: true acts_as_votable diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 69d46149a..52f8ccb29 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -1,9 +1,6 @@ class Milestone < ApplicationRecord include Imageable include Documentable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] translates :title, :description, touch: true include Globalizable diff --git a/app/models/poll.rb b/app/models/poll.rb index 3e9ef6322..a6526f8cf 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -49,7 +49,19 @@ class Poll < ApplicationRecord scope :not_budget, -> { where(budget_id: nil) } scope :created_by_admin, -> { where(related_type: nil) } - scope :sort_for_list, -> { joins(:translations).order(:geozone_restricted, :starts_at, "poll_translations.name") } + def self.sort_for_list + all.sort do |poll, another_poll| + if poll.geozone_restricted? == another_poll.geozone_restricted? + [poll.starts_at, poll.name] <=> [another_poll.starts_at, another_poll.name] + else + if poll.geozone_restricted? + 1 + else + -1 + end + end + end + end def self.overlaping_with(poll) where("? < ends_at and ? >= starts_at", poll.starts_at.beginning_of_day, diff --git a/app/models/poll/booth.rb b/app/models/poll/booth.rb index d10270064..4c45b9d1a 100644 --- a/app/models/poll/booth.rb +++ b/app/models/poll/booth.rb @@ -12,7 +12,7 @@ class Poll end def self.available - where(polls: { id: Poll.current_or_recounting }).joins(polls: :translations) + where(polls: { id: Poll.current_or_recounting }).joins(:polls) end def assignment_on_poll(poll) diff --git a/app/models/poll/officer.rb b/app/models/poll/officer.rb index c44256a5a..429add05a 100644 --- a/app/models/poll/officer.rb +++ b/app/models/poll/officer.rb @@ -7,7 +7,13 @@ class Poll validates :user_id, presence: true, uniqueness: true - delegate :name, :email, to: :user + def name + user&.name || I18n.t("shared.author_info.author_deleted") + end + + def email + user&.email || I18n.t("shared.author_info.email_deleted") + end def voting_days_assigned_polls officer_assignments.voting_days.includes(booth_assignment: :poll). diff --git a/app/models/poll/question/answer.rb b/app/models/poll/question/answer.rb index cde5e00d1..aab7acb59 100644 --- a/app/models/poll/question/answer.rb +++ b/app/models/poll/question/answer.rb @@ -6,9 +6,6 @@ class Poll::Question::Answer < ApplicationRecord translates :description, touch: true include Globalizable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] accepts_nested_attributes_for :documents, allow_destroy: true belongs_to :question, class_name: "Poll::Question", foreign_key: "question_id" diff --git a/app/models/proposal.rb b/app/models/proposal.rb index deb829058..3b4fbd0e2 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -15,9 +15,6 @@ class Proposal < ApplicationRecord include Mappable include Notifiable include Documentable - documentable max_documents_allowed: 3, - max_file_size: 3.megabytes, - accepted_content_types: [ "application/pdf" ] include EmbedVideosHelper include Relationable include Milestoneable diff --git a/app/models/setting.rb b/app/models/setting.rb index 2275a3d4c..6e87816dc 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -3,9 +3,12 @@ class Setting < ApplicationRecord default_scope { order(id: :asc) } + def prefix + key.split(".").first + end + def type - prefix = key.split(".").first - if %w[feature process proposals map html homepage].include? prefix + if %w[feature process proposals map html homepage uploads].include? prefix prefix else "configuration" @@ -16,6 +19,14 @@ class Setting < ApplicationRecord value.present? end + def content_type? + key.split(".").last == "content_types" + end + + def content_type_group + key.split(".").second + end + class << self def [](key) where(key: key).pluck(:value).first.presence @@ -40,5 +51,132 @@ class Setting < ApplicationRecord setting = where(key: key).first setting.destroy if setting.present? end + + def accepted_content_types_for(group) + mime_content_types = Setting["uploads.#{group}.content_types"]&.split(" ") || [] + Setting.mime_types[group].select { |_, content_type| mime_content_types.include?(content_type) }.keys + end + + def mime_types + { + "images" => { + "jpg" => "image/jpeg", + "png" => "image/png", + "gif" => "image/gif" + }, + "documents" => { + "pdf" => "application/pdf", + "doc" => "application/msword", + "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "xls" => "application/x-ole-storage", + "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "csv" => "text/plain", + "zip" => "application/zip" + } + } + end + + def defaults + { + "feature.featured_proposals": nil, + "feature.facebook_login": true, + "feature.google_login": true, + "feature.twitter_login": true, + "feature.public_stats": true, + "feature.signature_sheets": true, + "feature.user.recommendations": true, + "feature.user.recommendations_on_debates": true, + "feature.user.recommendations_on_proposals": true, + "feature.user.skip_verification": "true", + "feature.community": true, + "feature.map": nil, + "feature.allow_attached_documents": true, + "feature.allow_images": true, + "feature.help_page": true, + "feature.valuation_comment_notification": true, + "homepage.widgets.feeds.debates": true, + "homepage.widgets.feeds.processes": true, + "homepage.widgets.feeds.proposals": true, + # Code to be included at the top (inside ) of every page + "html.per_page_code_body": "", + # Code to be included at the top (inside ) of every page (useful for tracking) + "html.per_page_code_head": "", + "map.latitude": 51.48, + "map.longitude": 0.0, + "map.zoom": 10, + "process.debates": true, + "process.proposals": true, + "process.polls": true, + "process.budgets": true, + "process.legislation": true, + "proposals.successful_proposal_id": nil, + "proposals.poll_short_title": nil, + "proposals.poll_description": nil, + "proposals.poll_link": nil, + "proposals.email_short_title": nil, + "proposals.email_description": nil, + "proposals.poster_short_title": nil, + "proposals.poster_description": nil, + # Images and Documents + "uploads.images.title.min_length": 4, + "uploads.images.title.max_length": 80, + "uploads.images.min_width": 0, + "uploads.images.min_height": 475, + "uploads.images.max_size": 1, + "uploads.images.content_types": "image/jpeg", + "uploads.documents.max_amount": 3, + "uploads.documents.max_size": 3, + "uploads.documents.content_types": "application/pdf", + # Names for the moderation console, as a hint for moderators + # to know better how to assign users with official positions + "official_level_1_name": I18n.t("seeds.settings.official_level_1_name"), + "official_level_2_name": I18n.t("seeds.settings.official_level_2_name"), + "official_level_3_name": I18n.t("seeds.settings.official_level_3_name"), + "official_level_4_name": I18n.t("seeds.settings.official_level_4_name"), + "official_level_5_name": I18n.t("seeds.settings.official_level_5_name"), + "max_ratio_anon_votes_on_debates": 50, + "max_votes_for_debate_edit": 1000, + "max_votes_for_proposal_edit": 1000, + "max_votes_for_people_proposal_edit": 1000, + "comments_body_max_length": 1000, + "proposal_code_prefix": "CONSUL", + "votes_for_proposal_success": 10000, + "months_to_archive_proposals": 12, + # Users with this email domain will automatically be marked as level 1 officials + # Emails under the domain's subdomains will also be included + "email_domain_for_officials": "", + "facebook_handle": nil, + "instagram_handle": nil, + "telegram_handle": nil, + "twitter_handle": nil, + "twitter_hashtag": nil, + "youtube_handle": nil, + "url": "http://example.com", # Public-facing URL of the app. + # CONSUL installation's organization name + "org_name": "CONSUL", + "meta_title": nil, + "meta_description": nil, + "meta_keywords": nil, + "proposal_notification_minimum_interval_in_days": 3, + "direct_message_max_per_day": 3, + "mailer_from_name": "CONSUL", + "mailer_from_address": "noreply@consul.dev", + "min_age_to_participate": 16, + "hot_score_period_in_days": 31, + "related_content_score_threshold": -0.3, + "featured_proposals_number": 3, + "dashboard.emails": nil + } + end + + def reset_defaults + defaults.each { |name, value| self[name] = value } + end + + def add_new_settings + defaults.each do |name, value| + self[name] = value unless find_by(key: name) + end + end end end diff --git a/app/models/user.rb b/app/models/user.rb index ba9aec3d5..6bf6fe155 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -23,6 +23,7 @@ class User < ApplicationRecord has_many :identities, dependent: :destroy has_many :debates, -> { with_hidden }, foreign_key: :author_id has_many :proposals, -> { with_hidden }, foreign_key: :author_id + has_many :people_proposals, -> { with_hidden }, foreign_key: :author_id has_many :budget_investments, -> { with_hidden }, foreign_key: :author_id, class_name: "Budget::Investment" has_many :comments, -> { with_hidden } has_many :failed_census_calls @@ -131,9 +132,7 @@ class User < ApplicationRecord end def headings_voted_within_group(group) - Budget::Heading.joins(:translations) - .order("name") - .where(id: voted_investments.by_group(group).pluck(:heading_id)) + Budget::Heading.where(id: voted_investments.by_group(group).pluck(:heading_id)) end def voted_investments diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index 7fbf58053..24911cee6 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -141,8 +141,8 @@ <% end %> <% if feature?(:debates) %> -
  • > - <%= link_to t("admin.menu.hidden_debates"), admin_debates_path %> +
  • > + <%= link_to t("admin.menu.hidden_debates"), admin_hidden_debates_path %>
  • <% end %> @@ -152,8 +152,8 @@ <% end %> -
  • > - <%= link_to t("admin.menu.hidden_comments"), admin_comments_path %> +
  • > + <%= link_to t("admin.menu.hidden_comments"), admin_hidden_comments_path %>
  • > diff --git a/app/views/admin/administrators/edit.html.erb b/app/views/admin/administrators/edit.html.erb new file mode 100644 index 000000000..e9374ee78 --- /dev/null +++ b/app/views/admin/administrators/edit.html.erb @@ -0,0 +1,15 @@ +<%= back_link_to admin_administrators_path %> + +

    <%= t("admin.administrators.form.edit_title") %>

    + +
    + <%= @administrator.name %>
    + <%= @administrator.email %> +
    + +
    + <%= form_for [:admin, @administrator] do |f| %> + <%= f.text_field :description %> + <%= f.submit class: "button success" %> + <% end %> +
    diff --git a/app/views/admin/administrators/index.html.erb b/app/views/admin/administrators/index.html.erb index c727114bf..469905279 100644 --- a/app/views/admin/administrators/index.html.erb +++ b/app/views/admin/administrators/index.html.erb @@ -11,6 +11,7 @@ <%= t("admin.administrators.index.id") %> <%= t("admin.administrators.index.name") %> <%= t("admin.administrators.index.email") %> + <%= t("admin.administrators.index.description") %> <%= t("admin.shared.actions") %> <% @administrators.each do |administrator| %> @@ -24,13 +25,18 @@ <%= administrator.email %> + + <%= administrator.description %> + <% if administrator.persisted? %> + <%= link_to t("admin.actions.edit"), + edit_admin_administrator_path(administrator), + class: "button hollow" %> <%= link_to t("admin.administrators.administrator.delete"), admin_administrator_path(administrator), method: :delete, - class: "button hollow alert expanded" - %> + class: "button hollow alert" %> <% else %> <%= link_to t("admin.administrators.administrator.add"), { controller: "admin/administrators", action: :create, diff --git a/app/views/admin/budget_investments/_investments.html.erb b/app/views/admin/budget_investments/_investments.html.erb index 55603bbcd..9f7f97735 100644 --- a/app/views/admin/budget_investments/_investments.html.erb +++ b/app/views/admin/budget_investments/_investments.html.erb @@ -2,6 +2,7 @@ admin_budget_budget_investments_path(csv_params), class: "float-right small clear" %> + <% if params[:advanced_filters].include?("winners") %> <% if display_calculate_winners_button?(@budget) %> <%= link_to calculate_winner_button_text(@budget), diff --git a/app/views/admin/budget_investments/_select_investment.html.erb b/app/views/admin/budget_investments/_select_investment.html.erb index 788d9824b..2da7a3817 100644 --- a/app/views/admin/budget_investments/_select_investment.html.erb +++ b/app/views/admin/budget_investments/_select_investment.html.erb @@ -13,9 +13,9 @@ <% if investment.administrator.present? %> - "> - <%= investment.administrator.name %> - + "> + <%= investment.administrator.description_or_name %> + <% else %> <%= t("admin.budget_investments.index.no_admin_assigned") %> <% end %> diff --git a/app/views/admin/budget_investments/edit.html.erb b/app/views/admin/budget_investments/edit.html.erb index 20e3c5e56..81c4a6895 100644 --- a/app/views/admin/budget_investments/edit.html.erb +++ b/app/views/admin/budget_investments/edit.html.erb @@ -46,7 +46,7 @@
    <%= f.select(:administrator_id, - @admins.collect{ |a| [a.name_and_email, a.id ] }, + @admins.collect{ |a| [a.description_or_name_and_email, a.id ] }, { include_blank: t("admin.budget_investments.edit.undefined") }) %>
    @@ -105,6 +105,13 @@ +
    + <%= f.text_field :milestone_tag_list, + value: @investment.milestone_tag_list.sort.join(", "), + label: t("admin.budget_investments.edit.milestone_tags") %> +
    + +
    <%= f.submit(class: "button", value: t("admin.budget_investments.edit.submit_button")) %>
    diff --git a/app/views/admin/comments/index.html.erb b/app/views/admin/hidden_comments/index.html.erb similarity index 71% rename from app/views/admin/comments/index.html.erb rename to app/views/admin/hidden_comments/index.html.erb index adf27701e..28cabbcb5 100644 --- a/app/views/admin/comments/index.html.erb +++ b/app/views/admin/hidden_comments/index.html.erb @@ -1,7 +1,8 @@ -

    <%= t("admin.comments.index.title") %>

    +

    <%= t("admin.hidden_comments.index.title") %>

    <%= t("admin.shared.moderated_content") %>

    -<%= render "shared/filter_subnav", i18n_namespace: "admin.comments.index" %> + +<%= render "shared/filter_subnav", i18n_namespace: "admin.hidden_comments.index" %> <% if @comments.any? %>

    <%= page_entries_info @comments %>

    @@ -17,20 +18,20 @@ <%= text_with_links comment.body %>
    <% if comment.commentable.hidden? %> - (<%= t("admin.comments.index.hidden_#{comment.commentable_type.downcase}") %>: <%= comment.commentable.title %>) + (<%= t("admin.hidden_comments.index.hidden_#{comment.commentable_type.downcase}") %>: <%= comment.commentable.title %>) <% else %> <%= link_to comment.commentable.title, commentable_path(comment) %> <% end %> <%= link_to t("admin.actions.restore"), - restore_admin_comment_path(comment, request.query_parameters), + restore_admin_hidden_comment_path(comment, request.query_parameters), method: :put, data: { confirm: t("admin.actions.confirm") }, class: "button hollow warning" %> <% unless comment.confirmed_hide? %> <%= link_to t("admin.actions.confirm_hide"), - confirm_hide_admin_comment_path(comment, request.query_parameters), + confirm_hide_admin_hidden_comment_path(comment, request.query_parameters), method: :put, class: "button" %> <% end %> @@ -43,6 +44,6 @@ <%= paginate @comments %> <% else %>
    - <%= t("admin.comments.index.no_hidden_comments") %> + <%= t("admin.hidden_comments.index.no_hidden_comments") %>
    <% end %> diff --git a/app/views/admin/debates/index.html.erb b/app/views/admin/hidden_debates/index.html.erb similarity index 78% rename from app/views/admin/debates/index.html.erb rename to app/views/admin/hidden_debates/index.html.erb index 45a992f3e..a11e821d9 100644 --- a/app/views/admin/debates/index.html.erb +++ b/app/views/admin/hidden_debates/index.html.erb @@ -1,7 +1,8 @@ -

    <%= t("admin.debates.index.title") %>

    +

    <%= t("admin.hidden_debates.index.title") %>

    <%= t("admin.shared.moderated_content") %>

    -<%= render "shared/filter_subnav", i18n_namespace: "admin.debates.index" %> + +<%= render "shared/filter_subnav", i18n_namespace: "admin.hidden_debates.index" %> <% if @debates.any? %>

    <%= page_entries_info @debates %>

    @@ -25,13 +26,13 @@ <%= link_to t("admin.actions.restore"), - restore_admin_debate_path(debate, request.query_parameters), + restore_admin_hidden_debate_path(debate, request.query_parameters), method: :put, data: { confirm: t("admin.actions.confirm") }, class: "button hollow warning" %> <% unless debate.confirmed_hide? %> <%= link_to t("admin.actions.confirm_hide"), - confirm_hide_admin_debate_path(debate, request.query_parameters), + confirm_hide_admin_hidden_debate_path(debate, request.query_parameters), method: :put, class: "button" %> <% end %> @@ -44,6 +45,6 @@ <%= paginate @debates %> <% else %>
    - <%= t("admin.debates.index.no_hidden_debates") %> + <%= t("admin.hidden_debates.index.no_hidden_debates") %>
    <% end %> diff --git a/app/views/admin/milestones/_milestones.html.erb b/app/views/admin/milestones/_milestones.html.erb index 25163ceb4..bcbf61885 100644 --- a/app/views/admin/milestones/_milestones.html.erb +++ b/app/views/admin/milestones/_milestones.html.erb @@ -4,6 +4,14 @@ polymorphic_path([:admin, *resource_hierarchy_for(milestoneable.progress_bars.new)]), class: "button hollow float-right" %> +<% if milestoneable.milestone_tag_list.any? %> +
    + + <%= t("admin.milestones.index.milestone_tags") %>: + <%= milestoneable.milestone_tag_list.sort.join(", ") %> +
    +<% end %> + <% if milestoneable.milestones.any? %> diff --git a/app/views/admin/settings/_content_types_settings_form.html.erb b/app/views/admin/settings/_content_types_settings_form.html.erb new file mode 100644 index 000000000..67262c155 --- /dev/null +++ b/app/views/admin/settings/_content_types_settings_form.html.erb @@ -0,0 +1,19 @@ +<%= form_tag admin_update_content_types_path, method: :put, id: "edit_#{dom_id(setting)}" do %> + <%= hidden_field_tag "id", setting.id %> + +
    + <% group = setting.content_type_group %> + <% Setting.mime_types[group].each do |content_type, mime_type_value| %> + + <%= check_box_tag content_type, + setting.value.split(" ").include?(mime_type_value), + setting.value.split(" ").include?(mime_type_value) %> + <%= label_tag content_type, content_type.upcase %> + + <% end %> +
    + +
    + <%= submit_tag t("admin.settings.index.update_setting"), class: "button hollow expanded" %> +
    +<% end %> diff --git a/app/views/admin/settings/_featured_settings_form.html.erb b/app/views/admin/settings/_featured_settings_form.html.erb new file mode 100644 index 000000000..b1ec7ca7a --- /dev/null +++ b/app/views/admin/settings/_featured_settings_form.html.erb @@ -0,0 +1,6 @@ +<%= form_for(feature, url: admin_setting_path(feature), html: { id: "edit_#{dom_id(feature)}"}) do |f| %> + <%= f.hidden_field :value, id: dom_id(feature), value: (feature.enabled? ? "" : "active") %> + <%= f.submit(t("admin.settings.index.features.#{feature.enabled? ? "disable" : "enable"}"), + class: "button expanded #{feature.enabled? ? "hollow alert" : "success"}", + data: {confirm: t("admin.actions.confirm")}) %> +<% end %> diff --git a/app/views/admin/settings/_featured_settings_table.html.erb b/app/views/admin/settings/_featured_settings_table.html.erb index 5de1dc9b1..f4ba75823 100644 --- a/app/views/admin/settings/_featured_settings_table.html.erb +++ b/app/views/admin/settings/_featured_settings_table.html.erb @@ -32,13 +32,7 @@ <% end %> diff --git a/app/views/admin/settings/_filter_subnav.html.erb b/app/views/admin/settings/_filter_subnav.html.erb index d4e4a00d8..355e7a1c5 100644 --- a/app/views/admin/settings/_filter_subnav.html.erb +++ b/app/views/admin/settings/_filter_subnav.html.erb @@ -29,6 +29,12 @@ <% end %> +
  • + <%= link_to "#tab-images-and-documents" do %> + <%= t("admin.settings.index.images_and_documents") %> + <% end %> +
  • +
  • <%= link_to "#tab-proposals" do %> <%= t("admin.settings.index.dashboard.title") %> diff --git a/app/views/admin/settings/_images_and_documents_tab.html.erb b/app/views/admin/settings/_images_and_documents_tab.html.erb new file mode 100644 index 000000000..740f55f23 --- /dev/null +++ b/app/views/admin/settings/_images_and_documents_tab.html.erb @@ -0,0 +1,3 @@ +

    <%= t("admin.settings.index.images_and_documents") %>

    + +<%= render "settings_table", settings: @uploads_settings %> diff --git a/app/views/admin/settings/_settings_form.html.erb b/app/views/admin/settings/_settings_form.html.erb new file mode 100644 index 000000000..b6851669b --- /dev/null +++ b/app/views/admin/settings/_settings_form.html.erb @@ -0,0 +1,8 @@ +<%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %> +
    + <%= f.text_area :value, label: false, id: dom_id(setting), lines: 1 %> +
    +
    + <%= f.submit(t("admin.settings.index.update_setting"), class: "button hollow expanded") %> +
    +<% end %> diff --git a/app/views/admin/settings/_settings_table.html.erb b/app/views/admin/settings/_settings_table.html.erb index b6c75b440..ce99ffe4e 100644 --- a/app/views/admin/settings/_settings_table.html.erb +++ b/app/views/admin/settings/_settings_table.html.erb @@ -16,13 +16,10 @@
  • diff --git a/app/views/admin/settings/index.html.erb b/app/views/admin/settings/index.html.erb index 1de5b66be..a000cffc5 100644 --- a/app/views/admin/settings/index.html.erb +++ b/app/views/admin/settings/index.html.erb @@ -18,6 +18,10 @@ <%= render "map_configuration_tab" %> +
    + <%= render "images_and_documents_tab" %> +
    +
    <%= render "proposals_dashboard" %>
    diff --git a/app/views/budgets/_finished.html.erb b/app/views/budgets/_finished.html.erb new file mode 100644 index 000000000..60b715917 --- /dev/null +++ b/app/views/budgets/_finished.html.erb @@ -0,0 +1,35 @@ +
    +
    +

    <%= t("budgets.index.finished_budgets") %>

    + +
    + <% budgets.each do |budget| %> +
    +
    +
    +
    +
    +

    <%= budget.name %>

    +
    +
    + +
    +
    + <% if can?(:read_results, budget) %> + <%= link_to t("budgets.index.see_results"), + budget_results_path(budget), + class: "button" %> + <% end %> + + <%= link_to t("budgets.index.milestones"), + budget_executions_path(budget), + class: "button" %> +
    +
    +
    +
    +
    + <% end %> +
    +
    +
    diff --git a/app/views/budgets/_subnav.html.erb b/app/views/budgets/_subnav.html.erb new file mode 100644 index 000000000..f5f960377 --- /dev/null +++ b/app/views/budgets/_subnav.html.erb @@ -0,0 +1,17 @@ +
    +
    +
      + <% budget_subnav_items_for(budget).each do |item| %> + <% if item[:active] %> +
    • + <%= t("shared.you_are_in") %> + + <%= link_to item[:text], item[:url], class: "is-active" %> +
    • + <% else %> +
    • <%= link_to item[:text], item[:url] %>
    • + <% end %> + <% end %> +
    +
    +
    diff --git a/app/views/budgets/executions/show.html.erb b/app/views/budgets/executions/show.html.erb index a0e3324bc..f9410fdc8 100644 --- a/app/views/budgets/executions/show.html.erb +++ b/app/views/budgets/executions/show.html.erb @@ -25,21 +25,7 @@ -
    -
    -
      -
    • - <%= link_to t("budgets.results.link"), budget_results_path(@budget) %> -
    • -
    • - <%= link_to t("stats.budgets.link"), budget_stats_path(@budget) %> -
    • -
    • - <%= link_to t("budgets.executions.link"), budget_executions_path(@budget), class: "is-active" %> -
    • -
    -
    -
    +<%= render "budgets/subnav", budget: @budget %>
    @@ -58,13 +44,24 @@
    <%= form_tag(budget_executions_path(@budget), method: :get) do %>
    - <%= label_tag :status, t("budgets.executions.filters.label") %> + <%= label_tag :milestone_tag, t("budgets.executions.filters.milestone_tag.label") %> + <%= select_tag :milestone_tag, + options_for_select( + options_for_milestone_tags, + params[:milestone_tag] + ), + class: "js-submit-on-change", + prompt: t("budgets.executions.filters.milestone_tag.all", + count: @budget.investments.winners.with_milestones.count) %> +
    +
    + <%= label_tag :status, t("budgets.executions.filters.status.label") %> <%= select_tag :status, options_from_collection_for_select(@statuses, :id, lambda { |s| "#{s.name} (#{filters_select_counts(s.id)})" }, params[:status]), class: "js-submit-on-change", - prompt: t("budgets.executions.filters.all", + prompt: t("budgets.executions.filters.status.all", count: @budget.investments.winners.with_milestones.count) %>
    <% end %> diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index 9e198156f..5a7dde0ba 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -53,7 +53,7 @@ <% end %> <% end %> - <% if current_budget.finished? %> + <% if can?(:read_results, current_budget) %> <%= link_to t("budgets.show.see_results"), budget_results_path(current_budget, heading_id: current_budget.headings.first), class: "button margin-top expanded" %> @@ -126,38 +126,7 @@
    <% if @finished_budgets.present? %> -
    -
    -

    <%= t("budgets.index.finished_budgets") %>

    - -
    - <% @finished_budgets.each do |budget| %> -
    -
    -
    -
    -
    -

    <%= budget.name %>

    -
    -
    - -
    -
    - <%= link_to t("budgets.index.see_results"), - budget_results_path(budget.id), - class: "button" %> - <%= link_to t("budgets.index.milestones"), - budget_executions_path(budget.id), - class: "button" %> -
    -
    -
    -
    -
    - <% end %> -
    -
    -
    + <%= render "finished", budgets: @finished_budgets %> <% end %>
    <% else %> diff --git a/app/views/budgets/investments/_votes.html.erb b/app/views/budgets/investments/_votes.html.erb index 8d17f696e..2923fccee 100644 --- a/app/views/budgets/investments/_votes.html.erb +++ b/app/views/budgets/investments/_votes.html.erb @@ -36,7 +36,7 @@ verify_account: link_to(t("votes.verify_account"), verification_path), signin: link_to(t("votes.signin"), new_user_session_path), signup: link_to(t("votes.signup"), new_user_registration_path), - supported_headings: (current_user && current_user.headings_voted_within_group(investment.group).map(&:name).to_sentence) + supported_headings: (current_user && current_user.headings_voted_within_group(investment.group).map(&:name).sort.to_sentence) ).html_safe %>

    diff --git a/app/views/budgets/results/show.html.erb b/app/views/budgets/results/show.html.erb index d77f50b77..6a8829437 100644 --- a/app/views/budgets/results/show.html.erb +++ b/app/views/budgets/results/show.html.erb @@ -24,22 +24,7 @@
    -
    -
    -
      -
    • - <%= t("shared.you_are_in") %> - <%= link_to t("budgets.results.link"), budget_results_path(@budget), class: "is-active" %> -
    • -
    • - <%= link_to t("stats.budgets.link"), budget_stats_path(@budget) %> -
    • -
    • - <%= link_to t("budgets.executions.link"), budget_executions_path(@budget) %> -
    • -
    -
    -
    +<%= render "budgets/subnav", budget: @budget %>
    diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb index 89e4e146c..26af33856 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 can?(:read_results, @budget) %> <%= link_to t("budgets.show.see_results"), budget_results_path(@budget), class: "button margin-top expanded" %> diff --git a/app/views/budgets/stats/show.html.erb b/app/views/budgets/stats/show.html.erb index bbbfda4bb..60697135d 100644 --- a/app/views/budgets/stats/show.html.erb +++ b/app/views/budgets/stats/show.html.erb @@ -21,22 +21,7 @@
    -
    -
    -
      -
    • - <%= t("shared.you_are_in") %> - <%= link_to t("budgets.results.link"), budget_results_path(@budget) %> -
    • -
    • - <%= link_to t("stats.budgets.link"), budget_stats_path(@budget), class: "is-active" %> -
    • -
    • - <%= link_to t("budgets.executions.link"), budget_executions_path(@budget) %> -
    • -
    -
    -
    + <%= render "budgets/subnav", budget: @budget %>
    diff --git a/app/views/proposals/_support_status.html.erb b/app/views/proposals/_support_status.html.erb new file mode 100644 index 000000000..21fd09dc9 --- /dev/null +++ b/app/views/proposals/_support_status.html.erb @@ -0,0 +1,43 @@ +<% if proposal.selected? %> +
    + <%= t("proposals.proposal.selected") %> +
    +<% else %> +
    +
    +
    + +

    <%= t("votes.supports") %>

    + +
    + <% if proposal.draft? %> +
    +

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

    +
    + <% elsif proposal.successful? %> +
    + <%= render "supports", proposal: proposal %> +
    + <% elsif proposal.archived? %> +
    +

    + <%= t("proposals.proposal.supports", count: proposal.total_votes) %> +

    +

    <%= t("proposals.proposal.archived") %>

    +
    + <% else %> + <%= render "votes", { proposal: proposal, vote_url: vote_proposal_path(proposal, value: "yes") } %> + <% end %> +
    +
    +
    +
    + +
    +<% end %> diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index f405f15af..596c9624e 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -2,12 +2,12 @@ <% provide :title do %><%= @proposal.title %><% end %> <% content_for :meta_description do %><%= @proposal.summary %><% end %> <% provide :social_media_meta_tags do %> -<%= render "shared/social_media_meta_tags", - social_url: proposal_url(@proposal), - social_title: @proposal.title, - social_description: @proposal.summary, - twitter_image_url: (@proposal.image.present? ? @proposal.image_url(:thumb) : nil), - og_image_url: (@proposal.image.present? ? @proposal.image_url(:thumb) : nil) %> + <%= render "shared/social_media_meta_tags", + social_url: proposal_url(@proposal), + social_title: @proposal.title, + social_description: @proposal.summary, + twitter_image_url: (@proposal.image.present? ? @proposal.image_url(:thumb) : nil), + og_image_url: (@proposal.image.present? ? @proposal.image_url(:thumb) : nil) %> <% end %> <% content_for :canonical do %> <%= render "shared/canonical", href: proposal_url(@proposal) %> @@ -62,50 +62,7 @@ <% end %> - <% if @proposal.selected? %> -
    - <%= t("proposals.proposal.selected") %> -
    - <% else %> -
    -
    -
    - -

    <%= t("votes.supports") %>

    - -
    - <% if @proposal.draft? %> -
    -

    <%= t(".draft") %>

    -
    - <% elsif @proposal.successful? %> -
    - <%= render "supports", proposal: @proposal %> -
    - <% elsif @proposal.archived? %> -
    -

    - <%= t("proposals.proposal.supports", count: @proposal.total_votes) %> -

    -

    <%= t("proposals.proposal.archived") %>

    -
    - <% else %> - <%= render "votes", - { proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: "yes") } %> - <% end %> -
    -
    -
    -
    - -
    - <% end %> + <%= render "proposals/support_status", proposal: @proposal %> <%= render "proposals/social_share", proposal: @proposal, share_title: t("proposals.show.share") %> diff --git a/app/views/stats/index.json.erb b/app/views/stats/index.json.erb index f997b4f31..c2c27702f 100644 --- a/app/views/stats/index.json.erb +++ b/app/views/stats/index.json.erb @@ -1,6 +1,6 @@ { "<%= t("stats.index.visits") %>" : "<%= number_with_delimiter(@visits) %>", - "<%= t("stats.index.debates") %>" : <%= number_with_delimiter(@debates) %>", + "<%= t("stats.index.debates") %>" : "<%= number_with_delimiter(@debates) %>", "<%= t("stats.index.proposals") %>" : "<%= number_with_delimiter(@proposals) %>", "<%= t("stats.index.comments") %>" : "<%= number_with_delimiter(@comments) %>", "<%= t("stats.index.proposal_votes") %>" : "<%= number_with_delimiter(@proposal_votes) %>", @@ -9,4 +9,4 @@ "<%= t("stats.index.votes") %>" : "<%= number_with_delimiter(@votes) %>", "<%= t("stats.index.verified_users") %>" : "<%= number_with_delimiter(@verified_users) %>", "<%= t("stats.index.unverified_users") %>" : "<%= number_with_delimiter(@unverified_users) %>" -} \ No newline at end of file +} diff --git a/app/views/valuation/budget_investments/_valuation_comments.html.erb b/app/views/valuation/budget_investments/_valuation_comments.html.erb index 9f13ccb3d..0eacfa825 100644 --- a/app/views/valuation/budget_investments/_valuation_comments.html.erb +++ b/app/views/valuation/budget_investments/_valuation_comments.html.erb @@ -1,8 +1,10 @@

    <%= t("valuation.budget_investments.valuation_comments") %>

    <% unless @comment_tree.nil? %> - <%= render partial: "/comments/comment_tree", locals: { comment_tree: @comment_tree, - comment_flags: @comment_flags, - display_comments_count: false, - valuation: true, - allow_comments: !@budget.finished? } %> + <%= render partial: "/comments/comment_tree", locals: { + comment_tree: @comment_tree, + comment_flags: @comment_flags, + display_comments_count: false, + valuation: true, + allow_comments: !@budget.finished?, + admin_layout: true } %> <% end %> diff --git a/config/deploy.rb b/config/deploy.rb index dc6639ac1..602e86ae1 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -45,11 +45,19 @@ namespace :deploy do #before :starting, "rvm1:install:ruby" # install Ruby and create gemset #before :starting, "install_bundler_gem" # install bundler gem + after "deploy:migrate", "add_new_settings" after :publishing, "deploy:restart" after :published, "delayed_job:restart" after :published, "refresh_sitemap" after :finishing, "deploy:cleanup" + + + desc "Deploys and runs the tasks needed to upgrade to a new release" + task :upgrade do + after "add_new_settings", "execute_release_tasks" + invoke "deploy" + end end task :install_bundler_gem do @@ -67,3 +75,23 @@ task :refresh_sitemap do end end end + +task :add_new_settings do + on roles(:db) do + within release_path do + with rails_env: fetch(:rails_env) do + execute :rake, "settings:add_new_settings" + end + end + end +end + +task :execute_release_tasks do + on roles(:app) do + within release_path do + with rails_env: fetch(:rails_env) do + execute :rake, "consul:execute_release_tasks" + end + end + end +end diff --git a/config/environments/production.rb b/config/environments/production.rb index 2972eb78f..49ffa3202 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -48,6 +48,7 @@ Rails.application.configure do # config.action_cable.url = 'wss://example.com/cable' # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index b0beb1e42..321686723 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -115,9 +115,12 @@ ignore_missing: - "activerecord.errors.models.direct_message.*" - "errors.messages.blank" - "errors.messages.taken" + - "errors.messages.too_short" + - "errors.messages.too_long" - "devise.failure.invalid" - "devise.registrations.destroyed" - "devise.password_expired.*" + - "seeds.settings.*" ## Consider these keys used: ignore_unused: @@ -130,9 +133,9 @@ ignore_unused: - "date.order" - "unauthorized.*" - "admin.officials.level_*" - - "admin.comments.index.filter*" + - "admin.hidden_comments.index.filter*" - "admin.banners.index.filters.*" - - "admin.debates.index.filter*" + - "admin.hidden_debates.index.filter*" - "admin.hidden_proposals.index.filter*" - "admin.proposal_notifications.index.filter*" - "admin.budgets.index.filter*" @@ -147,7 +150,7 @@ ignore_unused: - "admin.legislation.processes.proposals.select_order" - "admin.legislation.draft_versions.*.submit_button" - "admin.legislation.questions.*.submit_button" - - "admin.comments.index.hidden_*" + - "admin.hidden_comments.index.hidden_*" - "admin.settings.index.features.*" - "admin.polls.*.submit_button" - "admin.booths.*.submit_button" @@ -206,7 +209,7 @@ ignore_unused: - admin.stats.polls.expired - "stats.polls.*_percentage" - landings.cambia_tu_ciudad.* - - 'seeds.settings.*' + - "seeds.settings.*" - "dashboard.polls.*.submit" #### ## Exclude these keys from the `i18n-tasks eq-base" report: diff --git a/config/locales/ar/budgets.yml b/config/locales/ar/budgets.yml index 63c1b6322..7b5c4a0d1 100644 --- a/config/locales/ar/budgets.yml +++ b/config/locales/ar/budgets.yml @@ -168,8 +168,9 @@ ar: heading_selection_title: "جسب المنطقة" no_winner_investments: "لا يوجد استثمارات فائزة" filters: - label: "حالة المشروع الحالية" - all: "الكل (%{count})" + status: + label: "حالة المشروع الحالية" + all: "الكل (%{count})" phases: errors: dates_range_invalid: "تاريخ البدأ لا يمكن ان يكون مساو او يتجاوز تاريخ الانتهاء" diff --git a/config/locales/ar/mailers.yml b/config/locales/ar/mailers.yml index db0ab258f..7cc1434e2 100644 --- a/config/locales/ar/mailers.yml +++ b/config/locales/ar/mailers.yml @@ -3,7 +3,7 @@ ar: no_reply: "تم إرسال هذه الرسالة من عنوان بريد إلكتروني لا يقبل الردود." comment: hi: مرحبا - new_comment_by_html: هناك تعليق جديد من %{commenter} + new_comment_by_html: هناك تعليق جديد من %{commenter} subject: تم التعليق على %{commentable} title: تعليق جديد config: @@ -16,7 +16,7 @@ ar: title: أكد حسابك باستخدام الرابط التالي reply: hi: مرحبا - new_reply_by_html: وهناك رد جديد من %{commenter} على التعليق الخاص بك + new_reply_by_html: وهناك رد جديد من %{commenter} على التعليق الخاص بك subject: يوجد رد على تعليقك title: اجابة جديدة على تعليقك unfeasible_spending_proposal: diff --git a/config/locales/ast/mailers.yml b/config/locales/ast/mailers.yml index 9bb3ab25c..a0a17c6d7 100644 --- a/config/locales/ast/mailers.yml +++ b/config/locales/ast/mailers.yml @@ -3,7 +3,7 @@ ast: no_reply: "Este mensaje se ha enviado desde una dirección de correo electrónico que no admite respuestas." comment: hi: Hola - new_comment_by_html: Hay un nuevo comentario de %{commenter} en + new_comment_by_html: Hay un nuevo comentario de %{commenter} en subject: Alguien ha comentado en tu %{commentable} title: Nuevo comentario config: @@ -16,7 +16,7 @@ ast: title: Verifica tu cuenta con el siguiente enlace reply: hi: Hola - new_reply_by_html: Hay una nueva respuesta de %{commenter} a tu comentario en + new_reply_by_html: Hay una nueva respuesta de %{commenter} a tu comentario en subject: Alguien ha respondido a tu comentario title: Nueva respuesta a tu comentario unfeasible_spending_proposal: diff --git a/config/locales/ca/mailers.yml b/config/locales/ca/mailers.yml index fc3c46535..6f4014ce2 100644 --- a/config/locales/ca/mailers.yml +++ b/config/locales/ca/mailers.yml @@ -3,7 +3,7 @@ ca: no_reply: "Aquest missatge s'ha enviat des d'una adreça de correu electrònic que no admet respostes." comment: hi: Hola - new_comment_by_html: Hi ha un nou comentari de %{commenter} en + new_comment_by_html: Hi ha un nou comentari de %{commenter} en subject: Algú ha comentat en el teu %{commentable} title: Nou comentari config: @@ -16,7 +16,7 @@ ca: title: Verifica el teu compte amb el següent enllaç reply: hi: Hola - new_reply_by_html: Hi ha una nova resposta de %{commenter} al teu comentari en + new_reply_by_html: Hi ha una nova resposta de %{commenter} al teu comentari en subject: Algú ha respost al teu comentari title: Nova resposta al teu comentari unfeasible_spending_proposal: diff --git a/config/locales/de-DE/budgets.yml b/config/locales/de-DE/budgets.yml index 30bb6305e..d69d70fd2 100644 --- a/config/locales/de-DE/budgets.yml +++ b/config/locales/de-DE/budgets.yml @@ -182,8 +182,9 @@ de: heading_selection_title: "Nach Bezirk" no_winner_investments: "Keine erfolgreichen Ausgabenvorschläge in diesem Status" filters: - label: "Aktueller Stand des Projekts" - all: "Alle (%{count})" + status: + label: "Aktueller Stand des Projekts" + all: "Alle (%{count})" phases: errors: dates_range_invalid: "Das Anfangsdatum kann nicht gleich oder später als das Enddatum sein" diff --git a/config/locales/de-DE/mailers.yml b/config/locales/de-DE/mailers.yml index 3bfbaa81a..1b9fbb7b7 100644 --- a/config/locales/de-DE/mailers.yml +++ b/config/locales/de-DE/mailers.yml @@ -3,7 +3,7 @@ de: no_reply: "Diese Nachricht wurde von einer E-Mail-Adresse gesendet, die keine Antworten akzeptiert." comment: hi: Hallo - new_comment_by_html: Es gibt einen neuen Kommentar von %{commenter} + new_comment_by_html: Es gibt einen neuen Kommentar von %{commenter} subject: Jemand hat Ihre %{commentable} kommentiert. title: Neuer Kommentar config: @@ -16,7 +16,7 @@ de: title: Bestätigen Sie Ihr Konto über den folgenden Link reply: hi: Hallo - new_reply_by_html: Es gibt eine neue Antwort von %{commenter} auf Ihren Kommentar + new_reply_by_html: Es gibt eine neue Antwort von %{commenter} auf Ihren Kommentar subject: Jemand hat auf Ihren Kommentar geantwortet title: Neue Antwort auf Ihren Kommentar unfeasible_spending_proposal: diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index e3c5c597b..fd472c7f1 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -281,6 +281,7 @@ en: tags_placeholder: "Write the tags you want separated by commas (,)" undefined: Undefined user_groups: "Groups" + milestone_tags: Milestone tags search_unfeasible: Search unfeasible milestones: index: @@ -297,6 +298,7 @@ en: documents: "Documents" milestone: Milestone new_milestone: Create new milestone + milestone_tags: Milestone Tags form: admin_statuses: Manage statuses no_statuses_defined: There are no defined statuses yet @@ -355,7 +357,7 @@ en: notice: "Progress bar updated successfully" delete: notice: "Progress bar deleted successfully" - comments: + hidden_comments: index: filter: Filter filters: @@ -430,7 +432,7 @@ en: request: Requested resource update: success: The task has been marked as solved. - debates: + hidden_debates: index: filter: Filter filters: @@ -728,6 +730,7 @@ en: title: Administrators name: Name email: Email + description: Description id: Administrator ID no_administrators: There are no administrators. administrator: @@ -736,6 +739,9 @@ en: restricted_removal: "Sorry, you can't remove yourself from the administrators" search: title: "Administrators: User search" + form: + edit_title: "Edit administrator" + updated: "Administrator updated successfully" moderators: index: title: Moderators @@ -875,6 +881,9 @@ en: user_invite: title: "User Invitation" description: "Sent to the person that has been invited to register an account." + evaluation_comment: + title: "New evaluation comment" + description: "Sent to administrators and evaluators related to commented investment" edit_info: "You can edit this email in" message_title: "Message's Title" message_body: "This is a sample of message's content." @@ -882,6 +891,7 @@ en: no_investments: "There aren't any budget investment created. Some example data is needed in order to preview the email." no_comments: "There aren't any comments created. Some example data is needed in order to preview the email." no_replies: "There aren't any replies created. Some example data is needed in order to preview the email." + no_evaluation_comments: "There aren't any evaluation comments created. Some example data is needed in order to preview the email." emails_download: index: title: Emails download @@ -1261,6 +1271,7 @@ en: title: Configuration settings update_setting: Update participation_processes: "Participation processes" + images_and_documents: "Images and documents" feature_flags: Features features: enabled: "Feature enabled" diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index 5f49b1d41..c209d4d2c 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -184,8 +184,12 @@ en: heading_selection_title: "By district" no_winner_investments: "No winner investments in this state" filters: - label: "Project's current state" - all: "All (%{count})" + status: + label: "Project's current state" + all: "All (%{count})" + milestone_tag: + label: "Milestone tag" + all: "All (%{count})" phases: errors: dates_range_invalid: "Start date can't be equal or later than End date" diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index 1cc83c602..5bb51982d 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -724,6 +724,7 @@ en: to: "To" author_info: author_deleted: User deleted + email_deleted: Email deleted back: Go back check: Select check_all: All diff --git a/config/locales/en/mailers.yml b/config/locales/en/mailers.yml index c92660de4..a1151d57d 100644 --- a/config/locales/en/mailers.yml +++ b/config/locales/en/mailers.yml @@ -3,7 +3,7 @@ en: no_reply: "This message was sent from an email address that does not accept replies." comment: hi: Hi - new_comment_by_html: There is a new comment from %{commenter} + new_comment_by_html: There is a new comment from %{commenter} subject: Someone has commented on your %{commentable} title: New comment config: @@ -17,7 +17,7 @@ en: title: Confirm your account using the following link reply: hi: Hi - new_reply_by_html: There is a new response from %{commenter} to your comment on + new_reply_by_html: There is a new response from %{commenter} to your comment on subject: Someone has responded to your comment title: New response to your comment proposal_notification_digest: @@ -70,6 +70,12 @@ en: hi: "Dear user," thanks: "Thank you again for participating." sincerely: "Sincererly" + evaluation_comment: + subject: "New evaluation comment" + title: New evaluation comment for %{investment} + hi: Hi + new_comment_by_html: There is a new evaluation comment from %{commenter} to the budget investment %{investment} + commenter_info: "%{commenter}, %{time}:" new_actions_notification_rake_created: subject: "More news about your citizen proposal" hi: "Hello %{name}," diff --git a/config/locales/en/settings.yml b/config/locales/en/settings.yml index cb0363f48..7af06fdad 100644 --- a/config/locales/en/settings.yml +++ b/config/locales/en/settings.yml @@ -116,6 +116,8 @@ en: public_stats_description: "Display public stats in the Administration panel" help_page: "Help page" help_page_description: 'Displays a Help menu that contains a page with an info section about each enabled feature. Also custom pages and menus can be created in the "Custom pages" and "Custom content blocks" sections' + valuation_comment_notification: "Valuation comment notification" + valuation_comment_notification_description: "Send an email to all associated users except valuation commenter to budget investment when a new valuation comment is created" map: latitude: "Latitude" latitude_description: "Latitude to show the map position" @@ -139,3 +141,25 @@ en: per_page_code_head_description: "This code will appear inside the label. Useful for entering custom scripts, analytics..." per_page_code_body: "Code to be included on every page ()" per_page_code_body_description: "This code will appear inside the label. Useful for entering custom scripts, analytics..." + uploads: + images: + min_width: "Image minimum width" + min_width_description: "Minimum width allowed for an uploaded image (in pixels)" + min_height: "Image minimum height" + min_height_description: "Minimum height allowed for an uploaded image (in pixels)" + max_size: "Image maximum size" + max_size_description: "Maximum size allowed for an uploaded image (in Megabytes/MB)" + content_types: "Accepted content types for images" + content_types_description: "Select all the content types allowed for uploaded images" + title: + min_length: "Image title minimum length" + min_length_description: "Title provided by the user when uploading an image (used as alt HTML attribute)" + max_length: "Image title maximum length" + max_length_description: "Title provided by the user when uploading an image (used as alt HTML attribute)" + documents: + max_amount: "Maximum number of documents" + max_amount_description: "Maximum number of documents that can be attached to a proposal, investment..." + max_size: "Document maximum size" + max_size_description: "Maximum size allowed for an uploaded document (in Megabytes/MB)" + content_types: "Accepted content types for documents" + content_types_description: "Select all the content types allowed for uploaded documents" diff --git a/config/locales/es-PE/mailers.yml b/config/locales/es-PE/mailers.yml index ea972263a..f04a585ab 100644 --- a/config/locales/es-PE/mailers.yml +++ b/config/locales/es-PE/mailers.yml @@ -3,7 +3,7 @@ es-PE: no_reply: "Este mensaje se ha enviado desde una dirección de correo electrónico que no admite respuestas." comment: hi: Hola - new_comment_by_html: Hay un nuevo comentario de %{commenter} en + new_comment_by_html: Hay un nuevo comentario de %{commenter} en subject: Alguien ha comentado en tu %{commentable} title: Nuevo comentario config: @@ -16,7 +16,7 @@ es-PE: title: Verifica tu cuenta con el siguiente enlace reply: hi: Hola - new_reply_by_html: Hay una nueva respuesta de %{commenter} a tu comentario en + new_reply_by_html: Hay una nueva respuesta de %{commenter} a tu comentario en subject: Alguien ha respondido a tu comentario title: Nueva respuesta a tu comentario unfeasible_spending_proposal: diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index e3bd118f7..4e5b8acd8 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -126,6 +126,8 @@ es: one: Enlace other: Enlaces attributes: + administrator: + description: Descripción budget: name: "Nombre" description_accepting: "Descripción durante la fase de presentación de proyectos" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 34fc7510b..907fc15c7 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -281,6 +281,7 @@ es: tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)" undefined: Sin definir user_groups: "Grupos" + milestone_tags: Etiquetas de Seguimiento search_unfeasible: Buscar inviables milestones: index: @@ -297,6 +298,7 @@ es: documents: "Documentos" milestone: Seguimiento new_milestone: Crear nuevo hito + milestone_tags: Etiquetas de Seguimiento form: admin_statuses: Gestionar estados no_statuses_defined: No hay estados definidos @@ -355,7 +357,7 @@ es: notice: "Barra de progreso actualizada" delete: notice: "Barra de progreso eliminada correctamente" - comments: + hidden_comments: index: filter: Filtro filters: @@ -430,7 +432,7 @@ es: request: Recurso solicitado update: success: La tarea ha sido marcada como resuelta - debates: + hidden_debates: index: filter: Filtro filters: @@ -727,6 +729,7 @@ es: title: Administradores name: Nombre email: Email + description: Descripción id: ID de Administrador no_administrators: No hay administradores. administrator: @@ -735,6 +738,9 @@ es: restricted_removal: "Lo sentimos, no puedes eliminarte a ti mismo de la lista" search: title: "Administradores: Búsqueda de usuarios" + form: + edit_title: "Editar administrador" + updated: "Administrador actualizado correctamente" moderators: index: title: Moderadores @@ -874,6 +880,9 @@ es: user_invite: title: "Invitación de usuarios" description: "Enviado a la persona que ha sido invitada a registrar una cuenta." + evaluation_comment: + title: "Nuevo comentario de evaluación" + description: "Enviado a administradores y evaluadores del presupuesto." edit_info: "Puedes editar este email en" message_title: "Título del mensaje" message_body: "Este es un ejemplo de contenido de un mensaje." @@ -881,6 +890,7 @@ es: no_investments: "No se ha creado ningún proyecto de gasto. Se necesita algún ejemplo para poder previsualizar el email." no_comments: "No se ha creado ningún comentario. Se necesita algún ejemplo para poder previsualizar el email." no_replies: "No se ha creado ninguna respuesta. Se necesita algún ejemplo para poder previsualizar el email." + no_evaluation_comments: "No se ha creado ningún comentario de evaluación. Se necesita algún ejemplo para poder previsualizar el email." emails_download: index: title: Descarga de emails @@ -1260,6 +1270,7 @@ es: title: Configuración global update_setting: Actualizar participation_processes: "Procesos de participación" + images_and_documents: "Imágenes y documentos" feature_flags: Funcionalidades features: enabled: "Funcionalidad activada" diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index cac1f50bd..855598a0d 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -184,8 +184,12 @@ es: heading_selection_title: "Ámbito de actuación" no_winner_investments: "No hay proyectos de gasto ganadores en este estado" filters: - label: "Estado actual del proyecto" - all: "Todos (%{count})" + status: + label: "Estado actual del proyecto" + all: "Todos (%{count})" + milestone_tag: + label: "Etiquetas de Seguimiento" + all: "Todos (%{count})" phases: errors: dates_range_invalid: "La fecha de comienzo no puede ser igual o superior a la de finalización" diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index b7a527c6a..52994ae87 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -722,6 +722,7 @@ es: to: "Hasta" author_info: author_deleted: Usuario eliminado + email_deleted: Email eliminado back: Volver check: Seleccionar check_all: Todos diff --git a/config/locales/es/mailers.yml b/config/locales/es/mailers.yml index b1d0de74b..92a810b39 100644 --- a/config/locales/es/mailers.yml +++ b/config/locales/es/mailers.yml @@ -3,7 +3,7 @@ es: no_reply: "Este mensaje se ha enviado desde una dirección de correo electrónico que no admite respuestas." comment: hi: Hola - new_comment_by_html: Hay un nuevo comentario de %{commenter} en + new_comment_by_html: Hay un nuevo comentario de %{commenter} en subject: Alguien ha comentado en tu %{commentable} title: Nuevo comentario config: @@ -17,7 +17,7 @@ es: title: Verifica tu cuenta con el siguiente enlace reply: hi: Hola - new_reply_by_html: Hay una nueva respuesta de %{commenter} a tu comentario en + new_reply_by_html: Hay una nueva respuesta de %{commenter} a tu comentario en subject: Alguien ha respondido a tu comentario title: Nueva respuesta a tu comentario proposal_notification_digest: @@ -70,6 +70,12 @@ es: hi: "Estimado/a usuario/a" thanks: "Gracias de nuevo por tu participación." sincerely: "Atentamente" + evaluation_comment: + subject: "Nuevo comentario de evaluación" + title: Nuevo comentario de evaluación para %{investment} + hi: Hola + new_comment_by_html: Hay un nuevo comentario de evaluación de %{commenter} en el presupuesto participativo %{investment} + commenter_info: "%{commenter}, %{time}" new_actions_notification_rake_created: subject: "Más novedades de tu propuesta ciudadana" hi: "Hola %{name}," diff --git a/config/locales/es/settings.yml b/config/locales/es/settings.yml index 42b0726d1..352e4f30d 100644 --- a/config/locales/es/settings.yml +++ b/config/locales/es/settings.yml @@ -116,6 +116,8 @@ es: public_stats_description: "Muestra las estadísticas públicas en el panel de Administración" help_page: "Página de ayuda" help_page_description: 'Muestra un menú Ayuda que contiene una página con una sección de información sobre cada funcionalidad habilitada. También se pueden crear páginas y menús personalizados en las secciones "Personalizar páginas" y "Personalizar bloques"' + valuation_comment_notification: "Notificar comentarios de evaluación" + valuation_comment_notification_description: "Envía un email a todos los usuarios menos al que haya comentado asociados a un presupuesto participativo cuando se cree un nuevo comentario de evaluación" map: latitude: "Latitud" latitude_description: "Latitud para mostrar la posición del mapa" @@ -139,3 +141,25 @@ es: per_page_code_head_description: "Esté código aparecerá dentro de la etiqueta . Útil para introducir scripts personalizados, analitycs..." per_page_code_body: "Código a incluir en cada página ()" per_page_code_body_description: "Esté código aparecerá dentro de la etiqueta . Útil para introducir scripts personalizados, analitycs..." + uploads: + images: + min_width: "Ancho mínimo de imagen" + min_width_description: "Ancho mínimo permitido al subir una imagen (en pixeles)" + min_height: "Alto mínimo de imagen" + min_height_description: "Alto mínimo permitido al subir una imagen (en pixeles)" + max_size: "Tamaño máximo de imagen" + max_size_description: "Tamaño máximo permitido al subir una imagen (en Megabytes/MB)" + content_types: "Tipos de imagenes permitidos" + content_types_description: "Selecciona todos los tipos permitidos para las imágenes subidas" + title: + min_length: "Longitud mínima del título de la imagen" + min_length_description: "El título es proporcionado por el usuario cuando se sube una imagen (usado como atributo HTML alt)" + max_length: "Longitud máxima del título de la imagen" + max_length_description: "El título es proporcionado por el usuario cuando se sube una imagen (usado como atributo HTML alt)" + documents: + max_amount: "Número máximo de documentos" + max_amount_description: "Número máximo de documentos que se pueden añadir a una propuesta, proyecto de gasto..." + max_size: "Tamaño máximo de documento" + max_size_description: "Tamaño máximo permitido al subir un documento (en Megabytes/MB)" + content_types: "Tipos de documentos permitidos" + content_types_description: "Selecciona todos los tipos permitidos para los documentos subidos" diff --git a/config/locales/fi-FI/budgets.yml b/config/locales/fi-FI/budgets.yml index 80cdcbf29..4a956a286 100644 --- a/config/locales/fi-FI/budgets.yml +++ b/config/locales/fi-FI/budgets.yml @@ -50,4 +50,5 @@ fi: link: "Tavoitteet" page_title: "%{budget} - Tavoitteet" filters: - all: "Kaikki (%{count})" + status: + all: "Kaikki (%{count})" diff --git a/config/locales/fr/mailers.yml b/config/locales/fr/mailers.yml index 4c46ab118..4df25de69 100644 --- a/config/locales/fr/mailers.yml +++ b/config/locales/fr/mailers.yml @@ -3,7 +3,7 @@ fr: no_reply: "Ce message a été envoyé d'une adresse qui n'accepte pas les réponses." comment: hi: Bonjour - new_comment_by_html: Il y a un nouveau commentaire de %{commenter} + new_comment_by_html: Il y a un nouveau commentaire de %{commenter} subject: Quelqu'un a commenté votre %{commentable} title: Nouveau commentaire config: @@ -17,7 +17,7 @@ fr: title: Confirmer votre compte en utilisant le lien suivant reply: hi: Bonjour - new_reply_by_html: Il y a une nouvelle réponse de %{commenter} à votre commentaire sur + new_reply_by_html: Il y a une nouvelle réponse de %{commenter} à votre commentaire sur subject: Quelqu'un a répondu à votre commentaire title: Nouvelle réponse à votre commentaire unfeasible_spending_proposal: diff --git a/config/locales/gl/budgets.yml b/config/locales/gl/budgets.yml index e578b008f..f9df44e6c 100644 --- a/config/locales/gl/budgets.yml +++ b/config/locales/gl/budgets.yml @@ -182,8 +182,9 @@ gl: heading_selection_title: "Por ámbito de actuación" no_winner_investments: "Non hai investimentos gañadores neste estado" filters: - label: "Estado actual do proxecto" - all: "Todo (%{count})" + status: + label: "Estado actual do proxecto" + all: "Todo (%{count})" phases: errors: dates_range_invalid: "A data de comezo non pode ser igual ou superior á de remate" diff --git a/config/locales/gl/mailers.yml b/config/locales/gl/mailers.yml index e8b37c71c..fb4b6e634 100644 --- a/config/locales/gl/mailers.yml +++ b/config/locales/gl/mailers.yml @@ -3,7 +3,7 @@ gl: no_reply: "Esta mensaxe enviouse desde un enderezo de correo electrónico que non admite respostas." comment: hi: Ola - new_comment_by_html: Hai un novo comentario de %{commenter} en + new_comment_by_html: Hai un novo comentario de %{commenter} en subject: Alguén comentou no teu %{commentable} title: Novo comentario config: @@ -17,7 +17,7 @@ gl: title: Verifica a túa conta co seguinte enlace reply: hi: Ola - new_reply_by_html: Hai unha nova resposta de %{commenter} ao tu comentario en + new_reply_by_html: Hai unha nova resposta de %{commenter} ao tu comentario en subject: Alguén respondeu ao teu comentario title: Nova resposta ao teu comentario unfeasible_spending_proposal: diff --git a/config/locales/he/mailers.yml b/config/locales/he/mailers.yml index 98b9a0d44..7629188b9 100644 --- a/config/locales/he/mailers.yml +++ b/config/locales/he/mailers.yml @@ -3,7 +3,7 @@ he: no_reply: "נא לא לענות לכתובת דואר זו." comment: hi: שלום - new_comment_by_html: ישנה הערה חדשה מ %{commenter} + new_comment_by_html: ישנה הערה חדשה מ %{commenter} subject: ישנה הערה חדשה %{commentable} title: הערה חדשה config: @@ -17,7 +17,7 @@ he: title: אשר את חשבונך באמצעות הקישור הבא reply: hi: שלום - new_reply_by_html: ישנה התייחסות חדשה מ %{commenter} לתגובתך בנושא + new_reply_by_html: ישנה התייחסות חדשה מ %{commenter} לתגובתך בנושא subject: מישהו הגיב להערה שלך title: התקבלה תגובה חדשה להערה שלך unfeasible_spending_proposal: diff --git a/config/locales/id-ID/mailers.yml b/config/locales/id-ID/mailers.yml index 939a868f9..6141cc2ad 100644 --- a/config/locales/id-ID/mailers.yml +++ b/config/locales/id-ID/mailers.yml @@ -3,7 +3,7 @@ id: no_reply: "Pesan ini dikirim dari alamat email yang tidak menerima balasan." comment: hi: Hai - new_comment_by_html: Ada komentar baru dari %{commenter} + new_comment_by_html: Ada komentar baru dari %{commenter} subject: Seseorang telah berkomentar tentang anda %{commentable} title: Komentar baru config: @@ -16,7 +16,7 @@ id: title: Konfirmasikan akun anda menggunakan tautan berikut reply: hi: Hai - new_reply_by_html: Ada respon baru dari %{commenter} untuk komentar anda + new_reply_by_html: Ada respon baru dari %{commenter} untuk komentar anda subject: Seseorang telah menanggapi komentar anda title: Tanggapan baru atas komentar anda unfeasible_spending_proposal: diff --git a/config/locales/it/budgets.yml b/config/locales/it/budgets.yml index 74df6ac9d..d2ae6a3ac 100644 --- a/config/locales/it/budgets.yml +++ b/config/locales/it/budgets.yml @@ -176,7 +176,8 @@ it: link: "Traguardi" heading_selection_title: "Per circoscrizione" filters: - all: "Tutto (%{count})" + status: + all: "Tutto (%{count})" phases: errors: dates_range_invalid: "La Data di inizio non può essere uguale o successiva alla Data finale" diff --git a/config/locales/it/mailers.yml b/config/locales/it/mailers.yml index f6bcfaa59..5f789b6f7 100644 --- a/config/locales/it/mailers.yml +++ b/config/locales/it/mailers.yml @@ -3,7 +3,7 @@ it: no_reply: "Questo messaggio è stato inviato da un indirizzo di posta elettronica che non accetta risposte." comment: hi: Salve - new_comment_by_html: C'è un nuovo commento da %{commenter} + new_comment_by_html: C'è un nuovo commento da %{commenter} subject: Qualcuno ha commentato sul tuo %{commentable} title: Nuovo commento config: @@ -17,7 +17,7 @@ it: title: Conferma il tuo account utilizzando il seguente link reply: hi: Ciao - new_reply_by_html: C'è una nuova risposta da %{commenter} per il tuo commento su + new_reply_by_html: C'è una nuova risposta da %{commenter} per il tuo commento su subject: Qualcuno ha risposto al tuo commento title: Nuova risposta al tuo commento unfeasible_spending_proposal: diff --git a/config/locales/nl/budgets.yml b/config/locales/nl/budgets.yml index 166c5419b..22bd22417 100644 --- a/config/locales/nl/budgets.yml +++ b/config/locales/nl/budgets.yml @@ -184,8 +184,9 @@ nl: heading_selection_title: "Per wijk" no_winner_investments: "Geen geaccepteerde investeringen in deze status" filters: - label: "Status van het project" - all: "Alle (%{count})" + status: + label: "Status van het project" + all: "Alle (%{count})" phases: errors: dates_range_invalid: "De begindatum dient kleiner te zijn dan de einddatum" diff --git a/config/locales/nl/mailers.yml b/config/locales/nl/mailers.yml index 53126e894..ead75920f 100644 --- a/config/locales/nl/mailers.yml +++ b/config/locales/nl/mailers.yml @@ -3,7 +3,7 @@ nl: no_reply: "This message was sent from an email address that does not accept replies." comment: hi: Hi - new_comment_by_html: There is a new comment from %{commenter} + new_comment_by_html: There is a new comment from %{commenter} subject: Someone has commented on your %{commentable} title: New comment config: @@ -17,7 +17,7 @@ nl: title: Confirm your account using the following link reply: hi: Hoi - new_reply_by_html: There is a new response from %{commenter} to your comment on + new_reply_by_html: There is a new response from %{commenter} to your comment on subject: Someone has responded to your comment title: New response to your comment unfeasible_spending_proposal: diff --git a/config/locales/pl-PL/mailers.yml b/config/locales/pl-PL/mailers.yml index f9eedc9ba..aed305cc6 100644 --- a/config/locales/pl-PL/mailers.yml +++ b/config/locales/pl-PL/mailers.yml @@ -3,7 +3,7 @@ pl: no_reply: "Ta wiadomość została wysłana z adresu e-mail, który nie akceptuje odpowiedzi." comment: hi: Cześć - new_comment_by_html: Nowy komentarz od %{commenter} + new_comment_by_html: Nowy komentarz od %{commenter} subject: Ktoś skomentował Twój %{commentable} title: Nowy komentarz config: @@ -17,7 +17,7 @@ pl: title: Potwierdź swoje konto za pomocą następującego linku reply: hi: Cześć - new_reply_by_html: Nowa odpowiedź od %{commenter} na Twój komentarz odnośnie + new_reply_by_html: Nowa odpowiedź od %{commenter} na Twój komentarz odnośnie subject: Ktoś odpowiedział na Twój komentarz title: Nowa odpowiedź na Twój komentarz unfeasible_spending_proposal: diff --git a/config/locales/pt-BR/mailers.yml b/config/locales/pt-BR/mailers.yml index bd7fa72c0..67c00eab7 100644 --- a/config/locales/pt-BR/mailers.yml +++ b/config/locales/pt-BR/mailers.yml @@ -3,7 +3,7 @@ pt-BR: no_reply: "Esta mensagem foi enviada de um endereço de e-mail que não aceita respostas." comment: hi: Oi - new_comment_by_html: Há um novo comentário de %{commenter} + new_comment_by_html: Há um novo comentário de %{commenter} subject: Alguém comentou sobre seu %{commentable} title: Novo comentário config: @@ -17,7 +17,7 @@ pt-BR: title: Confirme sua conta usando o seguinte link reply: hi: Oi - new_reply_by_html: Há uma nova resposta de %{commenter} ao seu comentário sobre + new_reply_by_html: Há uma nova resposta de %{commenter} ao seu comentário sobre subject: Alguém respondeu ao seu comentário title: Nova resposta ao seu comentário unfeasible_spending_proposal: diff --git a/config/locales/ru/mailers.yml b/config/locales/ru/mailers.yml index e0de1d260..a12f4ae46 100644 --- a/config/locales/ru/mailers.yml +++ b/config/locales/ru/mailers.yml @@ -3,7 +3,7 @@ ru: no_reply: "Это сообщение было отправлено с email адреса, который не принимает ответы." comment: hi: Здравствуйте - new_comment_by_html: Появился новый комментарий от %{commenter} + new_comment_by_html: Появился новый комментарий от %{commenter} subject: Кто-то прокомментировал ваш %{commentable} title: Новый комментарий config: @@ -17,7 +17,7 @@ ru: title: Подтвердить ваш аккаунт при помощи следующей ссылки reply: hi: Здравствуйте - new_reply_by_html: Появился новый ответ от %{commenter} на ваш комментарий на + new_reply_by_html: Появился новый ответ от %{commenter} на ваш комментарий на subject: Кто-то ответил на ваш комментарий title: Новый ответ на ваш комментарий unfeasible_spending_proposal: diff --git a/config/locales/sl-SI/mailers.yml b/config/locales/sl-SI/mailers.yml index c0379edd6..adaed2cd2 100644 --- a/config/locales/sl-SI/mailers.yml +++ b/config/locales/sl-SI/mailers.yml @@ -3,7 +3,7 @@ sl: no_reply: "To sporočilo je bilo poslano z e-naslova, ki ne sprejema odgovorov." comment: hi: Hej - new_comment_by_html: Imaš nov komentar uporabnika/-ce %{commenter} + new_comment_by_html: Imaš nov komentar uporabnika/-ce %{commenter} subject: Nekdo je komentiral na tvoj %{commentable} title: Nov komentar config: @@ -17,7 +17,7 @@ sl: title: Potrdi svoj račun z uporabo naslednje povezave reply: hi: Hej - new_reply_by_html: Imaš nov odgovor uporabnika/-ce %{commenter} na tvoj komentar o + new_reply_by_html: Imaš nov odgovor uporabnika/-ce %{commenter} na tvoj komentar o subject: Nekdo se je odzval na tvoj komentar title: Nov odgovor na tvoj komentar unfeasible_spending_proposal: diff --git a/config/locales/so-SO/budgets.yml b/config/locales/so-SO/budgets.yml index d47a4e51e..dbcf21d0c 100644 --- a/config/locales/so-SO/budgets.yml +++ b/config/locales/so-SO/budgets.yml @@ -181,8 +181,9 @@ so: heading_selection_title: "Degmo ahaan" no_winner_investments: "Mana jiraan maalgalin ku guuleysta gobolkan" filters: - label: "Mashruca hada gobolka kasocda" - all: "Dhaman%{count}" + status: + label: "Mashruca hada gobolka kasocda" + all: "Dhaman%{count}" phases: errors: dates_range_invalid: "Taariikhda bilowga ma noqon karto mid siman ama ka dambeysa taariikhda dhammaadka" diff --git a/config/locales/so-SO/mailers.yml b/config/locales/so-SO/mailers.yml index 9361e99e0..66fb5bbb7 100644 --- a/config/locales/so-SO/mailers.yml +++ b/config/locales/so-SO/mailers.yml @@ -3,7 +3,7 @@ so: no_reply: "Farriintan waxaa loo direy cinwaanka emailka ah ee aan aqbalin jawaab-celinta." comment: hi: Haye - new_comment_by_html: Waxa jira faalo cusub%{commenter} + new_comment_by_html: Waxa jira faalo cusub%{commenter} subject: Qof ayaa ka falooday%{commentable} title: Faalo cusub config: @@ -17,7 +17,7 @@ so: title: Xaqiiji xisaabtaada adigoo isticmaalaya xiriirka soo socda reply: hi: Haye - new_reply_by_html: Waxaa jira jawaab cusub oo ka socota %{commenter} si aad faallo uga bixiso + new_reply_by_html: Waxaa jira jawaab cusub oo ka socota %{commenter} si aad faallo uga bixiso subject: Qof ayaa kajawaabay faladadii title: Jawaabta cusub ee faalladaada unfeasible_spending_proposal: diff --git a/config/locales/sq-AL/budgets.yml b/config/locales/sq-AL/budgets.yml index cb376b4f8..6d3cecdc6 100644 --- a/config/locales/sq-AL/budgets.yml +++ b/config/locales/sq-AL/budgets.yml @@ -49,7 +49,7 @@ sq: all_phases: Shihni të gjitha fazat all_phases: Fazat e investimeve buxhetore map: Propozimet e investimeve buxhetore të vendosura gjeografikisht - investment_proyects: Lista e të gjitha projekteve të investimeve + investment_proyects: Lista e të gjitha projekteve të investimeve unfeasible_investment_proyects: Lista e të gjitha projekteve të investimeve të papranueshme not_selected_investment_proyects: Lista e të gjitha projekteve të investimeve të pa zgjedhur për votim finished_budgets: Përfunduan buxhetet pjesëmarrëse. @@ -171,7 +171,7 @@ sq: accepted: "Propozimi i pranuar i shpenzimeve:" discarded: "Propozimi i hedhur poshtë i shpenzimeve:" incompatibles: I papajtueshëm - investment_proyects: Lista e të gjitha projekteve të investimeve + investment_proyects: Lista e të gjitha projekteve të investimeve unfeasible_investment_proyects: Lista e të gjitha projekteve të investimeve të papranueshme not_selected_investment_proyects: Lista e të gjitha projekteve të investimeve të pa zgjedhur për votim executions: @@ -181,8 +181,9 @@ sq: heading_selection_title: "Nga rrethi" no_winner_investments: "Asnjë investim fitues në këtë gjendje" filters: - label: "Gjendja aktuale e projektit" - all: "Të gjitha (%{count})" + status: + label: "Gjendja aktuale e projektit" + all: "Të gjitha (%{count})" phases: errors: dates_range_invalid: "Data e fillimit nuk mund të jetë e barabartë ose më vonë se data e përfundimit" diff --git a/config/locales/sq-AL/mailers.yml b/config/locales/sq-AL/mailers.yml index 4dfbf0869..58896f106 100644 --- a/config/locales/sq-AL/mailers.yml +++ b/config/locales/sq-AL/mailers.yml @@ -3,7 +3,7 @@ sq: no_reply: "Ky mesazh u dërgua nga një adresë e-mail që nuk pranon përgjigje." comment: hi: Përshëndetje! - new_comment_by_html: Ka një koment të ri nga%{commenter} + new_comment_by_html: Ka një koment të ri nga%{commenter} subject: Dikush ka komentuar mbi %{commentable} tuaj title: Komenti i ri config: @@ -17,7 +17,7 @@ sq: title: Konfirmo llogarinë tënd duke përdorur lidhjen e mëposhtme reply: hi: Hi - new_reply_by_html: Ka një përgjigje të re nga %{commenter} në komentin tuaj + new_reply_by_html: Ka një përgjigje të re nga %{commenter} në komentin tuaj subject: Dikush i është përgjigjur komentit tuaj title: Përgjigje e re tek komentit tuaj unfeasible_spending_proposal: diff --git a/config/locales/sv-SE/mailers.yml b/config/locales/sv-SE/mailers.yml index ce305afb6..39567de93 100644 --- a/config/locales/sv-SE/mailers.yml +++ b/config/locales/sv-SE/mailers.yml @@ -3,7 +3,7 @@ sv: no_reply: "Det går inte att svara till den här e-postadressen." comment: hi: Hej - new_comment_by_html: Det finns en ny kommentar från %{commenter} + new_comment_by_html: Det finns en ny kommentar från %{commenter} subject: Någon har kommenterat på ditt %{commentable} title: Ny kommentar config: @@ -17,7 +17,7 @@ sv: title: Bekräfta kontot via länken nedan reply: hi: Hej - new_reply_by_html: Det finns ett nytt svar från %{commenter} till din kommentar på + new_reply_by_html: Det finns ett nytt svar från %{commenter} till din kommentar på subject: Någon har svarat på din kommentar title: Nytt svar på din kommentar unfeasible_spending_proposal: diff --git a/config/locales/val/budgets.yml b/config/locales/val/budgets.yml index 1e2cd7bcb..2cd0e9207 100644 --- a/config/locales/val/budgets.yml +++ b/config/locales/val/budgets.yml @@ -182,8 +182,9 @@ val: heading_selection_title: "Àmbit d'actuació" no_winner_investments: "No hi ha propostes en aquest estat" filters: - label: "Estat actual del projecte" - all: "Tots (%{count})" + status: + label: "Estat actual del projecte" + all: "Tots (%{count})" phases: errors: dates_range_invalid: "La data d'inici no ha de ser igual o posterior a la data de fi" diff --git a/config/locales/val/mailers.yml b/config/locales/val/mailers.yml index efa3c79de..a0474520b 100644 --- a/config/locales/val/mailers.yml +++ b/config/locales/val/mailers.yml @@ -3,7 +3,7 @@ val: no_reply: "Aquest missatge s'ha enviat des d'una direcció de correu electrònic que no admet respostes." comment: hi: Hola - new_comment_by_html: Hi ha un nou comentari de %{commenter} en + new_comment_by_html: Hi ha un nou comentari de %{commenter} en subject: Algú ha comentat en el teu %{commentable} title: Nou comentari config: @@ -17,7 +17,7 @@ val: title: Verifica el teu compte amb el següent enllaç reply: hi: Hola - new_reply_by_html: Hi ha noves respostes de %{commenter} al teu comentari en + new_reply_by_html: Hi ha noves respostes de %{commenter} al teu comentari en subject: Algú ha contestat el teu comentari title: Nova resposta al teu comentari unfeasible_spending_proposal: diff --git a/config/locales/zh-CN/budgets.yml b/config/locales/zh-CN/budgets.yml index f3588eb3a..5ac40d396 100644 --- a/config/locales/zh-CN/budgets.yml +++ b/config/locales/zh-CN/budgets.yml @@ -176,8 +176,9 @@ zh-CN: heading_selection_title: "按区域" no_winner_investments: "这个州里没有胜出的投资" filters: - label: "项目的当前状态" - all: "所有(%{count})" + status: + label: "项目的当前状态" + all: "所有(%{count})" phases: errors: dates_range_invalid: "开始日期不能等于或晚于结束日期" diff --git a/config/locales/zh-CN/mailers.yml b/config/locales/zh-CN/mailers.yml index 84376169c..c591c3942 100644 --- a/config/locales/zh-CN/mailers.yml +++ b/config/locales/zh-CN/mailers.yml @@ -3,7 +3,7 @@ zh-CN: no_reply: "此消息是从一个不接受回复的电子邮件地址发送的。" comment: hi: 您好 - new_comment_by_html: 来自%{commenter}的新评论 + new_comment_by_html: 来自%{commenter}的新评论 subject: 有人对您的%{commentable} 做了评论 title: 新评论 config: @@ -17,7 +17,7 @@ zh-CN: title: 使用以下链接来确认您的账户 reply: hi: 您好 - new_reply_by_html: %{commenter} 对您评论有新回复 + new_reply_by_html: %{commenter} 对您评论有新回复 subject: 有人回复了您的评论 title: 对您评论的新回复 unfeasible_spending_proposal: diff --git a/config/locales/zh-TW/mailers.yml b/config/locales/zh-TW/mailers.yml index b63e23491..ea06c2522 100644 --- a/config/locales/zh-TW/mailers.yml +++ b/config/locales/zh-TW/mailers.yml @@ -3,7 +3,7 @@ zh-TW: no_reply: "此郵件是從一個不接受回覆的電郵地址發送的。" comment: hi: 您好 - new_comment_by_html: %{commenter}發表了新評論 + new_comment_by_html: %{commenter}發表了新評論 subject: 有人評論了您的%{commentable} title: 新評論 config: @@ -17,7 +17,7 @@ zh-TW: title: 使用以下鏈接確認您的帳戶 reply: hi: 您好 - new_reply_by_html: %{commenter}對您的評論有新的回覆 + new_reply_by_html: %{commenter}對您的評論有新的回覆 subject: 有人回覆了您的評論 title: 對您的評論的新回覆 unfeasible_spending_proposal: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 39ed9b44b..be819f243 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -22,7 +22,7 @@ namespace :admin do end end - resources :debates, only: :index do + resources :hidden_debates, only: :index do member do put :restore put :confirm_hide @@ -75,7 +75,7 @@ namespace :admin do collection { get :search } end - resources :comments, only: :index do + resources :hidden_comments, only: :index do member do put :restore put :confirm_hide @@ -90,6 +90,7 @@ namespace :admin do resources :settings, only: [:index, :update] put :update_map, to: "settings#update_map" + put :update_content_types, to: "settings#update_content_types" resources :moderators, only: [:index, :create, :destroy] do get :search, on: :collection @@ -106,7 +107,7 @@ namespace :admin do get :search, on: :collection end - resources :administrators, only: [:index, :create, :destroy] do + resources :administrators, only: [:index, :create, :destroy, :edit, :update] do get :search, on: :collection end diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index f3c154b64..1d795b79e 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -39,6 +39,7 @@ require_relative "dev_seeds/notifications" require_relative "dev_seeds/widgets" require_relative "dev_seeds/admin_notifications" require_relative "dev_seeds/legislation_proposals" +require_relative "dev_seeds/legislation_people_proposals" require_relative "dev_seeds/milestones" require_relative "dev_seeds/pages" diff --git a/db/dev_seeds/legislation_people_proposals.rb b/db/dev_seeds/legislation_people_proposals.rb new file mode 100644 index 000000000..cccd84df3 --- /dev/null +++ b/db/dev_seeds/legislation_people_proposals.rb @@ -0,0 +1,12 @@ +section "Creating legislation people proposals" do + 10.times do + Legislation::PeopleProposal.create!(title: Faker::Lorem.sentence(3).truncate(60), + description: Faker::Lorem.paragraphs.join("\n\n"), + summary: Faker::Lorem.paragraph, + author: User.all.sample, + process: Legislation::Process.all.sample, + terms_of_service: "1", + validated: rand <= 2.0 / 3, + selected: rand <= 1.0 / 3) + end +end diff --git a/db/dev_seeds/legislation_processes.rb b/db/dev_seeds/legislation_processes.rb index f932549f7..c718685f0 100644 --- a/db/dev_seeds/legislation_processes.rb +++ b/db/dev_seeds/legislation_processes.rb @@ -11,6 +11,8 @@ section "Creating collaborative legislation" do debate_end_date: Date.current + (i - 5).days, proposals_phase_start_date: Date.current + (i - 7).days, proposals_phase_end_date: Date.current + (i - 5).days, + people_proposals_phase_start_date: Date.current + (i - 7).days, + people_proposals_phase_end_date: Date.current + (i - 5).days, draft_publication_date: Date.current + (i - 3).days, allegations_start_date: Date.current + (i - 2).days, allegations_end_date: Date.current + (i - 1).days, @@ -20,6 +22,7 @@ section "Creating collaborative legislation" do draft_publication_enabled: true, result_publication_enabled: true, proposals_phase_enabled: true, + people_proposals_phase_enabled: true, published: true) end diff --git a/db/dev_seeds/settings.rb b/db/dev_seeds/settings.rb index 70b6574e2..364773ab6 100644 --- a/db/dev_seeds/settings.rb +++ b/db/dev_seeds/settings.rb @@ -1,86 +1,27 @@ section "Creating Settings" do - Setting.create(key: "official_level_1_name", - value: I18n.t("seeds.settings.official_level_1_name")) - Setting.create(key: "official_level_2_name", - value: I18n.t("seeds.settings.official_level_2_name")) - Setting.create(key: "official_level_3_name", - value: I18n.t("seeds.settings.official_level_3_name")) - Setting.create(key: "official_level_4_name", - value: I18n.t("seeds.settings.official_level_4_name")) - Setting.create(key: "official_level_5_name", - value: I18n.t("seeds.settings.official_level_5_name")) - Setting.create(key: "max_ratio_anon_votes_on_debates", value: "50") - Setting.create(key: "max_votes_for_debate_edit", value: "1000") - Setting.create(key: "max_votes_for_proposal_edit", value: "1000") - Setting.create(key: "proposal_code_prefix", value: "MAD") - Setting.create(key: "votes_for_proposal_success", value: "100") - Setting.create(key: "months_to_archive_proposals", value: "12") - Setting.create(key: "comments_body_max_length", value: "1000") + Setting.reset_defaults - Setting.create(key: "twitter_handle", value: "@consul_dev") - Setting.create(key: "twitter_hashtag", value: "#consul_dev") - Setting.create(key: "facebook_handle", value: "CONSUL") - Setting.create(key: "youtube_handle", value: "CONSUL") - Setting.create(key: "telegram_handle", value: "CONSUL") - Setting.create(key: "instagram_handle", value: "CONSUL") - Setting.create(key: "url", value: "http://localhost:3000") - Setting.create(key: "org_name", value: "CONSUL") - - Setting.create(key: "process.debates", value: "true") - Setting.create(key: "process.proposals", value: "true") - Setting.create(key: "process.polls", value: "true") - Setting.create(key: "process.budgets", value: "true") - Setting.create(key: "process.legislation", value: "true") - - Setting.create(key: "feature.featured_proposals", value: "true") - - Setting.create(key: "feature.twitter_login", value: "true") - 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.user.recommendations", value: "true") - Setting.create(key: "feature.user.recommendations_on_debates", value: "true") - Setting.create(key: "feature.user.recommendations_on_proposals", value: "true") - Setting.create(key: "feature.user.skip_verification", value: "true") - Setting.create(key: "feature.community", value: "true") - Setting.create(key: "feature.map", value: "true") - Setting.create(key: "feature.allow_images", value: "true") - Setting.create(key: "feature.allow_attached_documents", value: "true") - Setting.create(key: "feature.public_stats", value: "true") - Setting.create(key: "feature.help_page", value: "true") - - Setting.create(key: "html.per_page_code_head", value: "") - Setting.create(key: "html.per_page_code_body", value: "") - - Setting.create(key: "comments_body_max_length", value: "1000") - Setting.create(key: "mailer_from_name", value: "CONSUL") - Setting.create(key: "mailer_from_address", value: "noreply@consul.dev") - Setting.create(key: "meta_title", value: "CONSUL") - Setting.create(key: "meta_description", value: "Citizen participation tool for an open, "\ - "transparent and democratic government") - Setting.create(key: "meta_keywords", value: "citizen participation, open government") - Setting.create(key: "min_age_to_participate", value: "16") - Setting.create(key: "map.latitude", value: 40.4332002) - Setting.create(key: "map.longitude", value: -3.7009591) - Setting.create(key: "map.zoom", value: 10) - - Setting.create(key: "featured_proposals_number", value: 3) - Setting.create(key: "proposal_notification_minimum_interval_in_days", value: 0) - Setting.create(key: "direct_message_max_per_day", value: 3) - - Setting.create(key: "related_content_score_threshold", value: -0.3) - Setting.create(key: "hot_score_period_in_days", value: 31) - - Setting.create(key: "homepage.widgets.feeds.proposals", value: "true") - Setting.create(key: "homepage.widgets.feeds.debates", value: "true") - Setting.create(key: "homepage.widgets.feeds.processes", value: "true") - - Setting.create(key: "proposals.successful_proposal_id", value: nil) - Setting.create(key: "proposals.poll_short_title", value: nil) - Setting.create(key: "proposals.poll_description", value: nil) - Setting.create(key: "proposals.poll_link", value: nil) - Setting.create(key: "proposals.email_short_title", value: nil) - Setting.create(key: "proposals.email_description", value: nil) - - Setting.create(key: "dashboard.emails", value: nil) + { + "facebook_handle": "CONSUL", + "feature.featured_proposals": "true", + "feature.map": "true", + "instagram_handle": "CONSUL", + "mailer_from_address": "noreply@consul.dev", + "mailer_from_name": "CONSUL", + "meta_description": "Citizen participation tool for an open, "\ + "transparent and democratic government", + "meta_keywords": "citizen participation, open government", + "meta_title": "CONSUL", + "org_name": "CONSUL", + "proposal_code_prefix": "MAD", + "proposal_notification_minimum_interval_in_days": 0, + "telegram_handle": "CONSUL", + "twitter_handle": "@consul_dev", + "twitter_hashtag": "#consul_dev", + "url": "http://localhost:3000", + "votes_for_proposal_success": "100", + "youtube_handle": "CONSUL" + }.each do |name, value| + Setting[name] = value + end end diff --git a/db/migrate/20190314150724_add_description_to_administrator.rb b/db/migrate/20190314150724_add_description_to_administrator.rb new file mode 100644 index 000000000..262a25b23 --- /dev/null +++ b/db/migrate/20190314150724_add_description_to_administrator.rb @@ -0,0 +1,5 @@ +class AddDescriptionToAdministrator < ActiveRecord::Migration[4.2] + def change + add_column :administrators, :description, :string + end +end diff --git a/db/migrate/20190410132842_add_selected_to_proposal.rb b/db/migrate/20190410132842_add_selected_to_proposal.rb index a97d6619c..3a20fefbf 100644 --- a/db/migrate/20190410132842_add_selected_to_proposal.rb +++ b/db/migrate/20190410132842_add_selected_to_proposal.rb @@ -1,4 +1,4 @@ -class AddSelectedToProposal < ActiveRecord::Migration +class AddSelectedToProposal < ActiveRecord::Migration[4.2] def change add_column :proposals, :selected, :bool, default: false, index: true end diff --git a/db/migrate/20190423070619_add_people_proposals_phase_to_legislation_processes.rb b/db/migrate/20190423070619_add_people_proposals_phase_to_legislation_processes.rb new file mode 100644 index 000000000..d55947330 --- /dev/null +++ b/db/migrate/20190423070619_add_people_proposals_phase_to_legislation_processes.rb @@ -0,0 +1,7 @@ +class AddPeopleProposalsPhaseToLegislationProcesses < ActiveRecord::Migration[4.2] + def change + add_column :legislation_processes, :people_proposals_phase_start_date, :date + add_column :legislation_processes, :people_proposals_phase_end_date, :date + add_column :legislation_processes, :people_proposals_phase_enabled, :boolean + end +end diff --git a/db/migrate/20190423072214_create_legislation_people_proposals_table.rb b/db/migrate/20190423072214_create_legislation_people_proposals_table.rb new file mode 100644 index 000000000..01b4f2cae --- /dev/null +++ b/db/migrate/20190423072214_create_legislation_people_proposals_table.rb @@ -0,0 +1,35 @@ +class CreateLegislationPeopleProposalsTable < ActiveRecord::Migration[4.2] + def change + create_table :legislation_people_proposals, force: :cascade do |t| + t.integer "legislation_process_id" + t.string "title", limit: 80 + t.text "description" + t.string "question" + t.integer "author_id" + t.datetime "hidden_at" + t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" + t.integer "cached_votes_up", default: 0 + t.integer "comments_count", default: 0 + t.datetime "confirmed_hide_at" + t.integer "hot_score", limit: 8, default: 0 + t.integer "confidence_score", default: 0 + t.string "responsible_name", limit: 60 + t.text "summary" + t.string "video_url" + t.tsvector "tsv" + t.datetime "retired_at" + t.string "retired_reason" + t.text "retired_explanation" + t.integer "community_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "cached_votes_total", default: 0 + t.integer "cached_votes_down", default: 0 + t.boolean "selected" + t.boolean "validated" + t.integer "cached_votes_score", default: 0 + end + add_index "legislation_people_proposals", ["cached_votes_score"], name: "index_legislation_people_proposals_on_cached_votes_score", using: :btree + end +end diff --git a/db/migrate/20190426072056_add_contact_fields_to_legislation_people_proposals.rb b/db/migrate/20190426072056_add_contact_fields_to_legislation_people_proposals.rb new file mode 100644 index 000000000..93a901265 --- /dev/null +++ b/db/migrate/20190426072056_add_contact_fields_to_legislation_people_proposals.rb @@ -0,0 +1,11 @@ +class AddContactFieldsToLegislationPeopleProposals < ActiveRecord::Migration[4.2] + def change + add_column :legislation_people_proposals, :email, :string + add_column :legislation_people_proposals, :phone, :string + add_column :legislation_people_proposals, :twitter, :string + add_column :legislation_people_proposals, :facebook, :string + add_column :legislation_people_proposals, :instagram, :string + add_column :legislation_people_proposals, :youtube, :string + add_column :legislation_people_proposals, :website, :string + end +end diff --git a/db/migrate/20190607160900_remove_question_from_legislation_people_proposal.rb b/db/migrate/20190607160900_remove_question_from_legislation_people_proposal.rb new file mode 100644 index 000000000..c43e63933 --- /dev/null +++ b/db/migrate/20190607160900_remove_question_from_legislation_people_proposal.rb @@ -0,0 +1,5 @@ +class RemoveQuestionFromLegislationPeopleProposal < ActiveRecord::Migration[5.0] + def change + remove_column :legislation_people_proposals, :question, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index ea2c98be3..fed6d8263 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190429125842) do +ActiveRecord::Schema.define(version: 20190607160900) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -67,6 +67,7 @@ ActiveRecord::Schema.define(version: 20190429125842) do create_table "administrators", force: :cascade do |t| t.integer "user_id" + t.string "description" t.index ["user_id"], name: "index_administrators_on_user_id", using: :btree end @@ -666,6 +667,44 @@ ActiveRecord::Schema.define(version: 20190429125842) do t.index ["status"], name: "index_legislation_draft_versions_on_status", using: :btree end + create_table "legislation_people_proposals", force: :cascade do |t| + t.integer "legislation_process_id" + t.string "title", limit: 80 + t.text "description" + t.integer "author_id" + t.datetime "hidden_at" + t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" + t.integer "cached_votes_up", default: 0 + t.integer "comments_count", default: 0 + t.datetime "confirmed_hide_at" + t.bigint "hot_score", default: 0 + t.integer "confidence_score", default: 0 + t.string "responsible_name", limit: 60 + t.text "summary" + t.string "video_url" + t.tsvector "tsv" + t.datetime "retired_at" + t.string "retired_reason" + t.text "retired_explanation" + t.integer "community_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "cached_votes_total", default: 0 + t.integer "cached_votes_down", default: 0 + t.boolean "selected" + t.boolean "validated" + t.integer "cached_votes_score", default: 0 + t.string "email" + t.string "phone" + t.string "twitter" + t.string "facebook" + t.string "instagram" + t.string "youtube" + t.string "website" + t.index ["cached_votes_score"], name: "index_legislation_people_proposals_on_cached_votes_score", using: :btree + end + create_table "legislation_process_translations", force: :cascade do |t| t.integer "legislation_process_id", null: false t.string "locale", null: false @@ -694,24 +733,27 @@ ActiveRecord::Schema.define(version: 20190429125842) do t.date "allegations_end_date" t.date "result_publication_date" t.datetime "hidden_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "summary" - t.boolean "debate_phase_enabled", default: false - t.boolean "allegations_phase_enabled", default: false - t.boolean "draft_publication_enabled", default: false - t.boolean "result_publication_enabled", default: false - t.boolean "published", default: true + t.boolean "debate_phase_enabled", default: false + t.boolean "allegations_phase_enabled", default: false + t.boolean "draft_publication_enabled", default: false + t.boolean "result_publication_enabled", default: false + t.boolean "published", default: true t.date "proposals_phase_start_date" t.date "proposals_phase_end_date" t.boolean "proposals_phase_enabled" t.text "proposals_description" t.date "draft_start_date" t.date "draft_end_date" - t.boolean "draft_phase_enabled", default: false - t.boolean "homepage_enabled", default: false + t.boolean "draft_phase_enabled", default: false + t.boolean "homepage_enabled", default: false t.text "background_color" t.text "font_color" + t.date "people_proposals_phase_start_date" + t.date "people_proposals_phase_end_date" + t.boolean "people_proposals_phase_enabled" t.index ["allegations_end_date"], name: "index_legislation_processes_on_allegations_end_date", using: :btree t.index ["allegations_start_date"], name: "index_legislation_processes_on_allegations_start_date", using: :btree t.index ["debate_end_date"], name: "index_legislation_processes_on_debate_end_date", using: :btree diff --git a/db/seeds.rb b/db/seeds.rb index 20bc74e08..37599fd0c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,117 +7,7 @@ if Administrator.count == 0 && !Rails.env.test? admin.create_administrator end -# Names for the moderation console, as a hint for moderators -# to know better how to assign users with official positions -Setting["official_level_1_name"] = "Cargo oficial 1" -Setting["official_level_2_name"] = "Cargo oficial 2" -Setting["official_level_3_name"] = "Cargo oficial 3" -Setting["official_level_4_name"] = "Cargo oficial 4" -Setting["official_level_5_name"] = "Cargo oficial 5" - -# Max percentage of allowed anonymous votes on a debate -Setting["max_ratio_anon_votes_on_debates"] = 50 - -# Max votes where a debate is still editable -Setting["max_votes_for_debate_edit"] = 1000 - -# Max votes where a proposal is still editable -Setting["max_votes_for_proposal_edit"] = 1000 - -# Max length for comments -Setting["comments_body_max_length"] = 1000 - -# Prefix for the Proposal codes -Setting["proposal_code_prefix"] = "CONSUL" - -# Number of votes needed for proposal success -Setting["votes_for_proposal_success"] = 53726 - -# Months to archive proposals -Setting["months_to_archive_proposals"] = 12 - -# Users with this email domain will automatically be marked as level 1 officials -# Emails under the domain's subdomains will also be included -Setting["email_domain_for_officials"] = "" - -# Code to be included at the top (inside ) of every page (useful for tracking) -Setting["html.per_page_code_head"] = "" - -# Code to be included at the top (inside ) of every page -Setting["html.per_page_code_body"] = "" - -# Social settings -Setting["twitter_handle"] = nil -Setting["twitter_hashtag"] = nil -Setting["facebook_handle"] = nil -Setting["youtube_handle"] = nil -Setting["telegram_handle"] = nil -Setting["instagram_handle"] = nil - -# Public-facing URL of the app. -Setting["url"] = "http://example.com" - -# CONSUL installation's organization name -Setting["org_name"] = "CONSUL" - -# Meta tags for SEO -Setting["meta_title"] = nil -Setting["meta_description"] = nil -Setting["meta_keywords"] = nil - -# Processes -Setting["process.debates"] = true -Setting["process.proposals"] = true -Setting["process.polls"] = true -Setting["process.budgets"] = true -Setting["process.legislation"] = true - -# Feature flags -Setting["feature.featured_proposals"] = nil -Setting["feature.twitter_login"] = true -Setting["feature.facebook_login"] = true -Setting["feature.google_login"] = true -Setting["feature.public_stats"] = true -Setting["feature.signature_sheets"] = true -Setting["feature.user.recommendations"] = true -Setting["feature.user.recommendations_on_debates"] = true -Setting["feature.user.recommendations_on_proposals"] = true -Setting["feature.user.skip_verification"] = "true" -Setting["feature.community"] = true -Setting["feature.map"] = nil -Setting["feature.allow_images"] = true -Setting["feature.allow_attached_documents"] = true -Setting["feature.help_page"] = true - -# Proposal notifications -Setting["proposal_notification_minimum_interval_in_days"] = 3 -Setting["direct_message_max_per_day"] = 3 - -# Email settings -Setting["mailer_from_name"] = "CONSUL" -Setting["mailer_from_address"] = "noreply@consul.dev" - -# Verification settings -Setting["min_age_to_participate"] = 16 - -# Featured proposals -Setting["featured_proposals_number"] = 3 - -# City map feature default configuration (Greenwich) -Setting["map.latitude"] = 51.48 -Setting["map.longitude"] = 0.0 -Setting["map.zoom"] = 10 - -# Related content -Setting["related_content_score_threshold"] = -0.3 - -# Homepage -Setting["homepage.widgets.feeds.proposals"] = true -Setting["homepage.widgets.feeds.debates"] = true -Setting["homepage.widgets.feeds.processes"] = true - -# Votes hot_score configuration -Setting["hot_score_period_in_days"] = 31 +Setting.reset_defaults WebSection.create(name: "homepage") WebSection.create(name: "debates") @@ -125,18 +15,5 @@ WebSection.create(name: "proposals") WebSection.create(name: "budgets") WebSection.create(name: "help_page") -# Proposals -Setting["proposals.successful_proposal_id"] = nil -Setting["proposals.poll_short_title"] = nil -Setting["proposals.poll_description"] = nil -Setting["proposals.poll_link"] = nil -Setting["proposals.email_short_title"] = nil -Setting["proposals.email_description"] = nil -Setting["proposals.poster_short_title"] = nil -Setting["proposals.poster_description"] = nil - -# Dashboard -Setting["dashboard.emails"] = nil - # Default custom pages load Rails.root.join("db", "pages.rb") diff --git a/lib/application_logger.rb b/lib/application_logger.rb new file mode 100644 index 000000000..a9f953f48 --- /dev/null +++ b/lib/application_logger.rb @@ -0,0 +1,11 @@ +class ApplicationLogger + def info(message) + logger.info(message) + end + + def logger + @logger ||= Logger.new(STDOUT).tap do |logger| + logger.formatter = proc { |severity, _datetime, _progname, msg| "#{severity} #{msg}\n" } + end + end +end diff --git a/lib/evaluation_comment_email.rb b/lib/evaluation_comment_email.rb new file mode 100644 index 000000000..6321e43dd --- /dev/null +++ b/lib/evaluation_comment_email.rb @@ -0,0 +1,31 @@ +class EvaluationCommentEmail + attr_reader :comment + + def initialize(comment) + @comment = comment + end + + def commentable + comment.commentable + end + + def to + @to ||= related_users + end + + def subject + I18n.t("mailers.evaluation_comment.subject") + end + + def can_be_sent? + commentable.present? && to.any? + end + + private + def related_users + return [] if comment.commentable.nil? + comment.commentable + .admin_and_valuator_users_associated + .reject { |associated_user| associated_user.user == comment.author } + end +end diff --git a/lib/tasks/budgets.rake b/lib/tasks/budgets.rake index c53c71103..d9fc8f50f 100644 --- a/lib/tasks/budgets.rake +++ b/lib/tasks/budgets.rake @@ -1,4 +1,14 @@ namespace :budgets do + desc "Regenerate ballot_lines_count cache" + task calculate_ballot_lines: :environment do + ApplicationLogger.new.info "Calculating ballot lines" + + Budget::Ballot.find_each.with_index do |ballot, index| + Budget::Ballot.reset_counters ballot.id, :lines + print "." if (index % 10_000).zero? + end + end + namespace :email do desc "Sends emails to authors of selected investments" diff --git a/lib/tasks/consul.rake b/lib/tasks/consul.rake new file mode 100644 index 000000000..acaa3686a --- /dev/null +++ b/lib/tasks/consul.rake @@ -0,0 +1,13 @@ +namespace :consul do + desc "Runs tasks needed to upgrade to the latest version" + task execute_release_tasks: "execute_release_1.0.0_tasks" + + desc "Runs tasks needed to upgrade from 1.0.0-beta to 1.0.0" + task "execute_release_1.0.0_tasks": [ + "poll:generate_slugs", + "stats_and_results:migrate_to_reports", + "budgets:calculate_ballot_lines", + "settings:remove_deprecated_settings", + "stats:generate" + ] +end diff --git a/lib/tasks/polls.rake b/lib/tasks/polls.rake index 0e689e00c..2bd16c46f 100644 --- a/lib/tasks/polls.rake +++ b/lib/tasks/polls.rake @@ -1,6 +1,8 @@ namespace :poll do desc "Generate slugs polls" task generate_slugs: :environment do + ApplicationLogger.new.info "Generating poll slugs" + Poll.find_each do |poll| poll.update_columns(slug: poll.generate_slug, updated_at: Time.current) if poll.generate_slug? end diff --git a/lib/tasks/regenerate_ballot_lines_cache.rake b/lib/tasks/regenerate_ballot_lines_cache.rake deleted file mode 100644 index d008ec556..000000000 --- a/lib/tasks/regenerate_ballot_lines_cache.rake +++ /dev/null @@ -1,8 +0,0 @@ -namespace :budgets do - desc "Regenerate ballot_lines_count cache" - task calculate_ballot_lines: :environment do - Budget::Ballot.find_each do |ballot| - Budget::Ballot.reset_counters ballot.id, :lines - end - end -end diff --git a/lib/tasks/settings.rake b/lib/tasks/settings.rake index 286c3e85c..5c4a4db19 100644 --- a/lib/tasks/settings.rake +++ b/lib/tasks/settings.rake @@ -2,6 +2,8 @@ namespace :settings do desc "Remove deprecated settings" task remove_deprecated_settings: :environment do + ApplicationLogger.new.info "Removing deprecated settings" + deprecated_keys = [ "place_name", "banner-style.banner-style-one", @@ -46,4 +48,8 @@ namespace :settings do Setting.rename_key from: "feature.homepage.widgets.feeds.processes", to: "homepage.widgets.feeds.processes" end + desc "Add new settings" + task add_new_settings: :environment do + Setting.add_new_settings + end end diff --git a/lib/tasks/stats.rake b/lib/tasks/stats.rake index a454d88cd..2d68613c5 100644 --- a/lib/tasks/stats.rake +++ b/lib/tasks/stats.rake @@ -1,6 +1,8 @@ namespace :stats do desc "Generates stats which are not cached yet" task generate: :environment do + ApplicationLogger.new.info "Updating budget and poll stats" + Budget.find_each do |budget| Budget::Stats.new(budget).generate print "." diff --git a/lib/tasks/stats_and_results.rake b/lib/tasks/stats_and_results.rake index 5aa8f7e39..bc4cdacca 100644 --- a/lib/tasks/stats_and_results.rake +++ b/lib/tasks/stats_and_results.rake @@ -1,6 +1,7 @@ namespace :stats_and_results do desc "Migrates stats_enabled and results_enabled data to enabled reports" task migrate_to_reports: :environment do + ApplicationLogger.new.info "Migrating stats and results" Migrations::Reports.new.migrate end end diff --git a/spec/controllers/budgets/ballots/lines_controller_spec.rb b/spec/controllers/budgets/ballots/lines_controller_spec.rb new file mode 100644 index 000000000..641a90a95 --- /dev/null +++ b/spec/controllers/budgets/ballots/lines_controller_spec.rb @@ -0,0 +1,25 @@ +require "rails_helper" + +describe Budgets::Ballot::LinesController do + + describe "#load_budget" do + + it "raises an error if budget slug is not found" do + controller.params[:budget_id] = "wrong_budget" + + expect do + controller.send(:load_budget) + end.to raise_error ActiveRecord::RecordNotFound + end + + it "raises an error if budget id is not found" do + controller.params[:budget_id] = 0 + + expect do + controller.send(:load_budget) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + +end diff --git a/spec/factories/budgets.rb b/spec/factories/budgets.rb index fc60927cc..11645104f 100644 --- a/spec/factories/budgets.rb +++ b/spec/factories/budgets.rb @@ -166,6 +166,10 @@ FactoryBot.define do trait :with_confirmed_hide do confirmed_hide_at { Time.current } end + + trait :with_milestone_tags do + after(:create) { |investment| investment.milestone_tags << create(:tag, :milestone) } + end end factory :budget_phase, class: "Budget::Phase" do diff --git a/spec/factories/classifications.rb b/spec/factories/classifications.rb index e905ea3d4..e8721a50c 100644 --- a/spec/factories/classifications.rb +++ b/spec/factories/classifications.rb @@ -5,6 +5,10 @@ FactoryBot.define do trait :category do kind "category" end + + trait :milestone do + kind "milestone" + end end factory :tagging, class: "ActsAsTaggableOn::Tagging" do diff --git a/spec/factories/legislations.rb b/spec/factories/legislations.rb index d9b1f74e5..b7014d18f 100644 --- a/spec/factories/legislations.rb +++ b/spec/factories/legislations.rb @@ -13,10 +13,13 @@ FactoryBot.define do allegations_end_date { Date.current + 3.days } proposals_phase_start_date { Date.current } proposals_phase_end_date { Date.current + 2.days } + people_proposals_phase_start_date { Date.current } + people_proposals_phase_end_date { Date.current + 2.days } result_publication_date { Date.current + 5.days } debate_phase_enabled true allegations_phase_enabled true proposals_phase_enabled true + people_proposals_phase_enabled true draft_publication_enabled true result_publication_enabled true published true @@ -63,6 +66,18 @@ FactoryBot.define do proposals_phase_enabled true end + trait :in_people_proposals_phase do + people_proposals_phase_start_date { Date.current - 1.day } + people_proposals_phase_end_date { Date.current + 2.days } + people_proposals_phase_enabled true + end + + trait :upcoming_people_proposals_phase do + people_proposals_phase_start_date { Date.current + 1.day } + people_proposals_phase_end_date { Date.current + 2.days } + people_proposals_phase_enabled true + end + trait :published do published true end @@ -86,15 +101,21 @@ FactoryBot.define do allegations_end_date nil proposals_phase_start_date nil proposals_phase_end_date nil + people_proposals_phase_start_date nil + people_proposals_phase_end_date nil result_publication_date nil debate_phase_enabled false allegations_phase_enabled false proposals_phase_enabled false + people_proposals_phase_enabled false draft_publication_enabled false result_publication_enabled false published true end + trait :with_milestone_tags do + after(:create) { |legislation| legislation.milestone_tags << create(:tag, :milestone) } + end end factory :legislation_draft_version, class: "Legislation::DraftVersion" do @@ -186,4 +207,26 @@ LOREM_IPSUM ancestry nil end + factory :legislation_people_proposal, class: "Legislation::PeopleProposal" do + sequence(:title) { |n| "People and group #{n} for a legislation" } + summary "This law should be implemented by..." + terms_of_service "1" + process factory: :legislation_process + author factory: :user + validated false + + trait :with_contact_info do + email "proposal@test.com" + website "https://proposal.io" + phone "666666666" + facebook "facebook.id" + twitter "TwitterId" + youtube "youtubechannelid" + instagram "instagramid" + end + + trait :validated do + validated true + end + end end diff --git a/spec/factories/polls.rb b/spec/factories/polls.rb index b7a532d82..7b49d3f5a 100644 --- a/spec/factories/polls.rb +++ b/spec/factories/polls.rb @@ -94,7 +94,6 @@ FactoryBot.define do transient { budget nil } poll { budget&.poll || association(:poll, budget: budget) } - trait :from_web do origin "web" token SecureRandom.hex(32) diff --git a/spec/factories/proposals.rb b/spec/factories/proposals.rb index 7cbd44acb..92a925627 100644 --- a/spec/factories/proposals.rb +++ b/spec/factories/proposals.rb @@ -67,6 +67,10 @@ FactoryBot.define do trait :published do published_at { Time.current } end + + trait :with_milestone_tags do + after(:create) { |proposal| proposal.milestone_tags << create(:tag, :milestone) } + end end factory :proposal_notification do diff --git a/spec/features/admin/activity_spec.rb b/spec/features/admin/activity_spec.rb index 113062427..891e59947 100644 --- a/spec/features/admin/activity_spec.rb +++ b/spec/features/admin/activity_spec.rb @@ -117,7 +117,7 @@ describe "Admin activity" do scenario "Shows admin restores" do debate = create(:debate, :hidden) - visit admin_debates_path + visit admin_hidden_debates_path within("#debate_#{debate.id}") do click_on "Restore" @@ -181,7 +181,7 @@ describe "Admin activity" do scenario "Shows admin restores" do comment = create(:comment, :hidden) - visit admin_comments_path + visit admin_hidden_comments_path within("#comment_#{comment.id}") do click_on "Restore" diff --git a/spec/features/admin/administrators_spec.rb b/spec/features/admin/administrators_spec.rb index a782e39d3..3ba76eaac 100644 --- a/spec/features/admin/administrators_spec.rb +++ b/spec/features/admin/administrators_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" describe "Admin administrators" do let!(:admin) { create(:administrator) } let!(:user) { create(:user, username: "Jose Luis Balbin") } - let!(:user_administrator) { create(:administrator) } + let!(:user_administrator) { create(:administrator, description: "admin_alias") } before do login_as(admin.user) @@ -14,6 +14,7 @@ describe "Admin administrators" do expect(page).to have_content user_administrator.id expect(page).to have_content user_administrator.name expect(page).to have_content user_administrator.email + expect(page).to have_content user_administrator.description expect(page).not_to have_content user.name end @@ -100,4 +101,19 @@ describe "Admin administrators" do end end + context "Edit" do + let!(:administrator1) { create(:administrator, user: create(:user, + username: "Bernard Sumner", + email: "bernard@sumner.com")) } + + scenario "admin can edit administrator1" do + visit(edit_admin_administrator_path(administrator1)) + fill_in "administrator_description", with: "Admin Alias" + click_button "Update Administrator" + + expect(page).to have_content("Administrator updated successfully") + expect(page).to have_content("Admin Alias") + end + end + end diff --git a/spec/features/admin/budget_groups_spec.rb b/spec/features/admin/budget_groups_spec.rb index e0b7e3132..cc0c75d62 100644 --- a/spec/features/admin/budget_groups_spec.rb +++ b/spec/features/admin/budget_groups_spec.rb @@ -32,6 +32,43 @@ describe "Admin budget groups" do end + context "Load" do + + let!(:budget) { create(:budget, slug: "budget_slug") } + let!(:group) { create(:budget_group, slug: "group_slug", budget: budget) } + + scenario "finds budget and group by slug" do + visit edit_admin_budget_group_path("budget_slug", "group_slug") + expect(page).to have_content(budget.name) + expect(page).to have_field "Group name", with: group.name + end + + scenario "raises an error if budget slug is not found" do + expect do + visit edit_admin_budget_group_path("wrong_budget", group) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit edit_admin_budget_group_path(0, group) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if group slug is not found" do + expect do + visit edit_admin_budget_group_path(budget, "wrong_group") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if group id is not found" do + expect do + visit edit_admin_budget_group_path(budget, 0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + context "Index" do scenario "Displaying no groups for budget" do diff --git a/spec/features/admin/budget_headings_spec.rb b/spec/features/admin/budget_headings_spec.rb index b4fd06db7..6b633058d 100644 --- a/spec/features/admin/budget_headings_spec.rb +++ b/spec/features/admin/budget_headings_spec.rb @@ -33,6 +33,56 @@ describe "Admin budget headings" do end + context "Load" do + + let!(:budget) { create(:budget, slug: "budget_slug") } + let!(:group) { create(:budget_group, slug: "group_slug", budget: budget) } + let!(:heading) { create(:budget_heading, slug: "heading_slug", group: group) } + + scenario "finds budget, group and heading by slug" do + visit edit_admin_budget_group_heading_path("budget_slug", "group_slug", "heading_slug") + expect(page).to have_content(budget.name) + expect(page).to have_content(group.name) + expect(page).to have_field "Heading name", with: heading.name + end + + scenario "raises an error if budget slug is not found" do + expect do + visit edit_admin_budget_group_heading_path("wrong_budget", group, heading) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit edit_admin_budget_group_heading_path(0, group, heading) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if group slug is not found" do + expect do + visit edit_admin_budget_group_heading_path(budget, "wrong_group", heading) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if group id is not found" do + expect do + visit edit_admin_budget_group_heading_path(budget, 0, heading) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if heading slug is not found" do + expect do + visit edit_admin_budget_group_heading_path(budget, group, "wrong_heading") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if heading id is not found" do + expect do + visit edit_admin_budget_group_heading_path(budget, group, 0) + end.to raise_error ActiveRecord::RecordNotFound + end + end + context "Index" do scenario "Displaying no headings for group" do diff --git a/spec/features/admin/budget_investments_spec.rb b/spec/features/admin/budget_investments_spec.rb index 8dd9743d3..8e870fa5a 100644 --- a/spec/features/admin/budget_investments_spec.rb +++ b/spec/features/admin/budget_investments_spec.rb @@ -32,6 +32,34 @@ describe "Admin budget investments" do end + context "Load" do + + let(:group) { create(:budget_group, budget: budget) } + let(:heading) { create(:budget_heading, group: group) } + let!(:investment) { create(:budget_investment, heading: heading) } + + before { budget.update(slug: "budget_slug") } + + scenario "finds investments using budget slug" do + visit admin_budget_budget_investments_path("budget_slug") + + expect(page).to have_link investment.title + end + + scenario "raises an error if budget slug is not found" do + expect do + visit admin_budget_budget_investments_path("wrong_budget", investment) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit admin_budget_budget_investments_path(0, investment) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + context "Index" do scenario "Displaying investments" do @@ -149,8 +177,9 @@ describe "Admin budget investments" do scenario "Filtering by admin", :js do user = create(:user, username: "Admin 1") + user2 = create(:user, username: "Admin 2") administrator = create(:administrator, user: user) - + create(:administrator, user: user2, description: "Alias") create(:budget_investment, title: "Realocate visitors", budget: budget, administrator: administrator) create(:budget_investment, title: "Destroy the city", budget: budget) @@ -166,6 +195,13 @@ describe "Admin budget investments" do expect(page).not_to have_link("Destroy the city") expect(page).to have_link("Realocate visitors") + select "Alias", from: "administrator_id" + click_button "Filter" + + expect(page).to have_content("There are no investment projects") + expect(page).not_to have_link("Destroy the city") + expect(page).not_to have_link("Realocate visitors") + select "All administrators", from: "administrator_id" click_button "Filter" @@ -1066,12 +1102,12 @@ describe "Admin budget investments" do scenario "Add administrator" do budget_investment = create(:budget_investment) user = create(:user, username: "Marta", email: "marta@admins.org") - create(:administrator, user: user) + create(:administrator, user: user, description: "Marta desc") visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) click_link "Edit classification" - select "Marta (marta@admins.org)", from: "budget_investment[administrator_id]" + select "Marta desc (marta@admins.org)", from: "budget_investment[administrator_id]" click_button "Update" expect(page).to have_content "Investment project updated succesfully." @@ -1295,6 +1331,23 @@ describe "Admin budget investments" do expect(page).to have_content "can't be blank" end + scenario "Add milestone tags" do + budget_investment = create(:budget_investment) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + + expect(page).not_to have_content("Milestone Tags:") + + click_link "Edit classification" + + fill_in "budget_investment_milestone_tag_list", with: "tag1, tag2" + + click_button "Update" + + expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content("Milestone Tags: tag1, tag2") + end + end context "Selecting" do diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index e7c99cfb4..912262afc 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -24,6 +24,29 @@ describe "Admin budgets" do end + context "Load" do + + let!(:budget) { create(:budget, slug: "budget_slug") } + + scenario "finds budget by slug" do + visit admin_budget_path("budget_slug") + expect(page).to have_content(budget.name) + end + + scenario "raises an error if budget slug is not found" do + expect do + visit admin_budget_path("wrong_budget") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit admin_budget_path(0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + context "Index" do scenario "Displaying no open budgets text" do diff --git a/spec/features/admin/comments_spec.rb b/spec/features/admin/comments_spec.rb index 5953e5252..7806df96e 100644 --- a/spec/features/admin/comments_spec.rb +++ b/spec/features/admin/comments_spec.rb @@ -12,7 +12,7 @@ describe "Admin comments" do proposal = create(:proposal, author: comment.author) create(:comment, commentable: proposal, user: comment.author, body: "Good Proposal!") - visit admin_comments_path + visit admin_hidden_comments_path expect(page).to have_content("SPAM from SPAMMER") expect(page).not_to have_content("Good Proposal!") @@ -21,7 +21,7 @@ describe "Admin comments" do click_link "Hide author" end - visit admin_comments_path + visit admin_hidden_comments_path expect(page).not_to have_content("SPAM from SPAMMER") expect(page).not_to have_content("Good Proposal!") end @@ -32,7 +32,7 @@ describe "Admin comments" do create(:comment, :hidden, commentable: debate, body: "This is SPAM comment on debate") create(:comment, :hidden, commentable: proposal, body: "This is SPAM comment on proposal") - visit admin_comments_path + visit admin_hidden_comments_path expect(page).to have_content("Debate with spam comment") expect(page).to have_content("Proposal with spam comment") @@ -43,7 +43,7 @@ describe "Admin comments" do expect(page).to have_content("Debate with spam comment") expect(page).not_to have_content("This is SPAM comment on debate") - visit admin_comments_path + visit admin_hidden_comments_path click_link "Proposal with spam comment" expect(page).to have_content("Proposal with spam comment") @@ -56,7 +56,7 @@ describe "Admin comments" do create(:comment, :hidden, commentable: debate, body: "This is SPAM comment on debate") create(:comment, :hidden, commentable: proposal, body: "This is SPAM comment on proposal") - visit admin_comments_path + visit admin_hidden_comments_path expect(page).to have_content("(Hidden proposal: Hidden proposal title)") expect(page).to have_content("(Hidden debate: Hidden debate title)") @@ -67,7 +67,7 @@ describe "Admin comments" do scenario "Restore" do comment = create(:comment, :hidden, body: "Not really SPAM") - visit admin_comments_path + visit admin_hidden_comments_path click_link "Restore" @@ -79,7 +79,7 @@ describe "Admin comments" do scenario "Confirm hide" do comment = create(:comment, :hidden, body: "SPAM") - visit admin_comments_path + visit admin_hidden_comments_path click_link "Confirm moderation" @@ -91,22 +91,22 @@ describe "Admin comments" do end scenario "Current filter is properly highlighted" do - visit admin_comments_path + visit admin_hidden_comments_path expect(page).not_to have_link("Pending") expect(page).to have_link("All") expect(page).to have_link("Confirmed") - visit admin_comments_path(filter: "Pending") + visit admin_hidden_comments_path(filter: "Pending") expect(page).not_to have_link("Pending") expect(page).to have_link("All") expect(page).to have_link("Confirmed") - visit admin_comments_path(filter: "all") + visit admin_hidden_comments_path(filter: "all") expect(page).to have_link("Pending") expect(page).not_to have_link("All") expect(page).to have_link("Confirmed") - visit admin_comments_path(filter: "with_confirmed_hide") + visit admin_hidden_comments_path(filter: "with_confirmed_hide") expect(page).to have_link("Pending") expect(page).to have_link("All") expect(page).not_to have_link("Confirmed") @@ -116,11 +116,11 @@ describe "Admin comments" do create(:comment, :hidden, body: "Unconfirmed comment") create(:comment, :hidden, :with_confirmed_hide, body: "Confirmed comment") - visit admin_comments_path(filter: "all") + visit admin_hidden_comments_path(filter: "all") expect(page).to have_content("Unconfirmed comment") expect(page).to have_content("Confirmed comment") - visit admin_comments_path(filter: "with_confirmed_hide") + visit admin_hidden_comments_path(filter: "with_confirmed_hide") expect(page).not_to have_content("Unconfirmed comment") expect(page).to have_content("Confirmed comment") end @@ -129,7 +129,7 @@ describe "Admin comments" do per_page = Kaminari.config.default_per_page (per_page + 2).times { create(:comment, :hidden, :with_confirmed_hide) } - visit admin_comments_path(filter: "with_confirmed_hide", page: 2) + visit admin_hidden_comments_path(filter: "with_confirmed_hide", page: 2) click_on("Restore", match: :first, exact: true) @@ -137,4 +137,4 @@ describe "Admin comments" do expect(current_url).to include("page=2") end -end \ No newline at end of file +end diff --git a/spec/features/admin/debates_spec.rb b/spec/features/admin/debates_spec.rb index 009e1900e..2b3e683c0 100644 --- a/spec/features/admin/debates_spec.rb +++ b/spec/features/admin/debates_spec.rb @@ -7,7 +7,7 @@ describe "Admin debates" do admin = create(:administrator) login_as(admin.user) - expect{ visit admin_debates_path }.to raise_exception(FeatureFlags::FeatureDisabled) + expect{ visit admin_hidden_debates_path }.to raise_exception(FeatureFlags::FeatureDisabled) Setting["process.debates"] = true end @@ -19,7 +19,7 @@ describe "Admin debates" do scenario "Restore" do debate = create(:debate, :hidden) - visit admin_debates_path + visit admin_hidden_debates_path click_link "Restore" @@ -31,7 +31,7 @@ describe "Admin debates" do scenario "Confirm hide" do debate = create(:debate, :hidden) - visit admin_debates_path + visit admin_hidden_debates_path click_link "Confirm moderation" @@ -43,22 +43,22 @@ describe "Admin debates" do end scenario "Current filter is properly highlighted" do - visit admin_debates_path + visit admin_hidden_debates_path expect(page).not_to have_link("Pending") expect(page).to have_link("All") expect(page).to have_link("Confirmed") - visit admin_debates_path(filter: "Pending") + visit admin_hidden_debates_path(filter: "Pending") expect(page).not_to have_link("Pending") expect(page).to have_link("All") expect(page).to have_link("Confirmed") - visit admin_debates_path(filter: "all") + visit admin_hidden_debates_path(filter: "all") expect(page).to have_link("Pending") expect(page).not_to have_link("All") expect(page).to have_link("Confirmed") - visit admin_debates_path(filter: "with_confirmed_hide") + visit admin_hidden_debates_path(filter: "with_confirmed_hide") expect(page).to have_link("All") expect(page).to have_link("Pending") expect(page).not_to have_link("Confirmed") @@ -68,15 +68,15 @@ describe "Admin debates" do create(:debate, :hidden, title: "Unconfirmed debate") create(:debate, :hidden, :with_confirmed_hide, title: "Confirmed debate") - visit admin_debates_path(filter: "pending") + visit admin_hidden_debates_path(filter: "pending") expect(page).to have_content("Unconfirmed debate") expect(page).not_to have_content("Confirmed debate") - visit admin_debates_path(filter: "all") + visit admin_hidden_debates_path(filter: "all") expect(page).to have_content("Unconfirmed debate") expect(page).to have_content("Confirmed debate") - visit admin_debates_path(filter: "with_confirmed_hide") + visit admin_hidden_debates_path(filter: "with_confirmed_hide") expect(page).not_to have_content("Unconfirmed debate") expect(page).to have_content("Confirmed debate") end @@ -85,7 +85,7 @@ describe "Admin debates" do per_page = Kaminari.config.default_per_page (per_page + 2).times { create(:debate, :hidden, :with_confirmed_hide) } - visit admin_debates_path(filter: "with_confirmed_hide", page: 2) + visit admin_hidden_debates_path(filter: "with_confirmed_hide", page: 2) click_on("Restore", match: :first, exact: true) diff --git a/spec/features/admin/settings_spec.rb b/spec/features/admin/settings_spec.rb index 4800b8c3e..ef72b5d0c 100644 --- a/spec/features/admin/settings_spec.rb +++ b/spec/features/admin/settings_spec.rb @@ -98,6 +98,40 @@ describe "Admin settings" do end + describe "Update content types" do + + scenario "stores the correct mime types" do + setting = Setting.create(key: "upload.images.content_types", value: "image/png") + admin = create(:administrator).user + login_as(admin) + visit admin_settings_path + find("#images-and-documents-tab").click + + within "#edit_setting_#{setting.id}" do + expect(find("#png")).to be_checked + expect(find("#jpg")).not_to be_checked + expect(find("#gif")).not_to be_checked + + check "gif" + + click_button "Update" + end + + expect(page).to have_content "Value updated" + expect(Setting["upload.images.content_types"]).to include "image/png" + expect(Setting["upload.images.content_types"]).to include "image/gif" + + visit admin_settings_path(anchor: "tab-images-and-documents") + + within "#edit_setting_#{setting.id}" do + expect(find("#png")).to be_checked + expect(find("#gif")).to be_checked + expect(find("#jpg")).not_to be_checked + end + end + + end + describe "Skip verification" do scenario "deactivate skip verification", :js do diff --git a/spec/features/admin/system_emails_spec.rb b/spec/features/admin/system_emails_spec.rb index 4c71f4f3f..b1aed5bed 100644 --- a/spec/features/admin/system_emails_spec.rb +++ b/spec/features/admin/system_emails_spec.rb @@ -14,7 +14,8 @@ describe "System Emails" do let(:system_emails) do %w[proposal_notification_digest budget_investment_created budget_investment_selected budget_investment_unfeasible budget_investment_unselected comment reply - direct_message_for_receiver direct_message_for_sender email_verification user_invite] + direct_message_for_receiver direct_message_for_sender email_verification user_invite + evaluation_comment] end context "System emails" do @@ -243,6 +244,31 @@ describe "System Emails" do visit admin_system_email_view_path("reply") expect(page).to have_content "There aren't any replies created." expect(page).to have_content "Some example data is needed in order to preview the email." + + visit admin_system_email_view_path("evaluation_comment") + expect(page).to have_content "There aren't any evaluation comments created." + expect(page).to have_content "Some example data is needed in order to preview the email." + end + + scenario "#evaluation_comment" do + admin = create(:administrator, user: create(:user, username: "Baby Doe")) + investment = create(:budget_investment, + title: "Cleaner city", + heading: heading, + author: user, + administrator: admin) + comment = create(:comment, :valuation, commentable: investment) + + visit admin_system_email_view_path("evaluation_comment") + + expect(page).to have_content "New evaluation comment for Cleaner city" + expect(page).to have_content "Hi #{admin.name}" + expect(page).to have_content "There is a new evaluation comment from #{comment.user.name} "\ + "to the budget investment Cleaner city" + expect(page).to have_content comment.body + + expect(page).to have_link "Cleaner city", + href: admin_budget_budget_investment_url( investment.budget, investment, anchor: "comments") end end diff --git a/spec/features/budgets/ballots_spec.rb b/spec/features/budgets/ballots_spec.rb index 0f9bb1c05..707dbf0da 100644 --- a/spec/features/budgets/ballots_spec.rb +++ b/spec/features/budgets/ballots_spec.rb @@ -8,6 +8,55 @@ describe "Ballots" do let!(:california) { create(:budget_heading, group: states, name: "California", price: 1000) } let!(:new_york) { create(:budget_heading, group: states, name: "New York", price: 1000000) } + context "Load" do + + let(:ballot) { create(:budget_ballot, user: user, budget: budget) } + + before do + budget.update(slug: "budget_slug") + ballot.investments << create(:budget_investment, :selected, heading: california) + login_as(user) + end + + scenario "finds ballot using budget slug" do + visit budget_ballot_path("budget_slug") + + expect(page).to have_content("You have voted one investment") + end + + scenario "raises an error if budget slug is not found" do + expect do + visit budget_ballot_path("wrong_budget") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit budget_ballot_path(0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + + context "Lines Load" do + + let!(:investment) { create(:budget_investment, :selected, heading: california) } + + before do + create(:budget_ballot, user: user, budget: budget) + budget.update(slug: "budget_slug") + login_as(user) + end + + scenario "finds ballot lines using budget slug", :js do + visit budget_investments_path("budget_slug", states, california) + add_to_ballot(investment) + + within("#sidebar") { expect(page).to have_content investment.title } + end + + end + context "Voting" do before do diff --git a/spec/features/budgets/budgets_spec.rb b/spec/features/budgets/budgets_spec.rb index d6fdf13e0..0f2f0a8bd 100644 --- a/spec/features/budgets/budgets_spec.rb +++ b/spec/features/budgets/budgets_spec.rb @@ -6,6 +6,30 @@ describe "Budgets" do let(:level_two_user) { create(:user, :level_two) } let(:allowed_phase_list) { ["balloting", "reviewing_ballots", "finished"] } + context "Load" do + + before { budget.update(slug: "budget_slug") } + + scenario "finds budget by slug" do + visit budget_path("budget_slug") + + expect(page).to have_content budget.name + end + + scenario "raises an error if budget slug is not found" do + expect do + visit budget_path("wrong_budget") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit budget_path(0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + context "Index" do scenario "Show normal index with links" do diff --git a/spec/features/budgets/executions_spec.rb b/spec/features/budgets/executions_spec.rb index dd8966ed9..579b7d038 100644 --- a/spec/features/budgets/executions_spec.rb +++ b/spec/features/budgets/executions_spec.rb @@ -11,6 +11,22 @@ describe "Executions" do let!(:investment4) { create(:budget_investment, :winner, heading: heading) } let!(:investment3) { create(:budget_investment, :incompatible, heading: heading) } + scenario "finds budget by id or slug" do + budget.update(slug: "budget_slug") + + visit budget_executions_path("budget_slug") + within(".budgets-stats") { expect(page).to have_content budget.name } + + visit budget_executions_path(budget) + within(".budgets-stats") { expect(page).to have_content budget.name } + + visit budget_executions_path("budget_slug") + within(".budgets-stats") { expect(page).to have_content budget.name } + + visit budget_executions_path(budget) + within(".budgets-stats") { expect(page).to have_content budget.name } + end + scenario "only displays investments with milestones" do create(:milestone, milestoneable: investment1) @@ -219,6 +235,52 @@ describe "Executions" do select "Bidding (0)", from: "status" expect(page).not_to have_content(investment1.title) end + + scenario "by milestone tag, only display tags for winner investments", :js do + create(:milestone, milestoneable: investment1, status: status1) + create(:milestone, milestoneable: investment2, status: status2) + create(:milestone, milestoneable: investment3, status: status2) + investment1.milestone_tag_list.add("tag1", "tag2") + investment1.save + investment2.milestone_tag_list.add("tag2") + investment2.save + investment3.milestone_tag_list.add("tag2") + investment3.save + + visit budget_path(budget) + + click_link "See results" + click_link "Milestones" + + expect(page).to have_content(investment1.title) + expect(page).to have_content(investment2.title) + + select "tag2 (2)", from: "milestone_tag" + + expect(page).to have_content(investment1.title) + expect(page).to have_content(investment2.title) + + select "Studying the project (1)", from: "status" + + expect(page).to have_content(investment1.title) + expect(page).not_to have_content(investment2.title) + + select "Bidding (1)", from: "status" + + expect(page).not_to have_content(investment1.title) + expect(page).to have_content(investment2.title) + + select "tag1 (1)", from: "milestone_tag" + + expect(page).not_to have_content(investment1.title) + expect(page).not_to have_content(investment2.title) + + select "All (2)", from: "milestone_tag" + + expect(page).not_to have_content(investment1.title) + expect(page).to have_content(investment2.title) + end + end context "Heading Order" do diff --git a/spec/features/budgets/groups_spec.rb b/spec/features/budgets/groups_spec.rb index 808646733..a5fad8eee 100644 --- a/spec/features/budgets/groups_spec.rb +++ b/spec/features/budgets/groups_spec.rb @@ -2,8 +2,45 @@ require "rails_helper" describe "Budget Groups" do - let(:budget) { create(:budget) } - let(:group) { create(:budget_group, budget: budget) } + let(:budget) { create(:budget, slug: "budget_slug") } + let!(:group) { create(:budget_group, slug: "group_slug", budget: budget) } + + context "Load" do + + scenario "finds group using budget slug and group slug" do + visit budget_group_path("budget_slug", "group_slug") + expect(page).to have_content "Select an option" + end + + scenario "finds group using budget id and group id" do + visit budget_group_path(budget, group) + expect(page).to have_content "Select an option" + end + + scenario "raises an error if budget slug is not found" do + expect do + visit budget_group_path("wrong_budget", group) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit budget_group_path(0, group) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if group slug is not found" do + expect do + visit budget_group_path(budget, "wrong_group") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if group id is not found" do + expect do + visit budget_group_path(budget, 0) + end.to raise_error ActiveRecord::RecordNotFound + end + end context "Show" do scenario "Headings are sorted by name" do diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb index eece48251..50c0e4d07 100644 --- a/spec/features/budgets/investments_spec.rb +++ b/spec/features/budgets/investments_spec.rb @@ -26,6 +26,53 @@ describe "Budget Investments" do it_behaves_like "relationable", Budget::Investment end + context "Load" do + + let(:investment) { create(:budget_investment, heading: heading) } + + before do + budget.update(slug: "budget_slug") + heading.update(slug: "heading_slug") + end + + scenario "finds investment using budget slug" do + visit budget_investment_path("budget_slug", investment) + + expect(page).to have_content investment.title + end + + scenario "raises an error if budget slug is not found" do + expect do + visit budget_investment_path("wrong_budget", investment) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit budget_investment_path(0, investment) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "finds investment using heading slug" do + visit budget_investment_path(budget, investment, heading_id: "heading_slug") + + expect(page).to have_content investment.title + end + + scenario "raises an error if heading slug is not found" do + expect do + visit budget_investment_path(budget, investment, heading_id: "wrong_heading") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if heading id is not found" do + expect do + visit budget_investment_path(budget, investment, heading_id: 0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + scenario "Index" do investments = [create(:budget_investment, heading: heading), create(:budget_investment, heading: heading), @@ -40,7 +87,7 @@ describe "Budget Investments" do investments.each do |investment| within("#budget-investments") do expect(page).to have_content investment.title - expect(page).to have_css("a[href='#{budget_investment_path(budget_id: budget.id, id: investment.id)}']", text: investment.title) + expect(page).to have_css("a[href='#{budget_investment_path(budget, id: investment.id)}']", text: investment.title) expect(page).not_to have_content(unfeasible_investment.title) end end @@ -476,7 +523,7 @@ describe "Budget Investments" do investment3 = create(:budget_investment, heading: heading) investment4 = create(:budget_investment, :feasible, heading: heading) - visit budget_investments_path(budget_id: budget.id, heading_id: heading.id, filter: "unfeasible") + visit budget_investments_path(budget, heading_id: heading.id, filter: "unfeasible") within("#budget-investments") do expect(page).to have_css(".budget-investment", count: 1) @@ -810,7 +857,7 @@ describe "Budget Investments" do scenario "Create with invisible_captcha honeypot field" do login_as(author) - visit new_budget_investment_path(budget_id: budget.id) + visit new_budget_investment_path(budget) select heading.name, from: "budget_investment_heading_id" fill_in "budget_investment_title", with: "I am a bot" @@ -822,14 +869,14 @@ describe "Budget Investments" do expect(page.status_code).to eq(200) expect(page.html).to be_empty - expect(page).to have_current_path(budget_investments_path(budget_id: budget.id)) + expect(page).to have_current_path(budget_investments_path(budget)) end scenario "Create budget investment too fast" do allow(InvisibleCaptcha).to receive(:timestamp_threshold).and_return(Float::INFINITY) login_as(author) - visit new_budget_investment_path(budget_id: budget.id) + visit new_budget_investment_path(budget) select heading.name, from: "budget_investment_heading_id" fill_in "budget_investment_title", with: "I am a bot" @@ -839,13 +886,13 @@ describe "Budget Investments" do click_button "Create Investment" expect(page).to have_content "Sorry, that was too quick! Please resubmit" - expect(page).to have_current_path(new_budget_investment_path(budget_id: budget.id)) + expect(page).to have_current_path(new_budget_investment_path(budget)) end scenario "Create" do login_as(author) - visit new_budget_investment_path(budget_id: budget.id) + visit new_budget_investment_path(budget) select heading.name, from: "budget_investment_heading_id" fill_in "budget_investment_title", with: "Build a skyscraper" @@ -872,7 +919,7 @@ describe "Budget Investments" do scenario "Errors on create" do login_as(author) - visit new_budget_investment_path(budget_id: budget.id) + visit new_budget_investment_path(budget) click_button "Create Investment" expect(page).to have_content error_message end @@ -948,7 +995,7 @@ describe "Budget Investments" do login_as(author) - visit new_budget_investment_path(budget_id: budget.id) + visit new_budget_investment_path(budget) select_options = find("#budget_investment_heading_id").all("option").collect(&:text) expect(select_options.first).to eq("") @@ -964,7 +1011,7 @@ describe "Budget Investments" do investment = create(:budget_investment, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).to have_content(investment.title) expect(page).to have_content(investment.description) @@ -984,7 +1031,7 @@ describe "Budget Investments" do scenario "Price & explanation is shown when Budget is on published prices phase" do Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase| budget.update(phase: phase) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).to have_content(investment.formatted_price) expect(page).to have_content(investment.price_explanation) @@ -1003,7 +1050,7 @@ describe "Budget Investments" do scenario "Price & explanation isn't shown when Budget is not on published prices phase" do (Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).each do |phase| budget.update(phase: phase) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content(investment.formatted_price) expect(page).not_to have_content(investment.price_explanation) @@ -1025,7 +1072,7 @@ describe "Budget Investments" do scenario "Price & explanation isn't shown for any Budget's phase" do Budget::Phase::PHASE_KINDS.each do |phase| budget.update(phase: phase) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content(investment.formatted_price) expect(page).not_to have_content(investment.price_explanation) @@ -1044,7 +1091,7 @@ describe "Budget Investments" do Setting["feature.community"] = true investment = create(:budget_investment, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).to have_content "Access the community" Setting["feature.community"] = false @@ -1054,14 +1101,14 @@ describe "Budget Investments" do Setting["feature.community"] = false investment = create(:budget_investment, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content "Access the community" end scenario "Don't display flaggable buttons" do investment = create(:budget_investment, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_selector ".js-follow" end @@ -1092,7 +1139,7 @@ describe "Budget Investments" do scenario "Budget in selecting phase" do budget.update(phase: "selecting") - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content("Unfeasibility explanation") expect(page).not_to have_content("Price explanation") @@ -1120,14 +1167,14 @@ describe "Budget Investments" do heading: heading, unfeasibility_explanation: "The unfeasible explanation") - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content("Unfeasibility explanation") expect(page).not_to have_content("Local government is not competent in this") expect(page).not_to have_content("This investment project has been marked as not feasible "\ "and will not go to balloting phase") - visit budget_investment_path(budget_id: budget.id, id: investment_2.id) + visit budget_investment_path(budget, id: investment_2.id) expect(page).to have_content("Unfeasibility explanation") expect(page).to have_content("The unfeasible explanation") @@ -1147,7 +1194,7 @@ describe "Budget Investments" do group: group, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).to have_content("This investment project has been selected for balloting phase") end @@ -1166,13 +1213,13 @@ describe "Budget Investments" do group: group, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content("Winning investment project") budget.update(phase: "finished") - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).to have_content("Winning investment project") end @@ -1189,7 +1236,7 @@ describe "Budget Investments" do group: group, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).to have_content("This investment project has not been selected for balloting phase") end @@ -1205,7 +1252,7 @@ describe "Budget Investments" do group: group, heading: heading) - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) within("aside") do expect(page).to have_content("Investment project") @@ -1225,7 +1272,7 @@ describe "Budget Investments" do heading: heading, unfeasibility_explanation: "Local government is not competent in this matter") - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content("Unfeasibility explanation") expect(page).not_to have_content("Local government is not competent in this matter") @@ -1243,7 +1290,7 @@ describe "Budget Investments" do heading: heading, unfeasibility_explanation: "Local government is not competent in this matter") - visit budget_investment_path(budget_id: budget.id, id: investment.id) + visit budget_investment_path(budget, id: investment.id) expect(page).not_to have_content("Unfeasibility explanation") expect(page).not_to have_content("Local government is not competent in this matter") @@ -1647,7 +1694,7 @@ describe "Budget Investments" do investment3 = create(:budget_investment, :selected, :feasible, heading: heading, valuation_finished: true) investment4 = create(:budget_investment, :selected, :feasible, heading: heading, valuation_finished: true) - visit budget_investments_path(budget_id: budget.id, heading_id: heading.id, filter: "unselected") + visit budget_investments_path(budget, heading_id: heading.id, filter: "unselected") within("#budget-investments") do expect(page).to have_css(".budget-investment", count: 1) @@ -1691,7 +1738,7 @@ describe "Budget Investments" do scenario "Do not display vote button for unselected investments in index" do investment = create(:budget_investment, :unselected, heading: heading) - visit budget_investments_path(budget_id: budget.id, heading_id: heading.id, filter: "unselected") + visit budget_investments_path(budget, heading_id: heading.id, filter: "unselected") expect(page).to have_content investment.title expect(page).not_to have_link("Vote") diff --git a/spec/features/budgets/results_spec.rb b/spec/features/budgets/results_spec.rb index f8e00bcb3..5b9277d56 100644 --- a/spec/features/budgets/results_spec.rb +++ b/spec/features/budgets/results_spec.rb @@ -15,6 +15,22 @@ describe "Results" do Budget::Result.new(budget, heading).calculate_winners end + scenario "No links to budget results with results disabled" do + budget.update(results_enabled: false) + + visit budgets_path + + expect(page).not_to have_link "See results" + + visit budget_path(budget) + + expect(page).not_to have_link "See results" + + visit budget_executions_path(budget) + + expect(page).not_to have_link "See results" + end + scenario "Diplays winner investments" do create(:budget_heading, group: group) @@ -52,6 +68,30 @@ describe "Results" do end end + scenario "Does not raise error if budget (slug or id) is not found" do + visit budget_results_path("wrong budget") + + within(".budgets-stats") do + expect(page).to have_content "Participatory budget results" + end + + visit budget_results_path(0) + + within(".budgets-stats") do + expect(page).to have_content "Participatory budget results" + end + end + + scenario "Loads budget and heading by slug" do + visit budget_results_path(budget.slug, heading.slug) + + expect(page).to have_selector("a.is-active", text: heading.name) + + within("#budget-investments-compatible") do + expect(page).to have_content investment1.title + end + end + scenario "Load first budget heading if not specified" do other_heading = create(:budget_heading, group: group) other_investment = create(:budget_investment, :winner, heading: other_heading) diff --git a/spec/features/budgets/stats_spec.rb b/spec/features/budgets/stats_spec.rb index 5b9305b33..d6113c7c0 100644 --- a/spec/features/budgets/stats_spec.rb +++ b/spec/features/budgets/stats_spec.rb @@ -2,14 +2,36 @@ require "rails_helper" describe "Stats" do - let(:budget) { create(:budget) } + let(:budget) { create(:budget, :finished) } let(:group) { create(:budget_group, budget: budget) } let(:heading) { create(:budget_heading, group: group, price: 1000) } + context "Load" do + + before { budget.update(slug: "budget_slug") } + + scenario "finds budget by slug" do + visit budget_stats_path("budget_slug") + + expect(page).to have_content budget.name + end + + scenario "raises an error if budget slug is not found" do + expect do + visit budget_stats_path("wrong_budget") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit budget_stats_path(0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + describe "Show" do describe "advanced stats" do - let(:budget) { create(:budget, :finished) } - scenario "advanced stats enabled" do budget.update(advanced_stats_enabled: true) diff --git a/spec/features/comments/budget_investments_spec.rb b/spec/features/comments/budget_investments_spec.rb index b5f064d93..b171d5b88 100644 --- a/spec/features/comments/budget_investments_spec.rb +++ b/spec/features/comments/budget_investments_spec.rb @@ -382,49 +382,113 @@ describe "Commenting Budget::Investments" do end describe "Administrators" do - scenario "can create comment as an administrator", :js do - admin = create(:administrator) + context "comment as administrator" do + scenario "can create comment", :js do + admin = create(:administrator) - login_as(admin.user) - visit budget_investment_path(investment.budget, investment) + login_as(admin.user) + visit budget_investment_path(investment.budget, investment) - fill_in "comment-body-budget_investment_#{investment.id}", with: "I am your Admin!" - check "comment-as-administrator-budget_investment_#{investment.id}" - click_button "Publish comment" + fill_in "comment-body-budget_investment_#{investment.id}", with: "I am your Admin!" + check "comment-as-administrator-budget_investment_#{investment.id}" + click_button "Publish comment" - within "#comments" do - expect(page).to have_content "I am your Admin!" - expect(page).to have_content "Administrator ##{admin.id}" + within "#comments" do + expect(page).to have_content "I am your Admin!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + end + + scenario "display administrator description on admin views", :js do + admin = create(:administrator, description: "user description") + + login_as(admin.user) + + visit admin_budget_budget_investment_path(investment.budget, investment) + + fill_in "comment-body-budget_investment_#{investment.id}", with: "I am your Admin!" + check "comment-as-administrator-budget_investment_#{investment.id}" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content "I am your Admin!" + end + + visit admin_budget_budget_investment_path(investment.budget, investment) + + within "#comments" do + expect(page).to have_content "I am your Admin!" + expect(page).to have_content "Administrator user description" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + end + + scenario "display administrator id on public views", :js do + admin = create(:administrator, description: "user description") + + login_as(admin.user) + visit admin_budget_budget_investment_path(investment.budget, investment) + + fill_in "comment-body-budget_investment_#{investment.id}", with: "I am your Admin!" + check "comment-as-administrator-budget_investment_#{investment.id}" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content "I am your Admin!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "div.is-admin" + expect(page).to have_css "img.admin-avatar" + end + end + + scenario "can create reply as an administrator", :js do + citizen = create(:user, username: "Ana") + manuela = create(:user, username: "Manuela") + admin = create(:administrator, user: manuela) + comment = create(:comment, commentable: investment, user: citizen) + + login_as(manuela) + visit budget_investment_path(investment.budget, investment) + + click_link "Reply" + + within "#js-comment-form-comment_#{comment.id}" do + fill_in "comment-body-comment_#{comment.id}", with: "Top of the world!" + check "comment-as-administrator-comment_#{comment.id}" + click_button "Publish reply" + end + + within "#comment_#{comment.id}" do + expect(page).to have_content "Top of the world!" + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "img.admin-avatar" + end + + expect(page).not_to have_selector("#js-comment-form-comment_#{comment.id}", visible: true) expect(page).to have_css "div.is-admin" - expect(page).to have_css "img.admin-avatar" - end - end - - scenario "can create reply as an administrator", :js do - citizen = create(:user, username: "Ana") - manuela = create(:user, username: "Manuela") - admin = create(:administrator, user: manuela) - comment = create(:comment, commentable: investment, user: citizen) - - login_as(manuela) - visit budget_investment_path(investment.budget, investment) - - click_link "Reply" - - within "#js-comment-form-comment_#{comment.id}" do - fill_in "comment-body-comment_#{comment.id}", with: "Top of the world!" - check "comment-as-administrator-comment_#{comment.id}" - click_button "Publish reply" end - within "#comment_#{comment.id}" do - expect(page).to have_content "Top of the world!" - expect(page).to have_content "Administrator ##{admin.id}" - expect(page).to have_css "div.is-admin" - expect(page).to have_css "img.admin-avatar" + scenario "public users not see admin description", :js do + manuela = create(:user, username: "Manuela") + admin = create(:administrator, user: manuela) + comment = create(:comment, + commentable: investment, + user: manuela, + administrator_id: admin.id) + + visit budget_investment_path(investment.budget, investment) + + within "#comment_#{comment.id}" do + expect(page).to have_content comment.body + expect(page).to have_content "Administrator ##{admin.id}" + expect(page).to have_css "img.admin-avatar" + expect(page).to have_css "div.is-admin" + end end - expect(page).not_to have_selector("#js-comment-form-comment_#{comment.id}", visible: true) end scenario "can not comment as a moderator" do diff --git a/spec/features/comments/budget_investments_valuation_spec.rb b/spec/features/comments/budget_investments_valuation_spec.rb index 1acb5819c..fd9bf56c7 100644 --- a/spec/features/comments/budget_investments_valuation_spec.rb +++ b/spec/features/comments/budget_investments_valuation_spec.rb @@ -171,7 +171,7 @@ describe "Internal valuation comments on Budget::Investments" do scenario "Create comment", :js do visit valuation_budget_budget_investment_path(budget, investment) - fill_in "comment-body-budget_investment_#{investment.id}", with: "Have you thought about...?" + fill_in "Leave your comment", with: "Have you thought about...?" click_button "Publish comment" within "#comments" do @@ -199,7 +199,7 @@ describe "Internal valuation comments on Budget::Investments" do click_link "Reply" within "#js-comment-form-comment_#{comment.id}" do - fill_in "comment-body-comment_#{comment.id}", with: "It will be done next week." + fill_in "Leave your comment", with: "It will be done next week." click_button "Publish reply" end @@ -261,8 +261,8 @@ describe "Internal valuation comments on Budget::Investments" do login_as(admin_user) visit valuation_budget_budget_investment_path(budget, investment) - fill_in "comment-body-budget_investment_#{investment.id}", with: "I am your Admin!" - check "comment-as-administrator-budget_investment_#{investment.id}" + fill_in "Leave your comment", with: "I am your Admin!" + check "Comment as admin" click_button "Publish comment" within "#comments" do @@ -282,8 +282,8 @@ describe "Internal valuation comments on Budget::Investments" do click_link "Reply" within "#js-comment-form-comment_#{comment.id}" do - fill_in "comment-body-comment_#{comment.id}", with: "Top of the world!" - check "comment-as-administrator-comment_#{comment.id}" + fill_in "Leave your comment", with: "Top of the world!" + check "Comment as admin" click_button "Publish reply" end @@ -298,4 +298,25 @@ describe "Internal valuation comments on Budget::Investments" do end end + scenario "Send email notification", :js do + ActionMailer::Base.deliveries = [] + + login_as(admin_user) + + expect(ActionMailer::Base.deliveries).to eq([]) + + visit valuation_budget_budget_investment_path(budget, investment) + fill_in "Leave your comment", with: "I am your Admin!" + check "Comment as admin" + click_button "Publish comment" + + within "#comments" do + expect(page).to have_content("I am your Admin!") + end + + expect(ActionMailer::Base.deliveries.count).to eq(1) + expect(ActionMailer::Base.deliveries.first.to).to eq([valuator_user.email]) + expect(ActionMailer::Base.deliveries.first.subject).to eq("New evaluation comment") + end + end diff --git a/spec/features/management/budget_investments_spec.rb b/spec/features/management/budget_investments_spec.rb index efb00d12f..14297ab41 100644 --- a/spec/features/management/budget_investments_spec.rb +++ b/spec/features/management/budget_investments_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" describe "Budget Investments" do let(:manager) { create(:manager) } - let(:budget) { create(:budget, phase: "selecting", name: "2033") } + let(:budget) { create(:budget, phase: "selecting", name: "2033", slug: "budget_slug") } let(:group) { create(:budget_group, budget: budget, name: "Whole city") } let(:heading) { create(:budget_heading, group: group, name: "Health") } @@ -18,6 +18,33 @@ describe "Budget Investments" do { "budget_id": "budget_id" }, management = true + context "Load" do + + let(:investment) { create(:budget_investment, budget: budget) } + let(:user) { create(:user, :level_two) } + + before { login_managed_user(user) } + + scenario "finds investment using budget slug" do + visit management_budget_investment_path("budget_slug", investment) + + expect(page).to have_content investment.title + end + + scenario "raises an error if budget slug is not found" do + expect do + visit management_budget_investment_path("wrong_budget", investment) + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit management_budget_investment_path(0, investment) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + context "Create" do before { heading.budget.update(phase: "accepting") } diff --git a/spec/features/valuation/budget_investments_spec.rb b/spec/features/valuation/budget_investments_spec.rb index 03b843e03..372389f64 100644 --- a/spec/features/valuation/budget_investments_spec.rb +++ b/spec/features/valuation/budget_investments_spec.rb @@ -11,6 +11,30 @@ describe "Valuation budget investments" do login_as(valuator.user) end + context "Load" do + + before { budget.update(slug: "budget_slug") } + + scenario "finds investment using budget slug" do + visit valuation_budget_budget_investments_path("budget_slug") + + expect(page).to have_content budget.name + end + + scenario "raises an error if budget slug is not found" do + expect do + visit valuation_budget_budget_investments_path("wrong_budget") + end.to raise_error ActiveRecord::RecordNotFound + end + + scenario "raises an error if budget id is not found" do + expect do + visit valuation_budget_budget_investments_path(0) + end.to raise_error ActiveRecord::RecordNotFound + end + + end + scenario "Disabled with a feature flag" do Setting["process.budgets"] = nil expect{ diff --git a/spec/lib/evaluation_comment_email_spec.rb b/spec/lib/evaluation_comment_email_spec.rb new file mode 100644 index 000000000..18b85c197 --- /dev/null +++ b/spec/lib/evaluation_comment_email_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +describe EvaluationCommentEmail do + + let(:author) { create(:user) } + let(:administrator) { create(:administrator)} + let(:investment) { create(:budget_investment, author: author, administrator: administrator) } + let(:commenter) { create(:user, email: "email@commenter.org") } + let(:comment) { create(:comment, commentable: investment, user: commenter) } + let(:comment_email) { EvaluationCommentEmail.new(comment) } + + describe "#commentable" do + it "returns the commentable object that contains the replied comment" do + expect(comment_email.commentable).to eq investment + end + end + + describe "#to" do + it "returns an array of users related to investment" do + expect(comment_email.to).to eq [administrator] + end + + it "returns empty array if commentable not exists" do + allow(comment).to receive(:commentable).and_return(nil) + expect(comment_email.to).to eq [] + end + + it "returns empty array if not associated users" do + allow(investment).to receive(:admin_and_valuator_users_associated).and_return([]) + expect(comment_email.to).to eq [] + end + end + + describe "#subject" do + it "returns the translation for a evaluation comment email subject" do + expect(comment_email.subject).to eq "New evaluation comment" + end + end + + describe "#can_be_sent?" do + it "returns true if investment has any associated users" do + expect(comment_email.can_be_sent?).to be true + end + + it "returns false if the comment doesn't exist" do + comment.update(commentable: nil) + + expect(comment_email.can_be_sent?).to be false + end + + it "returns false if recipients are empty" do + investment.administrator = nil + expect(comment_email.can_be_sent?).to be false + end + end + +end diff --git a/spec/models/administrator_spec.rb b/spec/models/administrator_spec.rb new file mode 100644 index 000000000..39b740980 --- /dev/null +++ b/spec/models/administrator_spec.rb @@ -0,0 +1,45 @@ +require "rails_helper" + +describe Administrator do + + describe "#description_or_name" do + let!(:user) { create(:user, username: "Billy Wilder" )} + + it "returns description if present" do + administrator = create(:administrator, user: user, description: "John Doe") + + expect(administrator.description_or_name).to eq("John Doe") + end + + it "returns name if description is nil" do + administrator = create(:administrator, user: user) + + expect(administrator.description_or_name).to eq("Billy Wilder") + end + + it "returns name if description is blank" do + administrator = create(:administrator, description: "") + + expect(administrator.description_or_name).to eq(administrator.name) + end + end + + describe "#description_or_name_and_email" do + let!(:user) { create(:user, username: "Billy Wilder", email: "test@test.com")} + + it "returns description and email if decription present" do + administrator = create(:administrator, + description: "John Doe", + user: user) + + expect(administrator.description_or_name_and_email).to eq("John Doe (test@test.com)") + end + + it "returns name and email if description is not present" do + administrator = create(:administrator, user: user) + + expect(administrator.description_or_name_and_email).to eq("Billy Wilder (test@test.com)") + end + end + +end diff --git a/spec/models/budget/group_spec.rb b/spec/models/budget/group_spec.rb index 2ab137ba5..ce1f47563 100644 --- a/spec/models/budget/group_spec.rb +++ b/spec/models/budget/group_spec.rb @@ -32,4 +32,42 @@ describe Budget::Group do end end + + describe "#sort_by_name" do + it "returns groups sorted by name ASC" do + thinkers = create(:budget_group, name: "Mmmm...") + sleepers = create(:budget_group, name: "Zzz...") + startled = create(:budget_group, name: "Aaaaah!") + + expect(Budget::Group.sort_by_name).to eq [startled, thinkers, sleepers] + end + + it "returns groups with multiple translations only once" do + create(:budget_group, name_en: "English", name_es: "Spanish") + + expect(Budget::Group.sort_by_name.count).to eq 1 + end + + context "fallback locales" do + before do + allow(I18n.fallbacks).to receive(:[]).and_return([:es]) + Globalize.set_fallbacks_to_all_available_locales + end + + it "orders by name considering fallback locale," do + budget = create(:budget, name: "Teams") + charlie = create(:budget_group, budget: budget, name: "Charlie") + delta = create(:budget_group, budget: budget, name: "Delta") + zulu = Globalize.with_locale(:es) do + create(:budget_group, budget: budget, name: "Zulu", name_fr: "Alpha") + end + bravo = Globalize.with_locale(:es) do + create(:budget_group, budget: budget, name: "Bravo") + end + + expect(Budget::Group.sort_by_name.count).to eq 4 + expect(Budget::Group.sort_by_name).to eq [bravo, charlie, delta, zulu] + end + end + end end diff --git a/spec/models/budget/heading_spec.rb b/spec/models/budget/heading_spec.rb index 2d69839e6..b32f6aeb1 100644 --- a/spec/models/budget/heading_spec.rb +++ b/spec/models/budget/heading_spec.rb @@ -319,6 +319,12 @@ describe Budget::Heading do expect(Budget::Heading.allow_custom_content.first).to eq first_heading expect(Budget::Heading.allow_custom_content.last).to eq last_heading end + + it "returns headings with multiple translations only once" do + create(:budget_heading, allow_custom_content: true, name_en: "English", name_es: "Spanish") + + expect(Budget::Heading.allow_custom_content.count).to eq 1 + end end end diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index 044dead99..439938eff 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -382,6 +382,30 @@ describe Budget::Investment do end end + describe "scoped_filter" do + + let!(:budget) { create(:budget, slug: "budget_slug") } + let!(:group) { create(:budget_group, budget: budget) } + let!(:heading) { create(:budget_heading, group: group) } + let!(:investment) { create(:budget_investment, :feasible, heading: heading) } + + it "finds budget by id or slug" do + result = described_class.scoped_filter({budget_id: budget.id}, nil) + expect(result.count).to be 1 + expect(result.first.id).to be investment.id + + result = described_class.scoped_filter({budget_id: "budget_slug"}, nil) + expect(result.count).to be 1 + expect(result.first.id).to be investment.id + end + + it "does not raise error if budget is not found" do + result = described_class.scoped_filter({budget_id: "wrong_budget"}, nil) + expect(result).to be_empty + end + + end + describe "scopes" do describe "valuation_open" do it "returns all investments with false valuation_finished" do @@ -1218,4 +1242,57 @@ describe Budget::Investment do end end end + + describe "admin_and_valuator_users_associated" do + let(:investment) { create(:budget_investment) } + let(:valuator_group) { create(:valuator_group) } + let(:valuator) { create(:valuator) } + let(:administrator) { create(:administrator) } + + it "returns empty array if not valuators or administrator assigned" do + expect(investment.admin_and_valuator_users_associated).to eq([]) + end + + it "returns all valuator and administrator users" do + valuator_group.valuators << valuator + investment.valuator_groups << valuator_group + expect(investment.admin_and_valuator_users_associated).to eq([valuator]) + investment.administrator = administrator + expect(investment.admin_and_valuator_users_associated).to eq([valuator, administrator]) + end + + it "returns uniq valuators or administrator users" do + valuator_group.valuators << valuator + investment.valuator_groups << valuator_group + investment.valuators << valuator + investment.administrator = administrator + expect(investment.admin_and_valuator_users_associated).to eq([valuator, administrator]) + + end + end + + describe "milestone_tags" do + context "without milestone_tags" do + let(:investment) {create(:budget_investment)} + + it "do not have milestone_tags" do + expect(investment.milestone_tag_list).to eq([]) + expect(investment.milestone_tags).to eq([]) + end + + it "add a new milestone_tag" do + investment.milestone_tag_list = "tag1,tag2" + + expect(investment.milestone_tag_list).to eq(["tag1", "tag2"]) + end + end + + context "with milestone_tags" do + let(:investment) {create(:budget_investment, :with_milestone_tags)} + + it "has milestone_tags" do + expect(investment.milestone_tag_list.count).to eq(1) + end + end + end end diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index 123035732..851d6076a 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -306,4 +306,46 @@ describe Budget do expect(budget.formatted_amount(1000.00)).to eq ("€1,000") end end + + describe "#milestone_tags" do + let(:investment1) { build(:budget_investment, :winner) } + let(:investment2) { build(:budget_investment, :winner) } + let(:investment3) { build(:budget_investment) } + + it "returns an empty array if not investments milestone_tags" do + budget.investments << investment1 + + expect(budget.milestone_tags).to eq([]) + end + + it "returns array of investments milestone_tags" do + investment1.milestone_tag_list = "tag1" + investment1.save + budget.investments << investment1 + + expect(budget.milestone_tags).to eq(["tag1"]) + end + + it "returns uniq list of investments milestone_tags" do + investment1.milestone_tag_list = "tag1" + investment1.save + investment2.milestone_tag_list = "tag1" + investment2.save + budget.investments << investment1 + budget.investments << investment2 + + expect(budget.milestone_tags).to eq(["tag1"]) + end + + it "returns tags only for winner investments" do + investment1.milestone_tag_list = "tag1" + investment1.save + investment3.milestone_tag_list = "tag2" + investment3.save + budget.investments << investment1 + budget.investments << investment3 + + expect(budget.milestone_tags).to eq(["tag1"]) + end + end end diff --git a/spec/models/legislation/people_proposals_spec.rb b/spec/models/legislation/people_proposals_spec.rb new file mode 100644 index 000000000..c1aef7571 --- /dev/null +++ b/spec/models/legislation/people_proposals_spec.rb @@ -0,0 +1,108 @@ +require "rails_helper" + +describe Legislation::PeopleProposal do + let(:people_proposal) { build(:legislation_people_proposal) } + + it "is valid" do + expect(people_proposal).to be_valid + end + + it "is not valid without a process" do + people_proposal.process = nil + expect(people_proposal).not_to be_valid + end + + it "is not valid without an author" do + people_proposal.author = nil + expect(people_proposal).not_to be_valid + end + + it "is not valid without a title" do + people_proposal.title = nil + expect(people_proposal).not_to be_valid + end + + it "is not valid without a summary" do + people_proposal.summary = nil + expect(people_proposal).not_to be_valid + end + + describe "#hot_score" do + let(:now) { Time.current } + + it "period is correctly calculated to get exact votes per day" do + new_people_proposal = create(:legislation_people_proposal, created_at: 23.hours.ago) + 2.times { new_people_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(new_people_proposal.hot_score).to be 2 + + old_people_proposal = create(:legislation_people_proposal, created_at: 25.hours.ago) + 2.times { old_people_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(old_people_proposal.hot_score).to be 1 + + older_people_proposal = create(:legislation_people_proposal, created_at: 49.hours.ago) + 3.times { older_people_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(old_people_proposal.hot_score).to be 1 + end + + it "remains the same for not voted people_proposals" do + new = create(:legislation_people_proposal, created_at: now) + old = create(:legislation_people_proposal, created_at: 1.day.ago) + older = create(:legislation_people_proposal, created_at: 2.month.ago) + expect(new.hot_score).to be 0 + expect(old.hot_score).to be 0 + expect(older.hot_score).to be 0 + end + + it "increases for people_proposals with more positive votes" do + more_positive_votes = create(:legislation_people_proposal) + 2.times { more_positive_votes.vote_by(voter: create(:user), vote: "yes") } + + less_positive_votes = create(:legislation_people_proposal) + less_positive_votes.vote_by(voter: create(:user), vote: "yes") + + expect(more_positive_votes.hot_score).to be > less_positive_votes.hot_score + end + + it "increases for people_proposals with the same amount of positive votes within less days" do + newer_people_proposal = create(:legislation_people_proposal, created_at: now) + 5.times { newer_people_proposal.vote_by(voter: create(:user), vote: "yes") } + + older_people_proposal = create(:legislation_people_proposal, created_at: 1.day.ago) + 5.times { older_people_proposal.vote_by(voter: create(:user), vote: "yes") } + + expect(newer_people_proposal.hot_score).to be > older_people_proposal.hot_score + end + + it "increases for people_proposals voted within the period (last month by default)" do + newer_people_proposal = create(:legislation_people_proposal, created_at: 2.months.ago) + 20.times { create(:vote, votable: newer_people_proposal, created_at: 3.days.ago) } + + older_people_proposal = create(:legislation_people_proposal, created_at: 2.months.ago) + 20.times { create(:vote, votable: older_people_proposal, created_at: 40.days.ago) } + + expect(newer_people_proposal.hot_score).to be > older_people_proposal.hot_score + end + + describe "actions which affect it" do + + let(:people_proposal) { create(:legislation_people_proposal) } + + before do + 5.times { people_proposal.vote_by(voter: create(:user), vote: "yes") } + 2.times { people_proposal.vote_by(voter: create(:user), vote: "no") } + end + + it "increases with positive votes" do + previous = people_proposal.hot_score + 3.times { people_proposal.vote_by(voter: create(:user), vote: "yes") } + expect(previous).to be < people_proposal.hot_score + end + + it "decreases with negative votes" do + previous = people_proposal.hot_score + 3.times { people_proposal.vote_by(voter: create(:user), vote: "no") } + expect(previous).to be > people_proposal.hot_score + end + end + end +end diff --git a/spec/models/legislation/process_spec.rb b/spec/models/legislation/process_spec.rb index c870a4049..0813b6064 100644 --- a/spec/models/legislation/process_spec.rb +++ b/spec/models/legislation/process_spec.rb @@ -194,4 +194,31 @@ describe Legislation::Process do end end + describe "milestone_tags" do + context "without milestone_tags" do + let(:process) {create(:legislation_process)} + + it "do not have milestone_tags" do + expect(process.milestone_tag_list).to eq([]) + expect(process.milestone_tags).to eq([]) + end + + it "add a new milestone_tag" do + process.milestone_tag_list = "tag1,tag2" + + expect(process.milestone_tag_list).to eq(["tag1", "tag2"]) + end + end + + context "with milestone_tags" do + + let(:process) {create(:legislation_process, :with_milestone_tags)} + + it "has milestone_tags" do + expect(process.milestone_tag_list.count).to eq(1) + end + end + + end + end diff --git a/spec/models/poll/booth_spec.rb b/spec/models/poll/booth_spec.rb index 8cde2263b..2a90d23c6 100644 --- a/spec/models/poll/booth_spec.rb +++ b/spec/models/poll/booth_spec.rb @@ -24,7 +24,7 @@ describe Poll::Booth do end end - describe "#available" do + describe ".available" do it "returns booths associated to current polls" do booth_for_current_poll = create(:poll_booth) @@ -40,5 +40,10 @@ describe Poll::Booth do expect(described_class.available).not_to include(booth_for_expired_poll) end + it "returns polls with multiple translations only once" do + create(:poll_booth, polls: [create(:poll, :current, name: "English", name_es: "Spanish")]) + + expect(Poll::Booth.available.count).to eq 1 + end end end diff --git a/spec/models/poll/officer_spec.rb b/spec/models/poll/officer_spec.rb index 4cdc581b1..d86efce14 100644 --- a/spec/models/poll/officer_spec.rb +++ b/spec/models/poll/officer_spec.rb @@ -2,6 +2,34 @@ require "rails_helper" describe Poll::Officer do + describe "#name" do + let(:officer) { create(:poll_officer) } + + it "returns user name if user is not deleted" do + expect(officer.name).to eq officer.user.name + end + + it "returns 'User deleted' if user is deleted" do + officer.user.destroy + + expect(officer.reload.name).to eq "User deleted" + end + end + + describe "#email" do + let(:officer) { create(:poll_officer) } + + it "returns user email if user is not deleted" do + expect(officer.email).to eq officer.user.email + end + + it "returns 'Email deleted' if user is deleted" do + officer.user.destroy + + expect(officer.reload.email).to eq "Email deleted" + end + end + describe "#voting_days_assigned_polls" do it "returns all polls with this officer assigned during voting days" do officer = create(:poll_officer) diff --git a/spec/models/poll/poll_spec.rb b/spec/models/poll/poll_spec.rb index 581123997..e03d941b1 100644 --- a/spec/models/poll/poll_spec.rb +++ b/spec/models/poll/poll_spec.rb @@ -359,4 +359,58 @@ describe Poll do end + describe "#sort_for_list" do + it "returns polls sorted by name ASC" do + starts_at = Time.current + 1.day + poll1 = create(:poll, geozone_restricted: true, starts_at: starts_at, name: "Zzz...") + poll2 = create(:poll, geozone_restricted: true, starts_at: starts_at, name: "Mmmm...") + poll3 = create(:poll, geozone_restricted: true, starts_at: starts_at, name: "Aaaaah!") + + expect(Poll.sort_for_list).to eq [poll3, poll2, poll1] + end + + it "returns not geozone restricted polls first" do + starts_at = Time.current + 1.day + poll1 = create(:poll, geozone_restricted: false, starts_at: starts_at, name: "Zzz...") + poll2 = create(:poll, geozone_restricted: true, starts_at: starts_at, name: "Aaaaaah!") + + expect(Poll.sort_for_list).to eq [poll1, poll2] + end + + it "returns polls earlier to start first" do + starts_at = Time.current + 1.day + poll1 = create(:poll, geozone_restricted: false, starts_at: starts_at - 1.hour, name: "Zzz...") + poll2 = create(:poll, geozone_restricted: false, starts_at: starts_at, name: "Aaaaah!") + + expect(Poll.sort_for_list).to eq [poll1, poll2] + end + + it "returns polls with multiple translations only once" do + create(:poll, name_en: "English", name_es: "Spanish") + + expect(Poll.sort_for_list.count).to eq 1 + end + + context "fallback locales" do + before do + allow(I18n.fallbacks).to receive(:[]).and_return([:es]) + Globalize.set_fallbacks_to_all_available_locales + end + + it "orders by name considering fallback locales" do + starts_at = Time.current + 1.day + poll1 = create(:poll, starts_at: starts_at, name: "Charlie") + poll2 = create(:poll, starts_at: starts_at, name: "Delta") + poll3 = Globalize.with_locale(:es) do + create(:poll, starts_at: starts_at, name: "Zzz...", name_fr: "Aaaah!") + end + poll4 = Globalize.with_locale(:es) do + create(:poll, starts_at: starts_at, name: "Bravo") + end + + expect(Poll.sort_for_list.count).to eq 4 + expect(Poll.sort_for_list).to eq [poll4, poll1, poll2, poll3] + end + end + end end diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index b55cbba9f..7d5d8067c 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -1084,4 +1084,32 @@ describe Proposal do end + describe "milestone_tags" do + + context "without milestone_tags" do + + let(:proposal) {create(:proposal)} + + it "do not have milestone_tags" do + expect(proposal.milestone_tag_list).to eq([]) + expect(proposal.milestone_tags).to eq([]) + end + + it "add a new milestone_tag" do + proposal.milestone_tag_list = "tag1,tag2" + + expect(proposal.milestone_tag_list).to eq(["tag1", "tag2"]) + end + end + + context "with milestone_tags" do + + let(:proposal) {create(:proposal, :with_milestone_tags)} + + it "has milestone_tags" do + expect(proposal.milestone_tag_list.count).to eq(1) + end + end + end + end diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb index c0e060a3c..fde1615cd 100644 --- a/spec/models/setting_spec.rb +++ b/spec/models/setting_spec.rb @@ -17,6 +17,16 @@ describe Setting do expect(described_class.where(key: "official_level_1_name", value: "Stormtrooper")).to exist end + describe "#prefix" do + it "returns the prefix of its key" do + expect(Setting.create(key: "prefix.key_name").prefix).to eq "prefix" + end + + it "returns the whole key for a non prefixed key" do + expect(Setting.create(key: "key_name").prefix).to eq "key_name" + end + end + describe "#type" do it "returns the key prefix for 'process' settings" do process_setting = Setting.create(key: "process.whatever") @@ -70,6 +80,26 @@ describe Setting do end end + describe "#content_type?" do + it "returns true if the last part of the key is content_types" do + expect(Setting.create(key: "key_name.content_types").content_type?).to be true + end + + it "returns false if the last part of the key is not content_types" do + expect(Setting.create(key: "key_name.whatever").content_type?).to be false + end + end + + describe "#content_type_group" do + it "returns the group for content_types settings" do + images = Setting.create(key: "update.images.content_types") + documents = Setting.create(key: "update.documents.content_types") + + expect(images.content_type_group).to eq "images" + expect(documents.content_type_group).to eq "documents" + end + end + describe ".rename_key" do it "renames the setting keeping the original value and deletes the old setting" do Setting["old_key"] = "old_value" @@ -117,4 +147,78 @@ describe Setting do expect(Setting.all).to eq all_settings end end + + describe ".accepted_content_types_for" do + it "returns the formats accepted according to the setting value" do + Setting["uploads.images.content_types"] = "image/jpeg image/gif" + Setting["uploads.documents.content_types"] = "application/pdf application/msword" + + expect(Setting.accepted_content_types_for("images")).to eq ["jpg", "gif"] + expect(Setting.accepted_content_types_for("documents")).to eq ["pdf", "doc"] + end + + it "returns empty array if setting does't exist" do + Setting.remove("uploads.images.content_types") + Setting.remove("uploads.documents.content_types") + + expect(Setting.accepted_content_types_for("images")).to be_empty + expect(Setting.accepted_content_types_for("documents")).to be_empty + end + end + + describe ".add_new_settings" do + context "default settings with strings" do + before do + allow(Setting).to receive(:defaults).and_return({ stub: "stub" }) + end + + it "creates the setting if it doesn't exist" do + expect(Setting.where(key: :stub)).to be_empty + + Setting.add_new_settings + + expect(Setting.where(key: :stub)).not_to be_empty + expect(Setting.find_by(key: :stub).value).to eq "stub" + end + + it "doesn't modify custom values" do + Setting["stub"] = "custom" + + Setting.add_new_settings + + expect(Setting.find_by(key: :stub).value).to eq "custom" + end + + it "doesn't modify custom nil values" do + Setting["stub"] = nil + + Setting.add_new_settings + + expect(Setting.find_by(key: :stub).value).to be_nil + end + end + + context "nil default settings" do + before do + allow(Setting).to receive(:defaults).and_return({ stub: nil }) + end + + it "creates the setting if it doesn't exist" do + expect(Setting.where(key: :stub)).to be_empty + + Setting.add_new_settings + + expect(Setting.where(key: :stub)).not_to be_empty + expect(Setting.find_by(key: :stub).value).to be_nil + end + + it "doesn't modify custom values" do + Setting["stub"] = "custom" + + Setting.add_new_settings + + expect(Setting.find_by(key: :stub).value).to eq "custom" + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 599459324..d11df7760 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" describe User do describe "#headings_voted_within_group" do - it "returns the headings voted by a user ordered by name" do + it "returns the headings voted by a user" do user1 = create(:user) user2 = create(:user) @@ -35,6 +35,15 @@ describe User do expect(user2.headings_voted_within_group(group)).not_to include(san_franciso) expect(user2.headings_voted_within_group(group)).not_to include(another_heading) end + + it "returns headings with multiple translations only once" do + user = create(:user) + group = create(:budget_group) + heading = create(:budget_heading, group: group, name_en: "English", name_es: "Spanish") + create(:vote, votable: create(:budget_investment, heading: heading), voter: user) + + expect(user.headings_voted_within_group(group).count).to eq 1 + end end describe "#debate_votes" do diff --git a/spec/shared/models/image_validations.rb b/spec/shared/models/image_validations.rb index 691d73bae..c2b02a636 100644 --- a/spec/shared/models/image_validations.rb +++ b/spec/shared/models/image_validations.rb @@ -41,7 +41,8 @@ shared_examples "image validations" do |imageable_factory| end it "is not valid for attachments larger than imageable max_file_size definition" do - allow(image).to receive(:attachment_file_size).and_return(Image::MAX_IMAGE_SIZE + 1.byte) + larger_size = Setting["uploads.images.max_size"].to_i.megabytes + 1.byte + allow(image).to receive(:attachment_file_size).and_return(larger_size) expect(image).not_to be_valid expect(image.errors[:attachment]).to include "must be in between 0 Bytes and 1 MB" @@ -67,4 +68,4 @@ shared_examples "image validations" do |imageable_factory| expect(image).not_to be_valid end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 513718dd3..a1c4b58a8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -45,6 +45,9 @@ RSpec.configure do |config| DatabaseCleaner.strategy = :transaction I18n.locale = :en Globalize.locale = I18n.locale + unless %i[controller feature request].include? example.metadata[:type] + Globalize.set_fallbacks_to_all_available_locales + end load Rails.root.join("db", "seeds.rb").to_s Setting["feature.user.skip_verification"] = nil end
    - <%= form_for(feature, url: admin_setting_path(feature), html: { id: "edit_#{dom_id(feature)}"}) do |f| %> - - <%= f.hidden_field :value, id: dom_id(feature), value: (feature.enabled? ? "" : "active") %> - <%= f.submit(t("admin.settings.index.features.#{feature.enabled? ? "disable" : "enable"}"), - class: "button expanded #{feature.enabled? ? "hollow alert" : "success"}", - data: {confirm: t("admin.actions.confirm")}) %> - <% end %> + <%= render "admin/settings/featured_settings_form", feature: feature %>
    - <%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %> -
    - <%= f.text_area :value, label: false, id: dom_id(setting), lines: 1 %> -
    -
    - <%= f.submit(t("admin.settings.index.update_setting"), class: "button hollow expanded") %> -
    + <% if setting.content_type? %> + <%= render "admin/settings/content_types_settings_form", setting: setting %> + <% else %> + <%= render "admin/settings/settings_form", setting: setting %> <% end %>
    + +

    + <%= t("mailers.evaluation_comment.title", investment: @email.commentable.title) %> +

    + +

    + <%= t("mailers.evaluation_comment.hi") %> <%= @email_to.name %>, +

    + +

    + <%= t("mailers.evaluation_comment.new_comment_by_html", commenter: @email.comment.author.name, investment: valuation_comments_link(@email.commentable)) %> +

    + + <%= t("mailers.evaluation_comment.commenter_info", commenter: @email.comment.author.name, time: l(@email.comment.created_at)) %> +
    + <%= simple_format text_with_links(@email.comment.body), {}, sanitize: false %> +
    +