From 6850f47d9859c6c107b697bfe1d1f8d3ee0986c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Wed, 7 Aug 2024 12:20:01 +0200 Subject: [PATCH 01/13] initial version for automated status transition logic --- back/app/services/input_status_service.rb | 56 +++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index cb66e698303e..8290bcd1ca62 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -1,12 +1,52 @@ # frozen_string_literal: true class InputStatusService + AUTOMATED_TRANSITIONS = { + 'proposed' => { + :threshold_reached => 'threshold_reached', + :expired => 'expired', + } + } attr_reader :input_status def initialize(input_status) @input_status = input_status end + AUTOMATED_TRANSITIONS = { + 'proposed' => [ + 'threshold_reached', + 'expired' + ] + } + + def self.automated_transitions! + AUTOMATED_TRANSITIONS.each do |code_from, transitions| + inputs_from = Idea.where(idea_status: { code: code_from }) # Only published inputs? + transitions.each do |code_to| + inputs_to = case code_to + when 'threshold_reached' + filter_threshold_reached(inputs_from) + when 'expired' + filter_expired_scope(inputs_from) + else + inputs_from.none + end + inputs_to.each do |input| + status_to = IdeaStatus.find_by(code: code_to, participation_method: input.participation_method_on_creation.class.method_str) + input.update!(idea_status: status_to) + LogActivityJob.perform_later( + input, + 'changed_input_status', + nil, + Time.zone.now.to_i, + payload: { input_status_from_code: code_from, input_status_to_code: code_to } + ) + end + end + end + end + def can_transition_manually? case input_status.participation_method when 'ideation' @@ -21,4 +61,20 @@ def can_transition_manually? def can_reorder? !input_status.automatic? end + + private + + private_class_method def self.filter_threshold_reached(inputs_from) + inputs_to = inputs_from.none + group_by_consultation_context_todo(inputs_from) do |inputs, consultation_context| + next if !consultation_context.pmethod.supports_input_status_todo?(code_to) + + inputs_to = inputs_to.or(inputs.where('likes_count >= ?', consultation_context.reacting_threshold_todo)) + end + inputs_to + end + + private_class_method def self.filter_expired_scope(inputs_from) + inputs_from.none + end end From 780dae3bf9b62d5e69d0a07db9087131abb6d108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Wed, 7 Aug 2024 17:19:41 +0200 Subject: [PATCH 02/13] spec + fixes for auto transition to threshold reached + introduced consultation context --- back/app/services/input_status_service.rb | 8 ++--- back/app/services/side_fx_reaction_service.rb | 2 ++ back/lib/consultation_context.rb | 32 +++++++++++++++++++ back/spec/acceptance/idea_reactions_spec.rb | 15 +++++++++ .../acceptance/initiative_reactions_spec.rb | 1 + back/spec/factories/phases.rb | 2 ++ 6 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 back/lib/consultation_context.rb diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index 8290bcd1ca62..5b95ac302634 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -22,7 +22,7 @@ def initialize(input_status) def self.automated_transitions! AUTOMATED_TRANSITIONS.each do |code_from, transitions| - inputs_from = Idea.where(idea_status: { code: code_from }) # Only published inputs? + inputs_from = Idea.published.includes(:idea_status).where(idea_status: { code: code_from }) transitions.each do |code_to| inputs_to = case code_to when 'threshold_reached' @@ -66,10 +66,10 @@ def can_reorder? private_class_method def self.filter_threshold_reached(inputs_from) inputs_to = inputs_from.none - group_by_consultation_context_todo(inputs_from) do |inputs, consultation_context| - next if !consultation_context.pmethod.supports_input_status_todo?(code_to) + ConsultationContext.grouped_inputs(inputs_from).each do |consultation_context, inputs| + next if !consultation_context.supports_automated_statuses? - inputs_to = inputs_to.or(inputs.where('likes_count >= ?', consultation_context.reacting_threshold_todo)) + inputs_to = inputs_to.or(inputs.where('likes_count >= ?', consultation_context.reacting_threshold)) end inputs_to end diff --git a/back/app/services/side_fx_reaction_service.rb b/back/app/services/side_fx_reaction_service.rb index f5693c388de1..2a7b7f791c73 100644 --- a/back/app/services/side_fx_reaction_service.rb +++ b/back/app/services/side_fx_reaction_service.rb @@ -4,6 +4,8 @@ class SideFxReactionService include SideFxHelper def after_create(reaction, current_user) + InputStatusService.automated_transitions! + if reaction.reactable_type == 'Initiative' AutomatedTransitionJob.perform_now diff --git a/back/lib/consultation_context.rb b/back/lib/consultation_context.rb new file mode 100644 index 000000000000..f296c4e37994 --- /dev/null +++ b/back/lib/consultation_context.rb @@ -0,0 +1,32 @@ +class ConsultationContext + attr_reader :context + + def self.grouped_inputs(inputs) + transitive_inputs = inputs.transitive + nontransitive_inputs = inputs.where.not(id: transitive_inputs) + result = {} + transitive_inputs.pluck(:project_id).uniq.each do |project_id| + result[new(Project.find(project_id))] = transitive_inputs.where(project_id: project_id) + end + nontransitive_inputs.pluck(:creation_phase_id).uniq.each do |phase_id| + result[new(Phase.find(phase_id))] = nontransitive_inputs.where(creation_phase_id: phase_id) + end + result + end + + def initialize(context) + @context = context + + if [Phase, Project].exclude?(context.class) + raise ClErrors::AssertionError, "Context class #{context.class} is not supported" + end + end + + def supports_automated_statuses? + context.pmethod.supports_automated_statuses? + end + + def reacting_threshold + context.reacting_threshold if context.respond_to?(:reacting_threshold) + end +end diff --git a/back/spec/acceptance/idea_reactions_spec.rb b/back/spec/acceptance/idea_reactions_spec.rb index 1ee6b8e7712b..ea689bef43d8 100644 --- a/back/spec/acceptance/idea_reactions_spec.rb +++ b/back/spec/acceptance/idea_reactions_spec.rb @@ -76,6 +76,21 @@ assert_status 422 end end + + describe do + let!(:status_threshold_reached) { create(:proposals_status, code: 'threshold_reached') } + let(:phase) { create(:proposals_phase, reacting_threshold: 3) } + let(:proposal) { create(:proposal, idea_status: create(:proposals_status, code: 'proposed'), creation_phase: phase, project: phase.project, phases: [phase]) } + let(:idea_id) { proposal.id } + + example 'Reaching the voting threshold immediately triggers status change', document: false do + create_list(:reaction, 2, reactable: proposal, mode: 'up') + + do_request + assert_status 201 + expect(proposal.reload.idea_status).to eq status_threshold_reached + end + end end post 'web_api/v1/ideas/:idea_id/reactions/up' do diff --git a/back/spec/acceptance/initiative_reactions_spec.rb b/back/spec/acceptance/initiative_reactions_spec.rb index 931d43cefd03..22a9efb62ebe 100644 --- a/back/spec/acceptance/initiative_reactions_spec.rb +++ b/back/spec/acceptance/initiative_reactions_spec.rb @@ -68,6 +68,7 @@ expect(@initiative.reload.likes_count).to eq 3 end + # TODO: cleanup-after-proposals-migration example 'Reaching the voting threshold immediately triggers status change', document: false do settings = AppConfiguration.instance.settings settings['initiatives']['reacting_threshold'] = 3 diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index 20d07366d052..71ade9da0cea 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -65,6 +65,8 @@ factory :proposals_phase do participation_method { 'proposals' } + start_at { Date.today - 7.days } + end_at { Date.today + 7.days } end factory :poll_phase do From 8a0853e51ef8a24ffca2770599cb8de13c4a6d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Wed, 7 Aug 2024 18:20:18 +0200 Subject: [PATCH 03/13] added todo --- back/app/services/input_status_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index 5b95ac302634..8ac0419635e4 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -67,6 +67,7 @@ def can_reorder? private_class_method def self.filter_threshold_reached(inputs_from) inputs_to = inputs_from.none ConsultationContext.grouped_inputs(inputs_from).each do |consultation_context, inputs| + # TODO: Filter out inputs in inactive contexts? next if !consultation_context.supports_automated_statuses? inputs_to = inputs_to.or(inputs.where('likes_count >= ?', consultation_context.reacting_threshold)) From 845f0bd0179c58f599bd0f255c98ac44ab731e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Thu, 8 Aug 2024 16:34:14 +0200 Subject: [PATCH 04/13] fix bug with allowed codes --- .../Admin/settings/statuses/components/IdeaStatusForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/app/containers/Admin/settings/statuses/components/IdeaStatusForm.tsx b/front/app/containers/Admin/settings/statuses/components/IdeaStatusForm.tsx index dd2681b2e676..3e1ca57f53a1 100644 --- a/front/app/containers/Admin/settings/statuses/components/IdeaStatusForm.tsx +++ b/front/app/containers/Admin/settings/statuses/components/IdeaStatusForm.tsx @@ -112,8 +112,8 @@ const IdeaStatusForm = ({ }; const codes = inputStatusCodes[variant]; - const allowedCodes = codes.filter((code) => - automatedInputStatusCodes.has(code) + const allowedCodes = codes.filter( + (code) => !automatedInputStatusCodes.has(code) ); return ( From e75ef19e29b9081dd7d2946f4c8264172b4ea282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Thu, 8 Aug 2024 16:42:20 +0200 Subject: [PATCH 05/13] fix offenses --- back/app/services/input_status_service.rb | 7 ++----- back/spec/factories/phases.rb | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index 8ac0419635e4..eb2b03e996d4 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -4,7 +4,7 @@ class InputStatusService AUTOMATED_TRANSITIONS = { 'proposed' => { :threshold_reached => 'threshold_reached', - :expired => 'expired', + :expired => 'expired' } } attr_reader :input_status @@ -14,10 +14,7 @@ def initialize(input_status) end AUTOMATED_TRANSITIONS = { - 'proposed' => [ - 'threshold_reached', - 'expired' - ] + 'proposed' => %w[threshold_reached expired] } def self.automated_transitions! diff --git a/back/spec/factories/phases.rb b/back/spec/factories/phases.rb index 71ade9da0cea..c767cde3a2bf 100644 --- a/back/spec/factories/phases.rb +++ b/back/spec/factories/phases.rb @@ -65,8 +65,8 @@ factory :proposals_phase do participation_method { 'proposals' } - start_at { Date.today - 7.days } - end_at { Date.today + 7.days } + start_at { Time.zone.today - 7.days } + end_at { Time.zone.today + 7.days } end factory :poll_phase do From 68729ffbc4175cdc5c2aa30b5beafd1fdc550693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Thu, 8 Aug 2024 18:50:50 +0200 Subject: [PATCH 06/13] reimplement auto status transitions: less generic: one method for one input, one hourly method --- back/app/models/idea.rb | 9 ++- back/app/services/input_status_service.rb | 71 ++++++++----------- back/app/services/side_fx_reaction_service.rb | 2 +- .../lib/tasks/core/hourly_jobs.rake | 1 + back/lib/consultation_context.rb | 32 --------- 5 files changed, 37 insertions(+), 78 deletions(-) delete mode 100644 back/lib/consultation_context.rb diff --git a/back/app/models/idea.rb b/back/app/models/idea.rb index 280896f88877..d86a36930591 100644 --- a/back/app/models/idea.rb +++ b/back/app/models/idea.rb @@ -183,9 +183,12 @@ def will_be_published? publication_status_change == %w[draft published] || publication_status_change == [nil, 'published'] end + def consultation_context + creation_phase || project + end + def custom_form - participation_context = creation_phase || project - participation_context.custom_form || CustomForm.new(participation_context: participation_context) + consultation_context.custom_form || CustomForm.new(participation_context: participation_context) end def input_term @@ -197,7 +200,7 @@ def input_term end def participation_method_on_creation - (creation_phase || project).pmethod + consultation_context.pmethod end private diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index eb2b03e996d4..92de07615640 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -2,48 +2,32 @@ class InputStatusService AUTOMATED_TRANSITIONS = { - 'proposed' => { - :threshold_reached => 'threshold_reached', - :expired => 'expired' - } + 'proposed' => %w[threshold_reached expired] } + attr_reader :input_status def initialize(input_status) @input_status = input_status end - AUTOMATED_TRANSITIONS = { - 'proposed' => %w[threshold_reached expired] - } - - def self.automated_transitions! - AUTOMATED_TRANSITIONS.each do |code_from, transitions| - inputs_from = Idea.published.includes(:idea_status).where(idea_status: { code: code_from }) - transitions.each do |code_to| - inputs_to = case code_to - when 'threshold_reached' - filter_threshold_reached(inputs_from) - when 'expired' - filter_expired_scope(inputs_from) - else - inputs_from.none - end - inputs_to.each do |input| - status_to = IdeaStatus.find_by(code: code_to, participation_method: input.participation_method_on_creation.class.method_str) - input.update!(idea_status: status_to) - LogActivityJob.perform_later( - input, - 'changed_input_status', - nil, - Time.zone.now.to_i, - payload: { input_status_from_code: code_from, input_status_to_code: code_to } - ) - end + def self.auto_transition_input!(input) + AUTOMATED_TRANSITIONS[input.idea_status.code]&.each do |code_to| + can_transition = case code_to + when 'threshold_reached' + threshold_reached_condition?(input) + else + false end + + apply_transition!(input, code_to) if can_transition end end + def self.auto_transition_hourly!(input) + # TODO: Implement this method + end + def can_transition_manually? case input_status.participation_method when 'ideation' @@ -61,18 +45,21 @@ def can_reorder? private - private_class_method def self.filter_threshold_reached(inputs_from) - inputs_to = inputs_from.none - ConsultationContext.grouped_inputs(inputs_from).each do |consultation_context, inputs| - # TODO: Filter out inputs in inactive contexts? - next if !consultation_context.supports_automated_statuses? - - inputs_to = inputs_to.or(inputs.where('likes_count >= ?', consultation_context.reacting_threshold)) - end - inputs_to + private_class_method def self.apply_transition!(input, code_to) + code_from = input.idea_status.code + status_to = IdeaStatus.find_by(code: code_to, participation_method: input.participation_method_on_creation.class.method_str) + input.update!(idea_status: status_to) + LogActivityJob.perform_later( + input, + 'changed_input_status', + nil, + Time.zone.now.to_i, + payload: { input_status_from_code: code_from, input_status_to_code: code_to } + ) end - private_class_method def self.filter_expired_scope(inputs_from) - inputs_from.none + private_class_method def self.threshold_reached_condition?(input) + threshold = input.consultation_context.try(:reacting_threshold) + threshold && input.likes_count >= threshold end end diff --git a/back/app/services/side_fx_reaction_service.rb b/back/app/services/side_fx_reaction_service.rb index 2a7b7f791c73..6a3b4c916435 100644 --- a/back/app/services/side_fx_reaction_service.rb +++ b/back/app/services/side_fx_reaction_service.rb @@ -4,7 +4,7 @@ class SideFxReactionService include SideFxHelper def after_create(reaction, current_user) - InputStatusService.automated_transitions! + InputStatusService.auto_transition_input!(reaction.reactable.reload) if reaction.reactable_type == 'Idea' if reaction.reactable_type == 'Initiative' AutomatedTransitionJob.perform_now diff --git a/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake b/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake index 8d28f68d69fb..e0baba88adda 100644 --- a/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake +++ b/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake @@ -6,6 +6,7 @@ namespace :cl2back do now = Time.zone.now Tenant.creation_finalized.each do |tenant| Apartment::Tenant.switch(tenant.schema_name) do + InputStatusService.auto_transition_hourly! # TODO: Run as background job AutomatedTransitionJob.perform_later CreatePeriodicActivitiesJob.perform_later now.to_i DeleteInvitesJob.perform_later diff --git a/back/lib/consultation_context.rb b/back/lib/consultation_context.rb deleted file mode 100644 index f296c4e37994..000000000000 --- a/back/lib/consultation_context.rb +++ /dev/null @@ -1,32 +0,0 @@ -class ConsultationContext - attr_reader :context - - def self.grouped_inputs(inputs) - transitive_inputs = inputs.transitive - nontransitive_inputs = inputs.where.not(id: transitive_inputs) - result = {} - transitive_inputs.pluck(:project_id).uniq.each do |project_id| - result[new(Project.find(project_id))] = transitive_inputs.where(project_id: project_id) - end - nontransitive_inputs.pluck(:creation_phase_id).uniq.each do |phase_id| - result[new(Phase.find(phase_id))] = nontransitive_inputs.where(creation_phase_id: phase_id) - end - result - end - - def initialize(context) - @context = context - - if [Phase, Project].exclude?(context.class) - raise ClErrors::AssertionError, "Context class #{context.class} is not supported" - end - end - - def supports_automated_statuses? - context.pmethod.supports_automated_statuses? - end - - def reacting_threshold - context.reacting_threshold if context.respond_to?(:reacting_threshold) - end -end From 95bd5f4c64b76f0785f656344f2bbbd2db5ee2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Thu, 8 Aug 2024 18:55:47 +0200 Subject: [PATCH 07/13] fix offense --- back/app/services/input_status_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index 92de07615640..4af02d24048b 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -19,7 +19,7 @@ def self.auto_transition_input!(input) else false end - + apply_transition!(input, code_to) if can_transition end end From 2c8f9d9e5eb238aafd3de5f61cdf5d96b701a744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Mon, 12 Aug 2024 14:41:17 +0200 Subject: [PATCH 08/13] automated transition to expired + spec --- back/app/services/input_status_service.rb | 25 +++++++++++++++++++++-- back/spec/acceptance/initiatives_spec.rb | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index 4af02d24048b..9f5cef1ace2a 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -24,8 +24,22 @@ def self.auto_transition_input!(input) end end - def self.auto_transition_hourly!(input) - # TODO: Implement this method + def self.auto_transition_hourly! + AUTOMATED_TRANSITIONS&.each do |code_from, codes_to| + inputs = Idea.includes(:idea_status).where(idea_status: { code: code_from }) + codes_to.each do |code_to| + inputs_to_transition = case code_to + when 'expired' + expired_scope(inputs) + else + inputs.none + end + + inputs_to_transition.each do |input| + apply_transition!(input, code_to) + end + end + end end def can_transition_manually? @@ -62,4 +76,11 @@ def can_reorder? threshold = input.consultation_context.try(:reacting_threshold) threshold && input.likes_count >= threshold end + + private_class_method def self.expired_scope(inputs, now = Time.zone.now) + inputs + .includes(:creation_phase) + .where.not(creation_phase: { expire_days_limit: nil }) + .where("published_at + creation_phase.expire_days_limit * interval '1 day' < ?", now) + end end diff --git a/back/spec/acceptance/initiatives_spec.rb b/back/spec/acceptance/initiatives_spec.rb index 53e9aa7b2fed..a10de82653b4 100644 --- a/back/spec/acceptance/initiatives_spec.rb +++ b/back/spec/acceptance/initiatives_spec.rb @@ -759,7 +759,7 @@ end end - # TODO: move-old-proposals-test + # TODO: cleanup-after-proposals-migration get 'web_api/v1/initiatives/:id/allowed_transitions' do before do admin_header_token From ddfeeae695d9932ce90fd611b889f0d274764357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Mon, 12 Aug 2024 14:45:19 +0200 Subject: [PATCH 09/13] auto_transition_hourly! in background job --- back/app/jobs/automated_transition_job.rb | 2 ++ back/app/services/input_status_service.rb | 2 ++ .../commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/back/app/jobs/automated_transition_job.rb b/back/app/jobs/automated_transition_job.rb index 1d194c583a41..a6d33cc88cc4 100644 --- a/back/app/jobs/automated_transition_job.rb +++ b/back/app/jobs/automated_transition_job.rb @@ -4,6 +4,8 @@ class AutomatedTransitionJob < ApplicationJob queue_as :default def run + InputStatusService.auto_transition_hourly! + return unless AppConfiguration.instance.feature_activated? 'initiatives' InitiativeStatusService.new.automated_transitions! diff --git a/back/app/services/input_status_service.rb b/back/app/services/input_status_service.rb index 9f5cef1ace2a..d19db4b3aec8 100644 --- a/back/app/services/input_status_service.rb +++ b/back/app/services/input_status_service.rb @@ -78,6 +78,8 @@ def can_reorder? end private_class_method def self.expired_scope(inputs, now = Time.zone.now) + # This code assumes that the consultation context corresponds to + # the creation phase, which is not yet the case for ideation. inputs .includes(:creation_phase) .where.not(creation_phase: { expire_days_limit: nil }) diff --git a/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake b/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake index e0baba88adda..8d28f68d69fb 100644 --- a/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake +++ b/back/engines/commercial/multi_tenancy/lib/tasks/core/hourly_jobs.rake @@ -6,7 +6,6 @@ namespace :cl2back do now = Time.zone.now Tenant.creation_finalized.each do |tenant| Apartment::Tenant.switch(tenant.schema_name) do - InputStatusService.auto_transition_hourly! # TODO: Run as background job AutomatedTransitionJob.perform_later CreatePeriodicActivitiesJob.perform_later now.to_i DeleteInvitesJob.perform_later From b2ed9c87a3305eb085a646e417b7df710543326c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Mon, 12 Aug 2024 14:57:50 +0200 Subject: [PATCH 10/13] added input status service spec --- back/spec/acceptance/idea_reactions_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/spec/acceptance/idea_reactions_spec.rb b/back/spec/acceptance/idea_reactions_spec.rb index ea689bef43d8..c305c812963d 100644 --- a/back/spec/acceptance/idea_reactions_spec.rb +++ b/back/spec/acceptance/idea_reactions_spec.rb @@ -79,7 +79,7 @@ describe do let!(:status_threshold_reached) { create(:proposals_status, code: 'threshold_reached') } - let(:phase) { create(:proposals_phase, reacting_threshold: 3) } + let(:phase) { create(:proposals_phase, reacting_threshold: 2) } let(:proposal) { create(:proposal, idea_status: create(:proposals_status, code: 'proposed'), creation_phase: phase, project: phase.project, phases: [phase]) } let(:idea_id) { proposal.id } From b541ad1e207c61e5ee2d3f3dddc499009397e995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Mon, 12 Aug 2024 14:58:18 +0200 Subject: [PATCH 11/13] added input status service spec --- .../services/input_status_service_spec.rb | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 back/spec/services/input_status_service_spec.rb diff --git a/back/spec/services/input_status_service_spec.rb b/back/spec/services/input_status_service_spec.rb new file mode 100644 index 000000000000..ed48f390b79c --- /dev/null +++ b/back/spec/services/input_status_service_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe InputStatusService do + describe 'automated transitions' do + before do + %w[proposed threshold_reached expired].each do |status_code| + create(:proposals_status, code: status_code) + end + end + + let(:phase) { create(:proposals_phase, reacting_threshold: 2, expire_days_limit: 20) } + let!(:proposal) { create(:proposal, idea_status: IdeaStatus.find_by(code: 'proposed'), creation_phase: phase, project: phase.project, phases: [phase], published_at: Time.now) } + + describe 'auto_transition_input!' do + it 'transitions when voting threshold was reached' do + create_list(:reaction, 3, reactable: proposal, mode: 'up') + + described_class.auto_transition_input!(proposal.reload) + + expect(proposal.reload.idea_status.code).to eq 'threshold_reached' + end + + it 'remains proposed if not expired nor threshold reached' do + create(:reaction, reactable: proposal, mode: 'up') + + travel_to(Time.now + 15.days) do + described_class.auto_transition_input!(proposal.reload) + expect(proposal.reload.idea_status.code).to eq 'proposed' + end + end + end + + describe 'auto_transition_hourly!' do + it 'transitions when expired' do + create(:idea) + travel_to(Time.now + 22.days) do + described_class.auto_transition_hourly! + expect(proposal.reload.idea_status.code).to eq 'expired' + end + end + + it 'remains proposed if not expired nor threshold reached' do + create(:reaction, reactable: proposal, mode: 'up') + + travel_to(Time.now + 19.days) do + described_class.auto_transition_hourly! + expect(proposal.reload.idea_status.code).to eq 'proposed' + end + end + end + end +end From d5146bbe8980fa571e4f7a2fb45cdbffdc609963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Mon, 12 Aug 2024 15:04:32 +0200 Subject: [PATCH 12/13] fix offenses --- back/spec/services/initiative_status_service_spec.rb | 2 +- back/spec/services/input_status_service_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/back/spec/services/initiative_status_service_spec.rb b/back/spec/services/initiative_status_service_spec.rb index a05837e9c89a..bfc0b4c74519 100644 --- a/back/spec/services/initiative_status_service_spec.rb +++ b/back/spec/services/initiative_status_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -# TODO: move-old-proposals-test +# TODO: cleanup-after-proposals-migration describe InitiativeStatusService do let(:service) { described_class.new } diff --git a/back/spec/services/input_status_service_spec.rb b/back/spec/services/input_status_service_spec.rb index ed48f390b79c..cd772c8f8779 100644 --- a/back/spec/services/input_status_service_spec.rb +++ b/back/spec/services/input_status_service_spec.rb @@ -16,15 +16,15 @@ describe 'auto_transition_input!' do it 'transitions when voting threshold was reached' do create_list(:reaction, 3, reactable: proposal, mode: 'up') - + described_class.auto_transition_input!(proposal.reload) - + expect(proposal.reload.idea_status.code).to eq 'threshold_reached' end it 'remains proposed if not expired nor threshold reached' do create(:reaction, reactable: proposal, mode: 'up') - + travel_to(Time.now + 15.days) do described_class.auto_transition_input!(proposal.reload) expect(proposal.reload.idea_status.code).to eq 'proposed' @@ -43,7 +43,7 @@ it 'remains proposed if not expired nor threshold reached' do create(:reaction, reactable: proposal, mode: 'up') - + travel_to(Time.now + 19.days) do described_class.auto_transition_hourly! expect(proposal.reload.idea_status.code).to eq 'proposed' From 52b22259a479ee449ef05efbd0d468d4eaf05d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Hoorens?= Date: Mon, 12 Aug 2024 15:06:39 +0200 Subject: [PATCH 13/13] fix undefined participation context --- back/app/models/idea.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/app/models/idea.rb b/back/app/models/idea.rb index d86a36930591..052edeedfe55 100644 --- a/back/app/models/idea.rb +++ b/back/app/models/idea.rb @@ -188,7 +188,7 @@ def consultation_context end def custom_form - consultation_context.custom_form || CustomForm.new(participation_context: participation_context) + consultation_context.custom_form || CustomForm.new(participation_context: consultation_context) end def input_term