diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 218c9ebb6..5a6639f0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,21 +34,12 @@ If you want to contribute code to solve an issue: * Fork the project. * Create a topic branch based on master. * Commit there your code to solve the issue. -* Make sure all test are passing (and add specs to test any new feature if needed). +* Make sure all test are passing (and add specs to test any new feature you've added). * Follow these [best practices](https://github.com/styleguide/ruby) * Open a *pull request* to the main repository describing what issue you are addressing. **Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) -## Cleaning up - -In the rush of time sometimes things get messy, you can help us cleaning things up: - -* implement [pending specs](https://travis-ci.org/consul/consul) -* increase [code coverage](https://coveralls.io/github/consul/consul?branch=master) -* improve [code quality](https://codeclimate.com/github/consul/consul) -* make [code consistent](https://github.com/bbatsov/rubocop) - ## Other ways of contributing without coding * If you think there's a feature missing, or find a bug, create an issue (make sure it has not already been reported). diff --git a/Dockerfile b/Dockerfile index 880d17e43..52547d243 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,11 @@ +# Use Ruby 2.3.6 as base image FROM ruby:2.3.6 +ENV DEBIAN_FRONTEND noninteractive + # Install essential Linux packages -RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client nodejs imagemagick sudo +RUN apt-get update -qq +RUN apt-get install -y build-essential libpq-dev postgresql-client nodejs imagemagick sudo libxss1 libappindicator1 libindicator7 unzip memcached # Files created inside the container repect the ownership RUN adduser --shell /bin/bash --disabled-password --gecos "" consul \ @@ -34,13 +38,22 @@ COPY Gemfile_custom Gemfile_custom # Prevent bundler warnings; ensure that the bundler version executed is >= that which created Gemfile.lock RUN gem install bundler -# Finish establishing our Ruby enviornment +# Finish establishing our Ruby environment RUN bundle install --full-index +# Install Chromium and ChromeDriver for E2E integration tests +RUN apt-get update -qq && apt-get install -y chromium +RUN wget -N http://chromedriver.storage.googleapis.com/2.40/chromedriver_linux64.zip +RUN unzip chromedriver_linux64.zip +RUN chmod +x chromedriver +RUN mv -f chromedriver /usr/local/share/chromedriver +RUN ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver +RUN ln -s /usr/local/share/chromedriver /usr/bin/chromedriver + # Copy the Rails application into place COPY . . # Define the script we want run once the container boots # Use the "exec" form of CMD so our script shuts down gracefully on SIGTERM (i.e. `docker stop`) -#CMD [ "config/containers/app_cmd.sh" ] +# CMD [ "config/containers/app_cmd.sh" ] CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"] diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 44258bc7a..0d5b962c1 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -33,6 +33,7 @@ //= require moderator_comment //= require moderator_debates //= require moderator_proposals +//= require moderator_budget_investments //= require moderator_proposal_notifications //= require prevent_double_submission //= require gettext diff --git a/app/assets/javascripts/moderator_budget_investments.js.coffee b/app/assets/javascripts/moderator_budget_investments.js.coffee new file mode 100644 index 000000000..612a058a4 --- /dev/null +++ b/app/assets/javascripts/moderator_budget_investments.js.coffee @@ -0,0 +1,8 @@ +App.ModeratorBudgetInvestments = + + add_class_faded: (id) -> + $("##{id}").addClass("faded") + $("#comments").addClass("faded") + + hide_moderator_actions: (id) -> + $("##{id} .js-moderator-investment-actions:first").hide() diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index a8d81a12d..7b74b2697 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -46,6 +46,10 @@ $sidebar-active: #f4fcd0; .top-links { background: #000; + + a { + line-height: rem-calc($line-height * 1.5); + } } .admin-top-bar { @@ -190,6 +194,11 @@ $sidebar-active: #f4fcd0; &.with-button { line-height: $line-height * 2; + + .button { + background: #fff; + color: $brand; + } } } @@ -204,6 +213,19 @@ $sidebar-active: #f4fcd0; table { + thead { + color: #fff; + } + + th { + background: $brand; + color: #fff; + + label { + color: #fff; + } + } + .break { word-break: break-word; } @@ -220,7 +242,6 @@ $sidebar-active: #f4fcd0; @include breakpoint(medium) { margin-left: $line-height / 2; - margin-right: $line-height / 2; margin-top: 0; } } @@ -335,6 +356,14 @@ $sidebar-active: #f4fcd0; margin-bottom: 0 !important; } +.enabled { + color: $color-success; +} + +.disabled { + color: $text-medium; +} + // 02. Sidebar // ----------- diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index a140fca4c..8172d13ab 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -878,11 +878,6 @@ footer { h1 { margin-top: $line-height; - img { - height: rem-calc(80); - width: rem-calc(80); - } - a { color: #fff; display: block; diff --git a/app/controllers/admin/hidden_budget_investments_controller.rb b/app/controllers/admin/hidden_budget_investments_controller.rb new file mode 100644 index 000000000..439f3c722 --- /dev/null +++ b/app/controllers/admin/hidden_budget_investments_controller.rb @@ -0,0 +1,34 @@ +class Admin::HiddenBudgetInvestmentsController < Admin::BaseController + include FeatureFlags + + has_filters %w{all with_confirmed_hide without_confirmed_hide}, only: :index + + feature_flag :budgets + + before_action :load_investment, only: [:confirm_hide, :restore] + + def index + @investments = Budget::Investment.only_hidden.send(@current_filter) + .order(hidden_at: :desc) + .page(params[:page]) + end + + def confirm_hide + @investment.confirm_hide + redirect_to request.query_parameters.merge(action: :index) + end + + def restore + @investment.restore + @investment.ignore_flag + Activity.log(current_user, :restore, @investment) + redirect_to request.query_parameters.merge(action: :index) + end + + private + + def load_investment + @investment = Budget::Investment.with_hidden.find(params[:id]) + end + +end diff --git a/app/controllers/admin/site_customization/information_texts_controller.rb b/app/controllers/admin/site_customization/information_texts_controller.rb new file mode 100644 index 000000000..25594fc4a --- /dev/null +++ b/app/controllers/admin/site_customization/information_texts_controller.rb @@ -0,0 +1,90 @@ +class Admin::SiteCustomization::InformationTextsController < Admin::SiteCustomization::BaseController + include Translatable + + def index + fetch_existing_keys + append_or_create_keys + @content = @content[@tab.to_s] + end + + def update + content_params.each do |content| + values = content[:values].slice(*translation_params(content[:values])) + + unless values.empty? + values.each do |key, value| + locale = key.split("_").last + + if value == t(content[:id], locale: locale) || value.match(/translation missing/) + next + else + text = I18nContent.find_or_create_by(key: content[:id]) + Globalize.locale = locale + text.update(value: value) + end + end + end + + end + + redirect_to admin_site_customization_information_texts_path, + notice: t('flash.actions.update.translation') + end + + private + + def i18n_content_params + attributes = [:key, :value] + params.require(:information_texts).permit(*attributes, translation_params(params[:information_texts])) + end + + def resource_model + I18nContent + end + + def resource + resource_model.find(content_params[:id]) + end + + def content_params + params.require(:contents).values + end + + def delete_translations + languages_to_delete = params[:delete_translations].select { |k, v| params[:delete_translations][k] == '1' }.keys + languages_to_delete.each do |locale| + I18nContentTranslation.destroy_all(locale: locale) + end + end + + def fetch_existing_keys + @existing_keys = {} + @tab = params[:tab] || :debates + + I18nContent.begins_with_key(@tab) + .all + .map{ |content| @existing_keys[content.key] = content } + end + + def append_or_create_keys + @content = {} + I18n.backend.send(:init_translations) unless I18n.backend.initialized? + + locale = params[:locale] || I18n.locale + translations = I18n.backend.send(:translations)[locale.to_sym] + + translations.each do |k, v| + @content[k.to_s] = flat_hash(v).keys + .map { |s| @existing_keys["#{k.to_s}.#{s}"].nil? ? + I18nContent.new(key: "#{k.to_s}.#{s}") : + @existing_keys["#{k.to_s}.#{s}"] } + end + end + + def flat_hash(h, f = nil, g = {}) + return g.update({ f => h }) unless h.is_a? Hash + h.each { |k, r| flat_hash(r, [f,k].compact.join('.'), g) } + return g + end + +end diff --git a/app/controllers/concerns/flag_actions.rb b/app/controllers/concerns/flag_actions.rb index 3f5d0b2c7..da637407a 100644 --- a/app/controllers/concerns/flag_actions.rb +++ b/app/controllers/concerns/flag_actions.rb @@ -3,18 +3,32 @@ module FlagActions def flag Flag.flag(current_user, flaggable) - respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + + if controller_name == 'investments' + respond_with flaggable, template: "budgets/#{controller_name}/_refresh_flag_actions" + else + respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + end end def unflag Flag.unflag(current_user, flaggable) - respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + + if controller_name == 'investments' + respond_with flaggable, template: "budgets/#{controller_name}/_refresh_flag_actions" + else + respond_with flaggable, template: "#{controller_name}/_refresh_flag_actions" + end end private def flaggable - instance_variable_get("@#{resource_model.to_s.downcase}") + if resource_model.to_s == 'Budget::Investment' + instance_variable_get("@investment") + else + instance_variable_get("@#{resource_model.to_s.downcase}") + end end -end \ No newline at end of file +end diff --git a/app/controllers/concerns/polymorphic.rb b/app/controllers/concerns/polymorphic.rb index 8fd4ab312..0e25b4231 100644 --- a/app/controllers/concerns/polymorphic.rb +++ b/app/controllers/concerns/polymorphic.rb @@ -3,7 +3,11 @@ module Polymorphic private def resource - @resource ||= instance_variable_get("@#{resource_name}") + if resource_model.to_s == 'Budget::Investment' + @resource ||= instance_variable_get("@investment") + else + @resource ||= instance_variable_get("@#{resource_name}") + end end def resource_name diff --git a/app/controllers/management/base_controller.rb b/app/controllers/management/base_controller.rb index bcebfebaa..1bae36a76 100644 --- a/app/controllers/management/base_controller.rb +++ b/app/controllers/management/base_controller.rb @@ -6,6 +6,7 @@ class Management::BaseController < ActionController::Base helper_method :managed_user helper_method :current_user + helper_method :manager_logged_in private @@ -22,7 +23,10 @@ class Management::BaseController < ActionController::Base end def managed_user - @managed_user ||= Verification::Management::ManagedUser.find(session[:document_type], session[:document_number]) + @managed_user ||= Verification::Management::ManagedUser.find( + session[:document_type], + session[:document_number] + ) end def check_verified_user(alert_msg) @@ -49,4 +53,11 @@ class Management::BaseController < ActionController::Base def clear_password session[:new_password] = nil end + + def manager_logged_in + if current_manager + @manager_logged_in = User.find_by(id: session[:manager]["login"].last(1)) + end + end + end diff --git a/app/controllers/moderation/budgets/investments_controller.rb b/app/controllers/moderation/budgets/investments_controller.rb new file mode 100644 index 000000000..7f33058e8 --- /dev/null +++ b/app/controllers/moderation/budgets/investments_controller.rb @@ -0,0 +1,24 @@ +class Moderation::Budgets::InvestmentsController < Moderation::BaseController + include FeatureFlags + include ModerateActions + + has_filters %w{pending_flag_review all with_ignored_flag}, only: :index + has_orders %w{flags created_at}, only: :index + + feature_flag :budgets + + before_action :load_resources, only: [:index, :moderate] + + load_and_authorize_resource class: 'Budget::Investment' + + private + + def resource_name + 'budget_investment' + end + + def resource_model + Budget::Investment + end + +end diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index 7130e661d..7bb872257 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -14,6 +14,7 @@ class StatsController < ApplicationController @debate_votes = daily_cache('debate_votes') { Vote.where(votable_type: 'Debate').count } @proposal_votes = daily_cache('proposal_votes') { Vote.where(votable_type: 'Proposal').count } @comment_votes = daily_cache('comment_votes') { Vote.where(votable_type: 'Comment').count } + @investment_votes = daily_cache('budget_investment_votes') { Vote.where(votable_type: 'Budget::Investment').count } @votes = daily_cache('votes') { Vote.count } @verified_users = daily_cache('verified_users') { User.with_hidden.level_two_or_three_verified.count } diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 5605e3a00..d16f9c87e 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -11,7 +11,7 @@ class Users::SessionsController < Devise::SessionsController end def after_sign_out_path_for(resource) - request.referer.present? ? request.referer : super + request.referer.present? && !request.referer.match("management") ? request.referer : super end def verifying_via_email? diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 988d03a00..772dcb9d3 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -1,19 +1,31 @@ module AdminHelper def side_menu - render "/#{namespace}/menu" + if namespace == 'moderation/budgets' + render "/moderation/menu" + else + render "/#{namespace}/menu" + end end def namespaced_root_path - "/#{namespace}" + if namespace == 'moderation/budgets' + "/moderation" + else + "/#{namespace}" + end end def namespaced_header_title - t("#{namespace}.header.title") + if namespace == 'moderation/budgets' + t("moderation.header.title") + else + t("#{namespace}.header.title") + end end def menu_moderated_content? - ["proposals", "debates", "comments", "hidden_users", "activity"].include?(controller_name) && controller.class.parent != Admin::Legislation + ["proposals", "debates", "comments", "hidden_users", "activity", "hidden_budget_investments"].include?(controller_name) && controller.class.parent != Admin::Legislation end def menu_budget? @@ -21,11 +33,11 @@ module AdminHelper end def menu_polls? - %w[polls questions answers].include?(controller_name) + %w[polls questions answers recounts results].include?(controller_name) end def menu_booths? - %w[officers booths officer_assignments booth_assignments recounts results shifts].include?(controller_name) + %w[officers booths shifts booth_assignments officer_assignments].include?(controller_name) end def menu_profiles? @@ -37,7 +49,7 @@ module AdminHelper end def menu_customization? - ["pages", "banners"].include?(controller_name) || menu_homepage? + ["pages", "banners", "information_texts"].include?(controller_name) || menu_homepage? end def menu_homepage? diff --git a/app/helpers/site_customization_helper.rb b/app/helpers/site_customization_helper.rb new file mode 100644 index 000000000..d9318fd9b --- /dev/null +++ b/app/helpers/site_customization_helper.rb @@ -0,0 +1,5 @@ +module SiteCustomizationHelper + def site_customization_display_translation?(locale) + I18nContentTranslation.existing_languages.include?(neutral_locale(locale)) || locale == I18n.locale ? "" : "display: none" + end +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 980dd1e39..4098b535e 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -52,8 +52,8 @@ module UsersHelper current_user && current_user.manager? end - def show_admin_menu? - current_administrator? || current_moderator? || current_valuator? || current_manager? + def show_admin_menu?(user = nil) + current_administrator? || current_moderator? || current_valuator? || current_manager? || (user && user.administrator?) end def interests_title_text(user) diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index e8bb005d4..40e951326 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -17,6 +17,9 @@ module Abilities can :restore, Legislation::Proposal cannot :restore, Legislation::Proposal, hidden_at: nil + can :restore, Budget::Investment + cannot :restore, Budget::Investment, hidden_at: nil + can :restore, User cannot :restore, User, hidden_at: nil @@ -32,6 +35,9 @@ module Abilities can :confirm_hide, Legislation::Proposal cannot :confirm_hide, Legislation::Proposal, hidden_at: nil + can :confirm_hide, Budget::Investment + cannot :confirm_hide, Budget::Investment, hidden_at: nil + can :confirm_hide, User cannot :confirm_hide, User, hidden_at: nil diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index d8d716ec2..7c84089b4 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -46,6 +46,9 @@ module Abilities can [:flag, :unflag], Legislation::Proposal cannot [:flag, :unflag], Legislation::Proposal, author_id: user.id + can [:flag, :unflag], Budget::Investment + cannot [:flag, :unflag], Budget::Investment, author_id: user.id + can [:create, :destroy], Follow can [:destroy], Document, documentable: { author_id: user.id } diff --git a/app/models/abilities/moderation.rb b/app/models/abilities/moderation.rb index 801e752ed..86f44f25e 100644 --- a/app/models/abilities/moderation.rb +++ b/app/models/abilities/moderation.rb @@ -63,6 +63,15 @@ module Abilities cannot :moderate, ProposalNotification, author_id: user.id can :index, ProposalNotification + + can :hide, Budget::Investment, hidden_at: nil + cannot :hide, Budget::Investment, author_id: user.id + + can :ignore_flag, Budget::Investment, ignored_flag_at: nil, hidden_at: nil + cannot :ignore_flag, Budget::Investment, author_id: user.id + + can :moderate, Budget::Investment + cannot :moderate, Budget::Investment, author_id: user.id end end end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index d69a0d64a..e531612a8 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -23,6 +23,7 @@ class Budget include Relationable include Notifiable include Filterable + include Flaggable belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :heading @@ -81,6 +82,8 @@ class Budget scope :winners, -> { selected.compatible.where(winner: true) } scope :unselected, -> { not_unfeasible.where(selected: false) } scope :last_week, -> { where("created_at >= ?", 7.days.ago)} + scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } + scope :sort_by_created_at, -> { reorder(created_at: :desc) } scope :by_budget, ->(budget) { where(budget: budget) } scope :by_group, ->(group_id) { where(group_id: group_id) } diff --git a/app/models/i18n_content.rb b/app/models/i18n_content.rb new file mode 100644 index 000000000..c291e696c --- /dev/null +++ b/app/models/i18n_content.rb @@ -0,0 +1,11 @@ +class I18nContent < ActiveRecord::Base + + scope :by_key, -> (key){ where(key: key) } + scope :begins_with_key, -> (key){ where("key ILIKE ?", "#{key}?%") } + + validates :key, uniqueness: true + + translates :value, touch: true + globalize_accessors locales: [:en, :es, :fr, :nl] + +end diff --git a/app/models/i18n_content_translation.rb b/app/models/i18n_content_translation.rb new file mode 100644 index 000000000..76447d83e --- /dev/null +++ b/app/models/i18n_content_translation.rb @@ -0,0 +1,5 @@ +class I18nContentTranslation < ActiveRecord::Base + def self.existing_languages + self.select(:locale).uniq.map{ |l| l.locale.to_sym }.to_a + end +end diff --git a/app/models/site_customization/image.rb b/app/models/site_customization/image.rb index e336f6f9a..b10f3799f 100644 --- a/app/models/site_customization/image.rb +++ b/app/models/site_customization/image.rb @@ -1,7 +1,7 @@ class SiteCustomization::Image < ActiveRecord::Base VALID_IMAGES = { "icon_home" => [330, 240], - "logo_header" => [80, 80], + "logo_header" => [260, 80], "social_media_icon" => [470, 246], "social_media_icon_twitter" => [246, 246], "apple-touch-icon-200" => [200, 200] diff --git a/app/models/user.rb b/app/models/user.rb index 67fe5d104..4b999207d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -182,6 +182,7 @@ class User < ActiveRecord::Base debates_ids = Debate.where(author_id: id).pluck(:id) comments_ids = Comment.where(user_id: id).pluck(:id) proposal_ids = Proposal.where(author_id: id).pluck(:id) + investment_ids = Budget::Investment.where(author_id: id).pluck(:id) proposal_notification_ids = ProposalNotification.where(author_id: id).pluck(:id) hide @@ -189,6 +190,7 @@ class User < ActiveRecord::Base Debate.hide_all debates_ids Comment.hide_all comments_ids Proposal.hide_all proposal_ids + Budget::Investment.hide_all investment_ids ProposalNotification.hide_all proposal_notification_ids end diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index ffec03232..911bc0713 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -7,7 +7,7 @@ <%= t("admin.menu.title_polls") %>