Skip to content

Adds Organizer matching to ECV filters#532

Merged
Reetus merged 1 commit intomasterfrom
ecv-filter-match-organizer
Mar 14, 2026
Merged

Adds Organizer matching to ECV filters#532
Reetus merged 1 commit intomasterfrom
ecv-filter-match-organizer

Conversation

@Reetus
Copy link
Owner

@Reetus Reetus commented Nov 11, 2025

Adds a new filter option to the Entity Collection View (ECV) that allows filtering based on items present in specified Organizer profiles. This allows users to easily find items that match their Organizer rules.

Summary by CodeRabbit

  • New Features
    • Added "Organizer Match" constraint for entity collection filtering.
    • Enhanced UI to support filtering options with conditional visibility controls.
    • Extended property selection with observable collection support for constraint options.
    • Added localisation support for the new filter constraint across multiple language resources.

Adds a new filter option to the Entity Collection View (ECV)
that allows filtering based on items present in specified
Organizer profiles. This allows users to easily find items
that match their Organizer rules.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 2025

Walkthrough

The PR introduces an "Organizer Match" constraint to the Entity Collection Viewer filter system. It adds resource strings across multiple locales, extends the PropertyEntry model with an Options collection, implements conditional UI controls with visibility bindings, and adds constraint evaluation logic that queries OrganizerManager items.

Changes

Cohort / File(s) Summary
Resource strings and code generation
ClassicAssist.Shared/Resources/Strings.Designer.cs, ClassicAssist.Shared/Resources/Strings.resx, ClassicAssist.Shared/Resources/Strings.en-AU.resx, ClassicAssist.Shared/Resources/Strings.en-GB.resx
Added "Organizer Match" resource entry and public static property accessor. Removes BOM character from XML declarations in en-AU and en-GB resource files.
Data model
ClassicAssist/Data/Autoloot/PropertyEntry.cs
Added ObservableCollection<string> Options property with backing field and SetProperty-based getter/setter for constraint options.
UI control and bindings
ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterControl.xaml
Introduced NullToBooleanConverter for conditional visibility. Added ComboBox bound to Constraint.Options with visibility controlled by constraint option availability. Extended data template with visibility triggers to show/hide additional controls based on Constraint.Options state.
Filter logic
ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterViewModel.cs
Added Organizer_Match constraint with Equal/NotEqual operators. Predicate evaluates whether an item exists in a named organizer with matching ID and hue. Options list populated from OrganizerManager item names.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Filter UI
    participant ViewModel as FilterViewModel
    participant OrganizerMgr as OrganizerManager
    participant Item

    User->>UI: Select Organizer from dropdown
    UI->>ViewModel: Constraint with organizer name
    ViewModel->>OrganizerMgr: GetInstance().Items[organizer name]
    OrganizerMgr-->>ViewModel: OrganizerEntry
    ViewModel->>Item: Check if item ID matches
    alt Item found in organizer
        Item-->>ViewModel: true (if Hue matches)
        ViewModel-->>UI: Include in results
    else Item not found
        Item-->>ViewModel: false
        ViewModel-->>UI: Exclude from results
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • The constraint predicate logic in ViewModel requires verification of organizer lookup and hue matching logic
  • XAML visibility binding patterns with NullToBooleanConverter should be traced through the control hierarchy
  • Resource string additions are straightforward but occur across multiple files
  • PropertyEntry Options property integration should be verified against existing property binding patterns

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Adds Organizer matching to ECV filters' accurately and concisely summarizes the main change: introducing a new filter feature for matching Organizers in Entity Collection View.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ecv-filter-match-organizer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterControl.xaml (1)

279-297: Collapse Options ComboBox when empty list to avoid empty control

Currently hidden only when Options is null. If it’s an empty collection, users see an empty dropdown. Add a trigger for Count == 0.

Apply this XAML tweak:

 <ComboBox Grid.Column="0" MinWidth="40"
           ItemsSource="{Binding Constraint.Options}"
           SelectedItem="{Binding Additional}"
           Width="{Binding ActualWidth, Converter={StaticResource CellWidthValueConverter}, ConverterParameter=15, ElementName=GridViewColumn, Mode=OneWay}">
   <ComboBox.Style>
     <Style TargetType="{x:Type ComboBox}"
            BasedOn="{StaticResource {x:Type ComboBox}}">
       <Setter Property="Visibility" Value="Visible" />
       <Style.Triggers>
         <DataTrigger Binding="{Binding Constraint.Options}" Value="{x:Null}">
           <Setter Property="Visibility" Value="Collapsed" />
         </DataTrigger>
