Skip to content

Commit debee1a

Browse files
authored
refactor(hierarchical): update group logic (algolia#10)
1 parent 7475fcc commit debee1a

File tree

6 files changed

+116
-61
lines changed

6 files changed

+116
-61
lines changed

helper_dart/lib/src/filter_group.dart

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,44 +42,51 @@ class FilterGroupID {
4242
enum FilterOperator { and, or }
4343

4444
/// Represents a filter group
45-
abstract class FilterGroup<T> extends DelegatingSet<T> {
45+
abstract class FilterGroup<T extends Filter> extends DelegatingSet<T> {
4646
/// Creates [FilterGroupID] instance.
4747
const FilterGroup._(this.groupID, this._filters) : super(_filters);
4848

4949
/// Creates [FilterGroup] as [FacetFilterGroup].
5050
@factory
5151
static FacetFilterGroup facet({
52+
required Set<FilterFacet> filters,
5253
String name = '',
53-
Set<FilterFacet> filters = const {},
5454
FilterOperator operator = FilterOperator.and,
5555
}) =>
5656
FacetFilterGroup(FilterGroupID(name, operator), filters);
5757

5858
/// Creates [FilterGroup] as [TagFilterGroup].
5959
@factory
6060
static TagFilterGroup tag({
61+
required Set<FilterTag> filters,
6162
String name = '',
62-
Set<FilterTag> filters = const {},
6363
FilterOperator operator = FilterOperator.and,
6464
}) =>
6565
TagFilterGroup(FilterGroupID(name, operator), filters);
6666

6767
/// Creates [FilterGroup] as [NumericFilterGroup].
6868
@factory
6969
static NumericFilterGroup numeric({
70+
required Set<FilterNumeric> filters,
7071
String name = '',
71-
Set<FilterNumeric> filters = const {},
7272
FilterOperator operator = FilterOperator.and,
7373
}) =>
7474
NumericFilterGroup(FilterGroupID(name, operator), filters);
7575

7676
/// Creates [FilterGroup] as [HierarchicalFilterGroup].
7777
@factory
7878
static HierarchicalFilterGroup hierarchical({
79+
required Set<FilterFacet> filters,
80+
required List<FilterFacet> path,
81+
required List<String> attributes,
7982
String name = '',
80-
Set<HierarchicalFilter> filters = const {},
8183
}) =>
82-
HierarchicalFilterGroup(name, filters);
84+
HierarchicalFilterGroup(
85+
FilterGroupID.and(name),
86+
filters,
87+
path,
88+
attributes,
89+
);
8390

8491
/// Filter group ID (name and operator)
8592
final FilterGroupID groupID;
@@ -110,7 +117,7 @@ class FacetFilterGroup extends FilterGroup<FilterFacet> {
110117

111118
/// Make a copy of the facet filters group.
112119
@override
113-
FilterGroup<FilterFacet> copyWith({
120+
FacetFilterGroup copyWith({
114121
FilterGroupID? groupID,
115122
Set<FilterFacet>? filters,
116123
}) =>
@@ -131,7 +138,7 @@ class TagFilterGroup extends FilterGroup<FilterTag> {
131138

132139
/// Make a copy of the tag filters group.
133140
@override
134-
FilterGroup<FilterTag> copyWith({
141+
TagFilterGroup copyWith({
135142
FilterGroupID? groupID,
136143
Set<FilterTag>? filters,
137144
}) =>
@@ -151,7 +158,7 @@ class NumericFilterGroup extends FilterGroup<FilterNumeric> {
151158

152159
/// Make a copy of the numeric filters group.
153160
@override
154-
FilterGroup<FilterNumeric> copyWith({
161+
NumericFilterGroup copyWith({
155162
FilterGroupID? groupID,
156163
Set<FilterNumeric>? filters,
157164
}) =>
@@ -166,28 +173,51 @@ class NumericFilterGroup extends FilterGroup<FilterNumeric> {
166173
}
167174

168175
/// Hierarchical filter group
169-
class HierarchicalFilterGroup extends FilterGroup<HierarchicalFilter> {
176+
class HierarchicalFilterGroup extends FilterGroup<FilterFacet> {
170177
/// Creates an [HierarchicalFilterGroup] instance.
171-
HierarchicalFilterGroup(String name, Set<HierarchicalFilter> filters)
172-
: this._(FilterGroupID(name), filters);
173-
174-
/// Creates a [HierarchicalFilterGroup] instance.
175-
HierarchicalFilterGroup._(super.groupID, super.filters) : super._() {
178+
HierarchicalFilterGroup(
179+
super.groupID,
180+
super.filters,
181+
this.path,
182+
this.attributes,
183+
) : super._() {
176184
assert(groupID.operator == FilterOperator.and);
177185
}
178186

187+
/// Filter facets path.
188+
final List<FilterFacet> path;
189+
190+
/// Attributes names.
191+
final List<String> attributes;
192+
179193
/// Make a copy of the hierarchical filters group.
180194
@override
181-
FilterGroup<HierarchicalFilter> copyWith({
195+
HierarchicalFilterGroup copyWith({
182196
FilterGroupID? groupID,
183-
Set<HierarchicalFilter>? filters,
197+
Set<FilterFacet>? filters,
198+
List<FilterFacet>? path,
199+
List<String>? attributes,
184200
}) =>
185-
HierarchicalFilterGroup._(
201+
HierarchicalFilterGroup(
186202
groupID ?? this.groupID,
187203
filters ?? _filters,
204+
path ?? this.path,
205+
attributes ?? this.attributes,
188206
);
189207

190208
@override
191209
String toString() =>
192210
'HierarchicalFilterGroup{groupID: $groupID, filters: $_filters}';
211+
212+
@override
213+
bool operator ==(Object other) =>
214+
identical(this, other) ||
215+
super == other &&
216+
other is HierarchicalFilterGroup &&
217+
runtimeType == other.runtimeType &&
218+
path == other.path &&
219+
attributes == other.attributes;
220+
221+
@override
222+
int get hashCode => super.hashCode ^ path.hashCode ^ attributes.hashCode;
193223
}

helper_dart/lib/src/filter_group_converter.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ class FilterGroupConverter {
1111
/// Converts [FilterGroup] to its SQL-like [String] representation.
1212
/// Returns `null` if the list is empty.
1313
String? sql(Set<FilterGroup> filterGroups) {
14-
final groups = filterGroups
15-
.whereType<FilterGroup<Filter>>()
16-
.whereNot((element) => element.isEmpty);
14+
final groups = filterGroups.whereNot((element) => element.isEmpty);
1715
if (groups.isEmpty) return null;
1816
return groups.map(_sqlGroup).join(' AND ');
1917
}

helper_dart/lib/src/filters.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,14 @@ abstract class Filters {
9191
final numerics =
9292
numericGroups.toList(NumericFilterGroup.new).unmodifiable();
9393
final hierarchical = hierarchicalGroups
94-
.toList((name, filter) => HierarchicalFilterGroup(name, {filter}))
94+
.toList(
95+
(name, group) => HierarchicalFilterGroup(
96+
FilterGroupID.and(name),
97+
{group.filter},
98+
group.path,
99+
group.attributes,
100+
),
101+
)
95102
.unmodifiable();
96103
return {...facets, ...tags, ...numerics, ...hierarchical};
97104
}
@@ -104,4 +111,28 @@ abstract class Filters {
104111
Map<FilterGroupID, Set<FilterNumeric>>? numericGroups,
105112
Map<String, HierarchicalFilter>? hierarchicalGroups,
106113
});
114+
115+
@override
116+
String toString() => 'Filters{'
117+
'facetGroups: $facetGroups, '
118+
'tagGroups: $tagGroups, numericGroups: '
119+
'$numericGroups, hierarchicalGroups: $hierarchicalGroups'
120+
'}';
121+
122+
@override
123+
bool operator ==(Object other) =>
124+
identical(this, other) ||
125+
other is Filters &&
126+
runtimeType == other.runtimeType &&
127+
facetGroups.equals(other.facetGroups) &&
128+
tagGroups.equals(other.tagGroups) &&
129+
numericGroups.equals(other.numericGroups) &&
130+
hierarchicalGroups.equals(other.hierarchicalGroups);
131+
132+
@override
133+
int get hashCode =>
134+
facetGroups.hashing() ^
135+
tagGroups.hashing() ^
136+
numericGroups.hashing() ^
137+
hierarchicalGroups.hashing();
107138
}

helper_dart/lib/src/query_builder.dart

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,16 @@ class QueryBuilder {
3333
}
3434

3535
/// Get hierarchical filters from the search state's filter groups.
36-
Iterable<HierarchicalFilter> _getHierarchicalFilters() =>
37-
_searchState.filterGroups
38-
?.whereType<HierarchicalFilterGroup>()
39-
.expand((filterGroup) => filterGroup) ??
40-
[];
36+
Iterable<HierarchicalFilterGroup> _getHierarchicalFilters() =>
37+
_searchState.filterGroups?.whereType<HierarchicalFilterGroup>() ?? [];
4138

4239
/// Number of generated hierarchical queries for given hierarchical
4340
/// filter
44-
int _getHierarchicalQueriesCount(HierarchicalFilter filter) {
45-
if (filter.attributes.length == filter.path.length) {
46-
return filter.attributes.length;
41+
int _getHierarchicalQueriesCount(HierarchicalFilterGroup group) {
42+
if (group.attributes.length == group.path.length) {
43+
return group.attributes.length;
4744
}
48-
return filter.path.isEmpty ? 0 : filter.path.length + 1;
45+
return group.path.isEmpty ? 0 : group.path.length + 1;
4946
}
5047

5148
/// Total number of queries
@@ -119,11 +116,8 @@ class QueryBuilder {
119116
/// Build additional queries to fetch correct facets count values
120117
/// for hierarchical facets
121118
List<SearchState> _buildHierarchicalFacetingQueries(SearchState query) {
122-
final hierarchicalFilters = query.filterGroups
123-
?.whereType<HierarchicalFilterGroup>()
124-
.expand((e) => e)
125-
.toList() ??
126-
[];
119+
final hierarchicalFilters =
120+
query.filterGroups?.whereType<HierarchicalFilterGroup>().toList() ?? [];
127121

128122
final queries = <SearchState>[];
129123
for (final hierarchicalFilter in hierarchicalFilters) {
@@ -152,15 +146,11 @@ class QueryBuilder {
152146
SearchState _hierarchicalQueryOf(
153147
String facet,
154148
FilterFacet? pathFilter,
155-
HierarchicalFilter hierarchicalFilter,
149+
HierarchicalFilterGroup group,
156150
SearchState state,
157151
) {
158152
final filterGroupsCopy = _copyFilterGroups()
159-
..forEach((filterGroup) {
160-
if (filterGroup.groupID.operator == FilterOperator.and) {
161-
filterGroup.removeWhere((filter) => filter == hierarchicalFilter);
162-
}
163-
});
153+
..removeWhere((filter) => filter == group);
164154

165155
if (pathFilter != null) {
166156
filterGroupsCopy.add(

helper_dart/test/filter_converter_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,9 @@ void main() {
162162

163163
test('Empty groups', () {
164164
final filterGroups = <FilterGroup<Filter>>{
165-
FilterGroup.facet(),
166-
FilterGroup.numeric(),
167-
FilterGroup.tag(),
165+
FilterGroup.facet(filters: const {}),
166+
FilterGroup.numeric(filters: const {}),
167+
FilterGroup.tag(filters: const {}),
168168
};
169169
const converter = FilterGroupConverter();
170170
expect(converter.unquoted(filterGroups), null);

helper_dart/test/query_builder_test.dart

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,17 @@ void main() {
118118
Filter.facet(lvl1, 'a > b'),
119119
Filter.facet(lvl2, 'a > b > c')
120120
];
121-
final hierarchicalFilter =
122-
HierarchicalFilter(attributes, path, Filter.facet(lvl2, 'a > b > c'));
123121

124122
final colorGroup = FacetFilterGroup(
125123
const FilterGroupID('color'),
126124
{Filter.facet('color', 'red')},
127125
);
128-
final hierarchicalGroup =
129-
HierarchicalFilterGroup('h', {hierarchicalFilter});
126+
final hierarchicalGroup = FilterGroup.hierarchical(
127+
name: 'h',
128+
path: path,
129+
attributes: attributes,
130+
filters: {Filter.facet(lvl2, 'a > b > c')},
131+
);
130132
final filterGroups = <FilterGroup>{colorGroup, hierarchicalGroup};
131133

132134
final query = SearchState(
@@ -147,7 +149,7 @@ void main() {
147149

148150
case 1:
149151
expect(query.facets, [lvl0]);
150-
expect(query.filterGroups!.length, 2);
152+
expect(query.filterGroups!.length, 1);
151153
expect(
152154
query.filterGroups!.first.groupID,
153155
const FilterGroupID('color'),
@@ -225,22 +227,26 @@ void main() {
225227
});
226228

227229
test('test disjunctive & hierarchical responses merging', () {
228-
final hierarchicalFilter = HierarchicalFilter(
229-
['category.lvl0', 'category.lvl1', 'category.lvl2', 'category.lvl3'],
230-
[
231-
Filter.facet('category.lvl0', 'a'),
232-
Filter.facet('category.lvl1', 'a > b'),
233-
Filter.facet('category.lvl2', 'a > b > c')
234-
],
235-
Filter.facet('category.lvl2', 'a > b > c'),
236-
);
237-
238230
final query = SearchState(
239231
indexName: 'index',
240232
query: 'phone',
241233
disjunctiveFacets: {'color', 'brand', 'size'},
242234
filterGroups: {
243-
HierarchicalFilterGroup('category', {hierarchicalFilter})
235+
FilterGroup.hierarchical(
236+
name: 'category',
237+
filters: {Filter.facet('category.lvl2', 'a > b > c')},
238+
path: [
239+
Filter.facet('category.lvl0', 'a'),
240+
Filter.facet('category.lvl1', 'a > b'),
241+
Filter.facet('category.lvl2', 'a > b > c')
242+
],
243+
attributes: [
244+
'category.lvl0',
245+
'category.lvl1',
246+
'category.lvl2',
247+
'category.lvl3'
248+
],
249+
)
244250
},
245251
);
246252

0 commit comments

Comments
 (0)