Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 16 additions & 11 deletions app/models/queries/work_packages/filter/typeahead_filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,21 @@ def where
parts.map { |part| "(#{conditions_for(part).join(' OR ')})" }.join(" AND ")
end

def conditions_for(part)
def conditions_for(part) # rubocop:disable Metrics/AbcSize
conditions = [subject_condition(part),
project_name_condition(part),
work_package_identifier_condition(part),
type_name_condition(part),
status_condition(part)]

if (match = part.match(/\A(#)?(#{Projects::Identifier::SEMANTIC_FORMAT.source}-?\d*)\z/i))
conditions << work_package_identifier_condition(hash_prefixed: match[1].present?, search_term: match[2])
end

if (match = part.match(/\A(#)?(\d+)\z/))
conditions << id_or_sequence_number_condition(hash_prefixed: match[1].present?, search_term: match[2])
end

conditions
conditions.compact
end

def subject_condition(string)
Expand All @@ -66,14 +69,6 @@ def project_name_condition(string)
Queries::Operators::Contains.sql_for_field([string], Project.table_name, "name")
end

def work_package_identifier_condition(string)
alias_condition = Queries::Operators::StartsWith.sql_for_field(
[string], WorkPackageSemanticAlias.table_name, "identifier"
)
"#{WorkPackage.table_name}.id IN " \
"(SELECT work_package_id FROM #{WorkPackageSemanticAlias.table_name} WHERE #{alias_condition})"
end

def type_name_condition(string)
Queries::Operators::Contains.sql_for_field([string], Type.table_name, "name")
end
Expand All @@ -96,6 +91,16 @@ def status_condition(string)
end
end

def work_package_identifier_condition(hash_prefixed:, search_term:)
return unless Setting::WorkPackageIdentifier.semantic? || hash_prefixed

alias_condition = Queries::Operators::StartsWith.sql_for_field(
[search_term], WorkPackageSemanticAlias.table_name, "identifier"
)
"#{WorkPackage.table_name}.id IN " \
"(SELECT work_package_id FROM #{WorkPackageSemanticAlias.table_name} WHERE #{alias_condition})"
end

def id_or_sequence_number_condition(hash_prefixed:, search_term:)
if Setting::WorkPackageIdentifier.classic? || hash_prefixed
id_condition(search_term)
Expand Down
25 changes: 16 additions & 9 deletions app/models/queries/work_packages/selects/exact_match_select.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,13 @@ def self.exact_match_condition_sql(query_string)
stripped = query_string.to_s.strip
return nil if stripped.blank? || stripped.match?(/\s/)

hash_prefixed = stripped.start_with?("#")
candidate = stripped.delete_prefix("#")
condition =
if candidate.match?(/\A[1-9]\d*\z/)
numeric_exact_match_condition(candidate, hash_prefixed: stripped.start_with?("#"))
elsif stripped.match?(/\A#{WorkPackage::SemanticIdentifier::SEMANTIC_ID_PATTERN.source}\z/i)
# So far, semantic identifiers are always upper case.
# We can leverage this to match in a way that allows index usage.
OpenProject::SqlSanitization.sanitize(
"#{WorkPackage.table_name}.id IN (SELECT work_package_id FROM " \
"#{WorkPackageSemanticAlias.table_name} WHERE identifier = ?)",
stripped.upcase
)
numeric_exact_match_condition(candidate, hash_prefixed:)
elsif candidate.match?(/\A#{WorkPackage::SemanticIdentifier::SEMANTIC_ID_PATTERN.source}\z/i)
semantic_exact_match_condition(candidate, hash_prefixed:)
end

return nil unless condition
Expand All @@ -95,4 +90,16 @@ def self.numeric_exact_match_condition(candidate, hash_prefixed:)
)
end
end

def self.semantic_exact_match_condition(candidate, hash_prefixed:)
return nil unless Setting::WorkPackageIdentifier.semantic? || hash_prefixed

# So far, semantic identifiers are always upper case.
# We can leverage this to match in a way that allows index usage.
OpenProject::SqlSanitization.sanitize(
"#{WorkPackage.table_name}.id IN (SELECT work_package_id FROM " \
"#{WorkPackageSemanticAlias.table_name} WHERE identifier = ?)",
candidate.upcase
)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
page.find(".menu-item", text: "Add existing").click

dropdown = search_autocomplete(page.find("ng-select.wp-inline-create--reference-autocompleter"),
query: "BOARDRANK-5",
query: "#BOARDRANK-5",
results_selector: "body")

within(dropdown) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@

it "shows the exact identifier match first in the dropdown, despite being older" do
dropdown = search_autocomplete(page.find_by_id("values-id"),
query: "IDFILTER-5",
query: "#IDFILTER-5",
results_selector: "body")

within(dropdown) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
filters.add_filter("Parent")

dropdown = search_autocomplete(page.find_by_id("values-parent"),
query: "PARENTFILTER-5",
query: "#PARENTFILTER-5",
results_selector: "body")

within(dropdown) do
Expand All @@ -87,7 +87,7 @@
filters.add_filter("Descendants of")

dropdown = search_autocomplete(page.find_by_id("values-ancestor"),
query: "PARENTFILTER-5",
query: "#PARENTFILTER-5",
results_selector: "body")

within(dropdown) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
field = wp_table.edit_field(child_work_package, :parent)
field.activate!

dropdown = field.autocomplete("PARENTFIELD-5", select: false)
dropdown = field.autocomplete("#PARENTFIELD-5", select: false)

within(dropdown) do
# The first option is always a "-" (clear/no-parent) entry, unrelated to ranking.
Expand Down
22 changes: 20 additions & 2 deletions spec/models/queries/work_packages/filter/typeahead_filter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,15 @@
end
end

context "when searching by work package identifier prefixed with a hash" do
let(:values) { ["##{identifier_work_package1.identifier}"] }

it "returns work packages with matching identifier" do
expect(subject).to include(identifier_work_package1)
expect(subject).not_to include(identifier_work_package2)
end
end

context "when searching for partial identifier" do
let(:values) { [project.identifier] }

Expand Down Expand Up @@ -343,14 +352,23 @@
identifier: "PHO-2")
end

context "and there are still entries in the semantic alias registry" do
let(:values) { [identifier_work_package1_semantic_alias.identifier] }
context "when searching by a semantic identifier prefixed with '#'" do
let(:values) { ["##{identifier_work_package1_semantic_alias.identifier}"] }

it "still finds by existing identifiers" do
expect(subject).to include(identifier_work_package1)
expect(subject).not_to include(identifier_work_package2)
end
end

context "when searching by a semantic identifier not prefixed with '#'" do
let(:values) { [identifier_work_package1_semantic_alias.identifier] }

it "does not find by existing identifiers" do
expect(subject).not_to include(identifier_work_package1)
expect(subject).not_to include(identifier_work_package2)
end
end
end

context "when searching by status" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,25 +121,46 @@ def ranked_ids(ids)
end
let(:query_string) { "COM-5" }

it "ranks the exact identifier match above one that only shares the prefix" do
expect(ranked_ids([exact_work_package.id, prefix_work_package.id]).first)
.to eq(exact_work_package.id)
end

context "when the query is given in lower case" do
let(:query_string) { "com-5" }

it "still matches COM-5" do
context "and the instance is in semantic identifier mode",
with_settings: { work_packages_identifier: Setting::WorkPackageIdentifier::SEMANTIC } do
it "ranks the exact identifier match above one that only shares the prefix" do
expect(ranked_ids([exact_work_package.id, prefix_work_package.id]).first)
.to eq(exact_work_package.id)
end

context "when the query is given in lower case" do
let(:query_string) { "com-5" }

it "still matches COM-5" do
expect(ranked_ids([exact_work_package.id, prefix_work_package.id]).first)
.to eq(exact_work_package.id)
end
end

context "when the query is hash-prefixed" do
let(:query_string) { "#COM-5" }

it "still ranks the exact identifier match above one that only shares the prefix" do
expect(ranked_ids([exact_work_package.id, prefix_work_package.id]).first)
.to eq(exact_work_package.id)
end
end
end

context "when matching a historical alias in classic mode",
with_settings: { work_packages_identifier: Setting::WorkPackageIdentifier::CLASSIC } do
it "still matches through the alias table" do
expect(ranked_ids([exact_work_package.id, prefix_work_package.id]).first)
.to eq(exact_work_package.id)
context "and the instance is in classic identifier mode" do
context "when the query is hash-prefixed" do
let(:query_string) { "#COM-5" }

it "still ranks the exact identifier match above one that only shares the prefix" do
expect(ranked_ids([exact_work_package.id, prefix_work_package.id]).first)
.to eq(exact_work_package.id)
end
end

context "when the query is not hash-prefixed" do
let(:query_string) { "COM-5" }

it { is_expected.to be_nil }
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/models/query/sort_criteria_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
context "when a typeahead filter is active on the query" do
let(:query) do
build_stubbed(:query, show_hierarchies: false).tap do |q|
q.add_filter(:typeahead, "**", "COM-5")
q.add_filter(:typeahead, "**", "#COM-5")
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def work_packages

describe "relation candidates for wp1 (in hierarchy) with exact_match sorting" do
let(:href) do
"/api/v3/work_packages/#{wp1.id}/available_relation_candidates?query=RELCAND-5" \
"/api/v3/work_packages/#{wp1.id}/available_relation_candidates?query=%23RELCAND-5" \
"&sortBy=[[\"exactMatch\",\"desc\"],[\"updatedAt\",\"desc\"]]"
end

Expand Down
2 changes: 1 addition & 1 deletion spec/requests/api/v3/work_packages/index_resource_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
{
typeahead: {
operator: "**",
values: "COM-5"
values: "#COM-5"
}
}
]
Expand Down
Loading