@@ -34,41 +34,71 @@ class PreviewQuery
3434 Result = Data . define ( :projects_data , :total_count )
3535 DISPLAY_COUNT = 5
3636
37+ # Priority-ordered format rules for identifier classification.
38+ FORMAT_RULES = [
39+ [ :too_long , -> ( id , max ) { id . length > max } ] ,
40+ [ :numerical , -> ( id , _ ) { id . match? ( /\A \d +\z / ) } ] ,
41+ [ :starts_with_number , -> ( id , _ ) { id . match? ( /\A \d / ) } ] ,
42+ [ :special_characters , -> ( id , _ ) { id . match? ( /[^a-zA-Z0-9]/ ) } ] ,
43+ [ :not_uppercase , -> ( id , _ ) { id != id . upcase } ]
44+ ] . freeze
45+
3746 def call
38- total = problematic_scope . count
39- preview = problematic_scope
40- . select ( :id , :name , :identifier )
41- . limit ( DISPLAY_COUNT )
42- . to_a
43-
44- suggestions = WorkPackages ::IdentifierAutofix ::ProjectIdentifierSuggestionGenerator . call (
45- preview ,
46- exclude : reserved_identifiers | in_use_identifiers
47- )
47+ Result . new ( projects_data : build_projects_data , total_count : problematic_scope . count )
48+ end
49+
50+ private
4851
49- projects_data = suggestions . map do |entry |
52+ def build_projects_data
53+ generate_suggestions . map do |entry |
5054 entry . merge ( error_reason : error_reason ( entry [ :current_identifier ] ) )
5155 end
56+ end
5257
53- Result . new ( projects_data :, total_count : total )
58+ def generate_suggestions
59+ ProjectIdentifierSuggestionGenerator . call (
60+ preview_projects ,
61+ in_use_identifiers :,
62+ reserved_identifiers :
63+ )
5464 end
5565
56- private
66+ def preview_projects
67+ problematic_scope
68+ . select ( :id , :name , :identifier )
69+ . limit ( DISPLAY_COUNT )
70+ . to_a
71+ end
5772
73+ # Scope conditions must cover all identifiers classifiable by #error_reason.
5874 def problematic_scope
59- @problematic_scope ||= Project . where (
60- "length(identifier) > ? OR identifier ~ ?" ,
61- ProjectIdentifierSuggestionGenerator ::IDENTIFIER_LENGTH [ :max ] ,
62- "[^a-zA-Z0-9_]"
63- )
75+ @problematic_scope ||= exceeds_max_length
76+ . or ( contains_non_alphanumeric )
77+ . or ( starts_with_digit )
78+ . or ( not_fully_uppercased )
6479 end
6580
81+ def exceeds_max_length = Project . where ( "length(identifier) > ?" , max_identifier_length )
82+ def contains_non_alphanumeric = Project . where ( "identifier ~ ?" , "[^a-zA-Z0-9]" )
83+ def starts_with_digit = Project . where ( "identifier ~ ?" , "^[0-9]" )
84+ def not_fully_uppercased = Project . where ( "identifier != UPPER(identifier)" )
85+
86+ def max_identifier_length = ProjectIdentifierSuggestionGenerator ::IDENTIFIER_LENGTH [ :max ]
87+
88+ # Must handle all identifiers matched by #problematic_scope.
6689 def error_reason ( identifier )
67- if identifier . length > ProjectIdentifierSuggestionGenerator ::IDENTIFIER_LENGTH [ :max ]
68- :too_long
69- elsif identifier . match? ( /[^a-zA-Z0-9_]/ )
70- :special_characters
71- elsif in_use_identifiers . include? ( identifier )
90+ format_error_reason ( identifier ) || collision_error_reason ( identifier ) || :unknown
91+ end
92+
93+ def format_error_reason ( identifier )
94+ FORMAT_RULES . each do |reason , check |
95+ return reason if check . call ( identifier , max_identifier_length )
96+ end
97+ nil
98+ end
99+
100+ def collision_error_reason ( identifier )
101+ if in_use_identifiers . include? ( identifier )
72102 :in_use
73103 elsif reserved_identifiers . include? ( identifier )
74104 :reserved
0 commit comments