Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TAN-3815 - Add community monitor survey BE (OLD) #10313

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
44aec87
[TAN-3815] Added hidden community monitor project
jamesspeake Feb 11, 2025
1667276
[TAN-3815] Added native_survey_method column
jamesspeake Feb 11, 2025
7924cd0
[TAN-3815] Test fix
jamesspeake Feb 11, 2025
557125b
[TAN-3815] Added DB migration for native_survey_method
jamesspeake Feb 11, 2025
dfcf6b3
[TAN-3815] Fixed tests
jamesspeake Feb 11, 2025
d1d2b20
[TAN-3815] Added model tests for community_monitor phase
jamesspeake Feb 11, 2025
bc4f5b9
[TAN-3815] Rubocop fix
jamesspeake Feb 11, 2025
45860ab
[TAN-3815] Added feature flag
jamesspeake Feb 11, 2025
f76c55e
[TAN-3815] Added native_survey_method attribute to phase serializer
jamesspeake Feb 11, 2025
1508955
[TAN-3815] Added native_survey_method attribute to public API, projec…
jamesspeake Feb 11, 2025
faa7d76
[TAN-3815] Created native_survey_method classes
jamesspeake Feb 11, 2025
0e2e0b8
[TAN-3815] Fixed feature flag for community monitor phase
jamesspeake Feb 11, 2025
bc8cd55
[TAN-3815] Added different form and allowed types for community_monit…
jamesspeake Feb 11, 2025
e36b26e
[TAN-3815] Added multilocs to community monitor project creation
jamesspeake Feb 12, 2025
0cf1592
[TAN-3815] Added comment
jamesspeake Feb 12, 2025
e519018
Merge branch 'master' into TAN-3815-community-monitor-hidden-project
jamesspeake Feb 12, 2025
7b5bc0e
Merge branch 'master' into TAN-3815-community-monitor-hidden-project
jamesspeake Feb 13, 2025
c006832
[TAN-3815] Added form_builder_config attribute to phase
jamesspeake Feb 13, 2025
a0d5aa2
[TAN-3815] Removed hidden as a visible_to
jamesspeake Feb 14, 2025
87bd42d
[TAN-3815] Added constraints to community monitor fields
jamesspeake Feb 14, 2025
37a6773
[TAN-3815] Removed private attribute
jamesspeake Feb 14, 2025
bd2abdc
[TAN-3815] Added default project_id: nil to community monitor app config
jamesspeake Feb 14, 2025
c9b0d4d
[TAN-3815] Moved hidden attribute to admin publication
jamesspeake Feb 14, 2025
92de3fe
[TAN-3815] Removed form_builder_config from API
jamesspeake Feb 14, 2025
e6c65d8
[TAN-3815] Fixed tests
jamesspeake Feb 14, 2025
ef10676
Merge branch 'master' into TAN-3815-community-monitor-hidden-project
jamesspeake Feb 14, 2025
7af6681
[TAN-3815] Changed permissions for community monitor
jamesspeake Feb 17, 2025
f65673c
[TAN-3815] Added outline tests for native_survey_method
jamesspeake Feb 17, 2025
8e88d85
[TAN-3815] Added admin publication tests and fixed other tests
jamesspeake Feb 17, 2025
2aa1064
[TAN-3815] changed check for .present?
jamesspeake Feb 17, 2025
a173464
[TAN-3815] Fixed projects spec
jamesspeake Feb 17, 2025
4c529e8
[TAN-3815] Fixed tests
jamesspeake Feb 17, 2025
de312c4
[TAN-3815] Exclude native survey responses from creating idea followers
jamesspeake Feb 17, 2025
d5746cf
[TAN-3815] Exclude community monitor survey responses from creating p…
jamesspeake Feb 18, 2025
5893bd4
[TAN-3815] Exclude hidden projects from creating followers
jamesspeake Feb 18, 2025
dd5cc6e
[TAN-3815] Rubocop fix
jamesspeake Feb 18, 2025
0220682
Merge branch 'master' into TAN-3815-community-monitor-hidden-project
jamesspeake Feb 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions back/app/controllers/web_api/v1/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,22 @@ def destroy_participation_data
).serializable_hash, status: :ok
end

def community_monitor
settings = AppConfiguration.instance.settings
settings.dig('community_monitor', 'enabled') || raise(ActiveRecord::RecordNotFound)

# 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)

authorize project
render json: WebApi::V1::ProjectSerializer.new(
project,
params: jsonapi_serializer_params,
include: %i[current_phase]
).serializable_hash
end

private

def sidefx
Expand Down Expand Up @@ -375,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)
10 changes: 9 additions & 1 deletion back/app/models/admin_publication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -87,6 +91,10 @@ def published?
publication_status == 'published'
end

def hidden?
publication_status == 'hidden'
end

def ever_published?
first_published_at.present?
end
Expand Down
1 change: 1 addition & 0 deletions back/app/models/notifications/project_phase_started.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class ProjectPhaseStarted < Notification

def self.make_notifications_on(activity)
phase = activity.item
return [] unless phase.project.published?
Copy link
Contributor Author

@jamesspeake jamesspeake Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could have just used unless phase.project.hidden? for this use case but felt there was actually no need to notify on these unless the project was published - draft or archived has no real use


ProjectPolicy::InverseScope.new(phase.project, User.from_follows(phase.project.followers)).resolve.map do |recipient|
new(recipient: recipient, phase: phase, project: phase.project)
Expand Down
19 changes: 8 additions & 11 deletions back/app/models/notifications/project_phase_upcoming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions back/app/models/phase.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -184,8 +185,10 @@ 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 }
validate :validate_community_monitor_phase
end

scope :published, lambda {
Expand Down Expand Up @@ -343,6 +346,22 @@ def validate_no_other_overlapping_phases
end
end

def validate_community_monitor_phase
return unless native_survey_method == 'community_monitor'

if project.phases.count > 1
errors.add(:native_survey_method, :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')
end

if 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
Expand Down
4 changes: 2 additions & 2 deletions back/app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion back/app/policies/admin_publication_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.not_hidden.where(publication: scope_for(klass)) } # scope per publication type
.reduce(&:or) # joining partial scopes
end
end
Expand Down
4 changes: 4 additions & 0 deletions back/app/policies/project_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ def active_moderator?
UserRoleService.new.can_moderate_project? record, user
end

def community_monitor?
active_moderator?
end

private

def update_status?
Expand Down
2 changes: 1 addition & 1 deletion back/app/serializers/web_api/v1/phase_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
1 change: 1 addition & 0 deletions back/app/services/project_copy_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion back/app/services/side_fx_idea_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,19 @@ def scrape_facebook(idea)
end

def create_followers(idea, user)
Follower.find_or_create_by(followable: idea, user: 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)
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.follow_idea_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
Expand Down
1 change: 1 addition & 0 deletions back/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: >
Expand Down
1 change: 1 addition & 0 deletions back/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
26 changes: 26 additions & 0 deletions back/config/schemas/settings.schema.json.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,32 @@
}
}
}
},

"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"
}
}
}
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
4 changes: 3 additions & 1 deletion back/db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
);


Expand Down Expand Up @@ -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'),
Expand Down
1 change: 1 addition & 0 deletions back/engines/commercial/multi_tenancy/db/seeds/projects.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
)
Expand Down
5 changes: 5 additions & 0 deletions back/engines/commercial/multi_tenancy/db/seeds/tenants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,11 @@ def create_localhost_tenant
platform_templates: {
enabled: false,
allowed: false
},
community_monitor: {
enabled: true,
allowed: true,
project_id: ''
}
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions back/lib/factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading