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-3625 Matrix field report builder group support #10200

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
77f54f9
added matrix_linear_scale and statements
sebastienhoorens Jan 17, 2025
97448a4
green pipeline + fix offenses
sebastienhoorens Jan 17, 2025
6c7c28d
factory for matrix field + statements
sebastienhoorens Jan 17, 2025
0673b11
fix generate_key tidying
sebastienhoorens Jan 17, 2025
426486a
fix offenses
sebastienhoorens Jan 17, 2025
d763e41
Merge branch 'TAN-3501-add-ranking-field' into TAN-3586-add-matrix-field
sebastienhoorens Jan 20, 2025
86ab7e2
statements -> matrix_statements
sebastienhoorens Jan 20, 2025
b0c24b7
statements -> matrix_statements
sebastienhoorens Jan 20, 2025
eca85d7
statements -> matrix_statements
sebastienhoorens Jan 20, 2025
69a63a4
first version update_all for matrix
sebastienhoorens Jan 20, 2025
bdca1f9
fix offenses
sebastienhoorens Jan 20, 2025
0f2c0e8
fix merge conflict
sebastienhoorens Jan 20, 2025
ef64c18
fix merge conflicts...
sebastienhoorens Jan 20, 2025
7cef983
matrix with linear scale labels
sebastienhoorens Jan 20, 2025
0075bce
statements sidefx
sebastienhoorens Jan 20, 2025
e461f6f
spec for inserting, updating and deleting statements
sebastienhoorens Jan 20, 2025
bf05d57
fix offenses
sebastienhoorens Jan 21, 2025
ed212c4
json schema for matrix field
sebastienhoorens Jan 21, 2025
a6ff3e7
json and ui schemas
sebastienhoorens Jan 21, 2025
22c6d04
fix offense
sebastienhoorens Jan 21, 2025
9eb003a
initial version for survey results
sebastienhoorens Jan 21, 2025
183a653
tidying matrix statements survey results
sebastienhoorens Jan 22, 2025
17c54e4
initial statements endpoint to get one
sebastienhoorens Jan 22, 2025
e143520
matrix statements endpoint: get one + index
sebastienhoorens Jan 22, 2025
eaa1b62
a bit more tidying: remove dead code and move private method
sebastienhoorens Jan 22, 2025
964f600
refactoring: stop passing around the group field
sebastienhoorens Jan 22, 2025
c23b8ed
towards grouped matrix results
sebastienhoorens Jan 22, 2025
9865cc1
xlsx for matrix fields: stuck on weird bug
sebastienhoorens Jan 22, 2025
62160f4
xlsx for matrix fields: << instead of += super weird
sebastienhoorens Jan 22, 2025
43555ee
spec for matrix fields in prompts + tidying: extract ranking_field_value
sebastienhoorens Jan 23, 2025
be2e5f0
implemented matrix_linear_scale_field_value and refactored input_fiel…
sebastienhoorens Jan 23, 2025
af17a36
Merge branch 'TAN-3501-add-ranking-field' into TAN-3586-add-matrix-field
sebastienhoorens Jan 23, 2025
38ec61a
green xlsx specs
sebastienhoorens Jan 23, 2025
18d7b66
fix offenses
sebastienhoorens Jan 23, 2025
ad6a830
disable form sync for matrix fields (for now) and forgot to substitut…
sebastienhoorens Jan 23, 2025
ef91326
forgot geojson_supported_fields
sebastienhoorens Jan 27, 2025
3d14eb2
Merge branch 'master' into TAN-3586-add-matrix-field
amanda-anderson Jan 28, 2025
cd15c7b
removed in progress group support for matrix
sebastienhoorens Jan 28, 2025
be0f19f
Revert "removed in progress group support for matrix"
sebastienhoorens Jan 28, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

class WebApi::V1::CustomFieldMatrixStatementsController < ApplicationController
before_action :set_statement, only: %i[show]
before_action :set_custom_field, only: %i[index]
skip_before_action :authenticate_user

def index
@statements = policy_scope(CustomFieldMatrixStatement).where(custom_field: @custom_field).order(:ordering)
render json: WebApi::V1::CustomFieldMatrixStatementSerializer.new(@statements, params: jsonapi_serializer_params).serializable_hash
end

def show
render json: WebApi::V1::CustomFieldMatrixStatementSerializer.new(@statement, params: jsonapi_serializer_params).serializable_hash
end

private

def set_custom_field
@custom_field = CustomField.find(params[:custom_field_id])
end

def set_statement
@statement = CustomFieldMatrixStatement.find(params[:id])
authorize @statement
end
end
14 changes: 12 additions & 2 deletions back/app/models/custom_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class CustomField < ApplicationRecord
acts_as_list column: :ordering, top_of_list: 0, scope: [:resource_id]

has_many :options, -> { order(:ordering) }, dependent: :destroy, class_name: 'CustomFieldOption', inverse_of: :custom_field
has_many :matrix_statements, -> { order(:ordering) }, dependent: :destroy, class_name: 'CustomFieldMatrixStatement', inverse_of: :custom_field
has_many :text_images, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :text_images

