diff --git a/app/assets/stylesheets/legislation_process.scss b/app/assets/stylesheets/legislation_process.scss index cb4488004..81c090a5f 100644 --- a/app/assets/stylesheets/legislation_process.scss +++ b/app/assets/stylesheets/legislation_process.scss @@ -1006,3 +1006,84 @@ font-weight: bold; } } + +// 10. Legislation summary +// ------------------------- + +.process-summary { + > section { + @include grid-row; + margin-top: $line-height * 1.5; + padding: 0 rem-calc(16); + + > header { + background: none; + border: 0; + margin: 0; + } + } + + h4, + p { + margin-bottom: 0; + } + + > section > header, + .question-title, + .annotation-title, + .comment-summary, + .proposal-summary { + @include breakpoint(medium) { + align-items: center; + display: flex; + + > :first-child { + $margin: rem-calc(map-get($grid-column-gutter, "medium")); + + margin-right: $margin; + width: calc(75% - #{$margin}); + } + } + } + + .debate-summary, + .proposal-summary, + .annotation-summary { + @extend %panel; + min-height: auto; + padding-bottom: rem-calc(16); + padding-top: rem-calc(16); + + &:not(:last-child) { + margin-bottom: $line-height / 2; + } + } + + .comments-count { + @include has-fa-icon(comments, r); + } + + .question-title:not(:only-child) { + margin-bottom: $line-height / 2; + } + + .annotation-title { + margin-bottom: $line-height / 2; + margin-top: $line-height / 2; + } + + .annotation-quote { + border: 1px solid $black; + padding: rem-calc(10); + } + + .comment-summary { + margin-bottom: $line-height / 2; + + > :first-child { + background-color: rgba(217, 216, 243, 0.2); + border-radius: rem-calc(10); + padding: rem-calc(12); + } + } +} diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 895e4d2cc..a825c77bb 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -612,6 +612,45 @@ } } +%panel { + background: #fff; + border: 1px solid; + border-color: #e5e6e9 #dfe0e4 #d0d1d5; + border-radius: 0; + box-shadow: 0 1px 3px 0 $border; + margin-bottom: rem-calc(12); + min-height: rem-calc(192); + padding: rem-calc(12) rem-calc(12) 0; + + @include breakpoint(medium) { + margin-bottom: rem-calc(-1); + padding-bottom: rem-calc(12); + } + + @include breakpoint(medium) { + .divider { + display: inline-block; + } + } + + h3 { + font-weight: bold; + margin-top: $line-height / 2; + + a { + color: $text; + } + } + + &.past-budgets { + min-height: 0; + + .button ~ .button { + margin-left: $line-height / 2; + } + } +} + .debate, .proposal, .investment-project, @@ -621,42 +660,7 @@ margin: $line-height / 4 0; .panel { - background: #fff; - border: 1px solid; - border-color: #e5e6e9 #dfe0e4 #d0d1d5; - border-radius: 0; - box-shadow: 0 1px 3px 0 $border; - margin-bottom: rem-calc(12); - min-height: rem-calc(192); - padding: rem-calc(12) rem-calc(12) 0; - - @include breakpoint(medium) { - margin-bottom: rem-calc(-1); - padding-bottom: rem-calc(12); - } - - @include breakpoint(medium) { - .divider { - display: inline-block; - } - } - - h3 { - font-weight: bold; - margin-top: $line-height / 2; - - a { - color: $text; - } - } - - &.past-budgets { - min-height: 0; - - .button ~ .button { - margin-left: $line-height / 2; - } - } + @extend %panel; } .debate-content, diff --git a/app/controllers/legislation/processes_controller.rb b/app/controllers/legislation/processes_controller.rb index d6dd6d3e5..2c8f01076 100644 --- a/app/controllers/legislation/processes_controller.rb +++ b/app/controllers/legislation/processes_controller.rb @@ -97,6 +97,12 @@ class Legislation::ProcessesController < Legislation::BaseController @phase = :milestones end + def summary + @phase = :summary + @proposals = @process.proposals.selected + @comments = @process.draft_versions.published.last&.best_comments || Comment.none + end + def proposals set_process @phase = :proposals_phase diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 25dcb60c7..24b70e453 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -90,7 +90,9 @@ module Abilities can :access, :ckeditor can :manage, Ckeditor::Picture - can [:manage], ::Legislation::Process + can [:read, :debate, :draft_publication, :allegations, :result_publication, + :milestones], Legislation::Process + can [:create, :update, :destroy], Legislation::Process can [:manage], ::Legislation::DraftVersion can [:manage], ::Legislation::Question can [:manage], ::Legislation::Proposal diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 89a29f4b9..432099150 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -21,6 +21,8 @@ module Abilities can :new, DirectMessage can [:read, :debate, :draft_publication, :allegations, :result_publication, :proposals, :milestones], Legislation::Process, published: true + can :summary, Legislation::Process, + id: Legislation::Process.past.published.where(result_publication_enabled: true).ids can [:read, :changes, :go_to_version], Legislation::DraftVersion can [:read], Legislation::Question can [:read, :map, :share], Legislation::Proposal diff --git a/app/models/comment.rb b/app/models/comment.rb index 1552ce3a5..7af862a73 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -49,6 +49,7 @@ class Comment < ApplicationRecord scope :sort_by_most_voted, -> { order(confidence_score: :desc, created_at: :desc) } scope :sort_descendants_by_most_voted, -> { order(confidence_score: :desc, created_at: :asc) } + scope :sort_by_supports, -> { order("cached_votes_up - cached_votes_down DESC") } scope :sort_by_newest, -> { order(created_at: :desc) } scope :sort_descendants_by_newest, -> { order(created_at: :desc) } @@ -129,6 +130,10 @@ class Comment < ApplicationRecord cached_votes_up) end + def votes_score + cached_votes_up - cached_votes_down + end + private def validate_body_length diff --git a/app/models/legislation/draft_version.rb b/app/models/legislation/draft_version.rb index 93b65eb8f..9019d6cb8 100644 --- a/app/models/legislation/draft_version.rb +++ b/app/models/legislation/draft_version.rb @@ -40,4 +40,8 @@ class Legislation::DraftVersion < ApplicationRecord def total_comments annotations.sum(:comments_count) end + + def best_comments + Comment.where(commentable: annotations, ancestry: nil).sort_by_supports.limit(10) + end end diff --git a/app/models/legislation/question.rb b/app/models/legislation/question.rb index ff9e0ea94..73f1cc946 100644 --- a/app/models/legislation/question.rb +++ b/app/models/legislation/question.rb @@ -44,4 +44,8 @@ class Legislation::Question < ApplicationRecord def comments_open? process.debate_phase.open? end + + def best_comments + comments.sort_by_supports.limit(3) + end end diff --git a/app/views/legislation/processes/_key_dates.html.erb b/app/views/legislation/processes/_key_dates.html.erb index 4e8ab85c0..7e5842d1a 100644 --- a/app/views/legislation/processes/_key_dates.html.erb +++ b/app/views/legislation/processes/_key_dates.html.erb @@ -52,6 +52,15 @@ <% end %> <% end %> + + <% if can?(:summary, process) %> +
  • > + <%= link_to summary_legislation_process_path(process) do %> +

    <%= t("legislation.summary.title") %>

    + <%= format_date(process.result_publication_date) %> + <% end %> +
  • + <% end %> diff --git a/app/views/legislation/processes/_summary_allegations.html.erb b/app/views/legislation/processes/_summary_allegations.html.erb new file mode 100644 index 000000000..345542dc1 --- /dev/null +++ b/app/views/legislation/processes/_summary_allegations.html.erb @@ -0,0 +1,32 @@ +
    +
    +

    <%= t("legislation.summary.allegations_phase") %>

    +

    <%= t("legislation.summary.top_comments", count: comments.count) %>

    +
    + + <% if comments.any? %> +
    + <% comments.group_by(&:commentable).each do |annotation, annotation_comments| %> +
    + <%= t("legislation.annotations.index.comments_about") %> +
    +
    + <%= annotation.quote %> +
    + + + <%= link_to t("legislation.summary.comments", count: annotation.comments.count), + polymorphic_path(annotation, anchor: "comments") %> + +
    + + <%= render "summary_comments", comments: annotation_comments %> +
    + <% end %> +
    + <% else %> +
    +

    <%= t("legislation.summary.no_allegation") %>

    +
    + <% end %> +
    diff --git a/app/views/legislation/processes/_summary_comments.html.erb b/app/views/legislation/processes/_summary_comments.html.erb new file mode 100644 index 000000000..7e8b61d60 --- /dev/null +++ b/app/views/legislation/processes/_summary_comments.html.erb @@ -0,0 +1,10 @@ +<% if comments.any? %> + <%= t("legislation.summary.most_voted_comments") %> + + <% comments.each do |comment| %> +
    + <%= link_to simple_format(sanitize(comment.body, tags: [])), comment_path(comment) %> +

    <%= t("legislation.summary.votes", count: comment.votes_score) %>

    +
    + <% end %> +<% end %> diff --git a/app/views/legislation/processes/_summary_debate.html.erb b/app/views/legislation/processes/_summary_debate.html.erb new file mode 100644 index 000000000..8530f192d --- /dev/null +++ b/app/views/legislation/processes/_summary_debate.html.erb @@ -0,0 +1,28 @@ +
    +
    +

    <%= t("legislation.summary.debate_phase") %>

    +

    <%= t("legislation.summary.debates", count: questions.count) %>

    +
    + + <% if questions.any? %> +
    + <% questions.each do |question| %> +
    +
    +

    <%= link_to question.title, polymorphic_path(question) %>

    + + <%= link_to t("legislation.summary.comments", count: question.comments.count), + polymorphic_path(question, anchor: "comments") %> + +
    + + <%= render "summary_comments", comments: question.best_comments %> +
    + <% end %> +
    + <% else %> +
    +

    <%= t("legislation.processes.debate.empty_questions") %>

    +
    + <% end %> +
    diff --git a/app/views/legislation/processes/_summary_proposals.html.erb b/app/views/legislation/processes/_summary_proposals.html.erb new file mode 100644 index 000000000..09ccca777 --- /dev/null +++ b/app/views/legislation/processes/_summary_proposals.html.erb @@ -0,0 +1,23 @@ +
    +
    +

    <%= t("legislation.summary.proposals_phase") %>

    +

    <%= t("legislation.summary.proposals", count: proposals.count) %>

    +
    + + <% if proposals.any? %> +
    + <% proposals.sort_by_supports.each do |proposal| %> +
    +

    + <%= link_to proposal.title, polymorphic_path(proposal) %> +

    +

    <%= t("legislation.summary.votes", count: proposal.votes_score) %>

    +
    + <% end %> +
    + <% else %> +
    +

    <%= t("legislation.processes.proposals.empty_proposals") %>

    +
    + <% end %> +
    diff --git a/app/views/legislation/processes/summary.html.erb b/app/views/legislation/processes/summary.html.erb new file mode 100644 index 000000000..97c211d7a --- /dev/null +++ b/app/views/legislation/processes/summary.html.erb @@ -0,0 +1,25 @@ +<% provide(:title) { @process.title } %> + +<%= render "legislation/processes/header", process: @process, header: :full %> + +<%= render "key_dates", process: @process, phase: @phase %> + +<% if @process.debate_phase.enabled? || @process.proposals_phase.enabled? || @process.allegations_phase.enabled? %> +
    + <% if @process.debate_phase.enabled? %> + <%= render "summary_debate", questions: @process.questions %> + <% end %> + + <% if @process.proposals_phase.enabled? %> + <%= render "summary_proposals", proposals: @proposals %> + <% end %> + + <% if @process.allegations_phase.enabled? %> + <%= render "summary_allegations", comments: @comments %> + <% end %> +
    +<% else %> +
    +

    <%= t("legislation.summary.process_empty") %>

    +
    +<% end %> diff --git a/config/locales/en/legislation.yml b/config/locales/en/legislation.yml index 967f6d1d9..665cb35ee 100644 --- a/config/locales/en/legislation.yml +++ b/config/locales/en/legislation.yml @@ -118,3 +118,31 @@ en: tags_label: "Categories" not_verified: "For vote proposals %{verify_account}." process_title: Collaborative legislation process + summary: + title: "Summary" + votes: + zero: "%{count} votes" + one: "%{count} vote" + other: "%{count} votes" + debate_phase: "Debate phase" + proposals_phase: "Proposals phase" + allegations_phase: "Comments phase" + debates: + zero: "No debates" + one: "%{count} debate" + other: "%{count} debates" + proposals: + zero: "No proposals" + one: "%{count} proposal" + other: "%{count} proposals" + comments: + zero: "No comments" + one: "%{count} comment" + other: "%{count} comments" + top_comments: + zero: "No comments" + one: "%{count} comment" + other: "Top comments" + most_voted_comments: "Most voted comments" + no_allegation: "There are no comments" + process_empty: "This process didn't have any participation phases" diff --git a/config/locales/es/legislation.yml b/config/locales/es/legislation.yml index fe723e250..2c88e185b 100644 --- a/config/locales/es/legislation.yml +++ b/config/locales/es/legislation.yml @@ -118,3 +118,31 @@ es: tags_label: "Categorías" not_verified: "Para votar propuestas %{verify_account}." process_title: Proceso de legislación colaborativa + summary: + title: "Resumen" + votes: + zero: "%{count} votos" + one: "%{count} voto" + other: "%{count} votos" + debate_phase: "Fase de debate" + proposals_phase: "Fase de propuestas" + allegations_phase: "Fase de comentarios" + debates: + zero: "No hay debates" + one: "%{count} debate" + other: "%{count} debates" + proposals: + zero: "No hay propuestas" + one: "%{count} propuesta" + other: "%{count} propuestas" + comments: + zero: "No hay comentarios" + one: "%{count} comentario" + other: "%{count} comentarios" + top_comments: + zero: "No hay comentarios" + one: "%{count} comentario" + other: "Los más votados" + most_voted_comments: "Comentarios más votados" + no_allegation: "No hay comentarios" + process_empty: "Este proceso no ha tenido fases de participación" diff --git a/config/routes/legislation.rb b/config/routes/legislation.rb index 2458a2bae..ee92ce26c 100644 --- a/config/routes/legislation.rb +++ b/config/routes/legislation.rb @@ -7,6 +7,7 @@ namespace :legislation do get :result_publication get :proposals get :milestones + get :summary end resources :questions, only: [:show] do diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index bbf8f91fe..c87cc889d 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -18,6 +18,10 @@ describe Abilities::Administrator do let(:legislation_question) { create(:legislation_question) } let(:poll_question) { create(:poll_question) } + let(:past_process) { create(:legislation_process, :past) } + let(:past_draft_process) { create(:legislation_process, :past, :not_published) } + let(:open_process) { create(:legislation_process, :open) } + let(:proposal_document) { build(:document, documentable: proposal, user: proposal.author) } let(:budget_investment_document) { build(:document, documentable: budget_investment) } let(:poll_question_document) { build(:document, documentable: poll_question) } @@ -67,6 +71,10 @@ describe Abilities::Administrator do it { should be_able_to(:comment_as_administrator, legislation_question) } it { should_not be_able_to(:comment_as_moderator, legislation_question) } + it { should be_able_to(:summary, past_process) } + it { should_not be_able_to(:summary, past_draft_process) } + it { should_not be_able_to(:summary, open_process) } + it { should be_able_to(:create, Budget) } it { should be_able_to(:update, Budget) } it { should be_able_to(:read_results, Budget) } diff --git a/spec/models/abilities/everyone_spec.rb b/spec/models/abilities/everyone_spec.rb index cfdbbf932..fdc03c74d 100644 --- a/spec/models/abilities/everyone_spec.rb +++ b/spec/models/abilities/everyone_spec.rb @@ -48,4 +48,8 @@ describe Abilities::Everyone do it { should be_able_to(:read_stats, create(:budget, :valuating, stats_enabled: true)) } it { should_not be_able_to(:read_stats, create(:budget, :valuating, stats_enabled: false)) } it { should_not be_able_to(:read_stats, create(:budget, :selecting, stats_enabled: true)) } + + it { should be_able_to(:summary, create(:legislation_process, :past)) } + it { should_not be_able_to(:summary, create(:legislation_process, :open)) } + it { should_not be_able_to(:summary, create(:legislation_process, :past, :not_published)) } end diff --git a/spec/system/legislation/summary_spec.rb b/spec/system/legislation/summary_spec.rb new file mode 100644 index 000000000..0763c16ba --- /dev/null +++ b/spec/system/legislation/summary_spec.rb @@ -0,0 +1,172 @@ +require "rails_helper" + +describe "Legislation" do + context "process summary page" do + scenario "summary tab is not shown for open processes" do + process = create(:legislation_process, :open) + + visit legislation_process_path(process) + + expect(page).not_to have_content "Summary" + end + + scenario "summary tab is shown por past processes" do + process = create(:legislation_process, :past) + + visit legislation_process_path(process) + + expect(page).to have_content "Summary" + end + end + + scenario "empty process" do + process = create(:legislation_process, :empty, + result_publication_enabled: true, + end_date: Date.current - 1.day + ) + + visit summary_legislation_process_path(process) + + expect(page).to have_content "This process didn't have any participation phases" + end + + scenario "empty phases" do + process = create(:legislation_process, end_date: Date.current - 1.day) + visit summary_legislation_process_path(process) + + expect(page).to have_content "Debate phase" + expect(page).to have_content "No debates" + expect(page).to have_content "There aren't any questions" + + expect(page).to have_content "Proposals phase" + expect(page).to have_content "No proposals" + expect(page).to have_content "There are no proposals" + + expect(page).to have_content "Comments phase" + expect(page).to have_content "No comments" + expect(page).to have_content "There are no comments" + end + + context "only debates exist" do + let(:process) { create(:legislation_process, end_date: Date.current - 1.day) } + let(:user) { create(:user, :level_two) } + + before do + create(:legislation_question, process: process, title: "Question 1") do |question| + create(:comment, user: user, commentable: question, body: "Answer 1") + create(:comment, user: user, commentable: question, body: "Answer 2") + end + + create(:legislation_question, process: process, title: "Question 2") do |question| + create(:comment, user: user, commentable: question, body: "Answer 3") + create(:comment, user: user, commentable: question, body: "Answer 4") + end + end + + scenario "shows debates list" do + visit summary_legislation_process_path(process) + + expect(page).to have_content "Debate phase" + expect(page).to have_content "2 debates" + expect(page).to have_link "Question 1" + expect(page).to have_content "Answer 1" + expect(page).to have_content "Answer 2" + expect(page).to have_link "Question 2" + expect(page).to have_content "Answer 3" + expect(page).to have_content "Answer 4" + + expect(page).to have_content "Proposals phase" + expect(page).to have_content "No proposals" + expect(page).to have_content "There are no proposals" + + expect(page).to have_content "Comments phase" + expect(page).to have_content "No comments" + expect(page).to have_content "There are no comments" + end + end + + context "only proposals exist" do + let(:process) { create(:legislation_process, end_date: Date.current - 1.day) } + + before do + create(:legislation_proposal, legislation_process_id: process.id, + title: "Legislation proposal 1", selected: true) + create(:legislation_proposal, legislation_process_id: process.id, + title: "Legislation proposal 2", selected: false) + create(:legislation_proposal, legislation_process_id: process.id, + title: "Legislation proposal 3", selected: true) + create(:legislation_proposal, legislation_process_id: process.id, + title: "Legislation proposal 4", selected: false) + end + + scenario "shows proposals list" do + visit summary_legislation_process_path(process) + + expect(page).to have_content "Debate phase" + expect(page).to have_content "No debates" + expect(page).to have_content "There aren't any questions" + + expect(page).to have_content "Proposals phase" + expect(page).to have_content "2 proposals" + expect(page).to have_link "Legislation proposal 1" + expect(page).not_to have_content "Legislation proposal 2" + expect(page).to have_link "Legislation proposal 3" + expect(page).not_to have_content "Legislation proposal 4" + + expect(page).to have_content "Comments phase" + expect(page).to have_content "No comments" + expect(page).to have_content "There are no comments" + end + end + + context "only text comments exist" do + let(:process) { create(:legislation_process, end_date: Date.current - 1.day) } + + before do + user = create(:user, :level_two) + draft_version_1 = create(:legislation_draft_version, process: process, + title: "Version 1", body: "Body of the first version", + status: "published") + draft_version_2 = create(:legislation_draft_version, process: process, + title: "Version 2", body: "Body of the second version and that's it all of it", + status: "published") + annotation0 = create(:legislation_annotation, + draft_version: draft_version_1, text: "my annotation123", + ranges: annotation_ranges(5, 10)) + annotation1 = create(:legislation_annotation, + draft_version: draft_version_2, text: "hola", + ranges: annotation_ranges(5, 10)) + annotation2 = create(:legislation_annotation, + draft_version: draft_version_2, + ranges: annotation_ranges(12, 19)) + + create(:comment, user: user, commentable: annotation0, body: "Comment 0") + create(:comment, user: user, commentable: annotation1, body: "Comment 1") + create(:comment, user: user, commentable: annotation2, body: "Comment 2") + create(:comment, user: user, commentable: annotation2, body: "Comment 3") + end + + scenario "shows coments list" do + visit summary_legislation_process_path(process) + + expect(page).to have_content "Debate phase" + expect(page).to have_content "No debates" + expect(page).to have_content "There aren't any questions" + + expect(page).to have_content "Proposals phase" + expect(page).to have_content "No proposals" + expect(page).to have_content "There are no proposals" + + expect(page).to have_content "Comments phase" + expect(page).to have_content "Top comments" + expect(page).not_to have_content "Comment 0" + expect(page).to have_link "Comment 1" + expect(page).to have_link "Comment 2" + expect(page).to have_link "Comment 3" + end + end + + def annotation_ranges(start_offset, end_offset) + [{ "start" => "/p[1]", "startOffset" => start_offset, "end" => "/p[1]", "endOffset" => end_offset }] + end +end