Skip to content

Commit f2e161d

Browse files
authored
[Filter] Improve relation filter to allow multiple types. (#1728)
1 parent 0234d1a commit f2e161d

3 files changed

Lines changed: 124 additions & 19 deletions

File tree

doc/03_Grid.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Available filters are:
5858
| classificationstore.boolean | array | `true`, `false` or `null` | true |
5959
| classificationstore.number | object | `from`, `to`, `is`, `setting` | true |
6060
| crm.consent | array | `true` or `false` | true |
61-
| dataobject.relation | object | `type`, `ids` | true |
61+
| dataobject.relation | array | array of `type`, `ids` objects | true |
6262

6363

6464
### Examples:
@@ -142,7 +142,7 @@ The filter value of a Classification Store looks a bit difrent. All Filter need
142142
```
143143

144144
Filter by DataObject Relation:
145-
The `dataobject.relation` filter allows filtering by relation fields. The filter value must contain a `type` (the element type: `asset`, `object`, or `document`) and an `ids` array with the relation IDs to filter by.
145+
The `dataobject.relation` filter allows filtering by relation fields. The filter value must be an array of objects, each containing a `type` (the element type: `asset`, `object`, or `document`) and an `ids` array with the relation IDs to filter by. This allows filtering by multiple element types at the same time.
146146

147147
To filter by this structure:
148148
```json
@@ -151,10 +151,16 @@ To filter by this structure:
151151
{
152152
"key": "bodyStyle",
153153
"type": "dataobject.relation",
154-
"filterValue": {
155-
"type": "object",
156-
"ids": [6]
157-
}
154+
"filterValue": [
155+
{
156+
"type": "object",
157+
"ids": [6]
158+
},
159+
{
160+
"type": "asset",
161+
"ids": [7, 9]
162+
}
163+
]
158164
}
159165
]
160166
...

src/DataIndex/Filter/DataObject/RelationFilter.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,30 @@ private function applyRelationFilter(ColumnFilter $column, QueryInterface $query
5757
{
5858
$filterValue = $column->getFilterValue();
5959

60-
if (!is_array($filterValue)) {
61-
throw new InvalidArgumentException('Value for relation filter must be an array');
60+
if (!is_array($filterValue) || $filterValue === []) {
61+
throw new InvalidArgumentException('Value for relation filter must be a non-empty array');
6262
}
6363

64-
$type = $filterValue['type'] ?? null;
65-
$ids = $filterValue['ids'] ?? null;
64+
foreach ($filterValue as $entry) {
65+
$query = $this->applyRelationEntry($column->getKey(), $entry, $query);
66+
}
67+
68+
return $query;
69+
}
70+
71+
/**
72+
* @throws InvalidArgumentException
73+
*/
74+
private function applyRelationEntry(string $key, mixed $entry, QueryInterface $query): QueryInterface
75+
{
76+
if (!is_array($entry)) {
77+
throw new InvalidArgumentException(
78+
'Each relation filter entry must be an array with type and ids'
79+
);
80+
}
81+
82+
$type = $entry['type'] ?? null;
83+
$ids = $entry['ids'] ?? null;
6684

6785
if ($type === null || !in_array($type, self::ALLOWED_TYPES, true)) {
6886
throw new InvalidArgumentException(
@@ -74,7 +92,7 @@ private function applyRelationFilter(ColumnFilter $column, QueryInterface $query
7492
throw new InvalidArgumentException('Value for relation filter must contain a non-empty ids array');
7593
}
7694

77-
$fieldKey = $column->getKey() . '.' . $type;
95+
$fieldKey = $key . '.' . $type;
7896

7997
return $query->filterMultiSelect($fieldKey, $ids);
8098
}

tests/Unit/DataIndex/Filter/DataObject/RelationFilterTest.php

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,43 @@ public function testRelationFilterThrowsExceptionWhenFilterValueNotArray(): void
6262
$queryMock = $this->makeEmpty(DataObjectQueryInterface::class);
6363

6464
$this->expectException(InvalidArgumentException::class);
65-
$this->expectExceptionMessage('Value for relation filter must be an array');
65+
$this->expectExceptionMessage('Value for relation filter must be a non-empty array');
66+
67+
$filter->apply($parameterMock, $queryMock);
68+
}
69+
70+
public function testRelationFilterThrowsExceptionWhenFilterValueIsEmptyArray(): void
71+
{
72+
$filter = new RelationFilter();
73+
$parameterMock = $this->makeEmpty(ColumnFiltersParameterInterface::class, [
74+
'getColumnFilterByType' => function () {
75+
return [
76+
new ColumnFilter('bodyStyle', ColumnType::DATAOBJECT_RELATION->value, []),
77+
];
78+
},
79+
]);
80+
$queryMock = $this->makeEmpty(DataObjectQueryInterface::class);
81+
82+
$this->expectException(InvalidArgumentException::class);
83+
$this->expectExceptionMessage('Value for relation filter must be a non-empty array');
84+
85+
$filter->apply($parameterMock, $queryMock);
86+
}
87+
88+
public function testRelationFilterThrowsExceptionWhenEntryIsNotArray(): void
89+
{
90+
$filter = new RelationFilter();
91+
$parameterMock = $this->makeEmpty(ColumnFiltersParameterInterface::class, [
92+
'getColumnFilterByType' => function () {
93+
return [
94+
new ColumnFilter('bodyStyle', ColumnType::DATAOBJECT_RELATION->value, ['invalid']),
95+
];
96+
},
97+
]);
98+
$queryMock = $this->makeEmpty(DataObjectQueryInterface::class);
99+
100+
$this->expectException(InvalidArgumentException::class);
101+
$this->expectExceptionMessage('Each relation filter entry must be an array with type and ids');
66102

67103
$filter->apply($parameterMock, $queryMock);
68104
}
@@ -73,7 +109,11 @@ public function testRelationFilterThrowsExceptionWhenTypeIsMissing(): void
73109
$parameterMock = $this->makeEmpty(ColumnFiltersParameterInterface::class, [
74110
'getColumnFilterByType' => function () {
75111
return [
76-
new ColumnFilter('bodyStyle', ColumnType::DATAOBJECT_RELATION->value, ['ids' => [1, 2]]),
112+
new ColumnFilter(
113+
'bodyStyle',
114+
ColumnType::DATAOBJECT_RELATION->value,
115+
[['ids' => [1, 2]]]
116+
),
77117
];
78118
},
79119
]);
@@ -94,7 +134,7 @@ public function testRelationFilterThrowsExceptionWhenTypeIsInvalid(): void
94134
new ColumnFilter(
95135
'bodyStyle',
96136
ColumnType::DATAOBJECT_RELATION->value,
97-
['type' => 'invalid', 'ids' => [1, 2]]
137+
[['type' => 'invalid', 'ids' => [1, 2]]]
98138
),
99139
];
100140
},
@@ -113,7 +153,11 @@ public function testRelationFilterThrowsExceptionWhenIdsIsMissing(): void
113153
$parameterMock = $this->makeEmpty(ColumnFiltersParameterInterface::class, [
114154
'getColumnFilterByType' => function () {
115155
return [
116-
new ColumnFilter('bodyStyle', ColumnType::DATAOBJECT_RELATION->value, ['type' => 'object']),
156+
new ColumnFilter(
157+
'bodyStyle',
158+
ColumnType::DATAOBJECT_RELATION->value,
159+
[['type' => 'object']]
160+
),
117161
];
118162
},
119163
]);
@@ -134,7 +178,7 @@ public function testRelationFilterThrowsExceptionWhenIdsIsEmpty(): void
134178
new ColumnFilter(
135179
'bodyStyle',
136180
ColumnType::DATAOBJECT_RELATION->value,
137-
['type' => 'object', 'ids' => []]
181+
[['type' => 'object', 'ids' => []]]
138182
),
139183
];
140184
},
@@ -156,7 +200,7 @@ public function testRelationFilterAppliesFilterWithObjectType(): void
156200
new ColumnFilter(
157201
'bodyStyle',
158202
ColumnType::DATAOBJECT_RELATION->value,
159-
['type' => 'object', 'ids' => [6]]
203+
[['type' => 'object', 'ids' => [6]]]
160204
),
161205
];
162206
},
@@ -183,7 +227,7 @@ public function testRelationFilterAppliesFilterWithAssetType(): void
183227
new ColumnFilter(
184228
'images',
185229
ColumnType::DATAOBJECT_RELATION->value,
186-
['type' => 'asset', 'ids' => [10, 20]]
230+
[['type' => 'asset', 'ids' => [10, 20]]]
187231
),
188232
];
189233
},
@@ -210,7 +254,7 @@ public function testRelationFilterAppliesFilterWithDocumentType(): void
210254
new ColumnFilter(
211255
'relatedDocuments',
212256
ColumnType::DATAOBJECT_RELATION->value,
213-
['type' => 'document', 'ids' => [5, 15, 25]]
257+
[['type' => 'document', 'ids' => [5, 15, 25]]]
214258
),
215259
];
216260
},
@@ -227,4 +271,41 @@ public function testRelationFilterAppliesFilterWithDocumentType(): void
227271

228272
$filter->apply($parameterMock, $queryMock);
229273
}
274+
275+
public function testRelationFilterAppliesFilterWithMultipleTypes(): void
276+
{
277+
$filter = new RelationFilter();
278+
$parameterMock = $this->makeEmpty(ColumnFiltersParameterInterface::class, [
279+
'getColumnFilterByType' => function () {
280+
return [
281+
new ColumnFilter(
282+
'relations',
283+
ColumnType::DATAOBJECT_RELATION->value,
284+
[
285+
['type' => 'object', 'ids' => [6]],
286+
['type' => 'asset', 'ids' => [7, 9]],
287+
]
288+
),
289+
];
290+
},
291+
]);
292+
293+
$callCount = 0;
294+
$queryMock = $this->makeEmpty(DataObjectQueryInterface::class, [
295+
'filterMultiSelect' => Expected::exactly(2, function ($fieldKey, $ids) use (&$callCount, &$queryMock) {
296+
if ($callCount === 0) {
297+
$this->assertSame('relations.object', $fieldKey);
298+
$this->assertSame([6], $ids);
299+
} else {
300+
$this->assertSame('relations.asset', $fieldKey);
301+
$this->assertSame([7, 9], $ids);
302+
}
303+
$callCount++;
304+
305+
return $queryMock;
306+
}),
307+
]);
308+
309+
$filter->apply($parameterMock, $queryMock);
310+
}
230311
}

0 commit comments

Comments
 (0)