From 44aec87288c38d1431a4eacf4893c842a2c5f026 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 10:37:22 +0000 Subject: [PATCH 01/55] [TAN-3815] Added hidden community monitor project --- .../web_api/v1/projects_controller.rb | 31 +++++++++++++++++++ back/app/models/project.rb | 6 ++-- back/app/policies/project_policy.rb | 4 +++ back/config/routes.rb | 1 + back/spec/acceptance/projects_spec.rb | 18 +++++++++++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 9c78350c00b4..a0c73017e6a3 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -317,6 +317,37 @@ def destroy_participation_data ).serializable_hash, status: :ok end + def community_monitor + # TODO: Use app config to get the project id + project = Project.unscoped.find_by(visible_to: 'nobody', internal_role: 'community_monitor') + + # Create the project if it doesn't exist + unless project + # TODO: Get multilocs from yml + project = Project.create!( + title_multiloc: { 'en' => 'Community Monitor' }, + visible_to: 'nobody', + internal_role: 'community_monitor' + ) + Phase.create!( + title_multiloc: { 'en' => 'Community Monitor' }, + project: project, + participation_method: 'native_survey', + start_at: Time.now, + campaigns_settings: { project_phase_started: true }, # TODO: Is this correct? + native_survey_title_multiloc: { 'en' => 'Community Monitor' }, + native_survey_button_multiloc: { 'en' => 'Take survey' } + ) + end + + authorize project + render json: WebApi::V1::ProjectSerializer.new( + project, + params: jsonapi_serializer_params, + include: %i[current_phase] + ).serializable_hash + end + private def sidefx diff --git a/back/app/models/project.rb b/back/app/models/project.rb index d52b0070c1b4..6c6480221ac2 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -37,7 +37,7 @@ class Project < ApplicationRecord attribute :preview_token, :string, default: -> { generate_preview_token } - VISIBLE_TOS = %w[public groups admins].freeze + VISIBLE_TOS = %w[public groups admins nobody].freeze slug from: proc { |project| project.title_multiloc.values.find(&:present?) } @@ -88,7 +88,7 @@ class Project < ApplicationRecord after_save :reassign_moderators, if: :folder_changed? after_commit :clear_folder_changes, if: :folder_changed? - INTERNAL_ROLES = %w[open_idea_box].freeze + INTERNAL_ROLES = %w[open_idea_box community_monitor].freeze validates :title_multiloc, presence: true, multiloc: { presence: true } validates :description_multiloc, multiloc: { presence: false, html: true } @@ -97,6 +97,8 @@ class Project < ApplicationRecord validates :internal_role, inclusion: { in: INTERNAL_ROLES, allow_nil: true } validate :admin_publication_must_exist, unless: proc { Current.loading_tenant_template } # TODO: This should always be validated! + default_scope { where.not(visible_to: 'nobody') } + pg_search_scope :search_by_all, against: %i[title_multiloc description_multiloc description_preview_multiloc slug], using: { tsearch: { prefix: true } } diff --git a/back/app/policies/project_policy.rb b/back/app/policies/project_policy.rb index c9e43fa10d5f..5ef3cc5ec785 100644 --- a/back/app/policies/project_policy.rb +++ b/back/app/policies/project_policy.rb @@ -185,6 +185,10 @@ def active_moderator? UserRoleService.new.can_moderate_project? record, user end + def community_monitor? + true + end + private def update_status? diff --git a/back/config/routes.rb b/back/config/routes.rb index f1f82d7290c0..5034f531a0c3 100644 --- a/back/config/routes.rb +++ b/back/config/routes.rb @@ -192,6 +192,7 @@ get 'finished_or_archived', action: 'index_finished_or_archived' get 'for_followed_item', action: 'index_for_followed_item' get 'with_active_participatory_phase', action: 'index_with_active_participatory_phase' + get 'community_monitor', action: 'community_monitor' end resource :review, controller: 'project_reviews' diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 36e2a8efa8c8..9067582f85f2 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1681,4 +1681,22 @@ end end end + + get 'web_api/v1/projects/community_monitor' do + context 'hidden community monitor project exists' do + let!(:project) { create(:project, visible_to: 'nobody', internal_role: 'community_monitor') } + + example 'Get community monitor project' do + do_request + assert_status 200 + end + end + + context 'hidden community monitor project does ' do + example 'Create and get community monitor project' do + do_request + assert_status 200 + end + end + end end From 166727639f18e9ed78e240151663f2e35c64a6f5 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 11:15:06 +0000 Subject: [PATCH 02/55] [TAN-3815] Added native_survey_method column --- back/app/models/phase.rb | 2 ++ back/db/structure.sql | 4 +++- back/lib/participation_method/native_survey.rb | 4 ++++ back/spec/factories/phases.rb | 1 + back/spec/models/phase_spec.rb | 1 + 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index b0d0af7c4660..d37e8932a22b 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -68,6 +68,7 @@ class Phase < ApplicationRecord PARTICIPATION_METHODS = ParticipationMethod::Base.all_methods.map(&:method_str).freeze VOTING_METHODS = %w[budgeting multiple_voting single_voting].freeze + NATIVE_SURVEY_METHODS = %w[standard community_monitor].freeze PRESENTATION_MODES = %w[card map].freeze REACTING_METHODS = %w[unlimited limited].freeze INPUT_TERMS = %w[idea question contribution project issue option proposal initiative petition].freeze @@ -184,6 +185,7 @@ class Phase < ApplicationRecord # native_survey? with_options if: :native_survey? do + validates :native_survey_method, presence: true, inclusion: { in: NATIVE_SURVEY_METHODS } validates :native_survey_title_multiloc, presence: true, multiloc: { presence: true } validates :native_survey_button_multiloc, presence: true, multiloc: { presence: true } end diff --git a/back/db/structure.sql b/back/db/structure.sql index 30223523daa0..c71ae316e6ef 100644 --- a/back/db/structure.sql +++ b/back/db/structure.sql @@ -1564,7 +1564,8 @@ CREATE TABLE public.phases ( manual_votes_count integer DEFAULT 0 NOT NULL, manual_voters_amount integer, manual_voters_last_updated_by_id uuid, - manual_voters_last_updated_at timestamp(6) without time zone + manual_voters_last_updated_at timestamp(6) without time zone, + native_survey_method character varying ); @@ -6826,6 +6827,7 @@ ALTER TABLE ONLY public.ideas_topics SET search_path TO public,shared_extensions; INSERT INTO "schema_migrations" (version) VALUES +('20250211103910'), ('20250204143605'), ('20250120125531'), ('20250117121004'), diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 099a7b4e0e85..6ed07a5f9793 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -17,6 +17,10 @@ def assign_defaults(input) input.idea_status ||= IdeaStatus.find_by!(code: 'proposed', participation_method: 'ideation') end + def assign_defaults_for_phase + phase.native_survey_method ||= 'standard' + end + # NOTE: This is only ever used by the analyses controller - otherwise the front-end always persists the form def create_default_form! form = CustomForm.new(participation_context: phase) diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index 724088142c8f..f0ff3583f351 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -104,6 +104,7 @@ factory :native_survey_phase do participation_method { 'native_survey' } + native_survey_method { 'standard' } native_survey_title_multiloc { { 'en' => 'Survey', 'nl-BE' => 'Vragenlijst' } } native_survey_button_multiloc { { 'en' => 'Take the survey', 'nl-BE' => 'De enquete invullen' } } factory :active_native_survey_phase do diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index e61ccf26c09b..5aacc4cbad0e 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -82,6 +82,7 @@ it 'can be changed from ideation to native_survey' do phase = create(:phase, participation_method: 'ideation') phase.participation_method = 'native_survey' + phase.native_survey_method = 'standard' phase.native_survey_title_multiloc = { en: 'Survey' } phase.native_survey_button_multiloc = { en: 'Take the survey' } phase.ideas_order = nil From 7924cd02fdc75bb0a5067a15f5e0bfaaac2389ea Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 11:25:59 +0000 Subject: [PATCH 03/55] [TAN-3815] Test fix --- back/app/services/projects_finder_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/back/app/services/projects_finder_service.rb b/back/app/services/projects_finder_service.rb index 146a69ee50d1..34887791b700 100644 --- a/back/app/services/projects_finder_service.rb +++ b/back/app/services/projects_finder_service.rb @@ -28,6 +28,7 @@ def participation_possible # second by project created_at, and third by project ID. # Secondary & ternary orderings prevent duplicates when paginating, when prior ordering involves equivalent values projects = Project + .unscoped # to avoid ambiguity clash with the default scope of the Project model .from(subquery, :projects) .distinct .order('phase_end_at ASC NULLS LAST, projects_created_at ASC, projects_id ASC') From 557125bc390626c9c777f4e793d0fa3d0e724257 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 11:32:07 +0000 Subject: [PATCH 04/55] [TAN-3815] Added DB migration for native_survey_method --- .../20250211103910_add_native_survey_method_to_phases.rb | 8 ++++++++ back/spec/acceptance/projects_spec.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb diff --git a/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb b/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb new file mode 100644 index 000000000000..2ef88505e71e --- /dev/null +++ b/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddNativeSurveyMethodToPhases < ActiveRecord::Migration[7.1] + def change + add_column :phases, :native_survey_method, :string, null: true, default: nil + Phase.where(participation_method: 'native_survey').update_all(native_survey_method: 'standard') + end +end \ No newline at end of file diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 9067582f85f2..7d94165e7bc7 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1692,7 +1692,7 @@ end end - context 'hidden community monitor project does ' do + context 'hidden community monitor project does' do example 'Create and get community monitor project' do do_request assert_status 200 From dfcf6b3a7c4b53ad8ec2f8689e5d944beaa2ee73 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 12:02:07 +0000 Subject: [PATCH 05/55] [TAN-3815] Fixed tests --- .../20250211103910_add_native_survey_method_to_phases.rb | 2 +- back/spec/services/side_fx_phase_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb b/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb index 2ef88505e71e..715314798d6d 100644 --- a/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb +++ b/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb @@ -5,4 +5,4 @@ def change add_column :phases, :native_survey_method, :string, null: true, default: nil Phase.where(participation_method: 'native_survey').update_all(native_survey_method: 'standard') end -end \ No newline at end of file +end diff --git a/back/spec/services/side_fx_phase_spec.rb b/back/spec/services/side_fx_phase_spec.rb index e387f113fb36..02e5c2e38258 100644 --- a/back/spec/services/side_fx_phase_spec.rb +++ b/back/spec/services/side_fx_phase_spec.rb @@ -61,6 +61,7 @@ phase.update!( # Required to avoid errors when switching to native survey below ideas_order: nil, + native_survey_method: 'standard', native_survey_title_multiloc: { en: 'Survey' }, native_survey_button_multiloc: { en: 'Take the survey' } ) From d1d2b20a36cd0d0cc5d733409af19aa0e333f779 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 12:47:50 +0000 Subject: [PATCH 06/55] [TAN-3815] Added model tests for community_monitor phase --- back/app/models/phase.rb | 17 ++++++++++++++++ back/spec/models/phase_spec.rb | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index d37e8932a22b..6e3477cab615 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -188,6 +188,7 @@ class Phase < ApplicationRecord validates :native_survey_method, presence: true, inclusion: { in: NATIVE_SURVEY_METHODS } validates :native_survey_title_multiloc, presence: true, multiloc: { presence: true } validates :native_survey_button_multiloc, presence: true, multiloc: { presence: true } + validate :validate_community_monitor_phase end scope :published, lambda { @@ -345,6 +346,22 @@ def validate_no_other_overlapping_phases end end + def validate_community_monitor_phase + return unless native_survey_method == 'community_monitor' + + if self.project.phases.count > 1 + errors.add(:native_survey_method, :too_many_phases, message: 'community_monitor project can only have one phase') + end + + if self.project.visible_to != 'nobody' + errors.add(:native_survey_method, :project_not_hidden, message: 'community_monitor projects must be hidden') + end + + if self.end_at.present? + errors.add(:native_survey_method, :has_end_at, message: 'community_monitor projects cannot have an end date') + end + end + def strip_title title_multiloc.each do |key, value| title_multiloc[key] = value.strip diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 5aacc4cbad0e..ddcab47196b4 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -467,6 +467,43 @@ end end + describe '#validate_community_monitor_phase' do + let(:project) { create(:project) } + let(:survey_phase) { create(:native_survey_phase, project: project, start_at: Time.zone.today, end_at: nil) } + + context 'survey is not a community monitor survey' do + it 'is valid when the phase is not a community monitor native survey' do + expect(survey_phase).to be_valid + end + end + + context 'survey is a community monitor survey' do + before do + project.update! visible_to: 'nobody', internal_role: 'community_monitor' + survey_phase.update! native_survey_method: 'community_monitor' + end + + it 'is valid' do + expect(survey_phase).to be_valid + end + + it 'is not valid when the project has more than one phase' do + project.phases << create(:phase, project: project, start_at: survey_phase.start_at - 10.days, end_at: survey_phase.start_at - 5.days) + expect(survey_phase).not_to be_valid + end + + it 'is not valid when the phase has an end date' do + survey_phase.end_at = Time.zone.today + 1.day + expect(survey_phase).not_to be_valid + end + + it 'is not valid when the project is not visible to anyone' do + project.visible_to = 'public' + expect(survey_phase).not_to be_valid + end + end + end + describe '#disliking_enabled' do it 'defaults to false when disable_disliking feature flag is enabled (default)' do # binding.pry From bc4f5b9890eb2ea3a7349de47585d91e91f28b66 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 13:59:09 +0000 Subject: [PATCH 07/55] [TAN-3815] Rubocop fix --- back/app/models/phase.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index 6e3477cab615..8c4484f8b3d5 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -349,15 +349,15 @@ def validate_no_other_overlapping_phases def validate_community_monitor_phase return unless native_survey_method == 'community_monitor' - if self.project.phases.count > 1 + if project.phases.count > 1 errors.add(:native_survey_method, :too_many_phases, message: 'community_monitor project can only have one phase') end - if self.project.visible_to != 'nobody' + if project.visible_to != 'nobody' errors.add(:native_survey_method, :project_not_hidden, message: 'community_monitor projects must be hidden') end - if self.end_at.present? + if end_at.present? errors.add(:native_survey_method, :has_end_at, message: 'community_monitor projects cannot have an end date') end end From 45860aba000a925b36340829c11b5a4dd475add8 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 15:00:39 +0000 Subject: [PATCH 08/55] [TAN-3815] Added feature flag --- .../web_api/v1/projects_controller.rb | 3 +++ back/config/schemas/settings.schema.json.erb | 27 +++++++++++++++++++ back/spec/acceptance/projects_spec.rb | 11 ++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index a0c73017e6a3..f9e4d53c863e 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -319,6 +319,9 @@ def destroy_participation_data def community_monitor # TODO: Use app config to get the project id + + AppConfiguration.instance.feature_activated?('community_monitor') || raise(ActiveRecord::RecordNotFound) + project = Project.unscoped.find_by(visible_to: 'nobody', internal_role: 'community_monitor') # Create the project if it doesn't exist diff --git a/back/config/schemas/settings.schema.json.erb b/back/config/schemas/settings.schema.json.erb index 8ecd3450d5a7..198c08b6e295 100644 --- a/back/config/schemas/settings.schema.json.erb +++ b/back/config/schemas/settings.schema.json.erb @@ -1360,6 +1360,33 @@ } } } + }, + + "community_monitor": { + "type": "object", + "title": "Community Monitor", + "description": "Long running public survey.", + "additionalProperties": false, + "required": [ + "allowed", + "enabled" + ], + "properties": { + "allowed": { + "type": "boolean", + "default": false + }, + "enabled": { + "type": "boolean", + "default": false + }, + "project_id": { + "title": "The ID of the required hidden project", + "description": "Do not edit unless you know what you're doing. This is usually set by the system", + "type": "string", + "private": true + } + } } }, "dependencies": { diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 7d94165e7bc7..5f0d0e99820e 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1687,16 +1687,23 @@ let!(:project) { create(:project, visible_to: 'nobody', internal_role: 'community_monitor') } example 'Get community monitor project' do + SettingsService.new.activate_feature! 'community_monitor' do_request assert_status 200 end end - context 'hidden community monitor project does' do - example 'Create and get community monitor project' do + context 'hidden community monitor project does not exist' do + example 'Create and get hidden community monitor project' do + SettingsService.new.activate_feature! 'community_monitor' do_request assert_status 200 end + + example 'Error: Hidden project does not get created without feature flag' do + do_request + assert_status 404 + end end end end From f76c55e14840e27830fd3610627364b49d7e25a3 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 16:12:23 +0000 Subject: [PATCH 09/55] [TAN-3815] Added native_survey_method attribute to phase serializer --- .../app/serializers/web_api/v1/phase_serializer.rb | 2 +- back/lib/participation_method/native_survey.rb | 2 +- .../web_api/v1/phase_serializer_spec.rb | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/back/app/serializers/web_api/v1/phase_serializer.rb b/back/app/serializers/web_api/v1/phase_serializer.rb index 3ec35518504c..45cb41fc14ec 100644 --- a/back/app/serializers/web_api/v1/phase_serializer.rb +++ b/back/app/serializers/web_api/v1/phase_serializer.rb @@ -15,7 +15,7 @@ class WebApi::V1::PhaseSerializer < WebApi::V1::BaseSerializer %i[ voting_method voting_max_total voting_min_total voting_max_votes_per_idea baskets_count - native_survey_title_multiloc native_survey_button_multiloc + native_survey_method native_survey_title_multiloc native_survey_button_multiloc expire_days_limit reacting_threshold autoshare_results_enabled ].each do |attribute_name| attribute attribute_name, if: proc { |phase| diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 6ed07a5f9793..42bf62f51886 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -122,7 +122,7 @@ def supports_permitted_by_everyone? end def supports_serializing?(attribute) - %i[native_survey_title_multiloc native_survey_button_multiloc].include?(attribute) + %i[native_survey_method native_survey_title_multiloc native_survey_button_multiloc].include?(attribute) end def supports_submission? diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index 397c63916c17..5efded1ae0b3 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -70,4 +70,18 @@ end end end + + context 'for a native survey phase' do + let(:user) { create(:user) } + let(:phase) { create(:native_survey_phase) } + + it 'includes native survey attributes' do + expect(result.dig(:data, :attributes).keys).to include( + :native_survey_method, + :native_survey_title_multiloc, + :native_survey_button_multiloc + ) + expect(result.dig(:data, :attributes, :native_survey_method)).to eq 'standard' + end + end end From 1508955dc081db6ffab37afdc1e20e1d60a35289 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 16:26:51 +0000 Subject: [PATCH 10/55] [TAN-3815] Added native_survey_method attribute to public API, project copy and seeds --- back/app/services/project_copy_service.rb | 1 + back/engines/commercial/multi_tenancy/db/seeds/projects.rb | 1 + .../app/serializers/public_api/v2/phase_serializer.rb | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/back/app/services/project_copy_service.rb b/back/app/services/project_copy_service.rb index d9b360a70ef9..79e1316d294e 100644 --- a/back/app/services/project_copy_service.rb +++ b/back/app/services/project_copy_service.rb @@ -336,6 +336,7 @@ def yml_phases(shift_timestamps: 0, timeline_start_at: nil) end if yml_phase['participation_method'] == 'native_survey' + yml_phase['native_survey_method'] = phase.native_survey_method yml_phase['native_survey_title_multiloc'] = phase.native_survey_title_multiloc yml_phase['native_survey_button_multiloc'] = phase.native_survey_button_multiloc end diff --git a/back/engines/commercial/multi_tenancy/db/seeds/projects.rb b/back/engines/commercial/multi_tenancy/db/seeds/projects.rb index b2f8d0cb063c..5fce7b2f3a58 100644 --- a/back/engines/commercial/multi_tenancy/db/seeds/projects.rb +++ b/back/engines/commercial/multi_tenancy/db/seeds/projects.rb @@ -48,6 +48,7 @@ def create_mixed_3_methods_project start_at: Time.zone.today + 11.days, end_at: nil, campaigns_settings: { project_phase_started: true }, + native_survey_method: 'standard', native_survey_title_multiloc: { 'en' => 'Survey' }, native_survey_button_multiloc: { 'en' => 'Take the survey' } ) diff --git a/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb b/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb index 3db3927232d7..e188ff27ee13 100644 --- a/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb +++ b/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb @@ -23,8 +23,10 @@ class PublicApi::V2::PhaseSerializer < PublicApi::V2::BaseSerializer :reacting_dislike_enabled, :reacting_dislike_method, :reacting_dislike_limited_max, + :voting_method, :voting_max_total, - :voting_min_total + :voting_min_total, + :native_survey_method def title multiloc_service.t(object.title_multiloc) From faa7d763c4714cad748960ea070c370ef4235eef Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 16:53:43 +0000 Subject: [PATCH 11/55] [TAN-3815] Created native_survey_method classes --- back/lib/factory.rb | 9 +++ back/lib/native_survey_method/base.rb | 64 +++++++++++++++++++ .../native_survey_method/community_monitor.rb | 7 ++ .../lib/participation_method/native_survey.rb | 57 ++--------------- 4 files changed, 86 insertions(+), 51 deletions(-) create mode 100644 back/lib/native_survey_method/base.rb create mode 100644 back/lib/native_survey_method/community_monitor.rb diff --git a/back/lib/factory.rb b/back/lib/factory.rb index bba153c661b6..18d4f6f78330 100644 --- a/back/lib/factory.rb +++ b/back/lib/factory.rb @@ -19,5 +19,14 @@ def voting_method_for(phase) end end + def native_survey_method_for(phase) + case phase&.native_survey_method + when 'community_monitor' + ::NativeSurveyMethod::CommunityMonitor.new(phase) + else + ::NativeSurveyMethod::Base.new(phase) + end + end + private_class_method :new end diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb new file mode 100644 index 000000000000..83a64064f753 --- /dev/null +++ b/back/lib/native_survey_method/base.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module NativeSurveyMethod + class Base + def initialize(phase) + @phase = phase + end + + def allowed_extra_field_input_types + %w[page number linear_scale rating text multiline_text select multiselect + multiselect_image file_upload shapefile_upload point line polygon + ranking matrix_linear_scale] + end + + def default_fields(custom_form) + return [] if custom_form.persisted? + + multiloc_service = MultilocService.new + [ + CustomField.new( + id: SecureRandom.uuid, + key: 'page1', + resource: custom_form, + input_type: 'page', + page_layout: 'default' + ), + CustomField.new( + id: SecureRandom.uuid, + key: CustomFieldService.new.generate_key( + multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title').values.first + ), + resource: custom_form, + input_type: 'select', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title'), + options: [ + CustomFieldOption.new( + id: SecureRandom.uuid, + key: 'option1', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option1') + ), + CustomFieldOption.new( + id: SecureRandom.uuid, + key: 'option2', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option2') + ) + ] + ), + CustomField.new( + id: SecureRandom.uuid, + key: 'survey_end', + resource: custom_form, + input_type: 'page', + page_layout: 'default', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), + description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') + ) + ] + end + + private + + attr_reader :phase + end +end \ No newline at end of file diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb new file mode 100644 index 000000000000..5cfb0b119772 --- /dev/null +++ b/back/lib/native_survey_method/community_monitor.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module NativeSurveyMethod + class CommunityMonitor < Base + # TODO: implement different methods for community monitor + end +end \ No newline at end of file diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 42bf62f51886..157e96e9b44b 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,16 +2,12 @@ module ParticipationMethod class NativeSurvey < Base + delegate :allowed_extra_field_input_types, :default_fields, to: :native_survey_method + def self.method_str 'native_survey' end - def allowed_extra_field_input_types - %w[page number linear_scale rating text multiline_text select multiselect - multiselect_image file_upload shapefile_upload point line polygon - ranking matrix_linear_scale] - end - def assign_defaults(input) input.publication_status ||= 'published' input.idea_status ||= IdeaStatus.find_by!(code: 'proposed', participation_method: 'ideation') @@ -40,51 +36,6 @@ def custom_form phase.custom_form || CustomForm.new(participation_context: phase) end - def default_fields(custom_form) - return [] if custom_form.persisted? - - multiloc_service = MultilocService.new - [ - CustomField.new( - id: SecureRandom.uuid, - key: 'page1', - resource: custom_form, - input_type: 'page', - page_layout: 'default' - ), - CustomField.new( - id: SecureRandom.uuid, - key: CustomFieldService.new.generate_key( - multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title').values.first - ), - resource: custom_form, - input_type: 'select', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title'), - options: [ - CustomFieldOption.new( - id: SecureRandom.uuid, - key: 'option1', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option1') - ), - CustomFieldOption.new( - id: SecureRandom.uuid, - key: 'option2', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option2') - ) - ] - ), - CustomField.new( - id: SecureRandom.uuid, - key: 'survey_end', - resource: custom_form, - input_type: 'page', - page_layout: 'default', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), - description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') - ) - ] - end - # Survey responses do not have a fixed field that can be used # to generate a slug, so use the id as the basis for the slug. def generate_slug(input) @@ -136,5 +87,9 @@ def supports_survey_form? def supports_toxicity_detection? false end + + def native_survey_method + Factory.instance.native_survey_method_for(phase) + end end end From 0e2e0b8e9c1eb121df077ff2093f399c9a298360 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 17:28:06 +0000 Subject: [PATCH 12/55] [TAN-3815] Fixed feature flag for community monitor phase --- .../web_api/v1/projects_controller.rb | 17 ++++++++++++----- back/lib/native_survey_method/base.rb | 2 +- .../native_survey_method/community_monitor.rb | 2 +- back/spec/acceptance/projects_spec.rb | 18 +++++++++++++++++- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index f9e4d53c863e..2a4a5f4dd2a8 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -318,15 +318,17 @@ def destroy_participation_data end def community_monitor - # TODO: Use app config to get the project id + settings = AppConfiguration.instance.settings + settings['community_monitor']&['enabled'] || raise(ActiveRecord::RecordNotFound) - AppConfiguration.instance.feature_activated?('community_monitor') || raise(ActiveRecord::RecordNotFound) - - project = Project.unscoped.find_by(visible_to: 'nobody', internal_role: 'community_monitor') + # Find the community monitor project + project = nil + project_id = settings['community_monitor']&['project_id'] + project = Project.unscoped.find(project_id) if project_id # Create the project if it doesn't exist unless project - # TODO: Get multilocs from yml + # TODO: JS Get multilocs from yml project = Project.create!( title_multiloc: { 'en' => 'Community Monitor' }, visible_to: 'nobody', @@ -338,9 +340,14 @@ def community_monitor participation_method: 'native_survey', start_at: Time.now, campaigns_settings: { project_phase_started: true }, # TODO: Is this correct? + native_survey_method: 'community_monitor', native_survey_title_multiloc: { 'en' => 'Community Monitor' }, native_survey_button_multiloc: { 'en' => 'Take survey' } ) + + # Set the ID in the settings + settings['community_monitor']['project_id'] = project.id + AppConfiguration.instance.update!(settings: settings) end authorize project diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index 83a64064f753..95ea47ad19a3 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -61,4 +61,4 @@ def default_fields(custom_form) attr_reader :phase end -end \ No newline at end of file +end diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index 5cfb0b119772..6a4c58277cc4 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -4,4 +4,4 @@ module NativeSurveyMethod class CommunityMonitor < Base # TODO: implement different methods for community monitor end -end \ No newline at end of file +end diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 5f0d0e99820e..1896bc2e1f38 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1687,7 +1687,10 @@ let!(:project) { create(:project, visible_to: 'nobody', internal_role: 'community_monitor') } example 'Get community monitor project' do - SettingsService.new.activate_feature! 'community_monitor' + settings = AppConfiguration.instance.settings + settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => project.id } + AppConfiguration.instance.update!(settings:) + do_request assert_status 200 end @@ -1698,6 +1701,8 @@ SettingsService.new.activate_feature! 'community_monitor' do_request assert_status 200 + expect(Project.unscoped.first.visible_to).to eq 'nobody' + expect(Phase.first.native_survey_method).to eq 'community_monitor' end example 'Error: Hidden project does not get created without feature flag' do @@ -1705,5 +1710,16 @@ assert_status 404 end end + + context 'stored community monitor project ID is incorrect' do + example 'Error: Hidden project does not exist' do + settings = AppConfiguration.instance.settings + settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => 'NON_EXISTENT' } + AppConfiguration.instance.update!(settings:) + + do_request + assert_status 404 + end + end end end From bc8cd55edeb8b65f33e1a612c67cb8e1ee0640c6 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 11 Feb 2025 19:33:43 +0000 Subject: [PATCH 13/55] [TAN-3815] Added different form and allowed types for community_monitor form --- .../web_api/v1/projects_controller.rb | 4 +- .../native_survey_method/community_monitor.rb | 56 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 2a4a5f4dd2a8..fe6ab22dc176 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -319,11 +319,11 @@ def destroy_participation_data def community_monitor settings = AppConfiguration.instance.settings - settings['community_monitor']&['enabled'] || raise(ActiveRecord::RecordNotFound) + settings.dig('community_monitor', 'enabled') || raise(ActiveRecord::RecordNotFound) # Find the community monitor project project = nil - project_id = settings['community_monitor']&['project_id'] + project_id = settings.dig('community_monitor', 'project_id') project = Project.unscoped.find(project_id) if project_id # Create the project if it doesn't exist diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index 6a4c58277cc4..8e5bc46ebae4 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -2,6 +2,60 @@ module NativeSurveyMethod class CommunityMonitor < Base - # TODO: implement different methods for community monitor + def allowed_extra_field_input_types + %w[page text linear_scale rating select multiselect] + end + def default_fields(custom_form) + return [] if custom_form.persisted? + + multiloc_service = MultilocService.new + [ + CustomField.new( + id: SecureRandom.uuid, + key: 'page1', + resource: custom_form, + input_type: 'page', + page_layout: 'default' + ), + CustomField.new( + id: SecureRandom.uuid, + key: CustomFieldService.new.generate_key( + multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title').values.first + ), + resource: custom_form, + input_type: 'select', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title'), + options: [ + CustomFieldOption.new( + id: SecureRandom.uuid, + key: 'option1', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option1') + ), + CustomFieldOption.new( + id: SecureRandom.uuid, + key: 'option2', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option2') + ) + ] + ), + CustomField.new( + id: SecureRandom.uuid, + key: 'test', + input_type: 'text', + title_multiloc: { en: 'Test' } + ), + CustomField.new( + id: SecureRandom.uuid, + key: 'survey_end', + resource: custom_form, + input_type: 'page', + page_layout: 'default', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), + description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') + ) + ] + end + + end end From e36b26ecd7a12185f64473e4fd58700f6cfd48de Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 12 Feb 2025 08:42:13 +0000 Subject: [PATCH 14/55] [TAN-3815] Added multilocs to community monitor project creation --- .../controllers/web_api/v1/projects_controller.rb | 12 ++++++------ back/config/locales/en.yml | 1 + back/lib/native_survey_method/community_monitor.rb | 5 ++--- back/spec/acceptance/projects_spec.rb | 13 +++++++++++-- back/spec/fixtures/locales/en.yml | 5 +++++ 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index fe6ab22dc176..d98ed6774042 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -321,28 +321,28 @@ def community_monitor settings = AppConfiguration.instance.settings settings.dig('community_monitor', 'enabled') || raise(ActiveRecord::RecordNotFound) - # Find the community monitor project + # Find the community monitor project from config project = nil project_id = settings.dig('community_monitor', 'project_id') project = Project.unscoped.find(project_id) if project_id # Create the project if it doesn't exist unless project - # TODO: JS Get multilocs from yml + multiloc_service = MultilocService.new project = Project.create!( - title_multiloc: { 'en' => 'Community Monitor' }, + title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), visible_to: 'nobody', internal_role: 'community_monitor' ) Phase.create!( - title_multiloc: { 'en' => 'Community Monitor' }, + title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), project: project, participation_method: 'native_survey', start_at: Time.now, campaigns_settings: { project_phase_started: true }, # TODO: Is this correct? native_survey_method: 'community_monitor', - native_survey_title_multiloc: { 'en' => 'Community Monitor' }, - native_survey_button_multiloc: { 'en' => 'Take survey' } + native_survey_title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), + native_survey_button_multiloc: multiloc_service.i18n_to_multiloc('phases.native_survey_button') ) # Set the ID in the settings diff --git a/back/config/locales/en.yml b/back/config/locales/en.yml index a9dccf5f1cbc..6a8c530e53d2 100644 --- a/back/config/locales/en.yml +++ b/back/config/locales/en.yml @@ -126,6 +126,7 @@ en: open_idea_phase_title: Current phase native_survey_title: Survey native_survey_button: Take the survey + community_monitor_title: Community monitor events: council_meeting_title: Council Meeting council_meeting_description: > diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index 8e5bc46ebae4..23a60a38f29a 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -5,6 +5,7 @@ class CommunityMonitor < Base def allowed_extra_field_input_types %w[page text linear_scale rating select multiselect] end + def default_fields(custom_form) return [] if custom_form.persisted? @@ -42,7 +43,7 @@ def default_fields(custom_form) id: SecureRandom.uuid, key: 'test', input_type: 'text', - title_multiloc: { en: 'Test' } + title_multiloc: { en: 'Test' } ), CustomField.new( id: SecureRandom.uuid, @@ -55,7 +56,5 @@ def default_fields(custom_form) ) ] end - - end end diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 1896bc2e1f38..b51b7ac90067 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1699,10 +1699,19 @@ context 'hidden community monitor project does not exist' do example 'Create and get hidden community monitor project' do SettingsService.new.activate_feature! 'community_monitor' + do_request assert_status 200 - expect(Project.unscoped.first.visible_to).to eq 'nobody' - expect(Phase.first.native_survey_method).to eq 'community_monitor' + + created_project = Project.unscoped.first + created_phase = Phase.first + expect(created_project.visible_to).to eq 'nobody' + expect(created_project.title_multiloc['en']).to eq 'Community monitor' + expect(created_phase.native_survey_method).to eq 'community_monitor' + expect(created_phase.title_multiloc['en']).to eq 'Community monitor' + + settings = AppConfiguration.instance.settings + expect(settings['community_monitor']['project_id']).to eq created_project.id end example 'Error: Hidden project does not get created without feature flag' do diff --git a/back/spec/fixtures/locales/en.yml b/back/spec/fixtures/locales/en.yml index c617bd54d079..e03da6a18905 100644 --- a/back/spec/fixtures/locales/en.yml +++ b/back/spec/fixtures/locales/en.yml @@ -62,6 +62,11 @@ en: description: other_input_field: title: Type your answer + phases: + open_idea_phase_title: Current phase + native_survey_title: Survey + native_survey_button: Take the survey + community_monitor_title: Community monitor custom_forms: categories: main_content: From 0cf159237a6ae4a45c84e66d8cea31ccf998a308 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 12 Feb 2025 08:50:37 +0000 Subject: [PATCH 15/55] [TAN-3815] Added comment --- back/app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/app/models/project.rb b/back/app/models/project.rb index 6c6480221ac2..81e22d0da01c 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -97,7 +97,7 @@ class Project < ApplicationRecord validates :internal_role, inclusion: { in: INTERNAL_ROLES, allow_nil: true } validate :admin_publication_must_exist, unless: proc { Current.loading_tenant_template } # TODO: This should always be validated! - default_scope { where.not(visible_to: 'nobody') } + default_scope { where.not(visible_to: 'nobody') } # To hide hidden projects such as community monitor pg_search_scope :search_by_all, against: %i[title_multiloc description_multiloc description_preview_multiloc slug], From c006832cda98ee77392eca20dd5708d4463ddd7c Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Thu, 13 Feb 2025 14:19:45 +0000 Subject: [PATCH 16/55] [TAN-3815] Added form_builder_config attribute to phase --- .../web_api/v1/phase_serializer.rb | 6 ++++ back/lib/native_survey_method/base.rb | 4 +++ .../native_survey_method/community_monitor.rb | 4 +++ back/lib/participation_method/base.rb | 4 +++ .../lib/participation_method/native_survey.rb | 9 +++++- .../web_api/v1/phase_serializer_spec.rb | 32 ++++++++++++++----- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/back/app/serializers/web_api/v1/phase_serializer.rb b/back/app/serializers/web_api/v1/phase_serializer.rb index 45cb41fc14ec..bac1ac481772 100644 --- a/back/app/serializers/web_api/v1/phase_serializer.rb +++ b/back/app/serializers/web_api/v1/phase_serializer.rb @@ -77,6 +77,12 @@ class WebApi::V1::PhaseSerializer < WebApi::V1::BaseSerializer user_basket object, params end + attribute :form_builder_config, if: proc { |_phase, params| + current_user(params).admin? + } do |phase| + phase.pmethod.form_builder_config + end + has_one :report, serializer: ReportBuilder::WebApi::V1::ReportSerializer has_many :permissions diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index 95ea47ad19a3..dcb4c3b96f4e 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -57,6 +57,10 @@ def default_fields(custom_form) ] end + def allow_logic? + true + end + private attr_reader :phase diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index 23a60a38f29a..77328d33514f 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -56,5 +56,9 @@ def default_fields(custom_form) ) ] end + + def allow_logic? + false + end end end diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 28d5e8d0fe09..997aeb823138 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -51,6 +51,10 @@ def custom_form context.custom_form || CustomForm.new(participation_context: context) end + def form_builder_config + {} + end + def default_fields(_custom_form) [] end diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 157e96e9b44b..47674c0487c7 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,7 +2,7 @@ module ParticipationMethod class NativeSurvey < Base - delegate :allowed_extra_field_input_types, :default_fields, to: :native_survey_method + delegate :allowed_extra_field_input_types, :default_fields, :allow_logic?, to: :native_survey_method def self.method_str 'native_survey' @@ -36,6 +36,13 @@ def custom_form phase.custom_form || CustomForm.new(participation_context: phase) end + def form_builder_config + { + fields_to_include: allowed_extra_field_input_types, + allow_logic: allow_logic? + } + end + # Survey responses do not have a fixed field that can be used # to generate a slug, so use the id as the basis for the slug. def generate_slug(input) diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index 5efded1ae0b3..e80f1ab81866 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -72,16 +72,32 @@ end context 'for a native survey phase' do - let(:user) { create(:user) } let(:phase) { create(:native_survey_phase) } - it 'includes native survey attributes' do - expect(result.dig(:data, :attributes).keys).to include( - :native_survey_method, - :native_survey_title_multiloc, - :native_survey_button_multiloc - ) - expect(result.dig(:data, :attributes, :native_survey_method)).to eq 'standard' + context 'normal user' do + let(:user) { create(:user) } + + it 'includes native survey attributes' do + expect(result.dig(:data, :attributes).keys).to include( + :native_survey_method, + :native_survey_title_multiloc, + :native_survey_button_multiloc + ) + expect(result.dig(:data, :attributes, :native_survey_method)).to eq 'standard' + end + + it 'does not include the form_builder_config attribute' do + expect(result.dig(:data, :attributes).keys).not_to include(:form_builder_config) + end + end + + context 'admin user' do + let(:user) { create(:admin) } + + it 'includes a form_builder_config attribute' do + expect(result.dig(:data, :attributes).keys).to include(:form_builder_config) + expect(result.dig(:data, :attributes, :form_builder_config).keys).to eq(%i[fields_to_include allow_logic]) + end end end end From a0d5aa2c04047bde79eee95dcd1dd7161f3274c3 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 08:38:01 +0000 Subject: [PATCH 17/55] [TAN-3815] Removed hidden as a visible_to --- back/app/controllers/web_api/v1/projects_controller.rb | 1 - back/app/models/phase.rb | 2 +- back/app/models/project.rb | 6 ++++-- back/app/serializers/web_api/v1/phase_serializer.rb | 2 +- .../engines/commercial/multi_tenancy/db/seeds/tenants.rb | 4 ++++ back/spec/acceptance/phases_spec.rb | 9 +++++++++ back/spec/acceptance/projects_spec.rb | 4 ++-- back/spec/models/phase_spec.rb | 2 +- 8 files changed, 22 insertions(+), 8 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index d98ed6774042..22ae57144b77 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -331,7 +331,6 @@ def community_monitor multiloc_service = MultilocService.new project = Project.create!( title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), - visible_to: 'nobody', internal_role: 'community_monitor' ) Phase.create!( diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index 8c4484f8b3d5..95cd4975be65 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -353,7 +353,7 @@ def validate_community_monitor_phase errors.add(:native_survey_method, :too_many_phases, message: 'community_monitor project can only have one phase') end - if project.visible_to != 'nobody' + if project.internal_role != 'community_monitor' errors.add(:native_survey_method, :project_not_hidden, message: 'community_monitor projects must be hidden') end diff --git a/back/app/models/project.rb b/back/app/models/project.rb index 81e22d0da01c..a729f25e6378 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -37,7 +37,7 @@ class Project < ApplicationRecord attribute :preview_token, :string, default: -> { generate_preview_token } - VISIBLE_TOS = %w[public groups admins nobody].freeze + VISIBLE_TOS = %w[public groups admins].freeze slug from: proc { |project| project.title_multiloc.values.find(&:present?) } @@ -97,7 +97,9 @@ class Project < ApplicationRecord validates :internal_role, inclusion: { in: INTERNAL_ROLES, allow_nil: true } validate :admin_publication_must_exist, unless: proc { Current.loading_tenant_template } # TODO: This should always be validated! - default_scope { where.not(visible_to: 'nobody') } # To hide hidden projects such as community monitor + # default_scope { where.not(visible_to: 'nobody') } # To hide hidden projects such as community monitor + + scope :not_hidden, -> { where.not(internal_role: 'community_monitor') } pg_search_scope :search_by_all, against: %i[title_multiloc description_multiloc description_preview_multiloc slug], diff --git a/back/app/serializers/web_api/v1/phase_serializer.rb b/back/app/serializers/web_api/v1/phase_serializer.rb index bac1ac481772..820ecba5fdae 100644 --- a/back/app/serializers/web_api/v1/phase_serializer.rb +++ b/back/app/serializers/web_api/v1/phase_serializer.rb @@ -78,7 +78,7 @@ class WebApi::V1::PhaseSerializer < WebApi::V1::BaseSerializer end attribute :form_builder_config, if: proc { |_phase, params| - current_user(params).admin? + current_user(params)&.admin? } do |phase| phase.pmethod.form_builder_config end diff --git a/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb b/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb index 71f2518c530e..9983c527d4e9 100644 --- a/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb +++ b/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb @@ -475,6 +475,10 @@ def create_localhost_tenant platform_templates: { enabled: false, allowed: false + }, + community_monitor: { + enabled: true, + allowed: true } }) ) diff --git a/back/spec/acceptance/phases_spec.rb b/back/spec/acceptance/phases_spec.rb index d1b5f66ab189..d62ed2e0206a 100644 --- a/back/spec/acceptance/phases_spec.rb +++ b/back/spec/acceptance/phases_spec.rb @@ -30,6 +30,15 @@ expect(json_response[:data].size).to eq 2 expect(json_response[:included].pluck(:type)).to include 'permission' end + + example 'List all phases of a project which is hidden (internal_role: community_monitor)' do + @project.update!(internal_role: 'community_monitor') + Permissions::PermissionsUpdateService.new.update_all_permissions + do_request + assert_status 200 + expect(json_response[:data].size).to eq 2 + end + end context 'when admin' do diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index b51b7ac90067..a90df2b6ca7c 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1684,7 +1684,7 @@ get 'web_api/v1/projects/community_monitor' do context 'hidden community monitor project exists' do - let!(:project) { create(:project, visible_to: 'nobody', internal_role: 'community_monitor') } + let!(:project) { create(:project, internal_role: 'community_monitor') } example 'Get community monitor project' do settings = AppConfiguration.instance.settings @@ -1705,7 +1705,7 @@ created_project = Project.unscoped.first created_phase = Phase.first - expect(created_project.visible_to).to eq 'nobody' + expect(created_project.internal_role).to eq 'community_monitor' expect(created_project.title_multiloc['en']).to eq 'Community monitor' expect(created_phase.native_survey_method).to eq 'community_monitor' expect(created_phase.title_multiloc['en']).to eq 'Community monitor' diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index ddcab47196b4..598cc90d6253 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -479,7 +479,7 @@ context 'survey is a community monitor survey' do before do - project.update! visible_to: 'nobody', internal_role: 'community_monitor' + project.update! internal_role: 'community_monitor' survey_phase.update! native_survey_method: 'community_monitor' end From 87bd42d295e999c80c30a89d072afbaafa7d1ec6 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 10:26:55 +0000 Subject: [PATCH 18/55] [TAN-3815] Added constraints to community monitor fields --- back/lib/native_survey_method/base.rb | 43 +++++++++------ .../native_survey_method/community_monitor.rb | 54 +++++++------------ .../lib/participation_method/native_survey.rb | 2 +- 3 files changed, 47 insertions(+), 52 deletions(-) diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index dcb4c3b96f4e..5ee9f046d903 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -17,13 +17,7 @@ def default_fields(custom_form) multiloc_service = MultilocService.new [ - CustomField.new( - id: SecureRandom.uuid, - key: 'page1', - resource: custom_form, - input_type: 'page', - page_layout: 'default' - ), + start_page_field(custom_form), CustomField.new( id: SecureRandom.uuid, key: CustomFieldService.new.generate_key( @@ -45,15 +39,7 @@ def default_fields(custom_form) ) ] ), - CustomField.new( - id: SecureRandom.uuid, - key: 'survey_end', - resource: custom_form, - input_type: 'page', - page_layout: 'default', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), - description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') - ) + end_page_field(custom_form, multiloc_service) ] end @@ -64,5 +50,30 @@ def allow_logic? private attr_reader :phase + + + private + + def start_page_field(custom_form) + CustomField.new( + id: SecureRandom.uuid, + key: 'page1', + resource: custom_form, + input_type: 'page', + page_layout: 'default' + ) + end + + def end_page_field(custom_form, multiloc_service) + CustomField.new( + id: SecureRandom.uuid, + key: 'survey_end', + resource: custom_form, + input_type: 'page', + page_layout: 'default', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), + description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') + ) + end end end diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index 77328d33514f..c133f06482f4 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -11,52 +11,36 @@ def default_fields(custom_form) multiloc_service = MultilocService.new [ + start_page_field(custom_form), CustomField.new( id: SecureRandom.uuid, - key: 'page1', + key: 'cm_living_in_city', + code: 'cm_living_in_city', resource: custom_form, - input_type: 'page', - page_layout: 'default' + input_type: 'rating', + maximum: 5, + title_multiloc: { 'en' => 'How do you rate living in our city?' } ), CustomField.new( id: SecureRandom.uuid, - key: CustomFieldService.new.generate_key( - multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title').values.first - ), + key: 'cm_council_services', + code: 'cm_council_services', resource: custom_form, - input_type: 'select', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title'), - options: [ - CustomFieldOption.new( - id: SecureRandom.uuid, - key: 'option1', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option1') - ), - CustomFieldOption.new( - id: SecureRandom.uuid, - key: 'option2', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option2') - ) - ] + input_type: 'rating', + maximum: 5, + title_multiloc: { 'en' => 'How do you rate the quality of council services?' } ), - CustomField.new( - id: SecureRandom.uuid, - key: 'test', - input_type: 'text', - title_multiloc: { en: 'Test' } - ), - CustomField.new( - id: SecureRandom.uuid, - key: 'survey_end', - resource: custom_form, - input_type: 'page', - page_layout: 'default', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), - description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') - ) + end_page_field(custom_form, multiloc_service) ] end + def constraints + { + cm_living_in_city: { locks: { enabled: true, title_multiloc: true, maximum: true } }, + cm_council_services: { locks: { enabled: true, title_multiloc: true, maximum: true } } + } + end + def allow_logic? false end diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 47674c0487c7..5690d4626ff9 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,7 +2,7 @@ module ParticipationMethod class NativeSurvey < Base - delegate :allowed_extra_field_input_types, :default_fields, :allow_logic?, to: :native_survey_method + delegate :allowed_extra_field_input_types, :default_fields, :allow_logic?, :constraints, to: :native_survey_method def self.method_str 'native_survey' From 37a6773f02a7d3c320ae7c8c6181f0938e438d25 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 10:43:46 +0000 Subject: [PATCH 19/55] [TAN-3815] Removed private attribute --- back/config/schemas/settings.schema.json.erb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/back/config/schemas/settings.schema.json.erb b/back/config/schemas/settings.schema.json.erb index 198c08b6e295..e78df7168c5b 100644 --- a/back/config/schemas/settings.schema.json.erb +++ b/back/config/schemas/settings.schema.json.erb @@ -1383,8 +1383,7 @@ "project_id": { "title": "The ID of the required hidden project", "description": "Do not edit unless you know what you're doing. This is usually set by the system", - "type": "string", - "private": true + "type": "string" } } } From bd2abdca794a52a893e9290f510e6fa0b678a63c Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 10:58:21 +0000 Subject: [PATCH 20/55] [TAN-3815] Added default project_id: nil to community monitor app config --- back/engines/commercial/multi_tenancy/db/seeds/tenants.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb b/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb index 9983c527d4e9..67ce1a86f894 100644 --- a/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb +++ b/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb @@ -478,7 +478,8 @@ def create_localhost_tenant }, community_monitor: { enabled: true, - allowed: true + allowed: true, + project_id: nil } }) ) From c9b0d4dcb27192dc16176c700cb4d4ba47535f67 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 13:16:45 +0000 Subject: [PATCH 21/55] [TAN-3815] Moved hidden attribute to admin publication --- .../web_api/v1/projects_controller.rb | 53 ++++++++++--------- back/app/models/admin_publication.rb | 6 ++- back/app/policies/admin_publication_policy.rb | 1 + back/app/services/projects_finder_service.rb | 1 - back/spec/acceptance/projects_spec.rb | 3 +- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 22ae57144b77..5fb923e6d115 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -321,33 +321,9 @@ def community_monitor settings = AppConfiguration.instance.settings settings.dig('community_monitor', 'enabled') || raise(ActiveRecord::RecordNotFound) - # Find the community monitor project from config - project = nil + # Find the community monitor project from config or create it project_id = settings.dig('community_monitor', 'project_id') - project = Project.unscoped.find(project_id) if project_id - - # Create the project if it doesn't exist - unless project - multiloc_service = MultilocService.new - project = Project.create!( - title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), - internal_role: 'community_monitor' - ) - Phase.create!( - title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), - project: project, - participation_method: 'native_survey', - start_at: Time.now, - campaigns_settings: { project_phase_started: true }, # TODO: Is this correct? - native_survey_method: 'community_monitor', - native_survey_title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), - native_survey_button_multiloc: multiloc_service.i18n_to_multiloc('phases.native_survey_button') - ) - - # Set the ID in the settings - settings['community_monitor']['project_id'] = project.id - AppConfiguration.instance.update!(settings: settings) - end + project = project_id ? Project.find(project_id) : create_community_monitor_project(settings) authorize project render json: WebApi::V1::ProjectSerializer.new( @@ -415,6 +391,31 @@ def base_render_mini_index include: %i[project_images current_phase] ) end + + def create_community_monitor_project(settings) + multiloc_service = MultilocService.new + project = Project.create!( + admin_publication_attributes: { publication_status: 'hidden' }, + title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), + internal_role: 'community_monitor' + ) + Phase.create!( + title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), + project: project, + participation_method: 'native_survey', + start_at: Time.now, + campaigns_settings: { project_phase_started: true }, # TODO: JS - Is this correct? + native_survey_method: 'community_monitor', + native_survey_title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), + native_survey_button_multiloc: multiloc_service.i18n_to_multiloc('phases.native_survey_button') + ) + + # Set the ID in the settings + settings['community_monitor']['project_id'] = project.id + AppConfiguration.instance.update!(settings: settings) + + project + end end WebApi::V1::ProjectsController.include(AggressiveCaching::Patches::WebApi::V1::ProjectsController) diff --git a/back/app/models/admin_publication.rb b/back/app/models/admin_publication.rb index 7593fdc46461..81817951de9d 100644 --- a/back/app/models/admin_publication.rb +++ b/back/app/models/admin_publication.rb @@ -52,7 +52,7 @@ # index_admin_publications_on_rgt (rgt) # class AdminPublication < ApplicationRecord - PUBLICATION_STATUSES = %w[draft published archived] + PUBLICATION_STATUSES = %w[draft published archived hidden] belongs_to :publication, polymorphic: true, touch: true @@ -79,6 +79,10 @@ class AdminPublication < ApplicationRecord where.not(publication_status: 'draft') } + scope :not_hidden, lambda { + where.not(publication_status: 'hidden') + } + def archived? publication_status == 'archived' end diff --git a/back/app/policies/admin_publication_policy.rb b/back/app/policies/admin_publication_policy.rb index 988b8b386317..e5191492898a 100644 --- a/back/app/policies/admin_publication_policy.rb +++ b/back/app/policies/admin_publication_policy.rb @@ -4,6 +4,7 @@ class AdminPublicationPolicy < ApplicationPolicy class Scope < ApplicationPolicy::Scope def resolve AdminPublication + .not_hidden .publication_types .map { |klass| scope.where(publication: scope_for(klass)) } # scope per publication type .reduce(&:or) # joining partial scopes diff --git a/back/app/services/projects_finder_service.rb b/back/app/services/projects_finder_service.rb index 34887791b700..146a69ee50d1 100644 --- a/back/app/services/projects_finder_service.rb +++ b/back/app/services/projects_finder_service.rb @@ -28,7 +28,6 @@ def participation_possible # second by project created_at, and third by project ID. # Secondary & ternary orderings prevent duplicates when paginating, when prior ordering involves equivalent values projects = Project - .unscoped # to avoid ambiguity clash with the default scope of the Project model .from(subquery, :projects) .distinct .order('phase_end_at ASC NULLS LAST, projects_created_at ASC, projects_id ASC') diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index a90df2b6ca7c..24033c1c0b55 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1703,8 +1703,9 @@ do_request assert_status 200 - created_project = Project.unscoped.first + created_project = Project.first created_phase = Phase.first + expect(created_project.admin_publication.publication_status).to eq 'hidden' expect(created_project.internal_role).to eq 'community_monitor' expect(created_project.title_multiloc['en']).to eq 'Community monitor' expect(created_phase.native_survey_method).to eq 'community_monitor' From 92de3fead2e4dd0be556b86f6f35ad282841f36c Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 13:33:29 +0000 Subject: [PATCH 22/55] [TAN-3815] Removed form_builder_config from API --- .../web_api/v1/phase_serializer.rb | 6 ---- back/lib/native_survey_method/base.rb | 5 +-- .../native_survey_method/community_monitor.rb | 2 +- back/lib/participation_method/base.rb | 4 +-- .../lib/participation_method/native_survey.rb | 9 +----- .../web_api/v1/phase_serializer_spec.rb | 32 +++++-------------- 6 files changed, 13 insertions(+), 45 deletions(-) diff --git a/back/app/serializers/web_api/v1/phase_serializer.rb b/back/app/serializers/web_api/v1/phase_serializer.rb index 820ecba5fdae..45cb41fc14ec 100644 --- a/back/app/serializers/web_api/v1/phase_serializer.rb +++ b/back/app/serializers/web_api/v1/phase_serializer.rb @@ -77,12 +77,6 @@ class WebApi::V1::PhaseSerializer < WebApi::V1::BaseSerializer user_basket object, params end - attribute :form_builder_config, if: proc { |_phase, params| - current_user(params)&.admin? - } do |phase| - phase.pmethod.form_builder_config - end - has_one :report, serializer: ReportBuilder::WebApi::V1::ReportSerializer has_many :permissions diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index 5ee9f046d903..ef27a3f30b71 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -43,7 +43,7 @@ def default_fields(custom_form) ] end - def allow_logic? + def logic_enabled? true end @@ -51,9 +51,6 @@ def allow_logic? attr_reader :phase - - private - def start_page_field(custom_form) CustomField.new( id: SecureRandom.uuid, diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index c133f06482f4..3454c567848b 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -41,7 +41,7 @@ def constraints } end - def allow_logic? + def logic_enabled? false end end diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 997aeb823138..d11f8cd56297 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -51,8 +51,8 @@ def custom_form context.custom_form || CustomForm.new(participation_context: context) end - def form_builder_config - {} + def logic_enabled? + false end def default_fields(_custom_form) diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 5690d4626ff9..099bd315fa2f 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,7 +2,7 @@ module ParticipationMethod class NativeSurvey < Base - delegate :allowed_extra_field_input_types, :default_fields, :allow_logic?, :constraints, to: :native_survey_method + delegate :allowed_extra_field_input_types, :default_fields, :logic_enabled?, :constraints, to: :native_survey_method def self.method_str 'native_survey' @@ -36,13 +36,6 @@ def custom_form phase.custom_form || CustomForm.new(participation_context: phase) end - def form_builder_config - { - fields_to_include: allowed_extra_field_input_types, - allow_logic: allow_logic? - } - end - # Survey responses do not have a fixed field that can be used # to generate a slug, so use the id as the basis for the slug. def generate_slug(input) diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index e80f1ab81866..5efded1ae0b3 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -72,32 +72,16 @@ end context 'for a native survey phase' do + let(:user) { create(:user) } let(:phase) { create(:native_survey_phase) } - context 'normal user' do - let(:user) { create(:user) } - - it 'includes native survey attributes' do - expect(result.dig(:data, :attributes).keys).to include( - :native_survey_method, - :native_survey_title_multiloc, - :native_survey_button_multiloc - ) - expect(result.dig(:data, :attributes, :native_survey_method)).to eq 'standard' - end - - it 'does not include the form_builder_config attribute' do - expect(result.dig(:data, :attributes).keys).not_to include(:form_builder_config) - end - end - - context 'admin user' do - let(:user) { create(:admin) } - - it 'includes a form_builder_config attribute' do - expect(result.dig(:data, :attributes).keys).to include(:form_builder_config) - expect(result.dig(:data, :attributes, :form_builder_config).keys).to eq(%i[fields_to_include allow_logic]) - end + it 'includes native survey attributes' do + expect(result.dig(:data, :attributes).keys).to include( + :native_survey_method, + :native_survey_title_multiloc, + :native_survey_button_multiloc + ) + expect(result.dig(:data, :attributes, :native_survey_method)).to eq 'standard' end end end From e6c65d8c66bea6670265fc66d9db1402e4934ced Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 14 Feb 2025 14:00:12 +0000 Subject: [PATCH 23/55] [TAN-3815] Fixed tests --- back/app/models/admin_publication.rb | 4 ++++ back/app/models/phase.rb | 2 +- back/app/models/project.rb | 4 ---- back/spec/models/phase_spec.rb | 5 +++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/back/app/models/admin_publication.rb b/back/app/models/admin_publication.rb index 81817951de9d..2e555e5786ab 100644 --- a/back/app/models/admin_publication.rb +++ b/back/app/models/admin_publication.rb @@ -91,6 +91,10 @@ def published? publication_status == 'published' end + def hidden? + publication_status == 'hidden' + end + def ever_published? first_published_at.present? end diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index 95cd4975be65..24caf7940ebe 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -353,7 +353,7 @@ def validate_community_monitor_phase errors.add(:native_survey_method, :too_many_phases, message: 'community_monitor project can only have one phase') end - if project.internal_role != 'community_monitor' + unless project.admin_publication.hidden? errors.add(:native_survey_method, :project_not_hidden, message: 'community_monitor projects must be hidden') end diff --git a/back/app/models/project.rb b/back/app/models/project.rb index a729f25e6378..602ac4a92cad 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -97,10 +97,6 @@ class Project < ApplicationRecord validates :internal_role, inclusion: { in: INTERNAL_ROLES, allow_nil: true } validate :admin_publication_must_exist, unless: proc { Current.loading_tenant_template } # TODO: This should always be validated! - # default_scope { where.not(visible_to: 'nobody') } # To hide hidden projects such as community monitor - - scope :not_hidden, -> { where.not(internal_role: 'community_monitor') } - pg_search_scope :search_by_all, against: %i[title_multiloc description_multiloc description_preview_multiloc slug], using: { tsearch: { prefix: true } } diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 598cc90d6253..391ae95839c7 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -479,6 +479,7 @@ context 'survey is a community monitor survey' do before do + project.admin_publication.update! publication_status: 'hidden' project.update! internal_role: 'community_monitor' survey_phase.update! native_survey_method: 'community_monitor' end @@ -497,8 +498,8 @@ expect(survey_phase).not_to be_valid end - it 'is not valid when the project is not visible to anyone' do - project.visible_to = 'public' + it 'is not valid when the project is not hidden' do + survey_phase.project.admin_publication.publication_status = 'published' expect(survey_phase).not_to be_valid end end From 7af66815431c78c421d746b6f07f2fa6920f17fb Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 10:27:16 +0000 Subject: [PATCH 24/55] [TAN-3815] Changed permissions for community monitor --- back/app/policies/project_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/app/policies/project_policy.rb b/back/app/policies/project_policy.rb index 5ef3cc5ec785..72f6ac2aa92d 100644 --- a/back/app/policies/project_policy.rb +++ b/back/app/policies/project_policy.rb @@ -186,7 +186,7 @@ def active_moderator? end def community_monitor? - true + active_moderator? end private From f65673c9ef388fa4b891ab3926104b8b01b4d4ba Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 13:24:38 +0000 Subject: [PATCH 25/55] [TAN-3815] Added outline tests for native_survey_method --- back/spec/factories/phases.rb | 6 ++++++ .../spec/lib/native_survey_method/base_spec.rb | 18 ++++++++++++++++++ .../community_survey_spec.rb | 18 ++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 back/spec/lib/native_survey_method/base_spec.rb create mode 100644 back/spec/lib/native_survey_method/community_survey_spec.rb diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index f0ff3583f351..728a248d47bc 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -113,6 +113,12 @@ phase.end_at = Time.now + 7.days end end + factory :community_monitor_native_survey_phase do + native_survey_method { 'community_monitor' } + native_survey_title_multiloc { { 'en' => 'Community Monitor', 'nl-BE' => 'Gemeenschapsmonitor' } } + start_at { Time.zone.today - 7.days } + end_at { nil } + end end factory :single_voting_phase do diff --git a/back/spec/lib/native_survey_method/base_spec.rb b/back/spec/lib/native_survey_method/base_spec.rb new file mode 100644 index 000000000000..37eda1557525 --- /dev/null +++ b/back/spec/lib/native_survey_method/base_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe NativeSurveyMethod::Base do + subject(:native_survey_method) { described_class.new phase } + + let(:phase) { build(:native_survey_phase) } + + describe '#logic_enabled?' do + it 'is enabled' do + native_survey_method.logic_enabled? + expect(native_survey_method.logic_enabled?).to be true + end + end + + # TODO: JS: Add more tests when we know what fields and defaults we want to set +end diff --git a/back/spec/lib/native_survey_method/community_survey_spec.rb b/back/spec/lib/native_survey_method/community_survey_spec.rb new file mode 100644 index 000000000000..d579399569d9 --- /dev/null +++ b/back/spec/lib/native_survey_method/community_survey_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe NativeSurveyMethod::CommunityMonitor do + subject(:native_survey_method) { described_class.new phase } + + let(:phase) { build(:community_monitor_native_survey_phase) } + + describe '#logic_enabled?' do + it 'is enabled' do + native_survey_method.logic_enabled? + expect(native_survey_method.logic_enabled?).to be false + end + end + + # TODO: JS: Add more tests when we know what fields and defaults we want to set +end From 8e88d856ab2446c10270b6e0c2d45306b2049ea0 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 15:34:49 +0000 Subject: [PATCH 26/55] [TAN-3815] Added admin publication tests and fixed other tests --- back/app/policies/admin_publication_policy.rb | 3 +- .../multi_tenancy/db/seeds/tenants.rb | 2 +- back/lib/native_survey_method/base.rb | 4 + .../acceptance/admin_publications_spec.rb | 146 +++++++----------- .../policies/admin_publication_policy_spec.rb | 20 +++ 5 files changed, 85 insertions(+), 90 deletions(-) diff --git a/back/app/policies/admin_publication_policy.rb b/back/app/policies/admin_publication_policy.rb index e5191492898a..3ebad02e5702 100644 --- a/back/app/policies/admin_publication_policy.rb +++ b/back/app/policies/admin_publication_policy.rb @@ -4,9 +4,8 @@ class AdminPublicationPolicy < ApplicationPolicy class Scope < ApplicationPolicy::Scope def resolve AdminPublication - .not_hidden .publication_types - .map { |klass| scope.where(publication: scope_for(klass)) } # scope per publication type + .map { |klass| scope.not_hidden.where(publication: scope_for(klass)) } # scope per publication type .reduce(&:or) # joining partial scopes end end diff --git a/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb b/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb index 67ce1a86f894..67d5f0fd70aa 100644 --- a/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb +++ b/back/engines/commercial/multi_tenancy/db/seeds/tenants.rb @@ -479,7 +479,7 @@ def create_localhost_tenant community_monitor: { enabled: true, allowed: true, - project_id: nil + project_id: '' } }) ) diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index ef27a3f30b71..ecb3ca927c8d 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -47,6 +47,10 @@ def logic_enabled? true end + def constraints + {} + end + private attr_reader :phase diff --git a/back/spec/acceptance/admin_publications_spec.rb b/back/spec/acceptance/admin_publications_spec.rb index fa2fd1d271bf..44c0a39e2117 100644 --- a/back/spec/acceptance/admin_publications_spec.rb +++ b/back/spec/acceptance/admin_publications_spec.rb @@ -49,51 +49,47 @@ parameter :review_state, 'Filter by project review status (pending, approved)', required: false example_request 'List all admin publications' do + hidden_project = create(:project, internal_role: 'community_monitor', admin_publication_attributes: { publication_status: 'hidden' }) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 10 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 8 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 2 + expect(response_data.size).to eq 10 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 8 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 2 + expect(response_data.pluck(:id)).not_to include(hidden_project.admin_publication.id) end example 'List all top-level admin publications' do do_request(depth: 0) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 7 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 5 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 2 + expect(response_data.size).to eq 7 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 5 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 2 end example 'List all admin publications in a folder' do do_request(folder: custom_folder.id) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 3 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 0 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 3 + expect(response_data.size).to eq 3 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 0 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 3 end example 'List all draft or archived admin publications' do do_request(publication_statuses: %w[draft archived]) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 5 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :id) }).to match_array [empty_draft_folder.id, projects[2].id, projects[3].id, projects[5].id, projects[6].id] - expect(json_response[:data].find { |d| d.dig(:relationships, :publication, :data, :type) == 'folder' }.dig(:attributes, :visible_children_count)).to eq 0 + expect(response_data.size).to eq 5 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :id) }).to match_array [empty_draft_folder.id, projects[2].id, projects[3].id, projects[5].id, projects[6].id] + expect(response_data.find { |d| d.dig(:relationships, :publication, :data, :type) == 'folder' }.dig(:attributes, :visible_children_count)).to eq 0 end example_request 'List projects only' do do_request(only_projects: 'true') expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 8 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 8 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 0 + expect(response_data.size).to eq 8 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 8 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 0 end example 'List publications admin can moderate', document: false do do_request filter_can_moderate: true - json_response = json_parse(response_body) assert_status 200 - expect(json_response[:data].size).to eq 10 + expect(response_data.size).to eq 10 end example 'List publications a specific user can moderate', document: false do @@ -261,16 +257,16 @@ example 'List all root-level admin publications is ordered correctly', document: false do do_request(depth: 0) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].map { |d| d.dig(:attributes, :publication_title_multiloc, :en) }) + + expect(response_data.map { |d| d.dig(:attributes, :publication_title_multiloc, :en) }) .to eq(%w[P1 F1 P2 F2 P6 P7 P8]) end example 'List only project publications maintains a flattened nested ordering', document: false do do_request(only_projects: 'true') expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].map { |d| d.dig(:attributes, :publication_title_multiloc, :en) }) + + expect(response_data.map { |d| d.dig(:attributes, :publication_title_multiloc, :en) }) .to eq(%w[P1 P2 P3-f2 P4-f2 P5-f2 P6 P7 P8]) end end @@ -296,9 +292,8 @@ ]) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].pluck(:id)) + expect(response_data.pluck(:id)) .to eq [non_draft_ids[3], non_draft_ids[0], non_draft_ids[1], non_draft_ids[4]] end @@ -316,9 +311,7 @@ ) expect(status).to eq(200) - json_response = json_parse(response_body) - - expect(json_response[:data].pluck(:id)).to eq [non_draft_ids[4], non_draft_ids[2]] + expect(response_data.pluck(:id)).to eq [non_draft_ids[4], non_draft_ids[2]] end example 'Does not include draft admin_publications', document: false do @@ -332,9 +325,7 @@ ]) expect(status).to eq(200) - json_response = json_parse(response_body) - - expect(json_response[:data].pluck(:id)) + expect(response_data.pluck(:id)) .to eq [non_draft_ids[3], non_draft_ids[0], non_draft_ids[1], non_draft_ids[4]] end @@ -342,9 +333,7 @@ do_request(ids: ['not_an_admin_publication_id']) expect(status).to eq(200) - json_response = json_parse(response_body) - - expect(json_response[:data]).to be_empty + expect(response_data).to be_empty end end @@ -370,9 +359,8 @@ old_second_publication = AdminPublication.find_by(ordering: ordering) do_request expect(response_status).to eq 200 - json_response = json_parse(response_body) - expect(json_response.dig(:data, :attributes, :ordering)).to eq ordering - expect(json_response.dig(:data, :id)).to eq id + expect(response_data.dig(:attributes, :ordering)).to eq ordering + expect(response_data[:id]).to eq id expect(AdminPublication.find(id).ordering).to eq(ordering) expect(old_second_publication.reload.ordering).to eq 2 # previous second is now third @@ -384,12 +372,10 @@ example_request 'Get one admin publication by id' do expect(status).to eq 200 - json_response = json_parse(response_body) - - expect(json_response.dig(:data, :id)).to eq projects.first.admin_publication.id - expect(json_response.dig(:data, :relationships, :publication, :data, :type)).to eq 'project' - expect(json_response.dig(:data, :relationships, :publication, :data, :id)).to eq projects.first.id - expect(json_response.dig(:data, :attributes, :publication_slug)).to eq projects.first.slug + expect(response_data[:id]).to eq projects.first.admin_publication.id + expect(response_data.dig(:relationships, :publication, :data, :type)).to eq 'project' + expect(response_data.dig(:relationships, :publication, :data, :id)).to eq projects.first.id + expect(response_data.dig(:attributes, :publication_slug)).to eq projects.first.slug end end @@ -397,13 +383,9 @@ example 'Get publication_status counts for top-level admin publications' do do_request(depth: 0) expect(status).to eq 200 - - json_response = json_parse(response_body) - - expect(json_response[:data][:attributes][:status_counts][:draft]).to eq 2 - expect(json_response[:data][:attributes][:status_counts][:archived]).to eq 2 - - expect(json_response[:data][:attributes][:status_counts][:published]).to eq 3 + expect(response_data[:attributes][:status_counts][:draft]).to eq 2 + expect(response_data[:attributes][:status_counts][:archived]).to eq 2 + expect(response_data[:attributes][:status_counts][:published]).to eq 3 end end end @@ -431,27 +413,25 @@ example 'Listed admin publications have correct visible children count', document: false do do_request(folder: nil, remove_not_allowed_parents: true) expect(status).to eq(200) - json_response = json_parse(response_body) # Only 3 of initial 6 projects are not in folder - expect(json_response[:data].size).to eq 3 + expect(response_data.size).to eq 3 # Only 1 folder expected - Draft folder created at top of file is not visible to resident, # nor should a folder with only a draft project in it - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 1 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 1 # 3 projects are inside folder, 3 top-level projects remain, of which 1 is not visible (draft) - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 2 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 2 # Only the two non-draft projects are visible to resident - expect(json_response[:data].find { |d| d.dig(:relationships, :publication, :data, :type) == 'folder' }.dig(:attributes, :visible_children_count)).to eq 2 + expect(response_data.find { |d| d.dig(:relationships, :publication, :data, :type) == 'folder' }.dig(:attributes, :visible_children_count)).to eq 2 end example 'Visible children count should take account of applied filters', document: false do projects.first.admin_publication.update! publication_status: 'archived' do_request(folder: nil, publication_statuses: ['published'], remove_not_allowed_parents: true) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 2 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 1 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 1 - expect(json_response[:data].find { |d| d.dig(:relationships, :publication, :data, :type) == 'folder' }.dig(:attributes, :visible_children_count)).to eq 1 + expect(response_data.size).to eq 2 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('folder')).to eq 1 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 1 + expect(response_data.find { |d| d.dig(:relationships, :publication, :data, :type) == 'folder' }.dig(:attributes, :visible_children_count)).to eq 1 end context 'search param' do @@ -595,7 +575,6 @@ build(:layout, craftjs_json: { sometext: { props: { text: { en: 'othertext' } } } }) ]) do_request search: 'sometext' - expect(response_data.size).to eq 1 expect(response_ids).to contain_exactly(project.admin_publication.id) end @@ -605,8 +584,7 @@ AdminPublication.publication_types.each { |claz| claz.all.each(&:destroy!) } do_request(publication_statuses: ['published']) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].size).to eq 0 + expect(response_data.size).to eq 0 end end @@ -625,8 +603,7 @@ do_request(ids: [folder_with_children.admin_publication.id]) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data].first.dig(:attributes, :visible_children_count)).to eq 1 + expect(response_data.first.dig(:attributes, :visible_children_count)).to eq 1 end example 'Does not includes folders containing only non-visible children', document: false do @@ -637,8 +614,7 @@ do_request(ids: [folder_with_non_visible_children.admin_publication.id]) expect(status).to eq(200) - json_response = json_parse(response_body) - expect(json_response[:data]).to be_empty + expect(response_data).to be_empty end end end @@ -651,12 +627,9 @@ example 'Get publication_status counts for top-level admin publications' do do_request(depth: 0) expect(status).to eq 200 - - json_response = json_parse(response_body) - expect(json_response[:data][:attributes][:status_counts][:draft]).to be_nil - expect(json_response[:data][:attributes][:status_counts][:published]).to eq 2 - - expect(json_response[:data][:attributes][:status_counts][:archived]).to eq 1 + expect(response_data[:attributes][:status_counts][:draft]).to be_nil + expect(response_data[:attributes][:status_counts][:published]).to eq 2 + expect(response_data[:attributes][:status_counts][:archived]).to eq 1 end end end @@ -686,10 +659,10 @@ example 'List only the projects the current user is moderator of' do do_request(filter_is_moderator_of: true, only_projects: true) - json_response = json_parse(response_body) + assert_status 200 - expect(json_response[:data].size).to eq 2 - expect(json_response[:data].map { |d| d.dig(:relationships, :publication, :data, :id) }) + expect(response_data.size).to eq 2 + expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :id) }) .to match_array [published_projects[0].id, published_projects[1].id] end @@ -789,7 +762,7 @@ expect(second_publication.ordering).to eq second_publication_ordering do_request - new_ordering = json_parse(response_body).dig(:data, :attributes, :ordering) + new_ordering = response_data.dig(:attributes, :ordering) expect(response_status).to eq 200 expect(new_ordering).to eq second_publication_ordering @@ -816,18 +789,17 @@ do_request include_publications: 'true' expect(status).to eq(200) - json_response = json_parse(response_body) - relationships_data = json_response[:data].map { |d| d.dig(:relationships, :publication, :data) } + relationships_data = response_data.map { |d| d.dig(:relationships, :publication, :data) } related_project_ids = relationships_data.select { |d| d[:type] == 'project' }.pluck(:id) related_folder_ids = relationships_data.select { |d| d[:type] == 'folder' }.pluck(:id) - included_projects = json_response[:included].select { |d| d[:type] == 'project' } - included_folder_ids = json_response[:included].select { |d| d[:type] == 'folder' }.pluck(:id) - included_phase_ids = json_response[:included].select { |d| d[:type] == 'phase' }.pluck(:id) - included_avatar_ids = json_response[:included].select { |d| d[:type] == 'avatar' }.pluck(:id) - included_image_ids = json_response[:included].select { |d| d[:type] == 'image' }.pluck(:id) + included_projects = json_response_body[:included].select { |d| d[:type] == 'project' } + included_folder_ids = json_response_body[:included].select { |d| d[:type] == 'folder' }.pluck(:id) + included_phase_ids = json_response_body[:included].select { |d| d[:type] == 'phase' }.pluck(:id) + included_avatar_ids = json_response_body[:included].select { |d| d[:type] == 'avatar' }.pluck(:id) + included_image_ids = json_response_body[:included].select { |d| d[:type] == 'image' }.pluck(:id) current_phase_ids = included_projects.filter_map { |d| d.dig(:relationships, :current_phase, :data, :id) } avatar_ids = included_projects.map { |d| d.dig(:relationships, :avatars, :data) }.flatten.pluck(:id) diff --git a/back/spec/policies/admin_publication_policy_spec.rb b/back/spec/policies/admin_publication_policy_spec.rb index 84c618de5162..2ed3fb21efb1 100644 --- a/back/spec/policies/admin_publication_policy_spec.rb +++ b/back/spec/policies/admin_publication_policy_spec.rb @@ -18,6 +18,11 @@ it 'should index the project holder' do expect(scope.resolve.size).to eq 1 end + + it 'should not index the project holder if hidden' do + admin_publication.update!(publication_status: 'hidden') + expect(scope.resolve.size).to eq 0 + end end context 'for a resident' do @@ -28,6 +33,11 @@ it 'should index the project holder' do expect(scope.resolve.size).to eq 1 end + + it 'should not index the project holder if hidden' do + admin_publication.update!(publication_status: 'hidden') + expect(scope.resolve.size).to eq 0 + end end context 'for an admin' do @@ -38,6 +48,11 @@ it 'should index the project holder' do expect(scope.resolve.size).to eq 1 end + + it 'should not index the project holder if hidden' do + admin_publication.update!(publication_status: 'hidden') + expect(scope.resolve.size).to eq 0 + end end context 'for a moderator of another project' do @@ -48,6 +63,11 @@ it 'indexes the project holder' do expect(scope.resolve.size).to eq 2 end + + it 'should not index any projects if the publication_status is hidden' do + admin_publication.update!(publication_status: 'hidden') + expect(scope.resolve.size).to eq 1 + end end end From 2aa1064b4eb4c7700a871ab706fbcdd876f48894 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 15:39:15 +0000 Subject: [PATCH 27/55] [TAN-3815] changed check for .present? --- back/app/controllers/web_api/v1/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 5fb923e6d115..c80f082534dc 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -323,7 +323,7 @@ def community_monitor # Find the community monitor project from config or create it project_id = settings.dig('community_monitor', 'project_id') - project = project_id ? Project.find(project_id) : create_community_monitor_project(settings) + project = project_id.present? ? Project.find(project_id) : create_community_monitor_project(settings) authorize project render json: WebApi::V1::ProjectSerializer.new( From a173464a6ed469e9db81decef68aeab2946c6076 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 16:39:47 +0000 Subject: [PATCH 28/55] [TAN-3815] Fixed projects spec --- back/spec/acceptance/projects_spec.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 24033c1c0b55..e2668daca4a6 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1275,7 +1275,7 @@ create(:project, admin_publication_attributes: { publication_status: status }) end end - let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES } + let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES.select{ |s| s != "hidden" } } get 'web_api/v1/projects' do with_options scope: :page do @@ -1683,6 +1683,9 @@ end get 'web_api/v1/projects/community_monitor' do + context 'when project admin' do + before { admin_header_token } + context 'hidden community monitor project exists' do let!(:project) { create(:project, internal_role: 'community_monitor') } From 4c529e80898f72bee1d7a893afe4910b6768f4d4 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 17:08:42 +0000 Subject: [PATCH 29/55] [TAN-3815] Fixed tests --- back/spec/acceptance/projects_spec.rb | 78 ++++++++++++++++----------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index e2668daca4a6..5e5356461223 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1684,54 +1684,70 @@ get 'web_api/v1/projects/community_monitor' do context 'when project admin' do - before { admin_header_token } + before { admin_header_token } - context 'hidden community monitor project exists' do - let!(:project) { create(:project, internal_role: 'community_monitor') } + context 'hidden community monitor project exists' do + let!(:project) { create(:project, internal_role: 'community_monitor') } - example 'Get community monitor project' do - settings = AppConfiguration.instance.settings - settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => project.id } - AppConfiguration.instance.update!(settings:) + example 'Get community monitor project' do + settings = AppConfiguration.instance.settings + settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => project.id } + AppConfiguration.instance.update!(settings:) - do_request - assert_status 200 + do_request + assert_status 200 + end end - end - context 'hidden community monitor project does not exist' do - example 'Create and get hidden community monitor project' do - SettingsService.new.activate_feature! 'community_monitor' + context 'hidden community monitor project does not exist' do + example 'Create and get hidden community monitor project' do + SettingsService.new.activate_feature! 'community_monitor' - do_request - assert_status 200 + do_request + assert_status 200 - created_project = Project.first - created_phase = Phase.first - expect(created_project.admin_publication.publication_status).to eq 'hidden' - expect(created_project.internal_role).to eq 'community_monitor' - expect(created_project.title_multiloc['en']).to eq 'Community monitor' - expect(created_phase.native_survey_method).to eq 'community_monitor' - expect(created_phase.title_multiloc['en']).to eq 'Community monitor' + created_project = Project.first + created_phase = Phase.first + expect(created_project.admin_publication.publication_status).to eq 'hidden' + expect(created_project.internal_role).to eq 'community_monitor' + expect(created_project.title_multiloc['en']).to eq 'Community monitor' + expect(created_phase.native_survey_method).to eq 'community_monitor' + expect(created_phase.title_multiloc['en']).to eq 'Community monitor' - settings = AppConfiguration.instance.settings - expect(settings['community_monitor']['project_id']).to eq created_project.id + settings = AppConfiguration.instance.settings + expect(settings['community_monitor']['project_id']).to eq created_project.id + end + + example 'Error: Hidden project does not get created without feature flag' do + do_request + assert_status 404 + end end - example 'Error: Hidden project does not get created without feature flag' do - do_request - assert_status 404 + context 'stored community monitor project ID is incorrect' do + example 'Error: Hidden project does not exist' do + settings = AppConfiguration.instance.settings + settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => 'NON_EXISTENT' } + AppConfiguration.instance.update!(settings:) + + do_request + assert_status 404 + end end end - context 'stored community monitor project ID is incorrect' do - example 'Error: Hidden project does not exist' do + context 'when resident' do + let!(:project) { create(:project, internal_role: 'community_monitor') } + + before { resident_header_token } + + example '[Error] Get community monitor project returns unauthorised' do settings = AppConfiguration.instance.settings - settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => 'NON_EXISTENT' } + settings['community_monitor'] = { 'enabled' => true, 'allowed' => true, 'project_id' => project.id } AppConfiguration.instance.update!(settings:) do_request - assert_status 404 + assert_status 401 end end end From de312c43aff955ecf41ccf6c38a90ea6dbde0432 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Mon, 17 Feb 2025 17:27:19 +0000 Subject: [PATCH 30/55] [TAN-3815] Exclude native survey responses from creating idea followers --- back/app/services/side_fx_idea_service.rb | 6 +++++- back/lib/participation_method/base.rb | 4 ++++ back/lib/participation_method/ideation.rb | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/back/app/services/side_fx_idea_service.rb b/back/app/services/side_fx_idea_service.rb index 9eabff6e9e05..4dc054b25e81 100644 --- a/back/app/services/side_fx_idea_service.rb +++ b/back/app/services/side_fx_idea_service.rb @@ -166,13 +166,17 @@ def scrape_facebook(idea) end def create_followers(idea, user) - Follower.find_or_create_by(followable: idea, user: user) + Follower.find_or_create_by(followable: idea, user: user) if create_idea_follower?(idea) Follower.find_or_create_by(followable: idea.project, user: user) return if !idea.project.in_folder? Follower.find_or_create_by(followable: idea.project.folder, user: user) end + def create_idea_follower?(idea) + idea.creation_phase ? idea.creation_phase.pmethod.create_idea_followers? : true # Defaults for true for ideas without a creation_phase + end + def serialize_idea(frozen_idea) serialized_idea = clean_time_attributes(frozen_idea.attributes) serialized_idea['location_point'] = serialized_idea['location_point'].to_s diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index d11f8cd56297..7b7e8b083a38 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -166,6 +166,10 @@ def use_reactions_as_votes? false end + def create_idea_followers? + false + end + private attr_reader :phase diff --git a/back/lib/participation_method/ideation.rb b/back/lib/participation_method/ideation.rb index cea83a0403d8..5af5fa39a2d3 100644 --- a/back/lib/participation_method/ideation.rb +++ b/back/lib/participation_method/ideation.rb @@ -396,6 +396,10 @@ def transitive? true end + def create_idea_followers? + true + end + private def proposed_budget_in_form? From d5746cf93f8cfd84c8ad00a99e1922605b6af758 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 08:40:03 +0000 Subject: [PATCH 31/55] [TAN-3815] Exclude community monitor survey responses from creating project followers --- back/app/services/side_fx_idea_service.rb | 8 ++++++-- back/lib/native_survey_method/base.rb | 4 ++++ back/lib/native_survey_method/community_monitor.rb | 4 ++++ back/lib/participation_method/base.rb | 6 +++++- back/lib/participation_method/ideation.rb | 6 +++++- back/lib/participation_method/native_survey.rb | 2 +- 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/back/app/services/side_fx_idea_service.rb b/back/app/services/side_fx_idea_service.rb index 4dc054b25e81..4aaa0c11041a 100644 --- a/back/app/services/side_fx_idea_service.rb +++ b/back/app/services/side_fx_idea_service.rb @@ -167,14 +167,18 @@ def scrape_facebook(idea) def create_followers(idea, user) Follower.find_or_create_by(followable: idea, user: user) if create_idea_follower?(idea) - Follower.find_or_create_by(followable: idea.project, user: user) + Follower.find_or_create_by(followable: idea.project, user: user) if create_project_follower?(idea) return if !idea.project.in_folder? Follower.find_or_create_by(followable: idea.project.folder, user: user) end def create_idea_follower?(idea) - idea.creation_phase ? idea.creation_phase.pmethod.create_idea_followers? : true # Defaults for true for ideas without a creation_phase + idea.creation_phase ? idea.creation_phase.pmethod.follow_idea_on_idea_submission? : true # Defaults for true for ideas without a creation_phase + end + + def create_project_follower?(idea) + idea.creation_phase ? idea.creation_phase.pmethod.follow_project_on_idea_submission? : true # Defaults for true for ideas without a creation_phase end def serialize_idea(frozen_idea) diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index ecb3ca927c8d..6add69fe4714 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -76,5 +76,9 @@ def end_page_field(custom_form, multiloc_service) description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') ) end + + def follow_project_on_idea_submission? + true + end end end diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index 3454c567848b..d82620b32071 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -44,5 +44,9 @@ def constraints def logic_enabled? false end + + def follow_project_on_idea_submission? + false + end end end diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 7b7e8b083a38..60cf5bd80371 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -166,7 +166,11 @@ def use_reactions_as_votes? false end - def create_idea_followers? + def follow_idea_on_idea_submission? + false + end + + def follow_project_on_idea_submission? false end diff --git a/back/lib/participation_method/ideation.rb b/back/lib/participation_method/ideation.rb index 5af5fa39a2d3..2fc807928f3f 100644 --- a/back/lib/participation_method/ideation.rb +++ b/back/lib/participation_method/ideation.rb @@ -396,7 +396,11 @@ def transitive? true end - def create_idea_followers? + def follow_idea_on_idea_submission? + true + end + + def follow_project_on_idea_submission? true end diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 099bd315fa2f..9bdcca11dcb6 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,7 +2,7 @@ module ParticipationMethod class NativeSurvey < Base - delegate :allowed_extra_field_input_types, :default_fields, :logic_enabled?, :constraints, to: :native_survey_method + delegate :allowed_extra_field_input_types, :default_fields, :logic_enabled?, :constraints, :follow_project_on_idea_submission?, to: :native_survey_method def self.method_str 'native_survey' From 5893bd42d4cbab92e5692515533899176307e273 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 11:40:58 +0000 Subject: [PATCH 32/55] [TAN-3815] Exclude hidden projects from creating followers --- .../notifications/project_phase_started.rb | 1 + .../notifications/project_phase_upcoming.rb | 19 +++++----- back/app/models/project.rb | 2 +- back/app/services/side_fx_idea_service.rb | 8 ++--- back/lib/native_survey_method/base.rb | 4 --- .../native_survey_method/community_monitor.rb | 4 --- back/lib/participation_method/base.rb | 4 --- back/lib/participation_method/ideation.rb | 4 --- .../lib/participation_method/native_survey.rb | 2 +- .../services/side_fx_idea_service_spec.rb | 35 ++++++++++++++----- 10 files changed, 41 insertions(+), 42 deletions(-) diff --git a/back/app/models/notifications/project_phase_started.rb b/back/app/models/notifications/project_phase_started.rb index 6db1d9dc5512..1a0449becdf0 100644 --- a/back/app/models/notifications/project_phase_started.rb +++ b/back/app/models/notifications/project_phase_started.rb @@ -72,6 +72,7 @@ class ProjectPhaseStarted < Notification def self.make_notifications_on(activity) phase = activity.item + return [] unless phase.project.published? ProjectPolicy::InverseScope.new(phase.project, User.from_follows(phase.project.followers)).resolve.map do |recipient| new(recipient: recipient, phase: phase, project: phase.project) diff --git a/back/app/models/notifications/project_phase_upcoming.rb b/back/app/models/notifications/project_phase_upcoming.rb index fe4bd75e330f..9d1d266eb47a 100644 --- a/back/app/models/notifications/project_phase_upcoming.rb +++ b/back/app/models/notifications/project_phase_upcoming.rb @@ -72,18 +72,15 @@ class ProjectPhaseUpcoming < Notification def self.make_notifications_on(activity) phase = activity.item + return [] unless phase.project.published? - if phase.project - recipients = UserRoleService.new.moderators_for phase - recipients.ids.map do |recipient_id| - new( - recipient_id: recipient_id, - phase: phase, - project: phase.project - ) - end - else - [] + recipients = UserRoleService.new.moderators_for phase + recipients.ids.map do |recipient_id| + new( + recipient_id: recipient_id, + phase: phase, + project: phase.project + ) end end end diff --git a/back/app/models/project.rb b/back/app/models/project.rb index 602ac4a92cad..6ec19d20f69a 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -142,7 +142,7 @@ class Project < ApplicationRecord alias project_id id - delegate :ever_published?, :never_published?, to: :admin_publication, allow_nil: true + delegate :published?, :ever_published?, :never_published?, :hidden?, to: :admin_publication, allow_nil: true class << self def search_ids_by_all_including_patches(term) diff --git a/back/app/services/side_fx_idea_service.rb b/back/app/services/side_fx_idea_service.rb index 4aaa0c11041a..2d5a5ba0b7fc 100644 --- a/back/app/services/side_fx_idea_service.rb +++ b/back/app/services/side_fx_idea_service.rb @@ -166,8 +166,10 @@ def scrape_facebook(idea) end def create_followers(idea, user) + return if idea.project.hidden? + Follower.find_or_create_by(followable: idea, user: user) if create_idea_follower?(idea) - Follower.find_or_create_by(followable: idea.project, user: user) if create_project_follower?(idea) + Follower.find_or_create_by(followable: idea.project, user: user) return if !idea.project.in_folder? Follower.find_or_create_by(followable: idea.project.folder, user: user) @@ -177,10 +179,6 @@ def create_idea_follower?(idea) idea.creation_phase ? idea.creation_phase.pmethod.follow_idea_on_idea_submission? : true # Defaults for true for ideas without a creation_phase end - def create_project_follower?(idea) - idea.creation_phase ? idea.creation_phase.pmethod.follow_project_on_idea_submission? : true # Defaults for true for ideas without a creation_phase - end - def serialize_idea(frozen_idea) serialized_idea = clean_time_attributes(frozen_idea.attributes) serialized_idea['location_point'] = serialized_idea['location_point'].to_s diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb index 6add69fe4714..ecb3ca927c8d 100644 --- a/back/lib/native_survey_method/base.rb +++ b/back/lib/native_survey_method/base.rb @@ -76,9 +76,5 @@ def end_page_field(custom_form, multiloc_service) description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') ) end - - def follow_project_on_idea_submission? - true - end end end diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/native_survey_method/community_monitor.rb index d82620b32071..3454c567848b 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/native_survey_method/community_monitor.rb @@ -44,9 +44,5 @@ def constraints def logic_enabled? false end - - def follow_project_on_idea_submission? - false - end end end diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 60cf5bd80371..7a75dd7b7554 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -170,10 +170,6 @@ def follow_idea_on_idea_submission? false end - def follow_project_on_idea_submission? - false - end - private attr_reader :phase diff --git a/back/lib/participation_method/ideation.rb b/back/lib/participation_method/ideation.rb index 2fc807928f3f..ad3f1fd2129b 100644 --- a/back/lib/participation_method/ideation.rb +++ b/back/lib/participation_method/ideation.rb @@ -400,10 +400,6 @@ def follow_idea_on_idea_submission? true end - def follow_project_on_idea_submission? - true - end - private def proposed_budget_in_form? diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 9bdcca11dcb6..78e7d3adbe3c 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,7 +2,7 @@ module ParticipationMethod class NativeSurvey < Base - delegate :allowed_extra_field_input_types, :default_fields, :logic_enabled?, :constraints, :follow_project_on_idea_submission?, to: :native_survey_method + delegate :allowed_extra_field_input_types, :default_fields, :logic_enabled?, :constraints, to: :native_survey_method def self.method_str 'native_survey' diff --git a/back/spec/services/side_fx_idea_service_spec.rb b/back/spec/services/side_fx_idea_service_spec.rb index 42ef3d27dc3e..9378a851fc7d 100644 --- a/back/spec/services/side_fx_idea_service_spec.rb +++ b/back/spec/services/side_fx_idea_service_spec.rb @@ -64,16 +64,35 @@ .with(idea, 'published', any_args) end - it 'creates a follower' do - project = create(:project) - folder = create(:project_folder, projects: [project]) - idea = create(:idea, project: project) + context 'followers' do + let!(:project) { create(:project) } + let!(:folder) { create(:project_folder, projects: [project]) } + let!(:idea) { create(:idea, project: project) } + + it 'creates idea, project and folder followers' do + expect do + service.after_create idea.reload, user + end.to change(Follower, :count).from(0).to(3) + + expect(user.follows.pluck(:followable_id)).to contain_exactly idea.id, project.id, folder.id + end - expect do - service.after_create idea.reload, user - end.to change(Follower, :count).from(0).to(3) + it 'does not create followers if the project is hidden' do + project.admin_publication.update!(publication_status: 'hidden') + expect do + service.after_create idea.reload, user + end.not_to change(Follower, :count) + end + + it 'creates only creates project and folder followers for native_survey responses' do + idea.update!(creation_phase: create(:native_survey_phase, project: project)) + expect do + service.after_create idea.reload, user + end.to change(Follower, :count).from(0).to(2) + + expect(user.follows.pluck(:followable_id)).to contain_exactly project.id, folder.id + end - expect(user.follows.pluck(:followable_id)).to contain_exactly idea.id, project.id, folder.id end it 'creates a cosponsorship' do From dd5cc6e0366b29f9b6cf0dfad13dfaa0820fab26 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 11:58:55 +0000 Subject: [PATCH 33/55] [TAN-3815] Rubocop fix --- back/spec/acceptance/phases_spec.rb | 1 - back/spec/acceptance/projects_spec.rb | 2 +- back/spec/services/side_fx_idea_service_spec.rb | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/back/spec/acceptance/phases_spec.rb b/back/spec/acceptance/phases_spec.rb index d62ed2e0206a..50d77551ae17 100644 --- a/back/spec/acceptance/phases_spec.rb +++ b/back/spec/acceptance/phases_spec.rb @@ -38,7 +38,6 @@ assert_status 200 expect(json_response[:data].size).to eq 2 end - end context 'when admin' do diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 5e5356461223..b162daeca839 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1275,7 +1275,7 @@ create(:project, admin_publication_attributes: { publication_status: status }) end end - let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES.select{ |s| s != "hidden" } } + let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES.select{ |ps| ps != "hidden" } } get 'web_api/v1/projects' do with_options scope: :page do diff --git a/back/spec/services/side_fx_idea_service_spec.rb b/back/spec/services/side_fx_idea_service_spec.rb index 9378a851fc7d..bcf125c622e8 100644 --- a/back/spec/services/side_fx_idea_service_spec.rb +++ b/back/spec/services/side_fx_idea_service_spec.rb @@ -92,7 +92,6 @@ expect(user.follows.pluck(:followable_id)).to contain_exactly project.id, folder.id end - end it 'creates a cosponsorship' do From f022b8cfa162ccde5608f875a0898ccf0ec7b404 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 13:30:49 +0000 Subject: [PATCH 34/55] [TAN-3815] Removed native_survey_method --- .../web_api/v1/projects_controller.rb | 3 +- back/app/models/phase.rb | 21 +++-- .../web_api/v1/phase_serializer.rb | 2 +- back/app/services/project_copy_service.rb | 2 +- ...3910_add_native_survey_method_to_phases.rb | 8 -- back/db/structure.sql | 4 +- .../multi_tenancy/db/seeds/projects.rb | 1 - .../public_api/v2/phase_serializer.rb | 3 +- back/lib/factory.rb | 9 --- back/lib/native_survey_method/base.rb | 80 ------------------- back/lib/participation_method/base.rb | 2 +- .../community_monitor_survey.rb} | 7 +- .../lib/participation_method/native_survey.rb | 74 ++++++++++++++--- back/spec/acceptance/projects_spec.rb | 2 +- back/spec/factories/phases.rb | 15 ++-- .../lib/native_survey_method/base_spec.rb | 18 ----- .../community_survey_spec.rb | 18 ----- back/spec/models/phase_spec.rb | 3 +- .../web_api/v1/phase_serializer_spec.rb | 2 - back/spec/services/side_fx_phase_spec.rb | 1 - 20 files changed, 99 insertions(+), 176 deletions(-) delete mode 100644 back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb delete mode 100644 back/lib/native_survey_method/base.rb rename back/lib/{native_survey_method/community_monitor.rb => participation_method/community_monitor_survey.rb} (90%) delete mode 100644 back/spec/lib/native_survey_method/base_spec.rb delete mode 100644 back/spec/lib/native_survey_method/community_survey_spec.rb diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index c80f082534dc..109d0f1e9923 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -402,10 +402,9 @@ def create_community_monitor_project(settings) Phase.create!( title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), project: project, - participation_method: 'native_survey', + participation_method: 'community_monitor_survey', start_at: Time.now, campaigns_settings: { project_phase_started: true }, # TODO: JS - Is this correct? - native_survey_method: 'community_monitor', native_survey_title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), native_survey_button_multiloc: multiloc_service.i18n_to_multiloc('phases.native_survey_button') ) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index 24caf7940ebe..de51cd1ecc5e 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -68,7 +68,6 @@ class Phase < ApplicationRecord PARTICIPATION_METHODS = ParticipationMethod::Base.all_methods.map(&:method_str).freeze VOTING_METHODS = %w[budgeting multiple_voting single_voting].freeze - NATIVE_SURVEY_METHODS = %w[standard community_monitor].freeze PRESENTATION_MODES = %w[card map].freeze REACTING_METHODS = %w[unlimited limited].freeze INPUT_TERMS = %w[idea question contribution project issue option proposal initiative petition].freeze @@ -183,12 +182,13 @@ class Phase < ApplicationRecord where(start_at: date) } + validate :validate_community_monitor_phase + # native_survey? with_options if: :native_survey? do - validates :native_survey_method, presence: true, inclusion: { in: NATIVE_SURVEY_METHODS } + # TODO: JS - do we need to validate these for community monitor? validates :native_survey_title_multiloc, presence: true, multiloc: { presence: true } validates :native_survey_button_multiloc, presence: true, multiloc: { presence: true } - validate :validate_community_monitor_phase end scope :published, lambda { @@ -264,6 +264,11 @@ def native_survey? participation_method == 'native_survey' end + # Used for validations (which are hard to delegate through the participation method) + def community_monitor_survey? + participation_method == 'community_monitor_survey' + end + def pmethod reload_participation_method if !@pmethod @pmethod @@ -347,18 +352,18 @@ def validate_no_other_overlapping_phases end def validate_community_monitor_phase - return unless native_survey_method == 'community_monitor' + return unless participation_method == 'community_monitor_survey' if project.phases.count > 1 - errors.add(:native_survey_method, :too_many_phases, message: 'community_monitor project can only have one phase') + errors.add(:community_monitor_survey, :too_many_phases, message: 'community_monitor project can only have one phase') end - unless project.admin_publication.hidden? - errors.add(:native_survey_method, :project_not_hidden, message: 'community_monitor projects must be hidden') + unless project.hidden? + errors.add(:community_monitor_survey, :project_not_hidden, message: 'community_monitor projects must be hidden') end if end_at.present? - errors.add(:native_survey_method, :has_end_at, message: 'community_monitor projects cannot have an end date') + errors.add(:community_monitor_survey, :has_end_at, message: 'community_monitor projects cannot have an end date') end end diff --git a/back/app/serializers/web_api/v1/phase_serializer.rb b/back/app/serializers/web_api/v1/phase_serializer.rb index 45cb41fc14ec..3ec35518504c 100644 --- a/back/app/serializers/web_api/v1/phase_serializer.rb +++ b/back/app/serializers/web_api/v1/phase_serializer.rb @@ -15,7 +15,7 @@ class WebApi::V1::PhaseSerializer < WebApi::V1::BaseSerializer %i[ voting_method voting_max_total voting_min_total voting_max_votes_per_idea baskets_count - native_survey_method native_survey_title_multiloc native_survey_button_multiloc + native_survey_title_multiloc native_survey_button_multiloc expire_days_limit reacting_threshold autoshare_results_enabled ].each do |attribute_name| attribute attribute_name, if: proc { |phase| diff --git a/back/app/services/project_copy_service.rb b/back/app/services/project_copy_service.rb index 79e1316d294e..9d94bb338d21 100644 --- a/back/app/services/project_copy_service.rb +++ b/back/app/services/project_copy_service.rb @@ -335,8 +335,8 @@ def yml_phases(shift_timestamps: 0, timeline_start_at: nil) yml_phase['document_annotation_embed_url'] = phase.document_annotation_embed_url end + # TODO: JS - Needed for community monitor? if yml_phase['participation_method'] == 'native_survey' - yml_phase['native_survey_method'] = phase.native_survey_method yml_phase['native_survey_title_multiloc'] = phase.native_survey_title_multiloc yml_phase['native_survey_button_multiloc'] = phase.native_survey_button_multiloc end diff --git a/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb b/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb deleted file mode 100644 index 715314798d6d..000000000000 --- a/back/db/migrate/20250211103910_add_native_survey_method_to_phases.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -class AddNativeSurveyMethodToPhases < ActiveRecord::Migration[7.1] - def change - add_column :phases, :native_survey_method, :string, null: true, default: nil - Phase.where(participation_method: 'native_survey').update_all(native_survey_method: 'standard') - end -end diff --git a/back/db/structure.sql b/back/db/structure.sql index c71ae316e6ef..30223523daa0 100644 --- a/back/db/structure.sql +++ b/back/db/structure.sql @@ -1564,8 +1564,7 @@ CREATE TABLE public.phases ( manual_votes_count integer DEFAULT 0 NOT NULL, manual_voters_amount integer, manual_voters_last_updated_by_id uuid, - manual_voters_last_updated_at timestamp(6) without time zone, - native_survey_method character varying + manual_voters_last_updated_at timestamp(6) without time zone ); @@ -6827,7 +6826,6 @@ ALTER TABLE ONLY public.ideas_topics SET search_path TO public,shared_extensions; INSERT INTO "schema_migrations" (version) VALUES -('20250211103910'), ('20250204143605'), ('20250120125531'), ('20250117121004'), diff --git a/back/engines/commercial/multi_tenancy/db/seeds/projects.rb b/back/engines/commercial/multi_tenancy/db/seeds/projects.rb index 5fce7b2f3a58..b2f8d0cb063c 100644 --- a/back/engines/commercial/multi_tenancy/db/seeds/projects.rb +++ b/back/engines/commercial/multi_tenancy/db/seeds/projects.rb @@ -48,7 +48,6 @@ def create_mixed_3_methods_project start_at: Time.zone.today + 11.days, end_at: nil, campaigns_settings: { project_phase_started: true }, - native_survey_method: 'standard', native_survey_title_multiloc: { 'en' => 'Survey' }, native_survey_button_multiloc: { 'en' => 'Take the survey' } ) diff --git a/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb b/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb index e188ff27ee13..3f5d3f7fee24 100644 --- a/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb +++ b/back/engines/commercial/public_api/app/serializers/public_api/v2/phase_serializer.rb @@ -25,8 +25,7 @@ class PublicApi::V2::PhaseSerializer < PublicApi::V2::BaseSerializer :reacting_dislike_limited_max, :voting_method, :voting_max_total, - :voting_min_total, - :native_survey_method + :voting_min_total def title multiloc_service.t(object.title_multiloc) diff --git a/back/lib/factory.rb b/back/lib/factory.rb index 18d4f6f78330..bba153c661b6 100644 --- a/back/lib/factory.rb +++ b/back/lib/factory.rb @@ -19,14 +19,5 @@ def voting_method_for(phase) end end - def native_survey_method_for(phase) - case phase&.native_survey_method - when 'community_monitor' - ::NativeSurveyMethod::CommunityMonitor.new(phase) - else - ::NativeSurveyMethod::Base.new(phase) - end - end - private_class_method :new end diff --git a/back/lib/native_survey_method/base.rb b/back/lib/native_survey_method/base.rb deleted file mode 100644 index ecb3ca927c8d..000000000000 --- a/back/lib/native_survey_method/base.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -module NativeSurveyMethod - class Base - def initialize(phase) - @phase = phase - end - - def allowed_extra_field_input_types - %w[page number linear_scale rating text multiline_text select multiselect - multiselect_image file_upload shapefile_upload point line polygon - ranking matrix_linear_scale] - end - - def default_fields(custom_form) - return [] if custom_form.persisted? - - multiloc_service = MultilocService.new - [ - start_page_field(custom_form), - CustomField.new( - id: SecureRandom.uuid, - key: CustomFieldService.new.generate_key( - multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title').values.first - ), - resource: custom_form, - input_type: 'select', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title'), - options: [ - CustomFieldOption.new( - id: SecureRandom.uuid, - key: 'option1', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option1') - ), - CustomFieldOption.new( - id: SecureRandom.uuid, - key: 'option2', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option2') - ) - ] - ), - end_page_field(custom_form, multiloc_service) - ] - end - - def logic_enabled? - true - end - - def constraints - {} - end - - private - - attr_reader :phase - - def start_page_field(custom_form) - CustomField.new( - id: SecureRandom.uuid, - key: 'page1', - resource: custom_form, - input_type: 'page', - page_layout: 'default' - ) - end - - def end_page_field(custom_form, multiloc_service) - CustomField.new( - id: SecureRandom.uuid, - key: 'survey_end', - resource: custom_form, - input_type: 'page', - page_layout: 'default', - title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), - description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') - ) - end - end -end diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 7a75dd7b7554..662c5b56b405 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -3,7 +3,7 @@ module ParticipationMethod class Base def self.all_methods - [DocumentAnnotation, Ideation, Information, NativeSurvey, Poll, Proposals, Survey, Volunteering, Voting] + [DocumentAnnotation, Ideation, Information, NativeSurvey, CommunityMonitorSurvey, Poll, Proposals, Survey, Volunteering, Voting] end def initialize(phase) diff --git a/back/lib/native_survey_method/community_monitor.rb b/back/lib/participation_method/community_monitor_survey.rb similarity index 90% rename from back/lib/native_survey_method/community_monitor.rb rename to back/lib/participation_method/community_monitor_survey.rb index 3454c567848b..6b0659fe2405 100644 --- a/back/lib/native_survey_method/community_monitor.rb +++ b/back/lib/participation_method/community_monitor_survey.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true -module NativeSurveyMethod - class CommunityMonitor < Base +module ParticipationMethod + class CommunityMonitorSurvey < NativeSurvey + def self.method_str + 'community_monitor_survey' + end def allowed_extra_field_input_types %w[page text linear_scale rating select multiselect] end diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 78e7d3adbe3c..62906fee6ee8 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -2,21 +2,56 @@ module ParticipationMethod class NativeSurvey < Base - delegate :allowed_extra_field_input_types, :default_fields, :logic_enabled?, :constraints, to: :native_survey_method - def self.method_str 'native_survey' end + def allowed_extra_field_input_types + %w[page number linear_scale rating text multiline_text select multiselect + multiselect_image file_upload shapefile_upload point line polygon + ranking matrix_linear_scale] + end + + def default_fields(custom_form) + return [] if custom_form.persisted? + + multiloc_service = MultilocService.new + [ + start_page_field(custom_form), + CustomField.new( + id: SecureRandom.uuid, + key: CustomFieldService.new.generate_key( + multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title').values.first + ), + resource: custom_form, + input_type: 'select', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.title'), + options: [ + CustomFieldOption.new( + id: SecureRandom.uuid, + key: 'option1', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option1') + ), + CustomFieldOption.new( + id: SecureRandom.uuid, + key: 'option2', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.default_select_field.option2') + ) + ] + ), + end_page_field(custom_form, multiloc_service) + ] + end + + def logic_enabled? + true + end + def assign_defaults(input) input.publication_status ||= 'published' input.idea_status ||= IdeaStatus.find_by!(code: 'proposed', participation_method: 'ideation') end - def assign_defaults_for_phase - phase.native_survey_method ||= 'standard' - end - # NOTE: This is only ever used by the analyses controller - otherwise the front-end always persists the form def create_default_form! form = CustomForm.new(participation_context: phase) @@ -73,7 +108,7 @@ def supports_permitted_by_everyone? end def supports_serializing?(attribute) - %i[native_survey_method native_survey_title_multiloc native_survey_button_multiloc].include?(attribute) + %i[native_survey_title_multiloc native_survey_button_multiloc].include?(attribute) end def supports_submission? @@ -88,8 +123,29 @@ def supports_toxicity_detection? false end - def native_survey_method - Factory.instance.native_survey_method_for(phase) + private + + + def start_page_field(custom_form) + CustomField.new( + id: SecureRandom.uuid, + key: 'page1', + resource: custom_form, + input_type: 'page', + page_layout: 'default' + ) + end + + def end_page_field(custom_form, multiloc_service) + CustomField.new( + id: SecureRandom.uuid, + key: 'survey_end', + resource: custom_form, + input_type: 'page', + page_layout: 'default', + title_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.title_text_3'), + description_multiloc: multiloc_service.i18n_to_multiloc('form_builder.form_end_page.description_text_3') + ) end end end diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index b162daeca839..b58f3fcc9435 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1711,7 +1711,7 @@ expect(created_project.admin_publication.publication_status).to eq 'hidden' expect(created_project.internal_role).to eq 'community_monitor' expect(created_project.title_multiloc['en']).to eq 'Community monitor' - expect(created_phase.native_survey_method).to eq 'community_monitor' + expect(created_phase.participation_method).to eq 'community_monitor_survey' expect(created_phase.title_multiloc['en']).to eq 'Community monitor' settings = AppConfiguration.instance.settings diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index 728a248d47bc..8d46ed3dad6f 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -104,7 +104,6 @@ factory :native_survey_phase do participation_method { 'native_survey' } - native_survey_method { 'standard' } native_survey_title_multiloc { { 'en' => 'Survey', 'nl-BE' => 'Vragenlijst' } } native_survey_button_multiloc { { 'en' => 'Take the survey', 'nl-BE' => 'De enquete invullen' } } factory :active_native_survey_phase do @@ -113,12 +112,14 @@ phase.end_at = Time.now + 7.days end end - factory :community_monitor_native_survey_phase do - native_survey_method { 'community_monitor' } - native_survey_title_multiloc { { 'en' => 'Community Monitor', 'nl-BE' => 'Gemeenschapsmonitor' } } - start_at { Time.zone.today - 7.days } - end_at { nil } - end + end + + factory :community_monitor_native_survey_phase do + participation_method { 'community_monitor_survey' } + native_survey_title_multiloc { { 'en' => 'Community Monitor', 'nl-BE' => 'Gemeenschapsmonitor' } } + native_survey_button_multiloc { { 'en' => 'Take the survey', 'nl-BE' => 'De enquete invullen' } } + start_at { Time.zone.today - 7.days } + end_at { nil } end factory :single_voting_phase do diff --git a/back/spec/lib/native_survey_method/base_spec.rb b/back/spec/lib/native_survey_method/base_spec.rb deleted file mode 100644 index 37eda1557525..000000000000 --- a/back/spec/lib/native_survey_method/base_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe NativeSurveyMethod::Base do - subject(:native_survey_method) { described_class.new phase } - - let(:phase) { build(:native_survey_phase) } - - describe '#logic_enabled?' do - it 'is enabled' do - native_survey_method.logic_enabled? - expect(native_survey_method.logic_enabled?).to be true - end - end - - # TODO: JS: Add more tests when we know what fields and defaults we want to set -end diff --git a/back/spec/lib/native_survey_method/community_survey_spec.rb b/back/spec/lib/native_survey_method/community_survey_spec.rb deleted file mode 100644 index d579399569d9..000000000000 --- a/back/spec/lib/native_survey_method/community_survey_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe NativeSurveyMethod::CommunityMonitor do - subject(:native_survey_method) { described_class.new phase } - - let(:phase) { build(:community_monitor_native_survey_phase) } - - describe '#logic_enabled?' do - it 'is enabled' do - native_survey_method.logic_enabled? - expect(native_survey_method.logic_enabled?).to be false - end - end - - # TODO: JS: Add more tests when we know what fields and defaults we want to set -end diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 391ae95839c7..821a868c3750 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -82,7 +82,6 @@ it 'can be changed from ideation to native_survey' do phase = create(:phase, participation_method: 'ideation') phase.participation_method = 'native_survey' - phase.native_survey_method = 'standard' phase.native_survey_title_multiloc = { en: 'Survey' } phase.native_survey_button_multiloc = { en: 'Take the survey' } phase.ideas_order = nil @@ -481,7 +480,7 @@ before do project.admin_publication.update! publication_status: 'hidden' project.update! internal_role: 'community_monitor' - survey_phase.update! native_survey_method: 'community_monitor' + survey_phase.update! participation_method: 'community_monitor_survey' end it 'is valid' do diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index 5efded1ae0b3..9b53821dfe23 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -77,11 +77,9 @@ it 'includes native survey attributes' do expect(result.dig(:data, :attributes).keys).to include( - :native_survey_method, :native_survey_title_multiloc, :native_survey_button_multiloc ) - expect(result.dig(:data, :attributes, :native_survey_method)).to eq 'standard' end end end diff --git a/back/spec/services/side_fx_phase_spec.rb b/back/spec/services/side_fx_phase_spec.rb index 02e5c2e38258..e387f113fb36 100644 --- a/back/spec/services/side_fx_phase_spec.rb +++ b/back/spec/services/side_fx_phase_spec.rb @@ -61,7 +61,6 @@ phase.update!( # Required to avoid errors when switching to native survey below ideas_order: nil, - native_survey_method: 'standard', native_survey_title_multiloc: { en: 'Survey' }, native_survey_button_multiloc: { en: 'Take the survey' } ) From c30dd87d149216b502fd67f1ebc31e8e160af40d Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 15:28:00 +0000 Subject: [PATCH 35/55] [TAN-3815] Fixed test --- back/app/controllers/web_api/v1/projects_controller.rb | 4 ++++ back/app/models/permission.rb | 1 + back/spec/acceptance/projects_spec.rb | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 109d0f1e9923..1810c9d41b2e 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -399,10 +399,14 @@ def create_community_monitor_project(settings) title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), internal_role: 'community_monitor' ) + sidefx.after_create(project, current_user) if project + Phase.create!( title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), project: project, participation_method: 'community_monitor_survey', + commenting_enabled: false, # TODO: JS - set in the participation method defaults + reacting_enabled: false, start_at: Time.now, campaigns_settings: { project_phase_started: true }, # TODO: JS - Is this correct? native_survey_title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), diff --git a/back/app/models/permission.rb b/back/app/models/permission.rb index dd7992aba298..43a7f597ea79 100644 --- a/back/app/models/permission.rb +++ b/back/app/models/permission.rb @@ -29,6 +29,7 @@ class Permission < ApplicationRecord 'ideation' => %w[posting_idea commenting_idea reacting_idea attending_event], 'proposals' => %w[posting_idea commenting_idea reacting_idea attending_event], 'native_survey' => %w[posting_idea attending_event], + 'community_monitor_survey' => %w[posting_idea attending_event], 'survey' => %w[taking_survey attending_event], 'poll' => %w[taking_poll attending_event], 'voting' => %w[voting commenting_idea attending_event], diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index b58f3fcc9435..b21701c3fe3d 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1687,7 +1687,7 @@ before { admin_header_token } context 'hidden community monitor project exists' do - let!(:project) { create(:project, internal_role: 'community_monitor') } + let!(:project) { create(:project, internal_role: 'community_monitor', admin_publication_attributes: { publication_status: 'hidden' }) } example 'Get community monitor project' do settings = AppConfiguration.instance.settings From 7bd9ae296c9770919944bb542df11ef071ee585b Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 15:38:40 +0000 Subject: [PATCH 36/55] [TAN-3815] Created community_monitor_project factory --- .../participation_method/community_monitor_survey.rb | 1 + back/lib/participation_method/native_survey.rb | 1 - back/spec/acceptance/admin_publications_spec.rb | 2 +- back/spec/acceptance/projects_spec.rb | 4 ++-- back/spec/factories/phases.rb | 2 +- back/spec/factories/projects.rb | 11 +++++++++++ 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/back/lib/participation_method/community_monitor_survey.rb b/back/lib/participation_method/community_monitor_survey.rb index 6b0659fe2405..1446ea0ae9bc 100644 --- a/back/lib/participation_method/community_monitor_survey.rb +++ b/back/lib/participation_method/community_monitor_survey.rb @@ -5,6 +5,7 @@ class CommunityMonitorSurvey < NativeSurvey def self.method_str 'community_monitor_survey' end + def allowed_extra_field_input_types %w[page text linear_scale rating select multiselect] end diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 62906fee6ee8..e3db2e2bb1be 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -125,7 +125,6 @@ def supports_toxicity_detection? private - def start_page_field(custom_form) CustomField.new( id: SecureRandom.uuid, diff --git a/back/spec/acceptance/admin_publications_spec.rb b/back/spec/acceptance/admin_publications_spec.rb index 44c0a39e2117..b1384d0db50e 100644 --- a/back/spec/acceptance/admin_publications_spec.rb +++ b/back/spec/acceptance/admin_publications_spec.rb @@ -49,7 +49,7 @@ parameter :review_state, 'Filter by project review status (pending, approved)', required: false example_request 'List all admin publications' do - hidden_project = create(:project, internal_role: 'community_monitor', admin_publication_attributes: { publication_status: 'hidden' }) + hidden_project = create(:community_monitor_project) expect(status).to eq(200) expect(response_data.size).to eq 10 expect(response_data.map { |d| d.dig(:relationships, :publication, :data, :type) }.count('project')).to eq 8 diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index b21701c3fe3d..7922e1935caa 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1275,7 +1275,7 @@ create(:project, admin_publication_attributes: { publication_status: status }) end end - let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES.select{ |ps| ps != "hidden" } } + let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES.reject { |ps| ps == 'hidden' } } get 'web_api/v1/projects' do with_options scope: :page do @@ -1687,7 +1687,7 @@ before { admin_header_token } context 'hidden community monitor project exists' do - let!(:project) { create(:project, internal_role: 'community_monitor', admin_publication_attributes: { publication_status: 'hidden' }) } + let!(:project) { create(:community_monitor_project) } example 'Get community monitor project' do settings = AppConfiguration.instance.settings diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index 8d46ed3dad6f..aa32495df61e 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -114,7 +114,7 @@ end end - factory :community_monitor_native_survey_phase do + factory :community_monitor_survey_phase do participation_method { 'community_monitor_survey' } native_survey_title_multiloc { { 'en' => 'Community Monitor', 'nl-BE' => 'Gemeenschapsmonitor' } } native_survey_button_multiloc { { 'en' => 'Take the survey', 'nl-BE' => 'De enquete invullen' } } diff --git a/back/spec/factories/projects.rb b/back/spec/factories/projects.rb index 973905aa4955..5c1d8885ad2b 100644 --- a/back/spec/factories/projects.rb +++ b/back/spec/factories/projects.rb @@ -612,6 +612,17 @@ ) end end + + factory :community_monitor_project do + internal_role { 'community_monitor' } + admin_publication_attributes { { publication_status: 'hidden' } } + after(:create) do |project| + project.phases << create( + :community_monitor_survey_phase, + project: project + ) + end + end end end end From fbc12488ae7493ab26eec66d0fed1480558b741e Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 16:24:18 +0000 Subject: [PATCH 37/55] [TAN-3815] Changed .native_survey? to pmethod.supports_survey_form? --- .../web_api/v1/ideas_controller.rb | 2 +- .../web_api/v1/phases_controller.rb | 4 +- back/app/models/idea.rb | 4 ++ .../web_api/v1/phase_serializer.rb | 4 ++ .../patches/side_fx_idea_service.rb | 2 +- .../lib/participation_method/native_survey.rb | 48 +++++++++---------- .../web_api/v1/phase_serializer_spec.rb | 1 + 7 files changed, 37 insertions(+), 28 deletions(-) diff --git a/back/app/controllers/web_api/v1/ideas_controller.rb b/back/app/controllers/web_api/v1/ideas_controller.rb index 52a8d84b1806..9abbb4d7ba73 100644 --- a/back/app/controllers/web_api/v1/ideas_controller.rb +++ b/back/app/controllers/web_api/v1/ideas_controller.rb @@ -179,7 +179,7 @@ def create input.phase_ids = [phase.id] if phase_ids.empty? # NOTE: Needs refactor allow_anonymous_participation? so anonymous_participation can be allow or force - if phase.native_survey? && phase.allow_anonymous_participation? + if phase.pmethod.supports_survey_form? && phase.allow_anonymous_participation? input.anonymous = true end input.author ||= current_user diff --git a/back/app/controllers/web_api/v1/phases_controller.rb b/back/app/controllers/web_api/v1/phases_controller.rb index c0b4a4f9912b..1771f0c55a74 100644 --- a/back/app/controllers/web_api/v1/phases_controller.rb +++ b/back/app/controllers/web_api/v1/phases_controller.rb @@ -74,8 +74,8 @@ def survey_results end def submission_count - count = if @phase.native_survey? - @phase.ideas.native_survey.published.count + count = if @phase.pmethod.supports_survey_form? + @phase.ideas.supports_survey.published.count else @phase.ideas.transitive.published.count end diff --git a/back/app/models/idea.rb b/back/app/models/idea.rb index 894b5778c10f..cfc56f0e42d7 100644 --- a/back/app/models/idea.rb +++ b/back/app/models/idea.rb @@ -235,6 +235,10 @@ class Idea < ApplicationRecord native_survey.where(publication_status: 'draft') } + # Equivalent to pmethod.supports_survey_form? + scope :supports_survey, -> { where(creation_phase: Phase.where(participation_method: %w[native_survey community_monitor_survey])) } + + # Filters out all the ideas for which the ParticipationMethod responds truety # to the given block. The block receives the ParticipationMethod object as an # argument diff --git a/back/app/serializers/web_api/v1/phase_serializer.rb b/back/app/serializers/web_api/v1/phase_serializer.rb index 3ec35518504c..1fc5acbee808 100644 --- a/back/app/serializers/web_api/v1/phase_serializer.rb +++ b/back/app/serializers/web_api/v1/phase_serializer.rb @@ -69,6 +69,10 @@ class WebApi::V1::PhaseSerializer < WebApi::V1::BaseSerializer object.custom_form_persisted? end + attribute :supports_survey_form do |phase| + phase.pmethod.supports_survey_form? + end + belongs_to :project has_one :user_basket, if: proc { |object, params| diff --git a/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb b/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb index a3b8847aedfc..c0978ee1fdfc 100644 --- a/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb +++ b/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb @@ -33,7 +33,7 @@ def before_publish_or_submit(idea, user) # If a survey is opened in multiple tabs then different draft responses can be created for the same user. # We need to remove any duplicates when the survey is submitted. def remove_duplicate_survey_responses_on_publish(idea) - return unless idea.creation_phase&.native_survey? && idea.publication_status_previously_changed?(from: 'draft', to: 'published') + return unless idea.creation_phase&.pmethod.supports_survey_form? && idea.publication_status_previously_changed?(from: 'draft', to: 'published') Idea.where( creation_phase_id: idea.creation_phase_id, diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index e3db2e2bb1be..589c788d7078 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -12,6 +12,30 @@ def allowed_extra_field_input_types ranking matrix_linear_scale] end + def assign_defaults(input) + input.publication_status ||= 'published' + input.idea_status ||= IdeaStatus.find_by!(code: 'proposed', participation_method: 'ideation') + end + + # NOTE: This is only ever used by the analyses controller - otherwise the front-end always persists the form + def create_default_form! + form = CustomForm.new(participation_context: phase) + + default_fields(form).reverse_each do |field| + field.save! + field.move_to_top + end + + form.save! + phase.reload + + form + end + + def custom_form + phase.custom_form || CustomForm.new(participation_context: phase) + end + def default_fields(custom_form) return [] if custom_form.persisted? @@ -47,30 +71,6 @@ def logic_enabled? true end - def assign_defaults(input) - input.publication_status ||= 'published' - input.idea_status ||= IdeaStatus.find_by!(code: 'proposed', participation_method: 'ideation') - end - - # NOTE: This is only ever used by the analyses controller - otherwise the front-end always persists the form - def create_default_form! - form = CustomForm.new(participation_context: phase) - - default_fields(form).reverse_each do |field| - field.save! - field.move_to_top - end - - form.save! - phase.reload - - form - end - - def custom_form - phase.custom_form || CustomForm.new(participation_context: phase) - end - # Survey responses do not have a fixed field that can be used # to generate a slug, so use the id as the basis for the slug. def generate_slug(input) diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index 9b53821dfe23..5015c33bca1c 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -80,6 +80,7 @@ :native_survey_title_multiloc, :native_survey_button_multiloc ) + expect(result.dig(:data, :attributes, :supports_survey_form)).to eq true end end end From bad1f8637143af0b66191f141db77f2b5738e438 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 17:08:37 +0000 Subject: [PATCH 38/55] [TAN-3815] Changed .native_survey? to pmethod.supports_survey_form? --- back/app/models/phase.rb | 16 ++++++---------- back/spec/models/phase_spec.rb | 1 + back/spec/policies/idea_policy_spec.rb | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index de51cd1ecc5e..fe1815f9e263 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -182,13 +182,12 @@ class Phase < ApplicationRecord where(start_at: date) } - validate :validate_community_monitor_phase - - # native_survey? - with_options if: :native_survey? do - # TODO: JS - do we need to validate these for community monitor? + # any type of native_survey phase + with_options if: ->(phase) { phase.pmethod.supports_survey_form? } do + # TODO: JS - do we need to validate these for community monitor? Assuming so for now validates :native_survey_title_multiloc, presence: true, multiloc: { presence: true } validates :native_survey_button_multiloc, presence: true, multiloc: { presence: true } + validate :validate_community_monitor_phase end scope :published, lambda { @@ -264,11 +263,6 @@ def native_survey? participation_method == 'native_survey' end - # Used for validations (which are hard to delegate through the participation method) - def community_monitor_survey? - participation_method == 'community_monitor_survey' - end - def pmethod reload_participation_method if !@pmethod @pmethod @@ -395,6 +389,8 @@ def reload_participation_method ParticipationMethod::Proposals.new(self) when 'native_survey' ParticipationMethod::NativeSurvey.new(self) + when 'community_monitor_survey' + ParticipationMethod::CommunityMonitorSurvey.new(self) when 'document_annotation' ParticipationMethod::DocumentAnnotation.new(self) when 'survey' diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 821a868c3750..45948ec072ae 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -323,6 +323,7 @@ end describe 'native_survey_title_multiloc' do + # Do each here it 'must contain a survey title if a native survey phase' do phase = build(:native_survey_phase) diff --git a/back/spec/policies/idea_policy_spec.rb b/back/spec/policies/idea_policy_spec.rb index 912301356076..a11a5eb37090 100644 --- a/back/spec/policies/idea_policy_spec.rb +++ b/back/spec/policies/idea_policy_spec.rb @@ -638,7 +638,7 @@ let!(:idea) do create(:idea_status_proposed) phase = project.phases.first - create(:idea, project: project, author: author, creation_phase: phase.native_survey? ? phase : nil) + create(:idea, project: project, author: author, creation_phase: phase.pmethod.supports_survey_form? ? phase : nil) end context "for a visitor with posting permissions granted to 'everyone'" do From 266ab4a450725388cecaf2b94460199a85c895d5 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 18 Feb 2025 17:12:42 +0000 Subject: [PATCH 39/55] [TAN-3815] Removed phase.native_survey? entirely --- back/app/models/phase.rb | 5 ----- .../multi_tenancy/templates/serializers/phase.rb | 4 ++-- back/spec/models/phase_spec.rb | 12 ------------ 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index fe1815f9e263..105208062888 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -258,11 +258,6 @@ def voting? participation_method == 'voting' end - # Used for validations (which are hard to delegate through the participation method) - def native_survey? - participation_method == 'native_survey' - end - def pmethod reload_participation_method if !@pmethod @pmethod diff --git a/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/phase.rb b/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/phase.rb index da928ca24592..2c31734a6136 100644 --- a/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/phase.rb +++ b/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/phase.rb @@ -43,8 +43,8 @@ class Phase < Base attribute(:survey_embed_url, if: :survey?) attribute(:survey_service, if: :survey?) attribute(:document_annotation_embed_url, if: :document_annotation?) - attribute(:native_survey_title_multiloc, if: :native_survey?) - attribute(:native_survey_button_multiloc, if: :native_survey?) + attribute(:native_survey_title_multiloc, if: proc { |phase| phase.pmethod.supports_survey_form? }) + attribute(:native_survey_button_multiloc, if: proc { |phase| phase.pmethod.supports_survey_form? }) end end end diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 45948ec072ae..e735fd28eacd 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -310,18 +310,6 @@ end end - describe '#native_survey?' do - it 'returns true when the participation method is native_survey' do - phase = create(:native_survey_phase) - expect(phase.native_survey?).to be true - end - - it 'returns false otherwise' do - phase = create(:poll_phase) - expect(phase.native_survey?).to be false - end - end - describe 'native_survey_title_multiloc' do # Do each here it 'must contain a survey title if a native survey phase' do From e5f18e5c37fc044f3fc1170e908e7a3313e4aee3 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 19 Feb 2025 08:02:35 +0000 Subject: [PATCH 40/55] [TAN-3815] Rubocop fixes --- back/app/models/idea.rb | 1 - .../services/idea_assignment/patches/side_fx_idea_service.rb | 2 +- back/spec/serializers/web_api/v1/phase_serializer_spec.rb | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/back/app/models/idea.rb b/back/app/models/idea.rb index cfc56f0e42d7..ea50d86ff7a1 100644 --- a/back/app/models/idea.rb +++ b/back/app/models/idea.rb @@ -238,7 +238,6 @@ class Idea < ApplicationRecord # Equivalent to pmethod.supports_survey_form? scope :supports_survey, -> { where(creation_phase: Phase.where(participation_method: %w[native_survey community_monitor_survey])) } - # Filters out all the ideas for which the ParticipationMethod responds truety # to the given block. The block receives the ParticipationMethod object as an # argument diff --git a/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb b/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb index c0978ee1fdfc..56a3e8258952 100644 --- a/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb +++ b/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb @@ -33,7 +33,7 @@ def before_publish_or_submit(idea, user) # If a survey is opened in multiple tabs then different draft responses can be created for the same user. # We need to remove any duplicates when the survey is submitted. def remove_duplicate_survey_responses_on_publish(idea) - return unless idea.creation_phase&.pmethod.supports_survey_form? && idea.publication_status_previously_changed?(from: 'draft', to: 'published') + return unless idea.creation_phase&.pmethod&.supports_survey_form? && idea.publication_status_previously_changed?(from: 'draft', to: 'published') Idea.where( creation_phase_id: idea.creation_phase_id, diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index 5015c33bca1c..ddf8fc7b711d 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -80,7 +80,7 @@ :native_survey_title_multiloc, :native_survey_button_multiloc ) - expect(result.dig(:data, :attributes, :supports_survey_form)).to eq true + expect(result.dig(:data, :attributes, :supports_survey_form)).to be true end end end From e269c3a2e2156bd560ec0a9caad36951c97eb021 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 19 Feb 2025 10:40:40 +0000 Subject: [PATCH 41/55] [TAN-3815] Fixed community_monitor phase tests --- back/app/models/phase.rb | 7 +++-- back/spec/factories/projects.rb | 6 ---- back/spec/models/phase_spec.rb | 56 +++++++++++++++++---------------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index 105208062888..ad9ec9f8cc0c 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -340,19 +340,20 @@ def validate_no_other_overlapping_phases end end + # TODO: JS - should probably move this to the participation method def validate_community_monitor_phase return unless participation_method == 'community_monitor_survey' if project.phases.count > 1 - errors.add(:community_monitor_survey, :too_many_phases, message: 'community_monitor project can only have one phase') + errors.add(:base, :too_many_phases, message: 'community_monitor project can only have one phase') end unless project.hidden? - errors.add(:community_monitor_survey, :project_not_hidden, message: 'community_monitor projects must be hidden') + errors.add(:base, :project_not_hidden, message: 'community_monitor projects must be hidden') end if end_at.present? - errors.add(:community_monitor_survey, :has_end_at, message: 'community_monitor projects cannot have an end date') + errors.add(:base, :has_end_at, message: 'community_monitor projects cannot have an end date') end end diff --git a/back/spec/factories/projects.rb b/back/spec/factories/projects.rb index 5c1d8885ad2b..06a626d51344 100644 --- a/back/spec/factories/projects.rb +++ b/back/spec/factories/projects.rb @@ -616,12 +616,6 @@ factory :community_monitor_project do internal_role { 'community_monitor' } admin_publication_attributes { { publication_status: 'hidden' } } - after(:create) do |project| - project.phases << create( - :community_monitor_survey_phase, - project: project - ) - end end end end diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index e735fd28eacd..d6a4b42a1c01 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -310,42 +310,44 @@ end end - describe 'native_survey_title_multiloc' do - # Do each here - it 'must contain a survey title if a native survey phase' do - phase = build(:native_survey_phase) + describe 'native_survey_title_multiloc and native_survey_button_multiloc' do + [ + { project: :project, phase: :native_survey_phase }, + { project: :community_monitor_project, phase: :community_monitor_survey_phase} + ].each do |factories| + context factories[:phase] do + let(:phase) { build(factories[:phase], project: create(factories[:project])) } + + it 'must contain a survey title' do + phase.native_survey_title_multiloc = { en: 'Survey' } + expect(phase).to be_valid + + phase.native_survey_title_multiloc = {} + expect(phase).not_to be_valid + + phase.native_survey_title_multiloc = nil + expect(phase).not_to be_valid + end - phase.native_survey_title_multiloc = { en: 'Survey' } - expect(phase).to be_valid + it 'must contain survey button text' do + phase.native_survey_button_multiloc = { en: 'Take the survey' } + expect(phase).to be_valid - phase.native_survey_title_multiloc = {} - expect(phase).not_to be_valid + phase.native_survey_button_multiloc = {} + expect(phase).not_to be_valid - phase.native_survey_title_multiloc = nil - expect(phase).not_to be_valid + phase.native_survey_button_multiloc = nil + expect(phase).not_to be_valid + end + end end - it 'does not need a survey title if not native survey' do + it 'does not need a survey title if not a type of native survey' do phase = build(:phase, native_survey_title_multiloc: {}) expect(phase).to be_valid end - end - - describe 'native_survey_button_multiloc' do - it 'must contain survey button text if a native survey phase' do - phase = build(:native_survey_phase) - - phase.native_survey_button_multiloc = { en: 'Take the survey' } - expect(phase).to be_valid - - phase.native_survey_button_multiloc = {} - expect(phase).not_to be_valid - - phase.native_survey_button_multiloc = nil - expect(phase).not_to be_valid - end - it 'does not need a survey title if not native survey' do + it 'does not need a survey title if not a type of native survey' do phase = build(:phase, native_survey_button_multiloc: {}) expect(phase).to be_valid end From 0c8783a028b9a3aa7a8e36589c3807de9a23d19d Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 19 Feb 2025 12:57:48 +0000 Subject: [PATCH 42/55] [TAN-3815] Changed method of hiding project to use hidden: true on project --- .../controllers/web_api/v1/projects_controller.rb | 4 ++-- back/app/models/admin_publication.rb | 6 +----- back/app/models/project.rb | 4 ++++ back/app/policies/admin_publication_policy.rb | 2 +- .../20250219104523_add_hidden_to_projects.rb | 5 +++++ back/db/structure.sql | 4 +++- back/spec/factories/projects.rb | 2 +- back/spec/models/phase_spec.rb | 3 +-- back/spec/models/project_spec.rb | 13 +++++++++++++ back/spec/policies/admin_publication_policy_spec.rb | 10 +++++----- back/spec/services/side_fx_idea_service_spec.rb | 2 +- 11 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 back/db/migrate/20250219104523_add_hidden_to_projects.rb diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 1810c9d41b2e..a10295fe5ae1 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -323,7 +323,7 @@ def community_monitor # Find the community monitor project from config or create it project_id = settings.dig('community_monitor', 'project_id') - project = project_id.present? ? Project.find(project_id) : create_community_monitor_project(settings) + project = project_id.present? ? Project.include_hidden.find(project_id) : create_community_monitor_project(settings) authorize project render json: WebApi::V1::ProjectSerializer.new( @@ -395,7 +395,7 @@ def base_render_mini_index def create_community_monitor_project(settings) multiloc_service = MultilocService.new project = Project.create!( - admin_publication_attributes: { publication_status: 'hidden' }, + hidden: true, title_multiloc: multiloc_service.i18n_to_multiloc('phases.community_monitor_title'), internal_role: 'community_monitor' ) diff --git a/back/app/models/admin_publication.rb b/back/app/models/admin_publication.rb index 2e555e5786ab..fe1ad734881b 100644 --- a/back/app/models/admin_publication.rb +++ b/back/app/models/admin_publication.rb @@ -52,7 +52,7 @@ # index_admin_publications_on_rgt (rgt) # class AdminPublication < ApplicationRecord - PUBLICATION_STATUSES = %w[draft published archived hidden] + PUBLICATION_STATUSES = %w[draft published archived] belongs_to :publication, polymorphic: true, touch: true @@ -79,10 +79,6 @@ class AdminPublication < ApplicationRecord where.not(publication_status: 'draft') } - scope :not_hidden, lambda { - where.not(publication_status: 'hidden') - } - def archived? publication_status == 'archived' end diff --git a/back/app/models/project.rb b/back/app/models/project.rb index 6ec19d20f69a..1cfa6889f6d6 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -23,6 +23,7 @@ # followers_count :integer default(0), not null # preview_token :string not null # header_bg_alt_text_multiloc :jsonb +# hidden :boolean default(FALSE), not null # # Indexes # @@ -97,6 +98,9 @@ class Project < ApplicationRecord validates :internal_role, inclusion: { in: INTERNAL_ROLES, allow_nil: true } validate :admin_publication_must_exist, unless: proc { Current.loading_tenant_template } # TODO: This should always be validated! + default_scope { where(hidden: false) } + scope :include_hidden, -> { unscoped } + pg_search_scope :search_by_all, against: %i[title_multiloc description_multiloc description_preview_multiloc slug], using: { tsearch: { prefix: true } } diff --git a/back/app/policies/admin_publication_policy.rb b/back/app/policies/admin_publication_policy.rb index 3ebad02e5702..988b8b386317 100644 --- a/back/app/policies/admin_publication_policy.rb +++ b/back/app/policies/admin_publication_policy.rb @@ -5,7 +5,7 @@ class Scope < ApplicationPolicy::Scope def resolve AdminPublication .publication_types - .map { |klass| scope.not_hidden.where(publication: scope_for(klass)) } # scope per publication type + .map { |klass| scope.where(publication: scope_for(klass)) } # scope per publication type .reduce(&:or) # joining partial scopes end end diff --git a/back/db/migrate/20250219104523_add_hidden_to_projects.rb b/back/db/migrate/20250219104523_add_hidden_to_projects.rb new file mode 100644 index 000000000000..50d8392beeee --- /dev/null +++ b/back/db/migrate/20250219104523_add_hidden_to_projects.rb @@ -0,0 +1,5 @@ +class AddHiddenToProjects < ActiveRecord::Migration[7.0] + def change + add_column :projects, :hidden, :boolean, default: false, null: false + end +end diff --git a/back/db/structure.sql b/back/db/structure.sql index 30223523daa0..5df3fc28dd72 100644 --- a/back/db/structure.sql +++ b/back/db/structure.sql @@ -1184,7 +1184,8 @@ CREATE TABLE public.projects ( votes_count integer DEFAULT 0 NOT NULL, followers_count integer DEFAULT 0 NOT NULL, preview_token character varying NOT NULL, - header_bg_alt_text_multiloc jsonb DEFAULT '{}'::jsonb + header_bg_alt_text_multiloc jsonb DEFAULT '{}'::jsonb, + hidden boolean DEFAULT false NOT NULL ); @@ -6826,6 +6827,7 @@ ALTER TABLE ONLY public.ideas_topics SET search_path TO public,shared_extensions; INSERT INTO "schema_migrations" (version) VALUES +('20250219104523'), ('20250204143605'), ('20250120125531'), ('20250117121004'), diff --git a/back/spec/factories/projects.rb b/back/spec/factories/projects.rb index 06a626d51344..3ead94c2ca0d 100644 --- a/back/spec/factories/projects.rb +++ b/back/spec/factories/projects.rb @@ -615,7 +615,7 @@ factory :community_monitor_project do internal_role { 'community_monitor' } - admin_publication_attributes { { publication_status: 'hidden' } } + hidden { true } end end end diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index d6a4b42a1c01..92a2ec0a2742 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -469,8 +469,7 @@ context 'survey is a community monitor survey' do before do - project.admin_publication.update! publication_status: 'hidden' - project.update! internal_role: 'community_monitor' + project.update! hidden: true, internal_role: 'community_monitor' survey_phase.update! participation_method: 'community_monitor_survey' end diff --git a/back/spec/models/project_spec.rb b/back/spec/models/project_spec.rb index 59e0e9ce05a3..36efb0e2da3d 100644 --- a/back/spec/models/project_spec.rb +++ b/back/spec/models/project_spec.rb @@ -142,4 +142,17 @@ expect(described_class.not_in_draft_folder).to match_array([project2, project3]) end end + + describe "'hidden' scopes" do + let!(:project) { create(:project) } + let!(:hidden_project) { create(:project, hidden: true) } + + it 'returns projects that are not hidden ' do + expect(described_class.all.count).to eq 1 + end + + it 'returns all projects that are not hidden ' do + expect(described_class.include_hidden.count).to eq 2 + end + end end diff --git a/back/spec/policies/admin_publication_policy_spec.rb b/back/spec/policies/admin_publication_policy_spec.rb index 2ed3fb21efb1..0a705315768c 100644 --- a/back/spec/policies/admin_publication_policy_spec.rb +++ b/back/spec/policies/admin_publication_policy_spec.rb @@ -19,8 +19,8 @@ expect(scope.resolve.size).to eq 1 end - it 'should not index the project holder if hidden' do - admin_publication.update!(publication_status: 'hidden') + it 'should not index the project holder if the project is hidden' do + admin_publication.publication.update!(hidden: true) expect(scope.resolve.size).to eq 0 end end @@ -35,7 +35,7 @@ end it 'should not index the project holder if hidden' do - admin_publication.update!(publication_status: 'hidden') + admin_publication.publication.update!(hidden: true) expect(scope.resolve.size).to eq 0 end end @@ -50,7 +50,7 @@ end it 'should not index the project holder if hidden' do - admin_publication.update!(publication_status: 'hidden') + admin_publication.publication.update!(hidden: true) expect(scope.resolve.size).to eq 0 end end @@ -65,7 +65,7 @@ end it 'should not index any projects if the publication_status is hidden' do - admin_publication.update!(publication_status: 'hidden') + admin_publication.publication.update!(hidden: true) expect(scope.resolve.size).to eq 1 end end diff --git a/back/spec/services/side_fx_idea_service_spec.rb b/back/spec/services/side_fx_idea_service_spec.rb index bcf125c622e8..e9c67cef3e8d 100644 --- a/back/spec/services/side_fx_idea_service_spec.rb +++ b/back/spec/services/side_fx_idea_service_spec.rb @@ -78,7 +78,7 @@ end it 'does not create followers if the project is hidden' do - project.admin_publication.update!(publication_status: 'hidden') + project.update!(hidden: true) expect do service.after_create idea.reload, user end.not_to change(Follower, :count) From 528916fe84cd425194949c923be18f0b42ac6a76 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 21 Feb 2025 10:44:36 +0000 Subject: [PATCH 43/55] [TAN-3815] Removed unneeded projects.* in projects finder --- back/app/services/projects_finder_service.rb | 2 +- back/db/structure.sql | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/back/app/services/projects_finder_service.rb b/back/app/services/projects_finder_service.rb index 146a69ee50d1..5a0ea0b9e5d5 100644 --- a/back/app/services/projects_finder_service.rb +++ b/back/app/services/projects_finder_service.rb @@ -22,7 +22,7 @@ def participation_possible subquery = projects_with_active_phase(subquery) .joins('INNER JOIN phases AS active_phases ON active_phases.project_id = projects.id') .where.not(phases: { participation_method: 'information' }) - .select('projects.*, projects.created_at AS projects_created_at, projects.id AS projects_id') + .select('projects.created_at AS projects_created_at, projects.id AS projects_id') # Perform the SELECT DISTINCT on the outer query and order first by the end date of the active phase, # second by project created_at, and third by project ID. diff --git a/back/db/structure.sql b/back/db/structure.sql index 5df3fc28dd72..3d16fdc7b9ff 100644 --- a/back/db/structure.sql +++ b/back/db/structure.sql @@ -1565,7 +1565,8 @@ CREATE TABLE public.phases ( manual_votes_count integer DEFAULT 0 NOT NULL, manual_voters_amount integer, manual_voters_last_updated_by_id uuid, - manual_voters_last_updated_at timestamp(6) without time zone + manual_voters_last_updated_at timestamp(6) without time zone, + user_fields_in_form boolean DEFAULT false NOT NULL ); @@ -6827,6 +6828,7 @@ ALTER TABLE ONLY public.ideas_topics SET search_path TO public,shared_extensions; INSERT INTO "schema_migrations" (version) VALUES +('20250220161323'), ('20250219104523'), ('20250204143605'), ('20250120125531'), From 4176e35a3c41ecfd0c59151b2c880566a3c7e91f Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 21 Feb 2025 11:08:03 +0000 Subject: [PATCH 44/55] [TAN-3815] Fixed tests and moved validation to the participation method --- back/app/models/admin_publication.rb | 4 --- back/app/models/phase.rb | 26 ++++--------------- back/app/models/project.rb | 2 +- back/lib/participation_method/base.rb | 4 +++ .../community_monitor_survey.rb | 14 ++++++++++ back/lib/participation_method/voting.rb | 2 +- back/spec/models/phase_spec.rb | 7 ++--- back/spec/models/project_spec.rb | 4 +-- 8 files changed, 31 insertions(+), 32 deletions(-) diff --git a/back/app/models/admin_publication.rb b/back/app/models/admin_publication.rb index fe1ad734881b..7593fdc46461 100644 --- a/back/app/models/admin_publication.rb +++ b/back/app/models/admin_publication.rb @@ -87,10 +87,6 @@ def published? publication_status == 'published' end - def hidden? - publication_status == 'hidden' - end - def ever_published? first_published_at.present? end diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index ad9ec9f8cc0c..5c3b28711015 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -160,7 +160,7 @@ class Phase < ApplicationRecord # voting? with_options if: :voting? do validates :voting_method, presence: true, inclusion: { in: VOTING_METHODS } - validate :validate_voting + validate :validate_phase_participation_method validates :voting_term_singular_multiloc, multiloc: { presence: false } validates :voting_term_plural_multiloc, multiloc: { presence: false } validates :autoshare_results_enabled, inclusion: { in: [true, false] } @@ -187,7 +187,7 @@ class Phase < ApplicationRecord # TODO: JS - do we need to validate these for community monitor? Assuming so for now validates :native_survey_title_multiloc, presence: true, multiloc: { presence: true } validates :native_survey_button_multiloc, presence: true, multiloc: { presence: true } - validate :validate_community_monitor_phase + validate :validate_phase_participation_method end scope :published, lambda { @@ -340,21 +340,9 @@ def validate_no_other_overlapping_phases end end - # TODO: JS - should probably move this to the participation method - def validate_community_monitor_phase - return unless participation_method == 'community_monitor_survey' - - if project.phases.count > 1 - errors.add(:base, :too_many_phases, message: 'community_monitor project can only have one phase') - end - - unless project.hidden? - errors.add(:base, :project_not_hidden, message: 'community_monitor projects must be hidden') - end - - if end_at.present? - errors.add(:base, :has_end_at, message: 'community_monitor projects cannot have an end date') - end + # Delegate any rules specific to a method to the participation method itself + def validate_phase_participation_method + pmethod.validate_phase end def strip_title @@ -405,10 +393,6 @@ def reload_participation_method def set_presentation_mode self.presentation_mode ||= 'card' end - - def validate_voting - Factory.instance.voting_method_for(self).validate_phase - end end Phase.include(Analysis::Patches::Phase) diff --git a/back/app/models/project.rb b/back/app/models/project.rb index 1cfa6889f6d6..aa715437a0bc 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -146,7 +146,7 @@ class Project < ApplicationRecord alias project_id id - delegate :published?, :ever_published?, :never_published?, :hidden?, to: :admin_publication, allow_nil: true + delegate :published?, :ever_published?, :never_published?, to: :admin_publication, allow_nil: true class << self def search_ids_by_all_including_patches(term) diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 662c5b56b405..eded2c951aba 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -170,6 +170,10 @@ def follow_idea_on_idea_submission? false end + def validate_phase + true + end + private attr_reader :phase diff --git a/back/lib/participation_method/community_monitor_survey.rb b/back/lib/participation_method/community_monitor_survey.rb index 1446ea0ae9bc..77ac6497a3d7 100644 --- a/back/lib/participation_method/community_monitor_survey.rb +++ b/back/lib/participation_method/community_monitor_survey.rb @@ -48,5 +48,19 @@ def constraints def logic_enabled? false end + + def validate_phase + if phase.project.phases.count > 1 + phase.errors.add(:base, :too_many_phases, message: 'community_monitor project can only have one phase') + end + + unless phase.project.hidden + phase.errors.add(:base, :project_not_hidden, message: 'community_monitor projects must be hidden') + end + + if phase.end_at.present? + phase.errors.add(:base, :has_end_at, message: 'community_monitor projects cannot have an end date') + end + end end end diff --git a/back/lib/participation_method/voting.rb b/back/lib/participation_method/voting.rb index a29cafb03fbe..6b9ee5b31159 100644 --- a/back/lib/participation_method/voting.rb +++ b/back/lib/participation_method/voting.rb @@ -2,7 +2,7 @@ module ParticipationMethod class Voting < Ideation - delegate :additional_export_columns, :supports_serializing?, to: :voting_method + delegate :additional_export_columns, :supports_serializing?, :validate_phase, to: :voting_method def self.method_str 'voting' diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 92a2ec0a2742..86827e05ea48 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -313,7 +313,7 @@ describe 'native_survey_title_multiloc and native_survey_button_multiloc' do [ { project: :project, phase: :native_survey_phase }, - { project: :community_monitor_project, phase: :community_monitor_survey_phase} + { project: :community_monitor_project, phase: :community_monitor_survey_phase } ].each do |factories| context factories[:phase] do let(:phase) { build(factories[:phase], project: create(factories[:project])) } @@ -347,7 +347,7 @@ expect(phase).to be_valid end - it 'does not need a survey title if not a type of native survey' do + it 'does not need a survey button if not a type of native survey' do phase = build(:phase, native_survey_button_multiloc: {}) expect(phase).to be_valid end @@ -488,7 +488,8 @@ end it 'is not valid when the project is not hidden' do - survey_phase.project.admin_publication.publication_status = 'published' + project.hidden = false + # survey_phase.project.admin_publication.publication_status = 'published' expect(survey_phase).not_to be_valid end end diff --git a/back/spec/models/project_spec.rb b/back/spec/models/project_spec.rb index 36efb0e2da3d..3e6b626d871c 100644 --- a/back/spec/models/project_spec.rb +++ b/back/spec/models/project_spec.rb @@ -147,11 +147,11 @@ let!(:project) { create(:project) } let!(:hidden_project) { create(:project, hidden: true) } - it 'returns projects that are not hidden ' do + it 'returns projects that are not hidden' do expect(described_class.all.count).to eq 1 end - it 'returns all projects that are not hidden ' do + it 'returns all projects that are not hidden' do expect(described_class.include_hidden.count).to eq 2 end end From a2776e039f8c7b5ad7377422f298924a28bf38b1 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 21 Feb 2025 11:49:48 +0000 Subject: [PATCH 45/55] [TAN-3815] Fixed phase validation --- back/app/models/phase.rb | 15 +++++++-------- back/spec/acceptance/projects_spec.rb | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/back/app/models/phase.rb b/back/app/models/phase.rb index 5c3b28711015..c825e1348a67 100644 --- a/back/app/models/phase.rb +++ b/back/app/models/phase.rb @@ -160,7 +160,6 @@ class Phase < ApplicationRecord # voting? with_options if: :voting? do validates :voting_method, presence: true, inclusion: { in: VOTING_METHODS } - validate :validate_phase_participation_method validates :voting_term_singular_multiloc, multiloc: { presence: false } validates :voting_term_plural_multiloc, multiloc: { presence: false } validates :autoshare_results_enabled, inclusion: { in: [true, false] } @@ -184,12 +183,12 @@ class Phase < ApplicationRecord # any type of native_survey phase with_options if: ->(phase) { phase.pmethod.supports_survey_form? } do - # TODO: JS - do we need to validate these for community monitor? Assuming so for now validates :native_survey_title_multiloc, presence: true, multiloc: { presence: true } validates :native_survey_button_multiloc, presence: true, multiloc: { presence: true } - validate :validate_phase_participation_method end + validate :validate_phase_participation_method + scope :published, lambda { joined = includes(project: { admin_publication: :parent }) joined.where( @@ -340,11 +339,6 @@ def validate_no_other_overlapping_phases end end - # Delegate any rules specific to a method to the participation method itself - def validate_phase_participation_method - pmethod.validate_phase - end - def strip_title title_multiloc.each do |key, value| title_multiloc[key] = value.strip @@ -393,6 +387,11 @@ def reload_participation_method def set_presentation_mode self.presentation_mode ||= 'card' end + + # Delegate any rules specific to a method to the participation method itself + def validate_phase_participation_method + pmethod.validate_phase + end end Phase.include(Analysis::Patches::Phase) diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index 7922e1935caa..ed52f14af67d 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1706,9 +1706,9 @@ do_request assert_status 200 - created_project = Project.first + created_project = Project.include_hidden.first created_phase = Phase.first - expect(created_project.admin_publication.publication_status).to eq 'hidden' + expect(created_project.hidden).to be true expect(created_project.internal_role).to eq 'community_monitor' expect(created_project.title_multiloc['en']).to eq 'Community monitor' expect(created_phase.participation_method).to eq 'community_monitor_survey' From 34f0270697f476928fc8a49890ecec21a5ccbc38 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 21 Feb 2025 13:31:39 +0000 Subject: [PATCH 46/55] [TAN-3815] Changed how hidden projects works --- back/app/controllers/web_api/v1/projects_controller.rb | 4 ++-- back/app/models/project.rb | 7 +++++-- back/app/policies/admin_publication_policy.rb | 2 +- back/app/services/projects_finder_service.rb | 2 +- .../participation_method/community_monitor_survey.rb | 2 +- back/spec/acceptance/projects_spec.rb | 2 +- back/spec/models/project_spec.rb | 10 ++++++---- 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index a10295fe5ae1..e1437cf35b64 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -127,7 +127,7 @@ def index_for_areas # Returns all non-draft projects that are visible to user, for the selected topics. # Ordered by created_at, newest first. def index_for_topics - projects = policy_scope(Project) + projects = policy_scope(Project).not_hidden projects = projects .not_draft .with_some_topics(params[:topics]) @@ -323,7 +323,7 @@ def community_monitor # Find the community monitor project from config or create it project_id = settings.dig('community_monitor', 'project_id') - project = project_id.present? ? Project.include_hidden.find(project_id) : create_community_monitor_project(settings) + project = project_id.present? ? Project.find(project_id) : create_community_monitor_project(settings) authorize project render json: WebApi::V1::ProjectSerializer.new( diff --git a/back/app/models/project.rb b/back/app/models/project.rb index aa715437a0bc..325067df223d 100644 --- a/back/app/models/project.rb +++ b/back/app/models/project.rb @@ -98,8 +98,7 @@ class Project < ApplicationRecord validates :internal_role, inclusion: { in: INTERNAL_ROLES, allow_nil: true } validate :admin_publication_must_exist, unless: proc { Current.loading_tenant_template } # TODO: This should always be validated! - default_scope { where(hidden: false) } - scope :include_hidden, -> { unscoped } + scope :not_hidden, -> { where(hidden: false) } pg_search_scope :search_by_all, against: %i[title_multiloc description_multiloc description_preview_multiloc slug], @@ -227,6 +226,10 @@ def refresh_preview_token self.preview_token = self.class.generate_preview_token end + def hidden? + hidden + end + private def admin_publication_must_exist diff --git a/back/app/policies/admin_publication_policy.rb b/back/app/policies/admin_publication_policy.rb index 988b8b386317..60803f9bda8b 100644 --- a/back/app/policies/admin_publication_policy.rb +++ b/back/app/policies/admin_publication_policy.rb @@ -5,7 +5,7 @@ class Scope < ApplicationPolicy::Scope def resolve AdminPublication .publication_types - .map { |klass| scope.where(publication: scope_for(klass)) } # scope per publication type + .map { |klass| scope.where(publication: klass == Project ? scope_for(klass).not_hidden : scope_for(klass)) } # scope per publication type .reduce(&:or) # joining partial scopes end end diff --git a/back/app/services/projects_finder_service.rb b/back/app/services/projects_finder_service.rb index 5a0ea0b9e5d5..ec4fa3f97005 100644 --- a/back/app/services/projects_finder_service.rb +++ b/back/app/services/projects_finder_service.rb @@ -1,6 +1,6 @@ class ProjectsFinderService def initialize(projects, user = nil, params = {}) - @projects = projects + @projects = projects.not_hidden @user = user @page_size = (params.dig(:page, :size) || 500).to_i @page_number = (params.dig(:page, :number) || 1).to_i diff --git a/back/lib/participation_method/community_monitor_survey.rb b/back/lib/participation_method/community_monitor_survey.rb index 77ac6497a3d7..e3fa578d61aa 100644 --- a/back/lib/participation_method/community_monitor_survey.rb +++ b/back/lib/participation_method/community_monitor_survey.rb @@ -54,7 +54,7 @@ def validate_phase phase.errors.add(:base, :too_many_phases, message: 'community_monitor project can only have one phase') end - unless phase.project.hidden + unless phase.project.hidden? phase.errors.add(:base, :project_not_hidden, message: 'community_monitor projects must be hidden') end diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index ed52f14af67d..e0a7613f222e 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1706,7 +1706,7 @@ do_request assert_status 200 - created_project = Project.include_hidden.first + created_project = Project.first created_phase = Phase.first expect(created_project.hidden).to be true expect(created_project.internal_role).to eq 'community_monitor' diff --git a/back/spec/models/project_spec.rb b/back/spec/models/project_spec.rb index 3e6b626d871c..ba191102c655 100644 --- a/back/spec/models/project_spec.rb +++ b/back/spec/models/project_spec.rb @@ -147,12 +147,14 @@ let!(:project) { create(:project) } let!(:hidden_project) { create(:project, hidden: true) } - it 'returns projects that are not hidden' do - expect(described_class.all.count).to eq 1 + it 'returns all projects that are not hidden' do + expect(described_class.all.count).to eq 2 end - it 'returns all projects that are not hidden' do - expect(described_class.include_hidden.count).to eq 2 + it 'returns projects that are not hidden' do + expect(described_class.not_hidden.count).to eq 1 end + + end end From bdc6522ad125dac551862b0c1912b646a6a5cc39 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 21 Feb 2025 14:10:09 +0000 Subject: [PATCH 47/55] [TAN-3815] Rubocop fix --- back/spec/models/project_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/back/spec/models/project_spec.rb b/back/spec/models/project_spec.rb index ba191102c655..1a746c5db6b0 100644 --- a/back/spec/models/project_spec.rb +++ b/back/spec/models/project_spec.rb @@ -154,7 +154,5 @@ it 'returns projects that are not hidden' do expect(described_class.not_hidden.count).to eq 1 end - - end end From 072d08a1e85242f968eea6d28f66a780a93806ee Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Fri, 21 Feb 2025 15:33:00 +0000 Subject: [PATCH 48/55] [TAN-3815] Added participation method tests --- back/lib/participation_method/base.rb | 4 +- .../community_monitor_survey.rb | 10 +- .../lib/participation_method/native_survey.rb | 2 +- back/spec/factories/phases.rb | 1 + .../community_monitor_survey_spec.rb | 174 ++++++++++++++++++ .../lib/participation_method/ideation_spec.rb | 3 + .../participation_method/information_spec.rb | 3 + .../native_survey_spec.rb | 5 +- .../lib/participation_method/none_spec.rb | 3 + .../lib/participation_method/poll_spec.rb | 3 + .../participation_method/proposals_spec.rb | 3 + .../lib/participation_method/survey_spec.rb | 3 + .../participation_method/volunteering_spec.rb | 3 + .../lib/participation_method/voting_spec.rb | 2 + back/spec/models/phase_spec.rb | 10 +- 15 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 back/spec/lib/participation_method/community_monitor_survey_spec.rb diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index eded2c951aba..47c2ac20499f 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -51,7 +51,7 @@ def custom_form context.custom_form || CustomForm.new(participation_context: context) end - def logic_enabled? + def form_logic_enabled? false end @@ -171,7 +171,7 @@ def follow_idea_on_idea_submission? end def validate_phase - true + # Default is to do nothing. end private diff --git a/back/lib/participation_method/community_monitor_survey.rb b/back/lib/participation_method/community_monitor_survey.rb index e3fa578d61aa..4d73e47881ca 100644 --- a/back/lib/participation_method/community_monitor_survey.rb +++ b/back/lib/participation_method/community_monitor_survey.rb @@ -10,6 +10,7 @@ def allowed_extra_field_input_types %w[page text linear_scale rating select multiselect] end + # TODO: Fields are currently placeholders def default_fields(custom_form) return [] if custom_form.persisted? @@ -19,7 +20,6 @@ def default_fields(custom_form) CustomField.new( id: SecureRandom.uuid, key: 'cm_living_in_city', - code: 'cm_living_in_city', resource: custom_form, input_type: 'rating', maximum: 5, @@ -28,7 +28,6 @@ def default_fields(custom_form) CustomField.new( id: SecureRandom.uuid, key: 'cm_council_services', - code: 'cm_council_services', resource: custom_form, input_type: 'rating', maximum: 5, @@ -39,13 +38,10 @@ def default_fields(custom_form) end def constraints - { - cm_living_in_city: { locks: { enabled: true, title_multiloc: true, maximum: true } }, - cm_council_services: { locks: { enabled: true, title_multiloc: true, maximum: true } } - } + {} # TODO: Any constraints to be added once we know what the fields are end - def logic_enabled? + def form_logic_enabled? false end diff --git a/back/lib/participation_method/native_survey.rb b/back/lib/participation_method/native_survey.rb index 589c788d7078..f04b9ba8194a 100644 --- a/back/lib/participation_method/native_survey.rb +++ b/back/lib/participation_method/native_survey.rb @@ -67,7 +67,7 @@ def default_fields(custom_form) ] end - def logic_enabled? + def form_logic_enabled? true end diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index aa32495df61e..d83020df2953 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -115,6 +115,7 @@ end factory :community_monitor_survey_phase do + project { create(:community_monitor_project) } participation_method { 'community_monitor_survey' } native_survey_title_multiloc { { 'en' => 'Community Monitor', 'nl-BE' => 'Gemeenschapsmonitor' } } native_survey_button_multiloc { { 'en' => 'Take the survey', 'nl-BE' => 'De enquete invullen' } } diff --git a/back/spec/lib/participation_method/community_monitor_survey_spec.rb b/back/spec/lib/participation_method/community_monitor_survey_spec.rb new file mode 100644 index 000000000000..ce6a3ba89b31 --- /dev/null +++ b/back/spec/lib/participation_method/community_monitor_survey_spec.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ParticipationMethod::CommunityMonitorSurvey do + subject(:participation_method) { described_class.new phase } + + let(:phase) { create(:community_monitor_survey_phase) } + + describe '#method_str' do + it 'returns community_monitor_survey' do + expect(described_class.method_str).to eq 'community_monitor_survey' + end + end + + describe '#assign_defaults' do + context 'when the proposed idea status is available' do + let!(:proposed) { create(:idea_status_proposed) } + let(:input) { build(:idea, publication_status: nil, idea_status: nil) } + + it 'sets the publication_status to "published" and the idea_status to "proposed"' do + participation_method.assign_defaults input + expect(input.publication_status).to eq 'published' + expect(input.idea_status).to eq proposed + end + end + + context 'when the proposed idea status is not available' do + let(:input) { build(:idea, idea_status: nil) } + + it 'raises ActiveRecord::RecordNotFound' do + expect { participation_method.assign_defaults input }.to raise_error ActiveRecord::RecordNotFound + end + end + end + + describe '#assign_defaults_for_phase' do + let(:phase) { build(:native_survey_phase) } + + it 'does not change the ideas_order' do + expect do + participation_method.assign_defaults_for_phase + end.not_to change(phase, :ideas_order) + end + end + + describe '#create_default_form!' do + it 'persists a default form with a page for the participation context' do + expect(phase.custom_form).to be_nil + + participation_method.create_default_form! + # create_default_form! does not reload associations for form/fields/options, + # so fetch the project from the database. The associations will be fetched + # when they are needed. + # Not doing this makes this test flaky, as create_default_form! creates fields + # and CustomField uses acts_as_list for ordering fields. The ordering is ok + # in the database, but not necessarily in memory. + phase_in_db = Phase.find(phase.id) + + expect(phase_in_db.custom_form.custom_fields.size).to eq 4 + expect(phase_in_db.custom_form.custom_fields.pluck(:input_type)).to match_array %w[page rating rating page] + end + end + + describe '#default_fields' do + it 'returns an empty list' do + expect( + participation_method.default_fields(create(:custom_form, participation_context: phase)).map(&:code) + ).to eq [] + end + end + + describe '#generate_slug' do + let(:input) { create(:input, slug: nil, project: phase.project, creation_phase: phase) } + + before { create(:idea_status_proposed) } + + it 'sets and persists the id as the slug of the input' do + expect(input.slug).to eq input.id + + input.update_column :slug, nil + input.reload + expect(participation_method.generate_slug(input)).to eq input.id + end + end + + describe '#author_in_form?' do + it 'returns false for a moderator when idea_author_change is activated' do + SettingsService.new.activate_feature! 'idea_author_change' + expect(participation_method.author_in_form?(create(:admin))).to be false + end + end + + describe '#budget_in_form?' do + it 'returns false for a moderator' do + expect(participation_method.budget_in_form?(create(:admin))).to be false + end + end + + describe '#custom_form' do + let(:project_form) { create(:custom_form, participation_context: phase.project) } + let(:phase) { create(:native_survey_phase) } + + it 'returns the custom form of the phase' do + expect(participation_method.custom_form.participation_context_id).to eq phase.id + end + end + + describe '#supports_serializing?' do + it 'returns true for native survey attributes' do + %i[native_survey_title_multiloc native_survey_button_multiloc].each do |attribute| + expect(participation_method.supports_serializing?(attribute)).to be true + end + end + + it 'returns false for the other attributes' do + %i[ + voting_method voting_max_total voting_min_total voting_max_votes_per_idea baskets_count + voting_term_singular_multiloc voting_term_plural_multiloc votes_count + ].each do |attribute| + expect(participation_method.supports_serializing?(attribute)).to be false + end + end + end + + describe '#supports_private_attributes_in_export?' do + it 'returns true if config setting is set to true' do + config = AppConfiguration.instance + config.settings['core']['private_attributes_in_export'] = true + config.save! + expect(participation_method.supports_private_attributes_in_export?).to be true + end + + it 'returns false if config setting is set to false' do + config = AppConfiguration.instance + config.settings['core']['private_attributes_in_export'] = false + config.save! + expect(participation_method.supports_private_attributes_in_export?).to be false + end + + it 'returns true if the setting is not present' do + expect(participation_method.supports_private_attributes_in_export?).to be true + end + end + + its(:additional_export_columns) { is_expected.to eq [] } + its(:allowed_ideas_orders) { is_expected.to be_empty } + its(:return_disabled_actions?) { is_expected.to be true } + its(:supports_assignment?) { is_expected.to be false } + its(:supports_built_in_fields?) { is_expected.to be false } + its(:supports_commenting?) { is_expected.to be false } + its(:supports_edits_after_publication?) { is_expected.to be false } + its(:supports_exports?) { is_expected.to be true } + its(:supports_input_term?) { is_expected.to be false } + its(:supports_inputs_without_author?) { is_expected.to be true } + its(:supports_multiple_posts?) { is_expected.to be false } + its(:supports_pages_in_form?) { is_expected.to be true } + its(:supports_permitted_by_everyone?) { is_expected.to be true } + its(:supports_public_visibility?) { is_expected.to be false } + its(:supports_reacting?) { is_expected.to be false } + its(:supports_status?) { is_expected.to be false } + its(:supports_submission?) { is_expected.to be true } + its(:supports_toxicity_detection?) { is_expected.to be false } + its(:use_reactions_as_votes?) { is_expected.to be false } + its(:transitive?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + + describe 'proposed_budget_in_form?' do # private method + it 'is expected to be false' do + expect(participation_method.send(:proposed_budget_in_form?)).to be false + end + end +end diff --git a/back/spec/lib/participation_method/ideation_spec.rb b/back/spec/lib/participation_method/ideation_spec.rb index 16d1b89b1848..21906c3cbc0f 100644 --- a/back/spec/lib/participation_method/ideation_spec.rb +++ b/back/spec/lib/participation_method/ideation_spec.rb @@ -228,6 +228,9 @@ its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be true } its(:supports_private_attributes_in_export?) { is_expected.to be true } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be true } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be true' do diff --git a/back/spec/lib/participation_method/information_spec.rb b/back/spec/lib/participation_method/information_spec.rb index 31bd6d66fa80..64fa802efba0 100644 --- a/back/spec/lib/participation_method/information_spec.rb +++ b/back/spec/lib/participation_method/information_spec.rb @@ -107,6 +107,9 @@ its(:transitive?) { is_expected.to be false } its(:use_reactions_as_votes?) { is_expected.to be false } its(:supports_private_attributes_in_export?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/native_survey_spec.rb b/back/spec/lib/participation_method/native_survey_spec.rb index d0e32aecc625..0e1f131e981d 100644 --- a/back/spec/lib/participation_method/native_survey_spec.rb +++ b/back/spec/lib/participation_method/native_survey_spec.rb @@ -18,7 +18,7 @@ let!(:proposed) { create(:idea_status_proposed) } let(:input) { build(:idea, publication_status: nil, idea_status: nil) } - it 'sets the publication_status to "publised" and the idea_status to "proposed"' do + it 'sets the publication_status to "published" and the idea_status to "proposed"' do participation_method.assign_defaults input expect(input.publication_status).to eq 'published' expect(input.idea_status).to eq proposed @@ -188,6 +188,9 @@ its(:supports_toxicity_detection?) { is_expected.to be false } its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be true } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/none_spec.rb b/back/spec/lib/participation_method/none_spec.rb index 090d3c468ac6..de06452ac9d1 100644 --- a/back/spec/lib/participation_method/none_spec.rb +++ b/back/spec/lib/participation_method/none_spec.rb @@ -90,6 +90,9 @@ its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be false } its(:supports_private_attributes_in_export?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/poll_spec.rb b/back/spec/lib/participation_method/poll_spec.rb index 5962903317b3..031a50961a59 100644 --- a/back/spec/lib/participation_method/poll_spec.rb +++ b/back/spec/lib/participation_method/poll_spec.rb @@ -106,6 +106,9 @@ its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be false } its(:supports_private_attributes_in_export?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/proposals_spec.rb b/back/spec/lib/participation_method/proposals_spec.rb index 2863e373ec9e..64349a84ca0a 100644 --- a/back/spec/lib/participation_method/proposals_spec.rb +++ b/back/spec/lib/participation_method/proposals_spec.rb @@ -235,6 +235,9 @@ its(:use_reactions_as_votes?) { is_expected.to be true } its(:transitive?) { is_expected.to be false } its(:supports_private_attributes_in_export?) { is_expected.to be true } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be true } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/survey_spec.rb b/back/spec/lib/participation_method/survey_spec.rb index 920cec013975..5e49a519374a 100644 --- a/back/spec/lib/participation_method/survey_spec.rb +++ b/back/spec/lib/participation_method/survey_spec.rb @@ -107,6 +107,9 @@ its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be false } its(:supports_private_attributes_in_export?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/volunteering_spec.rb b/back/spec/lib/participation_method/volunteering_spec.rb index 6b3115c62f2f..4b277638754d 100644 --- a/back/spec/lib/participation_method/volunteering_spec.rb +++ b/back/spec/lib/participation_method/volunteering_spec.rb @@ -106,6 +106,9 @@ its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be false } its(:supports_private_attributes_in_export?) { is_expected.to be false } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be false } + its(:validate_phase) { is_expected.to be_nil } describe 'proposed_budget_in_form?' do # private method it 'is expected to be false' do diff --git a/back/spec/lib/participation_method/voting_spec.rb b/back/spec/lib/participation_method/voting_spec.rb index d6cb46b48b90..f67fb89fe384 100644 --- a/back/spec/lib/participation_method/voting_spec.rb +++ b/back/spec/lib/participation_method/voting_spec.rb @@ -184,6 +184,8 @@ its(:use_reactions_as_votes?) { is_expected.to be false } its(:transitive?) { is_expected.to be true } its(:supports_private_attributes_in_export?) { is_expected.to be true } + its(:form_logic_enabled?) { is_expected.to be false } + its(:follow_idea_on_idea_submission?) { is_expected.to be true } describe 'proposed_budget_in_form?' do # private method it 'is expected to be true' do diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 86827e05ea48..76f418c41a26 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -312,11 +312,11 @@ describe 'native_survey_title_multiloc and native_survey_button_multiloc' do [ - { project: :project, phase: :native_survey_phase }, - { project: :community_monitor_project, phase: :community_monitor_survey_phase } - ].each do |factories| - context factories[:phase] do - let(:phase) { build(factories[:phase], project: create(factories[:project])) } + :native_survey_phase, + :community_monitor_survey_phase + ].each do |factory| + context factory do + let(:phase) { build(factory) } it 'must contain a survey title' do phase.native_survey_title_multiloc = { en: 'Survey' } From 3b63b205754eeeeb417f907223a33186c1e1c052 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Tue, 25 Feb 2025 15:34:11 +0000 Subject: [PATCH 49/55] [TAN-3815] Fixed survey results to work for new participation method --- back/app/services/survey_results_generator_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/app/services/survey_results_generator_service.rb b/back/app/services/survey_results_generator_service.rb index 01d791b3100f..498e900d9a31 100644 --- a/back/app/services/survey_results_generator_service.rb +++ b/back/app/services/survey_results_generator_service.rb @@ -7,7 +7,7 @@ def initialize(phase, group_mode: nil, group_field_id: nil) @group_field_id = group_field_id form = phase.custom_form || CustomForm.new(participation_context: phase) @fields = IdeaCustomFieldsService.new(form).enabled_fields - @inputs = phase.ideas.native_survey.published + @inputs = phase.ideas.supports_survey.published @locales = AppConfiguration.instance.settings('core', 'locales') end From 3d817d96bf6aef977f9b6306c9e4f4927eac6e31 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 26 Feb 2025 09:14:09 +0000 Subject: [PATCH 50/55] [TAN-3815] Added tests for community monitor survey results --- ...enerator_service_community_monitor_spec.rb | 284 ++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 back/spec/services/survey_results_generator_service_community_monitor_spec.rb diff --git a/back/spec/services/survey_results_generator_service_community_monitor_spec.rb b/back/spec/services/survey_results_generator_service_community_monitor_spec.rb new file mode 100644 index 000000000000..c6ca79522aeb --- /dev/null +++ b/back/spec/services/survey_results_generator_service_community_monitor_spec.rb @@ -0,0 +1,284 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# NOTE: These tests are basic and will be updated when additional features specific to community monitor are added +RSpec.describe SurveyResultsGeneratorService do + subject(:generator) { described_class.new survey_phase } + + let_it_be(:project) { create(:community_monitor_project) } + let_it_be(:survey_phase) { create(:community_monitor_survey_phase, project: project) } + let_it_be(:phases_of_inputs) { [survey_phase] } + + # Set-up custom form + let_it_be(:form) { create(:custom_form, participation_context: survey_phase) } + let_it_be(:page_field) { create(:custom_field_page, resource: form) } + let_it_be(:rating_field) do + create( + :custom_field_rating, + resource: form, + title_multiloc: { + 'en' => 'How satisfied are you with our service?', + 'fr-FR' => 'À quel point êtes-vous satisfait de notre service ?', + 'nl-NL' => 'Hoe tevreden ben je met onze service?' + }, + maximum: 7, + required: true + ) + end + + let_it_be(:text_field) do + create( + :custom_field, + resource: form, + title_multiloc: { 'en' => 'What is your favourite colour?' }, + description_multiloc: {} + ) + end + + # The following page for form submission should not be returned in the results + let_it_be(:last_page_field) do + create(:custom_field_page, resource: form, key: 'survey_end') + end + + let_it_be(:gender_user_custom_field) do + create(:custom_field_gender, :with_options) + end + + let_it_be(:domicile_user_custom_field) do + field = create(:custom_field_domicile) + create(:area, title_multiloc: { 'en' => 'Area 1' }) + create(:area, title_multiloc: { 'en' => 'Area 2' }) + field + end + + # Create responses + let_it_be(:responses) do + create(:idea_status_proposed) + male_user = create(:user, custom_field_values: { gender: 'male', domicile: domicile_user_custom_field.options[0].area.id }) + female_user = create(:user, custom_field_values: { gender: 'female', domicile: domicile_user_custom_field.options[1].area.id }) + no_gender_user = create(:user, custom_field_values: {}) + create( + :native_survey_response, + project: project, + phases: phases_of_inputs, + custom_field_values: { + rating_field.key => 3, + text_field.key => 'Red' + }, + author: female_user + ) + create( + :native_survey_response, + project: project, + phases: phases_of_inputs, + custom_field_values: { + rating_field.key => 4, + text_field.key => 'Blue' + }, + author: male_user + ) + create( + :native_survey_response, + project: project, + phases: phases_of_inputs, + custom_field_values: { + rating_field.key => 5, + text_field.key => 'Green' + }, + author: female_user + ) + create( + :native_survey_response, + project: project, + phases: phases_of_inputs, + custom_field_values: { + rating_field.key => 5, + text_field.key => 'Pink' + }, + author: no_gender_user + ) + end + + describe 'generate_results for community monitor surveys' do + let(:generated_results) { generator.generate_results } + + describe 'structure' do + it 'returns the correct totals' do + expect(generated_results[:totalSubmissions]).to eq 4 + end + + it 'returns the correct fields and structure' do + expect(generated_results[:results].count).to eq 3 + expect(generated_results[:results].pluck(:inputType)).to eq %w[page rating text] + end + end + + describe 'page fields' do + it 'returns correct values for a page field in full results' do + page_result = generated_results[:results][0] + expect(page_result[:inputType]).to eq 'page' + expect(page_result[:totalResponseCount]).to eq(4) + expect(page_result[:questionResponseCount]).to eq(4) + expect(page_result[:pageNumber]).to eq(1) + expect(page_result[:questionNumber]).to be_nil + end + end + + describe 'rating field' do + let(:expected_result_rating) do + { + customFieldId: rating_field.id, + inputType: 'rating', + question: { + 'en' => 'How satisfied are you with our service?', + 'fr-FR' => 'À quel point êtes-vous satisfait de notre service ?', + 'nl-NL' => 'Hoe tevreden ben je met onze service?' + }, + required: true, + grouped: false, + description: { 'en' => 'Please rate your experience from 1 (poor) to 5 (excellent).' }, + hidden: false, + logic: {}, + pageNumber: nil, + questionNumber: nil, + totalResponseCount: 4, + questionResponseCount: 4, + totalPickCount: 4, + answers: [ + { answer: 1, count: 0 }, + { answer: 2, count: 0 }, + { answer: 3, count: 1 }, + { answer: 4, count: 1 }, + { answer: 5, count: 2 }, + { answer: 6, count: 0 }, + { answer: 7, count: 0 }, + { answer: nil, count: 0 } + ], + multilocs: { + answer: { + 1 => { title_multiloc: { 'en' => '1', 'fr-FR' => '1', 'nl-NL' => '1' } }, + 2 => { title_multiloc: { 'en' => '2', 'fr-FR' => '2', 'nl-NL' => '2' } }, + 3 => { title_multiloc: { 'en' => '3', 'fr-FR' => '3', 'nl-NL' => '3' } }, + 4 => { title_multiloc: { 'en' => '4', 'fr-FR' => '4', 'nl-NL' => '4' } }, + 5 => { title_multiloc: { 'en' => '5', 'fr-FR' => '5', 'nl-NL' => '5' } }, + 6 => { title_multiloc: { 'en' => '6', 'fr-FR' => '6', 'nl-NL' => '6' } }, + 7 => { title_multiloc: { 'en' => '7', 'fr-FR' => '7', 'nl-NL' => '7' } } + } + } + } + end + + it 'returns the results for a rating field' do + expected_result_rating[:questionNumber] = 1 + expect(generated_results[:results][1]).to match expected_result_rating + end + + it 'returns a single result for a rating field' do + expect(generator.generate_results(field_id: rating_field.id)).to match expected_result_rating + end + + context 'with grouping' do + let(:grouped_rating_results) do + { + customFieldId: rating_field.id, + inputType: 'rating', + question: { + 'en' => 'How satisfied are you with our service?', + 'fr-FR' => 'À quel point êtes-vous satisfait de notre service ?', + 'nl-NL' => 'Hoe tevreden ben je met onze service?' + }, + required: true, + grouped: true, + description: { 'en' => 'Please rate your experience from 1 (poor) to 5 (excellent).' }, + hidden: false, + logic: {}, + pageNumber: nil, + questionNumber: nil, + totalResponseCount: 4, + questionResponseCount: 4, + totalPickCount: 4, + answers: [ + { + answer: 1, + count: 0, + groups: [] + }, + { + answer: 2, + count: 0, + groups: [] + }, + { + answer: 3, + count: 1, + groups: [ + { count: 1, group: 'female' } + ] + }, + { + answer: 4, + count: 1, + groups: [ + { count: 1, group: 'male' } + ] + }, + { + answer: 5, + count: 2, + groups: [ + { count: 1, group: 'female' }, + { count: 1, group: nil } + ] + }, + { + answer: 6, + count: 0, + groups: [] + }, + { + answer: 7, + count: 0, + groups: [] + }, + { + answer: nil, + count: 0, + groups: [] + } + ], + multilocs: { + answer: { + 1 => { title_multiloc: { 'en' => '1', 'fr-FR' => '1', 'nl-NL' => '1' } }, + 2 => { title_multiloc: { 'en' => '2', 'fr-FR' => '2', 'nl-NL' => '2' } }, + 3 => { title_multiloc: { 'en' => '3', 'fr-FR' => '3', 'nl-NL' => '3' } }, + 4 => { title_multiloc: { 'en' => '4', 'fr-FR' => '4', 'nl-NL' => '4' } }, + 5 => { title_multiloc: { 'en' => '5', 'fr-FR' => '5', 'nl-NL' => '5' } }, + 6 => { title_multiloc: { 'en' => '6', 'fr-FR' => '6', 'nl-NL' => '6' } }, + 7 => { title_multiloc: { 'en' => '7', 'fr-FR' => '7', 'nl-NL' => '7' } } + }, + group: { + 'female' => { title_multiloc: { 'en' => 'youth council', 'fr-FR' => 'conseil des jeunes', 'nl-NL' => 'jeugdraad' } }, + 'male' => { title_multiloc: { 'en' => 'youth council', 'fr-FR' => 'conseil des jeunes', 'nl-NL' => 'jeugdraad' } }, + 'unspecified' => { title_multiloc: { 'en' => 'youth council', 'fr-FR' => 'conseil des jeunes', 'nl-NL' => 'jeugdraad' } } + } + }, + legend: ['male', 'female', 'unspecified', nil] + } + end + + it 'returns a grouped result for a rating field' do + generator = described_class.new( + survey_phase, + group_mode: 'user_field', + group_field_id: gender_user_custom_field.id + ) + result = generator.generate_results( + field_id: rating_field.id + ) + expect(result).to match grouped_rating_results + end + end + end + end +end From f650cf5b11d9e5c3e539c4a1459fb142e93fc69d Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 26 Feb 2025 10:24:09 +0000 Subject: [PATCH 51/55] [TAN-3815] Added tests for community monitor survey update_all + added check for last page being present --- .../services/idea_custom_fields_service.rb | 7 + ...pdate_all_community_monitor_survey_spec.rb | 166 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 back/engines/commercial/idea_custom_fields/spec/acceptance/idea_custom_fields/phase_context/update_all_community_monitor_survey_spec.rb diff --git a/back/app/services/idea_custom_fields_service.rb b/back/app/services/idea_custom_fields_service.rb index 5e8732bcdcec..b62aff4fa2b7 100644 --- a/back/app/services/idea_custom_fields_service.rb +++ b/back/app/services/idea_custom_fields_service.rb @@ -104,12 +104,19 @@ def remove_ignored_update_params(field_params) def check_form_structure(fields, errors) return if fields.empty? + # Check the first field is of the correct type first_field_type = @participation_method.supports_pages_in_form? ? 'page' : 'section' cannot_have_type = @participation_method.supports_pages_in_form? ? 'section' : 'page' if fields[0][:input_type] != first_field_type error = { error: "First field must be of type '#{first_field_type}'" } errors['0'] = { structure: [error] } end + + # Check the last field is a page + if @participation_method.supports_pages_in_form? && fields.last[:input_type] != 'page' + errors["#{fields.length - 1}"] = { structure: [{ error: "Last field must be of type 'page'" }] } + end + fields.each_with_index do |field, index| next unless field[:input_type] == cannot_have_type diff --git a/back/engines/commercial/idea_custom_fields/spec/acceptance/idea_custom_fields/phase_context/update_all_community_monitor_survey_spec.rb b/back/engines/commercial/idea_custom_fields/spec/acceptance/idea_custom_fields/phase_context/update_all_community_monitor_survey_spec.rb new file mode 100644 index 000000000000..d493fce812d0 --- /dev/null +++ b/back/engines/commercial/idea_custom_fields/spec/acceptance/idea_custom_fields/phase_context/update_all_community_monitor_survey_spec.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'rspec_api_documentation/dsl' + +resource 'Idea Custom Fields' do + explanation 'Fields in idea forms which are customized by the city, scoped on the project level.' + before { header 'Content-Type', 'application/json' } + + patch 'web_api/v1/admin/phases/:phase_id/custom_fields/update_all' do + parameter :custom_fields, type: :array + with_options scope: 'custom_fields[]' do + parameter :id, 'The ID of an existing custom field to update. When the ID is not provided, a new field is created.', required: false + parameter :input_type, 'The type of the input. Required when creating a new field.', required: false + parameter :required, 'Whether filling out the field is mandatory', required: false + parameter :enabled, 'Whether the field is active or not', required: false + parameter :title_multiloc, 'A title of the field, as shown to users, in multiple locales', required: false + parameter :description_multiloc, 'An optional description of the field, as shown to users, in multiple locales', required: false + parameter :options, type: :array + end + with_options scope: 'options[]' do + parameter :id, 'The ID of an existing custom field option to update. When the ID is not provided, a new option is created.', required: false + parameter :title_multiloc, 'A title of the option, as shown to users, in multiple locales', required: false + parameter :image_id, 'If the option has an image, the ID of the image', required: false + end + + let(:first_page) do + { + id: '1234', + key: 'page1', + title_multiloc: { 'en' => 'First page' }, + input_type: 'page', + page_layout: 'default' + } + end + + let(:last_page) do + { + id: '1234', + key: 'survey_end', + title_multiloc: { 'en' => 'Final page' }, + description_multiloc: { 'en' => 'Thank you for participating!' }, + input_type: 'page', + page_layout: 'default' + } + end + + let(:context) { create(:community_monitor_survey_phase) } + let!(:custom_form) { create(:custom_form, participation_context: context) } + let(:phase_id) { context.id } + + context 'when admin' do + before { admin_header_token } + + example 'Insert one field, update one field, and destroy one field' do + field_to_update = create(:custom_field_rating, resource: custom_form, title_multiloc: { 'en' => 'Rating field' }) + create(:custom_field, resource: custom_form) # field to destroy + request = { + custom_fields: [ + first_page, + # Updated field + { + id: field_to_update.id, + title_multiloc: { 'en' => 'Updated rating field' }, + description_multiloc: { 'en' => 'Updated description' }, + required: true, + enabled: true, + maximum: 7 + }, + # Inserted field first to test reordering of fields. + { + input_type: 'text', + title_multiloc: { 'en' => 'Inserted field' }, + required: false, + enabled: false + }, + last_page + ] + } + do_request request + + assert_status 200 + expect(response_data.size).to eq 4 + expect(response_data[1]).to match({ + attributes: { + code: nil, + created_at: an_instance_of(String), + description_multiloc: { en: 'Updated description' }, + enabled: true, + input_type: 'rating', + key: field_to_update.key, + ordering: 1, + required: true, + title_multiloc: { en: 'Updated rating field' }, + updated_at: an_instance_of(String), + logic: {}, + constraints: {}, + random_option_ordering: false, + maximum: 7 + }, + id: an_instance_of(String), + type: 'custom_field', + relationships: { options: { data: [] }, resource: { data: { id: custom_form.id, type: 'custom_form' } } } + }) + expect(response_data[2]).to match({ + attributes: { + code: nil, + created_at: an_instance_of(String), + description_multiloc: {}, + enabled: false, + input_type: 'text', + key: Regexp.new('inserted_field'), + ordering: 2, + required: false, + title_multiloc: { en: 'Inserted field' }, + updated_at: an_instance_of(String), + logic: {}, + constraints: {}, + random_option_ordering: false + }, + id: an_instance_of(String), + type: 'custom_field', + relationships: { options: { data: [] }, resource: { data: { id: custom_form.id, type: 'custom_form' } } } + }) + end + + context 'Errors' do + example 'Unsupported field types' do + request = { + custom_fields: [ + first_page, + { input_type: 'multiselect_image', title_multiloc: { en: 'Not allowed' } }, + { input_type: 'html_multiloc', title_multiloc: { en: 'Not allowed' } }, + last_page + ] + } + do_request request + assert_status 422 + expect(json_response_body.dig(:errors, :'1')).to eq( + { input_type: [{ error: 'inclusion', value: 'multiselect_image' }] } + ) + expect(json_response_body.dig(:errors, :'2')).to eq( + { input_type: [{ error: 'inclusion', value: 'html_multiloc' }] } + ) + end + + example 'First page & last page must be present' do + request = { + custom_fields: [ + { input_type: 'text', title_multiloc: { en: 'Text field' } }, + { input_type: 'text', title_multiloc: { en: 'Text field' } } + ] + } + do_request request + assert_status 422 + expect(json_response_body.dig(:errors, :'0')).to eq( + { structure: [{ error: "First field must be of type 'page'" }] } + ) + expect(json_response_body.dig(:errors, :'1')).to eq( + { structure: [{ error: "Last field must be of type 'page'" }] } + ) + end + end + end + end +end From 33b6fac6b63db6c1017fbe4e863a8875a5624e39 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Wed, 26 Feb 2025 11:50:53 +0000 Subject: [PATCH 52/55] [TAN-3815] Added tests for community monitor survey idea creation + fixed assignees --- .../idea_assignment_service.rb | 2 +- back/lib/participation_method/base.rb | 4 ++ back/lib/participation_method/ideation.rb | 4 ++ back/spec/acceptance/ideas_create_spec.rb | 68 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) diff --git a/back/engines/commercial/idea_assignment/app/services/idea_assignment/idea_assignment_service.rb b/back/engines/commercial/idea_assignment/app/services/idea_assignment/idea_assignment_service.rb index ce7e4da14497..f4ae6817ceac 100644 --- a/back/engines/commercial/idea_assignment/app/services/idea_assignment/idea_assignment_service.rb +++ b/back/engines/commercial/idea_assignment/app/services/idea_assignment/idea_assignment_service.rb @@ -25,7 +25,7 @@ def clean_assignees_for_project!(project) end def automatically_assigned_idea_assignee(idea) - return if idea.participation_method_on_creation.instance_of?(ParticipationMethod::NativeSurvey) + return unless idea.participation_method_on_creation.automatically_assign_idea? idea&.project&.default_assignee end diff --git a/back/lib/participation_method/base.rb b/back/lib/participation_method/base.rb index 47c2ac20499f..6129641daf62 100644 --- a/back/lib/participation_method/base.rb +++ b/back/lib/participation_method/base.rb @@ -174,6 +174,10 @@ def validate_phase # Default is to do nothing. end + def automatically_assign_idea? + false + end + private attr_reader :phase diff --git a/back/lib/participation_method/ideation.rb b/back/lib/participation_method/ideation.rb index ad3f1fd2129b..7957f795bbed 100644 --- a/back/lib/participation_method/ideation.rb +++ b/back/lib/participation_method/ideation.rb @@ -400,6 +400,10 @@ def follow_idea_on_idea_submission? true end + def automatically_assign_idea? + true + end + private def proposed_budget_in_form? diff --git a/back/spec/acceptance/ideas_create_spec.rb b/back/spec/acceptance/ideas_create_spec.rb index d3efd5e4985d..e988d8ef9e98 100644 --- a/back/spec/acceptance/ideas_create_spec.rb +++ b/back/spec/acceptance/ideas_create_spec.rb @@ -544,6 +544,74 @@ def public_input_params(spec) end end end + + context 'in a community monitor survey phase' do + let(:project) { create(:community_monitor_project, default_assignee_id: create(:admin).id) } + let(:phase) { create(:community_monitor_survey_phase, project: project, with_permissions: true) } + + let(:extra_field_name) { 'custom_field_name1' } + let(:form) { create(:custom_form, participation_context: phase) } + let!(:text_field) { create(:custom_field_text, key: extra_field_name, required: true, resource: form) } + let(:custom_field_name1) { 'test value' } + + context "when visitor (permission is 'everyone')" do + before { phase.permissions.find_by(action: 'posting_idea').update! permitted_by: 'everyone' } + + example_request 'Create a community monitor survey response without author' do + assert_status 201 + idea_from_db = Idea.find(response_data[:id]) + expect(idea_from_db.author_id).to be_nil + expect(idea_from_db.custom_field_values.to_h).to eq({ + extra_field_name => 'test value' + }) + end + end + + context 'when resident' do + let(:resident) { create(:user) } + + before { header_token_for(resident) } + + example_request 'does not assign anyone to the created idea', document: false do + assert_status 201 + idea = Idea.find(response_data[:id]) + expect(idea.assignee_id).to be_nil + expect(idea.assigned_at).to be_nil + end + + context 'creating a draft community monitor survey response' do + let(:publication_status) { 'draft' } + + example_request 'sets the publication status to draft' do + assert_status 201 + idea = Idea.find(response_data[:id]) + expect(idea.publication_status).to eq 'draft' + end + end + + context 'Creating a community monitor survey response when posting anonymously is enabled' do + before { phase.update! allow_anonymous_participation: true } + + example_request 'Posting a survey automatically sets anonymous to true' do + assert_status 201 + expect(response_data.dig(:attributes, :anonymous)).to be true + expect(response_data.dig(:attributes, :author_name)).to be_nil + expect(response_data.dig(:relationships, :author, :data)).to be_nil + end + end + + context 'Creating a community monitor survey response when posting anonymously is not enabled' do + before { phase.update! allow_anonymous_participation: false } + + example_request 'Posting a survey does not set the survey to anonymous' do + assert_status 201 + expect(response_data.dig(:attributes, :anonymous)).to be false + expect(response_data.dig(:attributes, :author_name)).not_to be_nil + expect(response_data.dig(:relationships, :author, :data)).not_to be_nil + end + end + end + end end end From 45777596b8aa30878d5f1a76e9b404c733a20c92 Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Thu, 27 Feb 2025 10:06:21 +0000 Subject: [PATCH 53/55] [TAN-3815] Last changes after own review --- back/app/services/idea_custom_fields_service.rb | 2 +- back/app/services/project_copy_service.rb | 6 +++--- .../patches/side_fx_idea_service.rb | 2 +- .../templates/serializers/project.rb | 1 + back/spec/acceptance/projects_spec.rb | 2 +- back/spec/factories/phases.rb | 2 +- back/spec/models/phase_spec.rb | 6 +++--- .../web_api/v1/phase_serializer_spec.rb | 15 ++++++++++++++- 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/back/app/services/idea_custom_fields_service.rb b/back/app/services/idea_custom_fields_service.rb index b62aff4fa2b7..c1739ceaa8a0 100644 --- a/back/app/services/idea_custom_fields_service.rb +++ b/back/app/services/idea_custom_fields_service.rb @@ -114,7 +114,7 @@ def check_form_structure(fields, errors) # Check the last field is a page if @participation_method.supports_pages_in_form? && fields.last[:input_type] != 'page' - errors["#{fields.length - 1}"] = { structure: [{ error: "Last field must be of type 'page'" }] } + errors[(fields.length - 1).to_s] = { structure: [{ error: "Last field must be of type 'page'" }] } end fields.each_with_index do |field, index| diff --git a/back/app/services/project_copy_service.rb b/back/app/services/project_copy_service.rb index 9d94bb338d21..33171687111d 100644 --- a/back/app/services/project_copy_service.rb +++ b/back/app/services/project_copy_service.rb @@ -242,7 +242,8 @@ def yml_projects(shift_timestamps: 0, new_slug: nil, new_title_multiloc: nil, ne 'updated_at' => ti.updated_at.to_s } end, - 'include_all_areas' => @project.include_all_areas + 'include_all_areas' => @project.include_all_areas, + 'hidden' => @project.hidden, } yml_project['slug'] = new_slug if new_slug.present? store_ref yml_project, @project.id, :project @@ -335,8 +336,7 @@ def yml_phases(shift_timestamps: 0, timeline_start_at: nil) yml_phase['document_annotation_embed_url'] = phase.document_annotation_embed_url end - # TODO: JS - Needed for community monitor? - if yml_phase['participation_method'] == 'native_survey' + if %w[native_survey community_monitor_survey].include? yml_phase['participation_method'] yml_phase['native_survey_title_multiloc'] = phase.native_survey_title_multiloc yml_phase['native_survey_button_multiloc'] = phase.native_survey_button_multiloc end diff --git a/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb b/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb index 56a3e8258952..8c72cce9e731 100644 --- a/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb +++ b/back/engines/commercial/idea_assignment/app/services/idea_assignment/patches/side_fx_idea_service.rb @@ -33,7 +33,7 @@ def before_publish_or_submit(idea, user) # If a survey is opened in multiple tabs then different draft responses can be created for the same user. # We need to remove any duplicates when the survey is submitted. def remove_duplicate_survey_responses_on_publish(idea) - return unless idea.creation_phase&.pmethod&.supports_survey_form? && idea.publication_status_previously_changed?(from: 'draft', to: 'published') + return unless idea.participation_method_on_creation&.supports_survey_form? && idea.publication_status_previously_changed?(from: 'draft', to: 'published') Idea.where( creation_phase_id: idea.creation_phase_id, diff --git a/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/project.rb b/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/project.rb index 1ace81ba1fcb..9c5ed481ccaa 100644 --- a/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/project.rb +++ b/back/engines/commercial/multi_tenancy/app/services/multi_tenancy/templates/serializers/project.rb @@ -13,6 +13,7 @@ class Project < Base internal_role title_multiloc visible_to + hidden ] end end diff --git a/back/spec/acceptance/projects_spec.rb b/back/spec/acceptance/projects_spec.rb index e0a7613f222e..504acd72ed45 100644 --- a/back/spec/acceptance/projects_spec.rb +++ b/back/spec/acceptance/projects_spec.rb @@ -1275,7 +1275,7 @@ create(:project, admin_publication_attributes: { publication_status: status }) end end - let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES.reject { |ps| ps == 'hidden' } } + let(:publication_statuses) { AdminPublication::PUBLICATION_STATUSES } get 'web_api/v1/projects' do with_options scope: :page do diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index d83020df2953..617b06e7be1b 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -115,7 +115,7 @@ end factory :community_monitor_survey_phase do - project { create(:community_monitor_project) } + association :project, factory: :community_monitor_project participation_method { 'community_monitor_survey' } native_survey_title_multiloc { { 'en' => 'Community Monitor', 'nl-BE' => 'Gemeenschapsmonitor' } } native_survey_button_multiloc { { 'en' => 'Take the survey', 'nl-BE' => 'De enquete invullen' } } diff --git a/back/spec/models/phase_spec.rb b/back/spec/models/phase_spec.rb index 76f418c41a26..17ba98759126 100644 --- a/back/spec/models/phase_spec.rb +++ b/back/spec/models/phase_spec.rb @@ -311,9 +311,9 @@ end describe 'native_survey_title_multiloc and native_survey_button_multiloc' do - [ - :native_survey_phase, - :community_monitor_survey_phase + %i[ + native_survey_phase + community_monitor_survey_phase ].each do |factory| context factory do let(:phase) { build(factory) } diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index ddf8fc7b711d..38089ffd6067 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -75,7 +75,7 @@ let(:user) { create(:user) } let(:phase) { create(:native_survey_phase) } - it 'includes native survey attributes' do + it 'includes survey attributes' do expect(result.dig(:data, :attributes).keys).to include( :native_survey_title_multiloc, :native_survey_button_multiloc @@ -83,4 +83,17 @@ expect(result.dig(:data, :attributes, :supports_survey_form)).to be true end end + + context 'for a community monitor phase' do + let(:user) { create(:user) } + let(:phase) { create(:community_monitor_survey_phase) } + + it 'includes survey attributes' do + expect(result.dig(:data, :attributes).keys).to include( + :native_survey_title_multiloc, + :native_survey_button_multiloc + ) + expect(result.dig(:data, :attributes, :supports_survey_form)).to be true + end + end end From 2e28c557e5279d3a455aedc7294df01d175bbbdd Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Thu, 27 Feb 2025 10:35:16 +0000 Subject: [PATCH 54/55] [TAN-3815] Rubocop fixes --- back/app/services/project_copy_service.rb | 4 ++-- back/spec/serializers/web_api/v1/phase_serializer_spec.rb | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/back/app/services/project_copy_service.rb b/back/app/services/project_copy_service.rb index 33171687111d..68a264672d87 100644 --- a/back/app/services/project_copy_service.rb +++ b/back/app/services/project_copy_service.rb @@ -243,7 +243,7 @@ def yml_projects(shift_timestamps: 0, new_slug: nil, new_title_multiloc: nil, ne } end, 'include_all_areas' => @project.include_all_areas, - 'hidden' => @project.hidden, + 'hidden' => @project.hidden } yml_project['slug'] = new_slug if new_slug.present? store_ref yml_project, @project.id, :project @@ -336,7 +336,7 @@ def yml_phases(shift_timestamps: 0, timeline_start_at: nil) yml_phase['document_annotation_embed_url'] = phase.document_annotation_embed_url end - if %w[native_survey community_monitor_survey].include? yml_phase['participation_method'] + if phase.pmethod.supports_survey_form? yml_phase['native_survey_title_multiloc'] = phase.native_survey_title_multiloc yml_phase['native_survey_button_multiloc'] = phase.native_survey_button_multiloc end diff --git a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb index 38089ffd6067..40f713472c7e 100644 --- a/back/spec/serializers/web_api/v1/phase_serializer_spec.rb +++ b/back/spec/serializers/web_api/v1/phase_serializer_spec.rb @@ -90,9 +90,9 @@ it 'includes survey attributes' do expect(result.dig(:data, :attributes).keys).to include( - :native_survey_title_multiloc, - :native_survey_button_multiloc - ) + :native_survey_title_multiloc, + :native_survey_button_multiloc + ) expect(result.dig(:data, :attributes, :supports_survey_form)).to be true end end From 4f753907535d9db4e449be8dbfb42f74ddd9a9cf Mon Sep 17 00:00:00 2001 From: jamesspeake Date: Thu, 27 Feb 2025 10:54:53 +0000 Subject: [PATCH 55/55] [TAN-3815] Rerun migrations --- back/db/structure.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/back/db/structure.sql b/back/db/structure.sql index 3e1e850c9e00..31de48406c1c 100644 --- a/back/db/structure.sql +++ b/back/db/structure.sql @@ -1184,7 +1184,8 @@ CREATE TABLE public.projects ( votes_count integer DEFAULT 0 NOT NULL, followers_count integer DEFAULT 0 NOT NULL, preview_token character varying NOT NULL, - header_bg_alt_text_multiloc jsonb DEFAULT '{}'::jsonb + header_bg_alt_text_multiloc jsonb DEFAULT '{}'::jsonb, + hidden boolean DEFAULT false NOT NULL ); @@ -6827,6 +6828,7 @@ SET search_path TO public,shared_extensions; INSERT INTO "schema_migrations" (version) VALUES ('20250224150953'), +('20250219104523'), ('20250204143605'), ('20250120125531'), ('20250117121004'),