mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Add methods to resolve the user filter to users
This commit is contained in:
@@ -40,6 +40,7 @@ module ResourceAllocations
|
||||
attribute :end_date
|
||||
attribute :allocated_time
|
||||
attribute :user_filter
|
||||
attribute :filter_name
|
||||
|
||||
validate :user_allowed_to_allocate
|
||||
|
||||
|
||||
@@ -94,6 +94,14 @@ class ResourceAllocation < ApplicationRecord
|
||||
principal_id.present?
|
||||
end
|
||||
|
||||
def candidate_query
|
||||
UserQuery.new.tap do |query|
|
||||
user_filter.each do |filter|
|
||||
query.where(filter.field, filter.operator, filter.values)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def allocated_hours
|
||||
return if allocated_time.nil?
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ FactoryBot.define do
|
||||
|
||||
trait :with_user_filter do
|
||||
principal { nil }
|
||||
filter_name { "Full stack Developer (DE-EN)" }
|
||||
transient do
|
||||
job_title_custom_field do
|
||||
UserCustomField.find_by(name: "Job title") ||
|
||||
@@ -51,17 +52,28 @@ FactoryBot.define do
|
||||
name: "Job title",
|
||||
possible_values: ["Developer", "Designer", "Project Manager", "Product Manager"])
|
||||
end
|
||||
spoken_language_custom_field do
|
||||
UserCustomField.find_by(name: "Spoken language") ||
|
||||
create(:user_custom_field, :list,
|
||||
name: "Spoken language",
|
||||
multi_value: true,
|
||||
possible_values: %w[German English French Spanish Italian Dutch Portuguese Polish])
|
||||
end
|
||||
end
|
||||
# Build real UserQuery filter objects (not hashes): the serialization
|
||||
# coder dumps via `filter.field`, so it only accepts filter instances.
|
||||
# The filter matches developers who speak German or English ("DE-EN"),
|
||||
# leaving the other languages as non-matching values to test against.
|
||||
user_filter do
|
||||
cf = job_title_custom_field
|
||||
developer_option = cf.custom_options.find_by(value: "Developer")
|
||||
[
|
||||
{
|
||||
"attribute" => cf.column_name,
|
||||
"operator" => "=",
|
||||
"values" => [developer_option.id.to_s]
|
||||
}
|
||||
]
|
||||
job_title = job_title_custom_field
|
||||
language = spoken_language_custom_field
|
||||
developer_option = job_title.custom_options.find_by(value: "Developer")
|
||||
language_options = language.custom_options.where(value: %w[German English])
|
||||
|
||||
query = UserQuery.new
|
||||
query.where(job_title.column_name, "=", [developer_option.id.to_s])
|
||||
query.where(language.column_name, "=", language_options.map { |option| option.id.to_s })
|
||||
query.filters
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -379,5 +379,77 @@ RSpec.describe ResourceAllocation do
|
||||
allocation = create(:resource_allocation, entity: work_package, principal: owner)
|
||||
expect(allocation.reload.user_filter).to eq([])
|
||||
end
|
||||
|
||||
it "round-trips the custom-field filters from the :with_user_filter trait" do
|
||||
allocation = create(:resource_allocation, :with_user_filter, entity: work_package)
|
||||
|
||||
filters = allocation.reload.user_filter
|
||||
expect(filters.size).to eq(2)
|
||||
|
||||
job_title = UserCustomField.find_by(name: "Job title")
|
||||
language = UserCustomField.find_by(name: "Spoken language")
|
||||
|
||||
job_title_filter = filters.find { |f| f.name.to_s == job_title.column_name }
|
||||
language_filter = filters.find { |f| f.name.to_s == language.column_name }
|
||||
|
||||
expect(job_title_filter.operator).to eq("=")
|
||||
expect(job_title_filter.values).to eq(job_title.custom_options.where(value: "Developer").pluck(:id).map(&:to_s))
|
||||
|
||||
# "is (OR)" — matches users speaking German or English.
|
||||
expect(language_filter.operator).to eq("=")
|
||||
expect(language_filter.values)
|
||||
.to match_array(language.custom_options.where(value: %w[German English]).pluck(:id).map(&:to_s))
|
||||
end
|
||||
end
|
||||
|
||||
describe "matching users with the :with_user_filter criteria" do
|
||||
shared_let(:project) { create(:project, enabled_module_names: %w[resource_management]) }
|
||||
shared_let(:work_package) { create(:work_package, project:) }
|
||||
|
||||
# Materializes the "Job title" and "Spoken language" custom fields and the
|
||||
# Developer + (German OR English) filter.
|
||||
shared_let(:allocation) { create(:resource_allocation, :with_user_filter, entity: work_package) }
|
||||
shared_let(:job_title) { UserCustomField.find_by(name: "Job title") }
|
||||
shared_let(:language) { UserCustomField.find_by(name: "Spoken language") }
|
||||
|
||||
def option_id(custom_field, value)
|
||||
custom_field.custom_options.find_by(value:).id
|
||||
end
|
||||
|
||||
def user_with(job_title_value, *languages)
|
||||
create(:user).tap do |user|
|
||||
user.custom_field_values = {
|
||||
job_title.id => option_id(job_title, job_title_value),
|
||||
language.id => languages.map { |spoken| option_id(language, spoken) }
|
||||
}
|
||||
user.save!(validate: false)
|
||||
end
|
||||
end
|
||||
|
||||
shared_let(:german_developer) { user_with("Developer", "German") }
|
||||
shared_let(:english_developer) { user_with("Developer", "English") }
|
||||
shared_let(:bilingual_developer) { user_with("Developer", "French", "English") }
|
||||
shared_let(:french_developer) { user_with("Developer", "French") }
|
||||
shared_let(:german_designer) { user_with("Designer", "German") }
|
||||
|
||||
describe "#candidate_query" do
|
||||
# `UserQuery#results` is scoped to what the current user may see.
|
||||
current_user { create(:admin) }
|
||||
|
||||
it "is a UserQuery carrying the stored filter criteria" do
|
||||
query = allocation.candidate_query
|
||||
|
||||
expect(query).to be_a(UserQuery)
|
||||
expect(query.filters.map { |f| f.name.to_s })
|
||||
.to contain_exactly(job_title.column_name, language.column_name)
|
||||
end
|
||||
|
||||
it "resolves to developers speaking German or English (is (OR)), and excludes the rest" do
|
||||
results = allocation.candidate_query.results
|
||||
|
||||
expect(results).to include(german_developer, english_developer, bilingual_developer)
|
||||
expect(results).not_to include(french_developer, german_designer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user