diff --git a/README.md b/README.md index 27162bfd7..2690b84a7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Frontend tools used include [SCSS](http://sass-lang.com/) over [Foundation](http ## Configuration for development and test environments +**NOTE**: For more detailed instructions check the [docs](https://github.com/consul/consul/tree/master/doc/en/dev_test_setup.md) + Prerequisites: install git, Ruby 2.2.3, bundler gem, ghostscript and PostgreSQL (>=9.4). ``` diff --git a/README_ES.md b/README_ES.md index d8c394680..6051f1343 100644 --- a/README_ES.md +++ b/README_ES.md @@ -23,6 +23,8 @@ Las herramientas utilizadas para el frontend no están cerradas aún. Los estilo ## Configuración para desarrollo y tests +**NOTA**: para unas instrucciones más detalladas consulta la [documentación](https://github.com/consul/consul/tree/master/doc/es/dev_test_setup.md) + Prerequisitos: tener instalado git, Ruby 2.2.3, la gema `bundler`, ghostscript y PostgreSQL (9.4 o superior). ``` diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 673d27511..7c8e6b477 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -270,6 +270,21 @@ } } +.message { + @include supports; + background: none; + border-top: 0; + + @include breakpoint(medium) { + border-left: 1px solid $border; + margin: $line-height rem-calc(-25) 0 rem-calc(12); + } + + p { + font-size: $small-font-size; + } +} + // 02. New participation // --------------------- diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb index 3a79238ea..3f4398797 100644 --- a/app/controllers/concerns/commentable_actions.rb +++ b/app/controllers/concerns/commentable_actions.rb @@ -12,7 +12,7 @@ module CommentableActions @tag_cloud = tag_cloud @banners = Banner.with_active - + set_resource_votes(@resources) set_resources_instance end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index f49666507..ede5b0f5d 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -12,7 +12,7 @@ class ProposalsController < ApplicationController invisible_captcha only: [:create, :update], honeypot: :subtitle - has_orders %w{hot_score confidence_score created_at relevance}, only: :index + has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index has_orders %w{most_voted newest oldest}, only: :show load_and_authorize_resource @@ -26,6 +26,7 @@ class ProposalsController < ApplicationController end def index_customization + discard_archived load_retired load_proposal_ballots load_featured unless @proposal_successfull_exists @@ -81,6 +82,10 @@ class ProposalsController < ApplicationController @featured_proposals_votes = current_user ? current_user.proposal_votes(proposals) : {} end + def discard_archived + @resources = @resources.not_archived unless @current_order == "archival_date" + end + def load_retired if params[:retired].present? @resources = @resources.retired @@ -91,7 +96,7 @@ class ProposalsController < ApplicationController end def load_featured - @featured_proposals = Proposal.all.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?) + @featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?) if @featured_proposals.present? set_featured_proposal_votes(@featured_proposals) @resources = @resources.where('proposals.id NOT IN (?)', @featured_proposals.map(&:id)) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 4c3cc9e40..fc3d10738 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -44,6 +44,9 @@ class Proposal < ActiveRecord::Base scope :sort_by_random, -> { reorder("RANDOM()") } scope :sort_by_relevance, -> { all } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } + scope :sort_by_archival_date, -> { archived.order(created_at: :desc) } + scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago)} + scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago)} scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)} scope :retired, -> { where.not(retired_at: nil) } scope :not_retired, -> { where(retired_at: nil) } @@ -120,7 +123,7 @@ class Proposal < ActiveRecord::Base end def register_vote(user, vote_value) - if votable_by?(user) + if votable_by?(user) && !archived? vote_by(voter: user, vote: vote_value) end end @@ -160,6 +163,10 @@ class Proposal < ActiveRecord::Base total_votes >= Proposal.votes_needed_for_success end + def archived? + self.created_at <= Setting["months_to_archive_proposals"].to_i.months.ago + end + def notifications proposal_notifications end diff --git a/app/views/proposals/_featured_proposal.html.erb b/app/views/proposals/_featured_proposal.html.erb index 623932c38..5500b2418 100644 --- a/app/views/proposals/_featured_proposal.html.erb +++ b/app/views/proposals/_featured_proposal.html.erb @@ -7,7 +7,16 @@ <% else %> <%= proposal.author.name %> <% end %> +  •  + + <% if proposal.author.display_official_position_badge? %> + + <%= proposal.author.official_position %> + +  •  + <% end %> + <%= t("proposals.proposal.supports", count: proposal.total_votes) %> diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb index 464710493..e35f9f496 100644 --- a/app/views/proposals/_proposal.html.erb +++ b/app/views/proposals/_proposal.html.erb @@ -59,6 +59,10 @@ <%= t("proposal_ballots.successfull", voting: link_to(t("proposal_ballots.voting"), proposal_ballots_path)).html_safe %>

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

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

<% else %>
diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index da429b578..5efa3e24c 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -54,8 +54,10 @@ <%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button expanded' %>
- <%= render partial: 'proposals/proposal', collection: @proposals %> - <%= paginate @proposals %> +
+ <%= render partial: 'proposals/proposal', collection: @proposals %> + <%= paginate @proposals %> +
diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index f19a21f71..5cb7867af 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -108,6 +108,11 @@ <%= t("proposal_ballots.successfull", voting: link_to(t("proposal_ballots.voting"), proposal_ballots_path)).html_safe %>

+ <% elsif @proposal.archived? %> +

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

+

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

<% else %>
<%= render 'votes', @@ -115,7 +120,6 @@
<% end %>
-

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

diff --git a/bin/rails b/bin/rails index 4d608edeb..0a7aba05e 100755 --- a/bin/rails +++ b/bin/rails @@ -1,8 +1,17 @@ #!/usr/bin/env ruby -begin - load File.expand_path("../spring", __FILE__) -rescue LoadError -end -APP_PATH = File.expand_path('../../config/application', __FILE__) -require_relative '../config/boot' -require 'rails/commands' +# frozen_string_literal: true +# +# This file was generated by Bundler. +# +# The application 'rails' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("railties", "rails") diff --git a/bin/rake b/bin/rake index 8017a0271..486010f46 100755 --- a/bin/rake +++ b/bin/rake @@ -1,8 +1,17 @@ #!/usr/bin/env ruby -begin - load File.expand_path("../spring", __FILE__) -rescue LoadError -end -require_relative '../config/boot' -require 'rake' -Rake.application.run +# frozen_string_literal: true +# +# This file was generated by Bundler. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/bin/rspec b/bin/rspec index 5a3c87c5f..d738b23c0 100755 --- a/bin/rspec +++ b/bin/rspec @@ -1,9 +1,5 @@ #!/usr/bin/env ruby -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end +# frozen_string_literal: true # # This file was generated by Bundler. # @@ -11,11 +7,11 @@ end # this file is here to facilitate running it. # -require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) -require 'rubygems' -require 'bundler/setup' +require "rubygems" +require "bundler/setup" -load Gem.bin_path('rspec-core', 'rspec') +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/spring b/bin/spring index 62ec28f8c..43731fe82 100755 --- a/bin/spring +++ b/bin/spring @@ -1,15 +1,17 @@ #!/usr/bin/env ruby +# frozen_string_literal: true +# +# This file was generated by Bundler. +# +# The application 'spring' is installed as part of a gem, and +# this file is here to facilitate running it. +# -# This file loads spring without using Bundler, in order to be fast. -# It gets overwritten when you run the `spring binstub` command. +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) -unless defined?(Spring) - require 'rubygems' - require 'bundler' +require "rubygems" +require "bundler/setup" - if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) - Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq } - gem 'spring', match[1] - require 'spring/binstub' - end -end +load Gem.bin_path("spring", "spring") diff --git a/config/locales/en.yml b/config/locales/en.yml index 28fb793f6..233ebf735 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -302,6 +302,7 @@ en: hot_score: most active most_commented: most commented relevance: relevance + archival_date: Archived retired_proposals: Retired proposals retired_proposals_link: "Proposals retired by the author" retired_links: @@ -351,6 +352,7 @@ en: zero: No supports supports_necessary: "%{number} supports needed" total_percent: 100% + archived: "This proposal has been archived and can't collect supports." show: author_deleted: User deleted code: 'Proposal code:' diff --git a/config/locales/es.yml b/config/locales/es.yml index 1b70f49e4..54aa483cd 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -302,6 +302,7 @@ es: hot_score: Más activas hoy most_commented: Más comentadas relevance: Más relevantes + archival_date: Archivadas retired_proposals: Propuestas retiradas retired_proposals_link: "Propuestas retiradas por sus autores" retired_links: @@ -351,6 +352,7 @@ es: zero: Sin apoyos supports_necessary: "%{number} apoyos necesarios" total_percent: 100% + archived: "Esta propuesta ha sido archivada y ya no puede recoger apoyos." show: author_deleted: Usuario eliminado code: 'Código de la propuesta:' diff --git a/config/locales/pages.es.yml b/config/locales/pages.es.yml index c9f282a4f..3876e8a1b 100644 --- a/config/locales/pages.es.yml +++ b/config/locales/pages.es.yml @@ -33,13 +33,13 @@ es: Tanto los hilos, como los comentarios podrán ser valorados por cualquiera, de tal manera que será la propia ciudadanía, y nadie en su nombre, la que decida cuáles son los temas más importantes en cada momento. Estos serán presentados en la portada del espacio, pudiendo por supuesto accederse a todos los demás temas en páginas posteriores, o usando otros criterios de ordenación (los temas con más comentarios, los más nuevos, los más controvertidos, etc.). - Cada uno de los trabajadores del Ayuntamiento tiene un usuario propio, que será resaltado como tal, permitiendo que participen en los debates al mismo nivel que todos los demás ciudadanos. Esto permitirá crear espacios de comunicación directos entre unos y otros, evitando los inconvenientes que implica la comunicación medidada, y respondiendo a un planteamiento claro por parte del nuevo gobierno por el cual el Ayuntamiento trabaja para la ciudadanía, y ante ella debe responder. + Cada uno de los trabajadores del Ayuntamiento tiene un usuario propio, que será resaltado como tal, permitiendo que participen en los debates al mismo nivel que todos los demás ciudadanos. Esto permitirá crear espacios de comunicación directos entre unos y otros, evitando los inconvenientes que implica la comunicación mediada, y respondiendo a un planteamiento claro por parte del nuevo gobierno por el cual el Ayuntamiento trabaja para la ciudadanía, y ante ella debe responder. ## I.I. Espacio de propuestas - En este espacio, cualquier persona puede proponer una iniciativa con la intención de recabar los suficientes apoyos como para que la idea pase a ser consultada a toda la ciudadanía con caracter vinculante. + En este espacio, cualquier persona puede proponer una iniciativa con la intención de recabar los suficientes apoyos como para que la idea pase a ser consultada a toda la ciudadanía con carácter vinculante. Las propuestas pueden ser apoyadas por ciudadanos empadronados que hayan verificado su cuenta en la plataforma de participación, de tal manera que será la propia ciudadanía, y nadie en su nombre, la que decida cuáles son las propuestas que merecen la pena ser llevadas a cabo. - Una vez que una propuesta alcance una cantidad de apoyos equivalente al 2% del censo, automaticamente pasa a ser estudiada por un grupo de trabajo del Ayuntamiento y pasará a la siguiente fase de consulta popular, en la que la ciudadanía votará si se lleva a cabo o no. El plazo máximo para recabar los apoyos necesarios será de 12 meses. + Una vez que una propuesta alcance una cantidad de apoyos equivalente al 2% del censo, automáticamente pasa a ser estudiada por un grupo de trabajo del Ayuntamiento y pasará a la siguiente fase de consulta popular, en la que la ciudadanía votará si se lleva a cabo o no. El plazo máximo para recabar los apoyos necesarios será de 12 meses. how_to_use: text: |- Utilízalo en tu municipio libremente o ayúdanos a mejorarlo, es software libre. diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index 32874e32b..0035eb556 100755 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -11,6 +11,7 @@ en: max_votes_for_debate_edit: "Number of votes from which a Debate can no longer be edited" proposal_code_prefix: "Prefix for Proposal codes" votes_for_proposal_success: "Number of votes necessary for approval of a Proposal" + months_to_archive_proposals: "Months to archive Proposals" email_domain_for_officials: "Email domain for public officials" per_page_code: "Code to be included on every page" feature: diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index aa5dea14e..1235762b4 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -11,6 +11,7 @@ es: max_votes_for_debate_edit: "Número de votos en que un Debate deja de poderse editar" proposal_code_prefix: "Prefijo para los códigos de Propuestas" votes_for_proposal_success: "Número de votos necesarios para aprobar una Propuesta" + months_to_archive_proposals: "Meses para archivar las Propuestas" email_domain_for_officials: "Dominio de email para cargos públicos" per_page_code: "Código a incluir en cada página" feature: diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index d9d6d2483..0e0543695 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -13,6 +13,7 @@ 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.create(key: 'twitter_handle', value: '@consul_dev') diff --git a/db/seeds.rb b/db/seeds.rb index 939a5f045..a5d8537db 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -31,6 +31,9 @@ Setting["proposal_code_prefix"] = 'MAD' # 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"] = '' diff --git a/doc/README.md b/doc/README.md index 89e43aad5..c588b82b8 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,21 +1,21 @@ # Funcionalidades -Actualmente Consul soporta: +Actualmente Consul soporta: -* Registro y verificación de usuarios tanto en la misma aplicación como con distintos proveedores (Twitter, Facebook, Google) -* Distintos perfiles de usuario, tanto ciudadanos individuales como organizaciones. -* Distintos perfiles de administración, gestión y moderación. -* Espacio permanente de debates y propuestas. -* Comentarios anidados en debates y propuestas. -* Presupuestos participativos a través de distintas fases. +* Registro y verificación de usuarios tanto en la misma aplicación como con distintos proveedores (Twitter, Facebook, Google). +* Distintos perfiles de usuario, tanto ciudadanos individuales como organizaciones. +* Distintos perfiles de administración, gestión y moderación. +* Espacio permanente de debates y propuestas. +* Comentarios anidados en debates y propuestas. +* Presupuestos participativos a través de distintas fases. # Usuario -Para registrar un usuario nuevo es posible hacerlo en la propia aplicación, dando un nombre de usuario (Nombre público que aparecerá en tus publicaciones), un correo electrónico y una contraseña con la que se accederá a la web. Se deben aceptar las condiciones de uso. El usuario debe confirmar su correo electrónico para poder iniciar sesión +Para registrar un usuario nuevo es posible hacerlo en la propia aplicación, dando un nombre de usuario (nombre público que aparecerá en tus publicaciones), un correo electrónico y una contraseña con la que se accederá a la web. Se deben aceptar las condiciones de uso. El usuario debe confirmar su correo electrónico para poder iniciar sesión. ![Registro de usuario](imgs/user_registration.png "Registro de usuario") -Por otro lado también se puede habilitar el registro a través de servicios externos como Twitter, Facebook y Google. Para esto hace falta tener la configuración habilitada en Settings y las claves y secretos de estos servicios en el fichero *config/secrets.yml*. +Por otro lado también se puede habilitar el registro a través de servicios externos como Twitter, Facebook y Google. Para esto hace falta tener la configuración habilitada en Settings y las claves y secretos de estos servicios en el fichero *config/secrets.yml*. ``` twitter_key: "" @@ -26,7 +26,7 @@ Por otro lado también se puede habilitar el registro a través de servicios ext google_oauth2_secret: "" ``` -Una vez el usuario ha iniciado sesión le aparecerá la posibilidad de verificar su cuenta, a través de una conexión con el padrón municipal. +Una vez el usuario ha iniciado sesión le aparecerá la posibilidad de verificar su cuenta, a través de una conexión con el padrón municipal. ![Verificación de usuario](imgs/user_preverification.png?raw=true "Verificación de usuario") @@ -36,17 +36,17 @@ Para esta funcionalidad hace falta que el padrón municipal soporte la posibilid # Perfil de usuario -Dentro de su perfil cada usuario puede configurar si quiere mostrar públicamente su lista de actividades, así como las notificaciones que le enviará la aplicación a través de correo electrónico. Estas notificiaciones pueden ser: +Dentro de su perfil cada usuario puede configurar si quiere mostrar públicamente su lista de actividades, así como las notificaciones que le enviará la aplicación a través de correo electrónico. Estas notificiaciones pueden ser: -* Recibir un email cuando alguien comenta en sus propuestas o debates -* Recibir un email cuando alguien contesta a sus comentarios -* Recibir emails con información interesante sobre la web -* Recibir resumen de notificaciones sobre propuestas -* Recibir emails con mensajes privados +* Recibir un email cuando alguien comenta en sus propuestas o debates. +* Recibir un email cuando alguien contesta a sus comentarios. +* Recibir emails con información interesante sobre la web. +* Recibir resumen de notificaciones sobre propuestas. +* Recibir emails con mensajes privados. -# Paneles de administración, gestión y moderación +# Paneles de administración, gestión y moderación -Consul cuenta con tres perfiles de usuario diferenciados para hacer tareas de revisión y moderación de los contenidos. Se detallan a continuación: +Consul cuenta con tres perfiles de usuario diferenciados para hacer tareas de revisión y moderación de los contenidos. Se detallan a continuación: ## Administración @@ -56,7 +56,7 @@ Desde aquí puedes administrar el sistema, a través de las siguientes acciones: ### Temas de debate -Los temas (también llamadas tags, o etiquetas) de debate son palabras que definen los usuarios al crear debates, para catalogarlos (ej: sanidad, movilidad, arganzuela, ...). Aquí se pueden eliminar temas inapropiados, o marcarlos para ser propuestos al crear debates (cada usuario puede definir los que quiera, pero se le sugieren algunos que nos parecen útiles como catalogación por defecto; aquí se puede cambiar cuáles se sugieren) +Los temas (también llamadas tags, o etiquetas) de debate son palabras que definen los usuarios al crear debates, para catalogarlos (ej: sanidad, movilidad, arganzuela, ...). Aquí se pueden eliminar temas inapropiados, o marcarlos para ser propuestos al crear debates (cada usuario puede definir los que quiera, pero se le sugieren algunos que nos parecen útiles como catalogación por defecto; aquí se puede cambiar cuáles se sugieren). ### Propuestas/Debates/Comentarios ocultos @@ -77,8 +77,6 @@ En la web hay dos tipos de usuarios: individuales y organizaciones. Cualquier pe En caso de que el proceso de verificación haya sido negativo, se pulsa el botón "Rechazar". Para editar alguno de los datos de la organización, se pulsa el botón "Editar". -En caso de que el proceso de verificación haya sido negativo, se pulsa el botón "Rechazar". Para editar alguno de los datos de la organización, se pulsa el botón "Editar". - Las organizaciones que no aparecen en la lista pueden ser encontradas para actuar sobre ellas por medio del buscador en la parte superior. Para facilitar la gestión, arriba encontramos un filtro con las secciones: "pendientes" (las organizaciones que todavía no han sido verificadas o rechazadas), "verificadas", "rechazadas" y "todas". Es recomendable revisar regularmente la sección "pendientes". @@ -89,7 +87,7 @@ En la web, los usuarios individuales pueden ser usuarios normales, o cargos púb ### Moderadores -Mediante el buscador de la parte superior se pueden buscar usuarios, para activarlos o desactivarlos como moderadores de la web. Los moderadores al acceder a la web con su usuario ven en la parte superior una nueva sección llamada "Moderar" +Mediante el buscador de la parte superior se pueden buscar usuarios, para activarlos o desactivarlos como moderadores de la web. Los moderadores al acceder a la web con su usuario ven en la parte superior una nueva sección llamada "Moderar". ### Actividad de moderadores @@ -103,7 +101,7 @@ Opciones generales de configuración del sistema. Estadísticas generales del sistema. -## Moderación +## Moderación ![Panel de moderación](imgs/panel_moderation.png?raw=true "Panel de moderación") @@ -116,13 +114,14 @@ Cuando un usuario marca en una Propuesta/Debate/Comentario la opción de "denunc A la derecha de cada elemento aparece una caja que podemos marcar para seleccionar todos los que queramos de la lista. Una vez seleccionados uno o varios, encontramos al final de la página tres botones para realizar acciones sobre ellos: * Ocultar: hará que esos elementos dejen de mostrarse en la web. -* Bloquear autores: hará que el autor de ese elemento deje de poder acceder a la web, y que además todos las Propuestas/Debates/Comentarios de ese usuario dejen de mostrarse en la web. +* Bloquear autores: hará que el autor de ese elemento deje de poder acceder a la web, y que además todas las Propuestas/Debates/Comentarios de ese usuario dejen de mostrarse en la web. * Marcar como revisados cuando consideramos que esos elementos no deben ser moderados, que su contenido es correcto, y que por lo tanto deben dejar de ser mostrados en esta lista de elementos inapropiados. Para facilitar la gestión, arriba encontramos un filtro con las secciones: -Pendientes: las Propuestas/Debates/Comentarios sobre los que todavía no se ha pulsado "ocultar", "bloquear" o "marcar como revisados", y que por lo tanto deberían ser revisados todavía -Todos: mostrando todos las Propuestas/Debates/Comentarios de la web, y no sólo los marcados como inapropiados. -Marcados como revisados: los que algún moderador ha marcado como revisados y por lo tanto parecen correctos. + +* Pendientes: las Propuestas/Debates/Comentarios sobre los que todavía no se ha pulsado "ocultar", "bloquear" o "marcar como revisados", y que por lo tanto deberían ser revisados todavía. +* Todos: mostrando todos las Propuestas/Debates/Comentarios de la web, y no sólo los marcados como inapropiados. +* Marcados como revisados: los que algún moderador ha marcado como revisados y por lo tanto parecen correctos. Es recomendable revisar regularmente la sección "pendientes". @@ -136,12 +135,12 @@ Un buscador nos permite encontrar cualquier usuario introduciendo su nombre de u Desde aquí puedes gestionar usuarios a través de las siguientes acciones: -* Usuarios -* Editar cuenta de usuario -* Crear propuesta -* Apoyar propuestas -* Crear propuesta de inversión -* Apoyar propts. de inversión -* Imprimir propuestas -* Imprimir propts. de inversión -* Invitaciones para usuarios +* Usuarios. +* Editar cuenta de usuario. +* Crear propuesta. +* Apoyar propuestas. +* Crear propuesta de inversión. +* Apoyar propts. de inversión. +* Imprimir propuestas. +* Imprimir propts. de inversión. +* Invitaciones para usuarios. diff --git a/doc/en/dev_test_setup.md b/doc/en/dev_test_setup.md new file mode 100644 index 000000000..7fc7c967e --- /dev/null +++ b/doc/en/dev_test_setup.md @@ -0,0 +1,9 @@ +# Configuration for development and test environments (Mac OS X) + +## Linux + +## Mac OS X + +See [here](dev_test_setup_osx.md) + +## Windows diff --git a/doc/en/dev_test_setup_osx.md b/doc/en/dev_test_setup_osx.md new file mode 100644 index 000000000..088082261 --- /dev/null +++ b/doc/en/dev_test_setup_osx.md @@ -0,0 +1,128 @@ +# Configuration for development and test environments (Mac OS X) + +## Homebrew + +Homebrew is a very popular package manager for OS X. It's advised to use it since it makes the installation of some of the dependencies much easier. + +You can find the installation instructions at: [brew.sh](http://brew.sh) + +## XCode and XCode Command Line Tools + +To install *git* you'll first need to install *Xcode* (download it from the Mac App Store) and its *Xcode Command Line Tools* (you can install them from the Xcode's app menu) + +## Git + +You can download git from: [git-scm.com/download/mac](https://git-scm.com/download/mac) + +## Ruby y rbenv + +OS X already comes with a preinstalled Ruby version, but it's quite old and we need a newer one (2.2.3). One of the multiple ways of installing Ruby in OS X is through *rbenv*. The installation instructions are in its GitHub repository and are pretty straight-forward: + +[github.com/rbenv/rbenv](https://github.com/rbenv/rbenv) + +## Bundler + +``` +gem install bundler +``` + +## PostgreSQL (>=9.4) + +``` +brew install postgres +``` + +Once installed, we need to *initialize* it: + +``` +initdb /usr/local/var/postgres +``` + +Now we're going to configure some things related to the *default user*. First we start postgres server with: + +``` +postgres -D /usr/local/var/postgres +``` + +At this point we're supposed to have postgres correctly installed and a default user will automatically be created (whose name will match our username). This user hasn't got a password yet. + +If we run `psql` we'll login into the postgres console with the default user. Probably it will fail since its required that a default database exists for that user. We can create it by typing: + +``` +createdb 'your_username' +``` + +If we run `psql` again we should now get access to postgres console. With `\du` you can see the current users list. + +In case you want to set a password for your user you can make it throught postgres console by: + +``` +ALTER USER your_username WITH PASSWORD 'your_password'; +``` + +Now we'll create the *consul* user, the one the application is using. Run in postgres console: + +``` +CREATE ROLE consul WITH PASSWORD '000'; +ALTER ROLE consul WITH SUPERUSER; +ALTER ROLE consul WITH login; +``` + +If at any point during PostgreSQL installation you feel you have messed things up, you can uninstall it and start again by running: + +``` +brew uninstall postgres +``` + +You'll have to delete also this directory (otherwise the new installation will generate conflicts, source: [gist.github.com/lxneng/741932](https://gist.github.com/lxneng/741932)): + +``` +rm -rf /usr/local/var/postgres +``` + +## Postgis + +``` +brew install postgis +``` + +## Ghostscript + +``` +brew install ghostscript +``` + +## PhantomJS + +``` +brew install phantomjs +``` + +## Cloning the repository + +Now that we have all the dependencies installed we can download the repository: + +``` +git clone https://github.com/consul/consul.git +cd consul +bundle install +cp config/database.yml.example config/database.yml +cp config/secrets.yml.example config/secrets.yml +``` + +Now copy in `database.yml` the database user and password you used for *consul*. + +After this: + +``` +rake db:create +rake db:setup +rake db:dev_seed +RAILS_ENV=test bin/rake db:setup +``` + +To run the tests: + +``` +bundle exec rspec +``` diff --git a/doc/es/dev_test_setup.md b/doc/es/dev_test_setup.md new file mode 100644 index 000000000..30f5d12d3 --- /dev/null +++ b/doc/es/dev_test_setup.md @@ -0,0 +1,9 @@ +# Configuración para los entornos de desarrollo y pruebas + +## Linux + +## Mac OS X + +Consultar [aquí](dev_test_setup_osx.md) + +## Windows diff --git a/doc/es/dev_test_setup_osx.md b/doc/es/dev_test_setup_osx.md new file mode 100644 index 000000000..840f15976 --- /dev/null +++ b/doc/es/dev_test_setup_osx.md @@ -0,0 +1,128 @@ +# Configuración para los entornos de desarrollo y pruebas (Mac OS X) + +## Homebrew + +Homebrew es un gestor de paquetes para OS X muy popular. Es recomendable usarlo pues facilita enormemente la instalación de algunos de los paquetes necesarios. + +Puedes encontrar las instrucciones de instalación en: [brew.sh](http://brew.sh) + +## XCode y XCode Command Line Tools + +Para utilizar git necesitarás instalar *Xcode* (está en la Mac App Store) y las *Xcode Command Line Tools* (se instalan desde el menú de Xcode). + +## Git y Github + +Puedes descargar git desde: [git-scm.com/download/mac](https://git-scm.com/download/mac) + +## Ruby y rbenv + +OS X ya viene con una versión preinstalada de ruby, pero es bastante vieja y en nuestro caso no nos sirve. Una de las formas de instalar Ruby es a través de rbenv. Las instrucciones de instalación están en su GitHub y son bastante claras: + +[github.com/rbenv/rbenv](https://github.com/rbenv/rbenv) + +Después instala la versión de Ruby 2.2.3 + +## Bundler + +``` +gem install bundler +``` + +## PostgreSQL (>=9.4) + +``` +brew install postgres +``` + +Una vez instalado es necesario *inicializar* la instalación: + +``` +initdb /usr/local/var/postgres +``` + +Ahora vamos a configurar algunos aspectos del usuario por defecto. Primero iniciamos el servidor de postgres con: + +``` +postgres -D /usr/local/var/postgres +``` + +Llegados a este punto se supone que tenemos postgres correctamente instalado y se nos habrá creado un usuario por defecto (cuyo nombre es nuestro nombre de usuario), y que (todavía) no tiene contraseña. + +Si ejecutamos `psql` accederemos a la consola de postgres con el usuario por defecto. Probablemente fallará porque es necesario que de antemano exista una base de datos por defecto para dicho usuario. Podemos crearla ejecutando sobre la terminal: + +``` +createdb 'tu_nombre_de_usuario' +``` + +Si ahora ejecutamos `psql` de nuevo deberíamos poder acceder correctamente a la consola de postgres. Si sobre la consola de postgres ejecutas `\du` puede ver la lista de usuarios actual. + +En el caso de que quieras asignarte una contraseña puedes hacerlo desde la consola de postgres con: + +``` +ALTER USER tu_nombre_usuario WITH PASSWORD 'tu_contraseña'; +``` + +Ahora vamos a crear el usuario *consul*, que es el que utiliza la aplicación. Ejecuta sobre la consola de postgres: + +``` +CREATE ROLE consul WITH PASSWORD '000'; +ALTER ROLE consul WITH SUPERUSER; +ALTER ROLE consul WITH login; +``` + +Si en algún momento durante la instalación de PostgreSQL y postgis sospechas que te has equivocado y deseas desinstalarlo y volver a empezar desde cero: + +``` +brew uninstall postgres +``` + +También tendrás que borrar el siguiente directorio para que no de conflictos cuando intentes volver a instalarlo (fuente: [gist.github.com/lxneng/741932](https://gist.github.com/lxneng/741932)): + +``` +rm -rf /usr/local/var/postgres +``` + +## Postgis + +``` +brew install postgis +``` + +## Ghostscript + +``` +brew install ghostscript +``` + +## PhantomJS + +``` +brew install phantomjs +``` + +## Clonar el repositorio + +Ahora que ya tenemos todas las dependencias instalado podemos bajarnos el proyecto: + +``` +git clone https://github.com/consul/consul.git +cd consul +bundle install +cp config/database.yml.example config/database.yml +cp config/secrets.yml.example config/secrets.yml +``` + +Ahora copia en `database.yml` el usuario y la contraseña que pusiste para *consul*. Cuando ya lo hayas hecho: + +``` +rake db:create +rake db:setup +rake db:dev_seed +RAILS_ENV=test bin/rake db:setup +``` + +Para ejecutar los tests: + +``` +bundle exec rspec +``` diff --git a/spec/factories.rb b/spec/factories.rb index cdcfe7381..13371a504 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -163,6 +163,10 @@ FactoryGirl.define do end end + trait :archived do + created_at 25.months.ago + end + trait :with_hot_score do before(:save) { |d| d.calculate_hot_score } end diff --git a/spec/features/official_positions_spec.rb b/spec/features/official_positions_spec.rb index 01f78370a..971a8fc6d 100644 --- a/spec/features/official_positions_spec.rb +++ b/spec/features/official_positions_spec.rb @@ -50,7 +50,7 @@ feature 'Official positions' do @proposal1 = create(:proposal, author: @user1) @proposal2 = create(:proposal, author: @user2) - featured_proposals = 3.times { create(:proposal) } + create_featured_proposals end scenario "Index" do diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index c7be4956b..9149531fd 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -3,48 +3,50 @@ require 'rails_helper' feature 'Proposals' do - scenario 'Index' do - featured_proposals = create_featured_proposals - proposals = [create(:proposal), create(:proposal), create(:proposal)] + context 'Index' do + scenario 'Lists featured and regular proposals' do + featured_proposals = create_featured_proposals + proposals = [create(:proposal), create(:proposal), create(:proposal)] - visit proposals_path + visit proposals_path - expect(page).to have_selector('#proposals .proposal-featured', count: 3) - featured_proposals.each do |featured_proposal| - within('#featured-proposals') do - expect(page).to have_content featured_proposal.title - expect(page).to have_css("a[href='#{proposal_path(featured_proposal)}']") + expect(page).to have_selector('#proposals .proposal-featured', count: 3) + featured_proposals.each do |featured_proposal| + within('#featured-proposals') do + expect(page).to have_content featured_proposal.title + expect(page).to have_css("a[href='#{proposal_path(featured_proposal)}']") + end + end + + expect(page).to have_selector('#proposals .proposal', count: 3) + proposals.each do |proposal| + within('#proposals') do + expect(page).to have_content proposal.title + expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.title) + expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.summary) + end end end - expect(page).to have_selector('#proposals .proposal', count: 3) - proposals.each do |proposal| - within('#proposals') do - expect(page).to have_content proposal.title - expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.title) - expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.summary) + scenario 'Pagination' do + per_page = Kaminari.config.default_per_page + (per_page + 5).times { create(:proposal) } + + visit proposals_path + + expect(page).to have_selector('#proposals .proposal', count: per_page) + + within("ul.pagination") do + expect(page).to have_content("1") + expect(page).to have_content("2") + expect(page).to_not have_content("3") + click_link "Next", exact: false end + + expect(page).to have_selector('#proposals .proposal', count: 2) end end - scenario 'Paginated Index' do - per_page = Kaminari.config.default_per_page - (per_page + 5).times { create(:proposal) } - - visit proposals_path - - expect(page).to have_selector('#proposals .proposal', count: per_page) - - within("ul.pagination") do - expect(page).to have_content("1") - expect(page).to have_content("2") - expect(page).to_not have_content("3") - click_link "Next", exact: false - end - - expect(page).to have_selector('#proposals .proposal', count: 2) - end - scenario 'Show' do proposal = create(:proposal) @@ -676,6 +678,95 @@ feature 'Proposals' do end end + feature 'Archived proposals' do + + scenario 'show on archived tab' do + create_featured_proposals + archived_proposals = create_archived_proposals + + visit proposals_path + click_link 'Archived' + + within("#proposals-list") do + archived_proposals.each do |proposal| + expect(page).to have_content(proposal.title) + end + end + end + + scenario 'do not show in other index tabs' do + create_featured_proposals + archived_proposal = create(:proposal, :archived) + + visit proposals_path + + within("#proposals-list") do + expect(page).to_not have_content archived_proposal.title + end + + orders = %w{hot_score confidence_score created_at relevance} + orders.each do |order| + visit proposals_path(order: order) + + within("#proposals-list") do + expect(page).to_not have_content archived_proposal.title + end + end + end + + scenario 'do not show support buttons in index' do + create_featured_proposals + archived_proposals = create_archived_proposals + + visit proposals_path(order: 'archival_date') + + within("#proposals-list") do + archived_proposals.each do |proposal| + within("#proposal_#{proposal.id}_votes") do + expect(page).to_not have_css(".supports") + expect(page).to have_content "This proposal has been archived and can't collect supports" + end + end + end + end + + scenario 'do not show support buttons in show' do + archived_proposal = create(:proposal, :archived) + + visit proposal_path(archived_proposal) + expect(page).to_not have_css(".supports") + expect(page).to have_content "This proposal has been archived and can't collect supports" + end + + scenario 'do not show in featured proposals section' do + featured_proposal = create(:proposal, :with_confidence_score, cached_votes_up: 100) + archived_proposal = create(:proposal, :archived, :with_confidence_score, cached_votes_up: 10000) + + visit proposals_path + + within("#featured-proposals") do + expect(page).to have_content(featured_proposal.title) + expect(page).to_not have_content(archived_proposal.title) + end + within("#proposals-list") do + expect(page).to_not have_content(featured_proposal.title) + expect(page).to_not have_content(archived_proposal.title) + end + + click_link "Archived" + + within("#featured-proposals") do + expect(page).to have_content(featured_proposal.title) + expect(page).to_not have_content(archived_proposal.title) + end + within("#proposals-list") do + expect(page).to_not have_content(featured_proposal.title) + expect(page).to have_content(archived_proposal.title) + end + end + + end + context "Search" do context "Basic search" do diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index 247cd9e66..d98392eaa 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -204,6 +204,13 @@ describe Proposal do expect {proposal.register_vote(user, 'yes')}.to change{proposal.reload.votes_for.size}.by(0) end end + + it "should not register vote for archived proposals" do + user = create(:user, verified_at: Time.now) + archived_proposal = create(:proposal, :archived) + + expect {archived_proposal.register_vote(user, 'yes')}.to change{proposal.reload.votes_for.size}.by(0) + end end describe '#cached_votes_up' do @@ -811,4 +818,30 @@ describe Proposal do end end + describe "archived" do + before(:each) do + @new_proposal = create(:proposal) + @archived_proposal = create(:proposal, :archived) + end + + it "archived? is true only for proposals created more than n (configured months) ago" do + expect(@new_proposal.archived?).to eq false + expect(@archived_proposal.archived?).to eq true + end + + it "scope archived" do + archived = Proposal.archived + + expect(archived.size).to eq(1) + expect(archived.first).to eq(@archived_proposal) + end + + it "scope archived" do + not_archived = Proposal.not_archived + + expect(not_archived.size).to eq(1) + expect(not_archived.first).to eq(@new_proposal) + end + end + end diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index 8eaa49d4b..d445ae890 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -197,6 +197,11 @@ module CommonActions create(:proposal, title: "Fire and blood", question: "You talking to me?", cached_votes_up: Proposal.votes_needed_for_success + 1)] end + def create_archived_proposals + [create(:proposal, title: "This is an expired proposal", created_at: Setting["months_to_archive_proposals"].to_i.months.ago), + create(:proposal, title: "This is an oldest expired proposal", created_at: (Setting["months_to_archive_proposals"].to_i + 2).months.ago)] + end + def tag_names(tag_cloud) tag_cloud.tags.map(&:name) end