+        <DataTrigger Binding="{Binding Constraint.Options.Count}" Value="0">
+          <Setter Property="Visibility" Value="Collapsed" />
+        </DataTrigger>
       </Style.Triggers>
     </Style>
   </ComboBox.Style>
 </ComboBox>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f49b039 and 647d158.

📒 Files selected for processing (7)
  • ClassicAssist.Shared/Resources/Strings.Designer.cs (1 hunks)
  • ClassicAssist.Shared/Resources/Strings.en-AU.resx (2 hunks)
  • ClassicAssist.Shared/Resources/Strings.en-GB.resx (2 hunks)
  • ClassicAssist.Shared/Resources/Strings.resx (1 hunks)
  • ClassicAssist/Data/Autoloot/PropertyEntry.cs (4 hunks)
  • ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterControl.xaml (6 hunks)
  • ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterViewModel.cs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ClassicAssist/Data/Autoloot/PropertyEntry.cs (1)
ClassicAssist/Data/Options.cs (1)
  • Options (22-773)
ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterViewModel.cs (3)
ClassicAssist.Shared/Misc/ExtensionMethods.cs (1)
  • AddSorted (35-50)
ClassicAssist/Data/Autoloot/PropertyEntry.cs (1)
  • PropertyEntry (28-119)
ClassicAssist/Data/Organizer/OrganizerEntry.cs (1)
  • OrganizerEntry (7-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
ClassicAssist.Shared/Resources/Strings.resx (1)

2089-2091: LGTM!

The new "Organizer Match" resource entry is correctly formatted and positioned consistently with other entries in the file.

ClassicAssist.Shared/Resources/Strings.en-GB.resx (2)

1-1: Good cleanup removing the BOM.

The XML declaration is now standard UTF-8 without the BOM, which improves file compatibility.


2086-2088: LGTM!

The "Organizer Match" resource entry is correctly added and consistent with the base resource file.

ClassicAssist/Data/Autoloot/PropertyEntry.cs (1)

21-21: LGTM!

The new Options property follows the existing pattern in the class, using SetProperty for proper change notification. The use of ObservableCollection is appropriate for UI binding scenarios and aligns with the MVVM architecture.

Also applies to: 36-36, 78-82

ClassicAssist.Shared/Resources/Strings.en-AU.resx (2)

1-1: Good cleanup removing the BOM.

The XML declaration is now standard UTF-8 without the BOM, improving file consistency across locale files.


2086-2088: LGTM!

The "Organizer Match" resource entry is correctly added and consistent with the base resource file. The localization support is properly maintained across all locale files.

ClassicAssist.Shared/Resources/Strings.Designer.cs (1)

3867-3875: Resource accessor addition looks correct

Accessor name and key are consistent with existing patterns (e.g., Autoloot_Match). No issues.

ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterViewModel.cs (1)

21-21: Trivial imports

Added namespaces are expected for the new feature.

Also applies to: 30-30

ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterControl.xaml (3)

21-21: New converter namespace alias is fine

Alias resolves to ClassicAssist.Shared UI converters; no issues.


64-71: Template/behaviour tweaks look good

Layout and behaviour parameter binding are consistent and safe.

Also applies to: 83-85


35-35: No issues found—converter semantics and binding logic are correct.

The NullToBooleanConverter returns true when the value is non-null and false when null. The binding at line 344 correctly uses this: when Constraint.Options is non-null, the converter returns true, the DataTrigger matches, and the TextBox visibility is set to Collapsed. This behaviour is intentional and appropriate. Line 35 is simply the resource key declaration. The UI logic is sound.

Comment on lines +123 to +140
Constraints.AddSorted( new PropertyEntry
{
Name = Strings.Organizer_Match,
ConstraintType = PropertyType.PredicateWithValue,
AllowedOperators = AutolootAllowedOperators.Equal | AutolootAllowedOperators.NotEqual,
Predicate = ( item, entry ) =>
{
if ( entry.Additional == null )
{
return false;
}

OrganizerEntry organizer = OrganizerManager.GetInstance().Items.FirstOrDefault( e => e.Name == entry.Additional );

return organizer != null && organizer.Items.Any( e => e.ID == item.ID && ( e.Hue == -1 || e.Hue == item.Hue ) );
},
Options = new ObservableCollection<string>( OrganizerManager.GetInstance().Items?.Select( o => o.Name ) ?? new List<string>() )
} );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Organizer_Match ignores NotEqual; add operator handling and input guards

Currently NotEqual behaves like Equal. Also harden for empty Additional and a null singleton edge case.

Apply this diff:

             Constraints.AddSorted( new PropertyEntry
             {
                 Name = Strings.Organizer_Match,
                 ConstraintType = PropertyType.PredicateWithValue,
                 AllowedOperators = AutolootAllowedOperators.Equal | AutolootAllowedOperators.NotEqual,
                 Predicate = ( item, entry ) =>
                 {
-                    if ( entry.Additional == null )
-                    {
-                        return false;
-                    }
-
-                    OrganizerEntry organizer = OrganizerManager.GetInstance().Items.FirstOrDefault( e => e.Name == entry.Additional );
-
-                    return organizer != null && organizer.Items.Any( e => e.ID == item.ID && ( e.Hue == -1 || e.Hue == item.Hue ) );
+                    if ( string.IsNullOrEmpty( entry.Additional ) )
+                    {
+                        return false;
+                    }
+
+                    var mgr = OrganizerManager.GetInstance();
+                    var organizer = mgr?.Items?.FirstOrDefault( e => string.Equals( e.Name, entry.Additional, StringComparison.Ordinal ) );
+
+                    if ( organizer == null )
+                    {
+                        // Missing organiser: treat as no match for both operators.
+                        return false;
+                    }
+
+                    bool isMatch = organizer.Items.Any( e => e.ID == item.ID && ( e.Hue == -1 || e.Hue == item.Hue ) );
+                    return entry.Operator == AutolootOperator.NotEqual ? !isMatch : isMatch;
                 },
-                Options = new ObservableCollection<string>( OrganizerManager.GetInstance().Items?.Select( o => o.Name ) ?? new List<string>() )
+                Options = new ObservableCollection<string>(
+                    ( OrganizerManager.GetInstance().Items?.Select( o => o.Name )
+                      ?? new List<string>() ).OrderBy( n => n, StringComparer.InvariantCultureIgnoreCase ) )
             } );

Additionally, consider refreshing Options if organiser names can change at runtime; otherwise users must reopen the view to see new profiles.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Constraints.AddSorted( new PropertyEntry
{
Name = Strings.Organizer_Match,
ConstraintType = PropertyType.PredicateWithValue,
AllowedOperators = AutolootAllowedOperators.Equal | AutolootAllowedOperators.NotEqual,
Predicate = ( item, entry ) =>
{
if ( entry.Additional == null )
{
return false;
}
OrganizerEntry organizer = OrganizerManager.GetInstance().Items.FirstOrDefault( e => e.Name == entry.Additional );
return organizer != null && organizer.Items.Any( e => e.ID == item.ID && ( e.Hue == -1 || e.Hue == item.Hue ) );
},
Options = new ObservableCollection<string>( OrganizerManager.GetInstance().Items?.Select( o => o.Name ) ?? new List<string>() )
} );
Constraints.AddSorted( new PropertyEntry
{
Name = Strings.Organizer_Match,
ConstraintType = PropertyType.PredicateWithValue,
AllowedOperators = AutolootAllowedOperators.Equal | AutolootAllowedOperators.NotEqual,
Predicate = ( item, entry ) =>
{
if ( string.IsNullOrEmpty( entry.Additional ) )
{
return false;
}
var mgr = OrganizerManager.GetInstance();
var organizer = mgr?.Items?.FirstOrDefault( e => string.Equals( e.Name, entry.Additional, StringComparison.Ordinal ) );
if ( organizer == null )
{
// Missing organiser: treat as no match for both operators.
return false;
}
bool isMatch = organizer.Items.Any( e => e.ID == item.ID && ( e.Hue == -1 || e.Hue == item.Hue ) );
return entry.Operator == AutolootOperator.NotEqual ? !isMatch : isMatch;
},
Options = new ObservableCollection<string>(
( OrganizerManager.GetInstance().Items?.Select( o => o.Name )
?? new List<string>() ).OrderBy( n => n, StringComparer.InvariantCultureIgnoreCase ) )
} );
🤖 Prompt for AI Agents
In ClassicAssist/UI/Views/ECV/Filter/EntityCollectionFilterViewModel.cs around
lines 123-140, the Predicate treats NotEqual the same as Equal and lacks
null/empty guards; update the predicate to first return false if entry or
entry.Additional is null or whitespace, guard against
OrganizerManager.GetInstance() or its Items being null before querying, then
resolve the organizer and implement operator-aware logic: for Equal return true
when a matching organizer item exists (ID and hue check), for NotEqual return
true when no such matching organizer item exists; also ensure Options is
initialized safely from OrganizerManager.GetInstance().Items (null-coalescing to
empty list) and add or call a refresh mechanism to rebuild Options when
organizer names change at runtime.

@Reetus Reetus merged commit 8346327 into master Mar 14, 2026
4 of 5 checks passed
@Reetus Reetus deleted the ecv-filter-match-organizer branch March 14, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant