diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 2e7f143..3532896 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -87,7 +87,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.RUBY_VERSION }} - bundler-cache: true + bundler-cache: true - run: | sudo apt install libu2f-udev imagemagick wget --no-verbose -O /tmp/chrome.deb https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${{env.CHROME_VERSION}}-1_amd64.deb @@ -112,6 +112,8 @@ jobs: - run: mkdir -p ./spec/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v2 + with: + chromedriver-version: ${{ env.CHROME_VERSION }} - run: bundle exec rake "test:run[exclude, spec/system/**/*_spec.rb, ${{ matrix.slice }}]" name: RSpec - run: ./.github/upload_coverage.sh decidim-app $GITHUB_EVENT_PATH @@ -184,6 +186,8 @@ jobs: - run: mkdir -p ./spec/tmp/screenshots name: Create the screenshots folder - uses: nanasess/setup-chromedriver@v2 + with: + chromedriver-version: ${{ env.CHROME_VERSION }} - run: bundle exec rake "test:run[include, spec/system/**/*_spec.rb, ${{ matrix.slice }}]" name: RSpec - run: ./.github/upload_coverage.sh decidim-app $GITHUB_EVENT_PATH diff --git a/app/views/decidim/decidim_awesome/admin/proposals/_private_body.html.erb b/app/views/decidim/decidim_awesome/admin/proposals/_private_body.html.erb new file mode 100644 index 0000000..75b357f --- /dev/null +++ b/app/views/decidim/decidim_awesome/admin/proposals/_private_body.html.erb @@ -0,0 +1,22 @@ +
+ +
+
+ <% if proposal.private_body %> +
<%= Decidim::ContentProcessor.render_without_format(render_sanitized_content(proposal, :private_body)).html_safe %>
+ <% end %> + <% if proposal&.extra_fields&.private_body_updated_at %> +
+ <%= t("decidim.decidim_awesome.admin.proposal_custom_fields.private_data_last_update", time_ago: time_ago_in_words(proposal.extra_fields.private_body_updated_at)) if proposal.extra_fields.private_body.present? %> + <%= t("decidim.decidim_awesome.admin.proposal_custom_fields.private_data_last_remove", time_ago: time_ago_in_words(proposal.extra_fields.private_body_updated_at)) if proposal.extra_fields.private_body.blank? %> + <%= link_to t("decidim.decidim_awesome.admin.proposal_custom_fields.remove_private_data"), decidim_admin_decidim_awesome.maintenance_path(:private_data) if proposal.extra_fields.private_body_updated_at && proposal.extra_fields.private_body_updated_at < Decidim::DecidimAwesome.private_data_expiration_time.ago %> +
+ <% end %> +
+
+
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 0a8bc06..394c7a6 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -115,6 +115,7 @@ ignore_missing: - decidim.budgets_importer.actions.import - index.confirmed_orders_count - decidim.proposals.proposals.index.{new_proposal,collaborative_drafts_list} + - decidim.decidim_awesome.admin.proposal_custom_fields.* # Consider these keys used: ignore_unused: - faker.* diff --git a/spec/system/admin_edit_proposals_custom_fields_spec.rb b/spec/system/admin_edit_proposals_custom_fields_spec.rb new file mode 100644 index 0000000..ce92358 --- /dev/null +++ b/spec/system/admin_edit_proposals_custom_fields_spec.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Admin edits proposals", type: :system do + let(:manifest_name) { "proposals" } + let(:organization) { participatory_process.organization } + let!(:user) { create(:user, :admin, :confirmed, organization: organization) } + let!(:config) { create(:awesome_config, organization: organization, var: :proposal_custom_fields, value: custom_fields) } + let!(:private_config) { create(:awesome_config, organization: organization, var: :proposal_private_custom_fields, value: private_custom_fields) } + let(:config_helper) { create(:awesome_config, organization: organization, var: :proposal_custom_field_bar) } + let(:private_config_helper) { create(:awesome_config, organization: organization, var: :proposal_private_custom_field_baz) } + let!(:constraint) { create(:config_constraint, awesome_config: config_helper, settings: { "participatory_space_manifest" => "participatory_processes", "participatory_space_slug" => slug }) } + let!(:private_constraint) { create(:config_constraint, awesome_config: private_config_helper, settings: { "participatory_space_manifest" => "participatory_processes" }) } + let(:slug) { participatory_process.slug } + + let(:data1) { '{"type":"text","label":"Full Name","subtype":"text","className":"form-control","name":"text-1476748004559"}' } + let(:data2) { '{"type":"select","label":"Occupation","className":"form-control","name":"select-1476748006618","values":[{"label":"Street Sweeper","value":"option-1","selected":true},{"label":"Moth Man","value":"option-2"},{"label":"Chemist","value":"option-3"}]}' } + let(:data3) { '{"type":"textarea","label":"Short Bio","rows":"5","className":"form-control","name":"textarea-1476748007461"}' } + let(:private_data1) { '{"type":"text","label":"Phone Number","subtype":"text","className":"form-control","name":"text-1476748004579"}' } + + let(:custom_fields) do + { + "foo" => "[#{data1},#{data2}]", + "bar" => "[#{data3}]" + } + end + let(:private_custom_fields) do + { + "baz" => "[#{private_data1}]" + } + end + + let!(:proposal) do + create(:extended_proposal, + :official, + component: component, + body: { + en: '
Bio
I shot the sheriff
', + ca: '
Bio
Jo disparo al sheriff
' + }) + end + + include_context "when managing a component as an admin" + + before do + visit_component_admin + + proposal.update_private_body!('
Phone Number
555-555-555
') + end + + context "when editing the proposal" do + before do + find("a.action-icon--edit-proposal").click + end + + it "displays custom fields" do + expect(page).to have_content("Title") + expect(page).not_to have_content("Body") + expect(page).to have_content("Full Name") + expect(page).to have_content("Occupation") + expect(page).to have_content("Street Sweeper") + expect(page).to have_content("Short Bio") + expect(page).to have_xpath("//textarea[@class='form-control'][@id='textarea-1476748007461'][@user-data='I shot the sheriff']") + expect(page).not_to have_css(".form-error.is-visible") + expect(page).to have_content("This information won't be published") + within "#proposal-custom-field-private_body" do + expect(page).to have_content("Phone Number") + expect(page).to have_xpath("//input[@class='form-control'][@id='text-1476748004579'][@user-data='555-555-555']") + end + + within "#proposal-body-tabs" do + click_link_or_button "CatalĂ " + expect(page).to have_xpath("//textarea[@class='form-control'][@id='textarea-1476748007461'][@user-data='Jo disparo al sheriff']") + end + end + + context "and there are out of scope" do + let(:custom_fields) do + { + "bar" => "[#{data3}]" + } + end + let(:slug) { "another-slug" } + + it "displays normal proposal editor" do + expect(page).to have_content("Title") + expect(page).to have_content("Body") + expect(page).not_to have_content("Full Name") + expect(page).not_to have_content("Occupation") + expect(page).not_to have_content("Street Sweeper") + expect(page).not_to have_content("Short Bio") + expect(page).to have_content("This information won't be published") + within "#proposal-custom-field-private_body" do + expect(page).to have_content("Phone Number") + end + end + end + + context "and some are scoped to other places" do + let(:slug) { "another-slug" } + + it "displays the scoped fields" do + expect(page).to have_content("Title") + expect(page).not_to have_content("Body") + expect(page).to have_content("Full Name") + expect(page).to have_content("Occupation") + expect(page).to have_content("Street Sweeper") + expect(page).not_to have_content("Short Bio") + expect(page).not_to have_css(".form-error.is-visible") + expect(page).to have_content("This information won't be published") + within "#proposal-custom-field-private_body" do + expect(page).to have_content("Phone Number") + end + end + end + + context "when private fields are scoped to other places" do + let!(:private_constraint) { create(:config_constraint, awesome_config: private_config_helper, settings: { "participatory_space_manifest" => "assemblies" }) } + + it "displays the scoped fields" do + expect(page).to have_content("Title") + expect(page).not_to have_content("Body") + expect(page).to have_content("Full Name") + expect(page).to have_content("Occupation") + expect(page).to have_content("Street Sweeper") + expect(page).to have_content("Short Bio") + expect(page).not_to have_css(".form-error.is-visible") + expect(page).not_to have_content("This information won't be published") + expect(page).not_to have_css("#proposal-custom-field-private_body") + expect(page).not_to have_content("Phone Number") + end + end + + context "when creating the proposal" do + it "saves the proposal in XML" do + fill_in :proposal_title_en, with: "A far west character" + fill_in :"text-1476748004559", with: "Lucky Luke" + fill_in :"textarea-1476748007461", with: "I shot everything" + + click_link_or_button "Update" + + expect(Decidim::Proposals::Proposal.last.body["en"]).to include('
Lucky Luke
') + expect(Decidim::Proposals::Proposal.last.body["en"]).to include('
I shot everything
') + end + + context "and has multiple languages" do + it "saves the proposal in XML" do + fill_in :proposal_title_en, with: "A far west character" + fill_in :"text-1476748004559", with: "Lucky Luke" + fill_in :"textarea-1476748007461", with: "I shot everything" + + within "#proposal-body-tabs" do + click_link_or_button "CatalĂ " + end + + fill_in :"text-1476748004559", with: "Lucky Luke" + fill_in :"textarea-1476748007461", with: "Li agrada disparar" + + click_link_or_button "Update" + + expect(Decidim::Proposals::Proposal.last.body["en"]).to include('
Lucky Luke
') + expect(Decidim::Proposals::Proposal.last.body["en"]).to include('
I shot everything
') + expect(Decidim::Proposals::Proposal.last.body["ca"]).to include('
Li agrada disparar
') + end + end + end + end + + context "when answering the proposal" do + it "displays custom fields" do + find("a.action-icon--show-proposal").click + expect(page).to have_content("Bio") + within "#textarea-1476748007461" do + expect(page).to have_content("I shot the sheriff") + end + click_link_or_button "Private body" + expect(page).to have_content("Phone Number") + within "#text-1476748004579" do + expect(page).to have_content("555-555-555") + end + expect(page).to have_content("This data was last updated less than a minute ago.") + expect(page).not_to have_content("You might want to remove it") + end + + context "when private data is required to be removed" do + before do + # rubocop:disable Rails/SkipsModelValidations + proposal.extra_fields.update_column(:private_body_updated_at, 4.months.ago) + # rubocop:enable Rails/SkipsModelValidations + find("a.action-icon--show-proposal").click + end + + it "displays a warning" do + click_link_or_button "Private body" + expect(page).to have_content("Phone Number") + expect(page).to have_content("This data was last updated 4 months ago.") + expect(page).to have_content("You might want to remove it") + end + end + + context "when private data is removed" do + before do + proposal.extra_fields.update(private_body: nil) + find("a.action-icon--show-proposal").click + end + + it "shows destroyed date" do + click_link_or_button "Private body" + expect(page).not_to have_content("Phone Number") + expect(page).to have_content("This data was destroyed less than a minute ago.") + expect(page).not_to have_content("555-555-555") + expect(page).not_to have_content("You might want to remove it") + end + end + + context "when no private data, nor private data last update is present" do + before do + # rubocop:disable Rails/SkipsModelValidations + proposal.extra_fields.update_column(:private_body, nil) + proposal.extra_fields.update_column(:private_body_updated_at, nil) + # rubocop:enable Rails/SkipsModelValidations + find("a.action-icon--show-proposal").click + end + + it "does not display the private data" do + click_link_or_button "Private body" + expect(page).not_to have_content("Phone Number") + expect(page).not_to have_content("This data was last updated") + expect(page).not_to have_content("This data was last destroyed") + expect(page).not_to have_content("You might want to remove it") + end + end + + context "when private fields are scoped to other places" do + let!(:private_constraint) { create(:config_constraint, awesome_config: private_config_helper, settings: { "participatory_space_manifest" => "assemblies" }) } + + it "does not display private custom fields" do + find("a.action-icon--show-proposal").click + expect(page).to have_content("Bio") + within "#textarea-1476748007461" do + expect(page).to have_content("I shot the sheriff") + end + expect(page).not_to have_content("Private body") + expect(page).not_to have_content("Phone Number") + expect(page).not_to have_content("555-555-555") + end + end + end +end