Expand All @@ -59,7 +60,7 @@ class CustomField < ApplicationRecord
INPUT_TYPES = %w[
checkbox date file_upload files html html_multiloc image_files linear_scale multiline_text multiline_text_multiloc
multiselect multiselect_image number page point line polygon select select_image shapefile_upload text text_multiloc
topic_ids section cosponsor_ids ranking
topic_ids section cosponsor_ids ranking matrix_linear_scale
].freeze
CODES = %w[
author_id birthyear body_multiloc budget domicile gender idea_files_attributes idea_images_attributes
Expand Down Expand Up @@ -89,6 +90,7 @@ class CustomField < ApplicationRecord
validates :minimum_select_count, comparison: { greater_than_or_equal_to: 0 }, if: :multiselect?, allow_nil: true
validates :page_layout, presence: true, inclusion: { in: PAGE_LAYOUTS }, if: :page?
validates :page_layout, absence: true, unless: :page?
# validates :matrix_statements, presence: true, if: :supports_matrix_statements?

before_validation :set_default_enabled
before_validation :set_default_answer_visible_to
Expand Down Expand Up @@ -125,6 +127,14 @@ def supports_xlsx_export?
%w[page section].exclude?(input_type)
end

def supports_linear_scale?
%w[linear_scale matrix_linear_scale].include?(input_type)
end

def supports_matrix_statements?
input_type == 'matrix_linear_scale'
end

def average_rankings(scope)
# This basically starts from all combinations of scope ID, option key (value)
# and position (ordinality) and then calculates the average position for each
Expand Down Expand Up @@ -338,7 +348,7 @@ def generate_key
title = title_multiloc.values.first
return unless title

self.key = CustomFieldService.new.generate_key(title, false) do |key_proposal|
self.key = CustomFieldService.new.generate_key(title) do |key_proposal|
self.class.find_by(key: key_proposal, resource_type: resource_type)
end
end
Expand Down
42 changes: 42 additions & 0 deletions back/app/models/custom_field_matrix_statement.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# == Schema Information
#
# Table name: custom_field_matrix_statements
#
# id :uuid not null, primary key
# custom_field_id :uuid not null
# title_multiloc :jsonb not null
# key :string not null
# ordering :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_custom_field_matrix_statements_on_custom_field_id (custom_field_id)
# index_custom_field_matrix_statements_on_key (key)
#
# Foreign Keys
#
# fk_rails_... (custom_field_id => custom_fields.id)
#
class CustomFieldMatrixStatement < ApplicationRecord
# non-persisted attribute to enable form copying
attribute :temp_id, :string, default: nil

belongs_to :custom_field

before_validation :generate_key, on: :create
acts_as_list column: :ordering, top_of_list: 0, scope: :custom_field

validates :title_multiloc, presence: true, multiloc: { presence: true }
validates :key, presence: true, uniqueness: { scope: [:custom_field_id] },
format: { with: /\A[\w-]+\z/ } # Can only consist of word characters or dashes
validates :custom_field, presence: true

private

def generate_key
title = title_multiloc.values.first
self.key ||= title && CustomFieldService.new.generate_key(title)
end
end
2 changes: 1 addition & 1 deletion back/app/models/custom_field_option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def generate_key
title = title_multiloc.values.first
return unless title

self.key = CustomFieldService.new.generate_key(title, other) do |key_proposal|
self.key = CustomFieldService.new.generate_key(title, other:) do |key_proposal|
self.class.find_by(key: key_proposal, custom_field: custom_field)
end
end
Expand Down
11 changes: 11 additions & 0 deletions back/app/policies/custom_field_matrix_statement_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CustomFieldMatrixStatementPolicy < ApplicationPolicy
class Scope < ApplicationPolicy::Scope
def resolve
scope
end
end

def show?
true
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class WebApi::V1::CustomFieldMatrixStatementSerializer < WebApi::V1::BaseSerializer
attributes :key, :title_multiloc, :ordering, :created_at, :updated_at

attribute :temp_id, if: proc { |object|
object.temp_id.present?
}
end
5 changes: 4 additions & 1 deletion back/app/serializers/web_api/v1/custom_field_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ class WebApi::V1::CustomFieldSerializer < WebApi::V1::BaseSerializer
:linear_scale_label_5_multiloc,
:linear_scale_label_6_multiloc,
:linear_scale_label_7_multiloc,
if: proc { |object, _params| object.linear_scale? }
if: proc { |object, _params| object.supports_linear_scale? }

attributes :select_count_enabled, :maximum_select_count, :minimum_select_count, if: proc { |object, _params|
object.multiselect?
}

has_many :options, record_type: :custom_field_option, serializer: ::WebApi::V1::CustomFieldOptionSerializer
has_many :matrix_statements, record_type: :custom_field_matrix_statement, serializer: ::WebApi::V1::CustomFieldMatrixStatementSerializer, if: proc { |field|
field.supports_matrix_statements?
}
has_one :resource, record_type: :custom_form, serializer: ::WebApi::V1::CustomFormSerializer
end

Expand Down
2 changes: 2 additions & 0 deletions back/app/services/custom_field_params_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ def supported_keys_in_custom_field_values(custom_field)
%i[id content name]
when 'html_multiloc', 'multiline_text_multiloc', 'text_multiloc'
CL2_SUPPORTED_LOCALES
when 'matrix_linear_scale'
custom_field.matrix_statements.pluck(:key).map(&:to_sym)
end
end

Expand Down
2 changes: 1 addition & 1 deletion back/app/services/custom_field_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def fields_to_ui_schema(fields, locale = 'en')
end
end

def generate_key(title, other)
def generate_key(title, other: false)
return 'other' if other == true

keyify(title)
Expand Down
2 changes: 1 addition & 1 deletion back/app/services/export/geojson/geojson_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(phase, field)
@phase = phase
@field = field
@inputs = phase.ideas.native_survey.published
@fields_in_form = IdeaCustomFieldsService.new(phase.custom_form).all_fields.filter(&:supports_xlsx_export?)
@fields_in_form = IdeaCustomFieldsService.new(phase.custom_form).geojson_supported_fields
@multiloc_service = MultilocService.new(app_configuration: @app_configuration)
@value_visitor = Geojson::ValueVisitor
end
Expand Down
13 changes: 13 additions & 0 deletions back/app/services/export/xlsx/input_sheet_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ def longitude_report_field
ComputedFieldForReport.new(column_header_for('longitude'), ->(input) { input.location_point&.coordinates&.first })
end

def matrix_statement_report_field(statement)
ComputedFieldForReport.new(
multiloc_service.t(statement.title_multiloc),
->(input) { input.custom_field_values.dig(statement.custom_field.key, statement.key) }
)
end

def created_at_report_field
ComputedFieldForReport.new(column_header_for('created_at'), ->(input) { input.created_at })
end
Expand Down Expand Up @@ -144,6 +151,12 @@ def input_report_fields
input_fields << latitude_report_field
input_fields << longitude_report_field
end
if field.input_type == 'matrix_linear_scale'
field.matrix_statements.each do |statement|
input_fields << matrix_statement_report_field(statement)
end
next
end
input_fields << Export::CustomFieldForExport.new(field, @value_visitor)
input_fields << Export::CustomFieldForExport.new(field.other_option_text_field, @value_visitor) if field.other_option_text_field
end
Expand Down
4 changes: 4 additions & 0 deletions back/app/services/field_visitor_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ def visit_linear_scale(field)
default(field)
end

def visit_matrix_linear_scale(field)
default(field)
end

def visit_file_upload(field)
default(field)
end
Expand Down
4 changes: 2 additions & 2 deletions back/app/services/idea_custom_fields_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ def submittable_fields_with_other_options

# Used in the printable PDF export
def printable_fields
ignore_field_types = %w[section page date files image_files point file_upload shapefile_upload topic_ids cosponsor_ids ranking]
ignore_field_types = %w[section page date files image_files point file_upload shapefile_upload topic_ids cosponsor_ids ranking matrix_linear_scale]
fields = enabled_fields.reject { |field| ignore_field_types.include? field.input_type }
insert_other_option_text_fields(fields)
end

def importable_fields
ignore_field_types = %w[page section date files image_files file_upload shapefile_upload point line polygon cosponsor_ids ranking]
ignore_field_types = %w[page section date files image_files file_upload shapefile_upload point line polygon cosponsor_ids ranking matrix_linear_scale]
enabled_fields_with_other_options.reject { |field| ignore_field_types.include? field.input_type }
end

Expand Down
15 changes: 15 additions & 0 deletions back/app/services/json_schema_generator_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,21 @@ def visit_shapefile_upload(field)
visit_file_upload(field)
end

def visit_matrix_linear_scale(field)
{
type: 'object',
minProperties: (field.required ? field.matrix_statement_ids.size : 0),
maxProperties: field.matrix_statement_ids.size,
properties: field.matrix_statements.pluck(:key).map(&:to_sym).index_with do
{
type: 'number',
minimum: 1,
maximum: field.maximum
}
end
}
end

private

attr_reader :locales, :multiloc_service
Expand Down
24 changes: 24 additions & 0 deletions back/app/services/side_fx_custom_field_matrix_statement_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

class SideFxCustomFieldMatrixStatementService
include SideFxHelper

def after_create(statement, current_user)
LogActivityJob.perform_later(statement, 'created', current_user, statement.created_at.to_i)
end

def after_update(statement, current_user)
LogActivityJob.perform_later(statement, 'changed', current_user, statement.updated_at.to_i)
end

def after_destroy(frozen_statement, current_user)
serialized_statement = clean_time_attributes(frozen_statement.attributes)
LogActivityJob.perform_later(
encode_frozen_resource(frozen_statement),
'deleted',
current_user,
Time.now.to_i,
payload: { custom_field_matrix_statement: serialized_statement }
)
end
end
Loading