Skip to content

Commit 271051c

Browse files
Jonas Saabelclaude
andcommitted
feat: add support for relation, people, status, unique_id, and files filters
Add five new property type filters to DataSourceQueryBuilder to support common filtering use cases that were previously missing: - relation: filter by related pages (contains, doesNotContain, isEmpty, isNotEmpty) - people: filter by users/assignees (contains, doesNotContain, isEmpty, isNotEmpty) - status: filter by workflow status (equals, doesNotEqual, isEmpty, isNotEmpty) - unique_id: filter by auto-incrementing IDs (numeric comparisons) - files: filter by attachment presence (isEmpty, isNotEmpty) Changes: - Add RelationCondition, PeopleCondition, StatusCondition, UniqueIdCondition, FilesCondition data models - Add corresponding filter builder classes with DSL methods - Extend DataSourceFilter with new filter field properties - Add comprehensive unit tests for all new filter types - Add integration test suite for manual verification against live API All filters follow the established DSL pattern and have been manually verified to work correctly with the Notion API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d97b3e9 commit 271051c

File tree

5 files changed

+1147
-0
lines changed

5 files changed

+1147
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Development Journal - November 4, 2025
2+
3+
## Missing Query Filter Property Types
4+
5+
### 🎯 Objective
6+
Identify and implement missing property type filters in the DataSourceQueryBuilder. During integration with another project, we discovered that relation properties (a very common use case) are not supported. This investigation aims to catalog all missing types and implement them.
7+
8+
### 🔍 Discovery
9+
10+
#### Investigation Location
11+
- **Builder Implementation**: `src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQueryBuilder.kt`
12+
- **Data Models**: `src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQuery.kt`
13+
- **API Documentation**: `reference/notion-api/documentation/general/07_Filter_Database_Entries.md`
14+
15+
#### Currently Supported Property Types (10 types)
16+
1.**title** - text operations (equals, contains, starts_with, ends_with, is_empty, etc.)
17+
2.**rich_text** - text operations (same as title)
18+
3.**number** - numeric comparisons (equals, greater_than, less_than, etc.)
19+
4.**select** - equals, does_not_equal, is_empty, is_not_empty
20+
5.**multi_select** - contains, does_not_contain, is_empty, is_not_empty
21+
6.**date** - temporal comparisons (equals, before, after, past_week, next_month, etc.)
22+
7.**checkbox** - boolean comparisons (equals, does_not_equal)
23+
8.**url** - text operations (same as rich_text)
24+
9.**email** - text operations (same as rich_text)
25+
10.**phone_number** - text operations (same as rich_text)
26+
27+
#### Missing Property Types (8 types)
28+
29+
According to the Notion API documentation (line 85 in the filter docs), the following property types support filtering but are **NOT YET IMPLEMENTED**:
30+
31+
##### High Priority (Common Use Cases)
32+
1.**relation** - Filter by related pages
33+
- Operations: `contains`, `does_not_contain`, `is_empty`, `is_not_empty`
34+
- Value type: UUID string
35+
- Use case: Very common for filtering by related entities
36+
- API docs: lines 286-306
37+
38+
2.**people** - Filter by users (also applies to created_by, last_edited_by)
39+
- Operations: `contains`, `does_not_contain`, `is_empty`, `is_not_empty`
40+
- Value type: UUID string
41+
- Use case: Filter by assignee, creator, or editor
42+
- API docs: lines 260-284
43+
44+
3.**status** - Filter by status property
45+
- Operations: `equals`, `does_not_equal`, `is_empty`, `is_not_empty`
46+
- Value type: string
47+
- Use case: Common for workflow/project management databases
48+
- API docs: lines 429-449
49+
50+
4.**unique_id** - Filter by auto-incrementing ID property
51+
- Operations: `equals`, `does_not_equal`, `greater_than`, `less_than`, `greater_than_or_equal_to`, `less_than_or_equal_to`
52+
- Value type: number (NOT a UUID, despite the name)
53+
- Use case: Range queries on sequential IDs
54+
- API docs: lines 475-509
55+
- Note: Uses "unique_id" as the filter key, not "ID"
56+
57+
##### Medium Priority
58+
5.**files** - Filter by file attachment presence
59+
- Operations: `is_empty`, `is_not_empty`
60+
- Use case: Find pages with/without attachments
61+
- API docs: lines 166-184
62+
63+
6.**timestamp** - Filter by created_time or last_edited_time
64+
- Special filter type (doesn't use "property" field)
65+
- Operations: Uses date filter conditions
66+
- Use case: Find recently created/edited pages
67+
- API docs: lines 452-473
68+
- Note: This is distinct from date properties; applies to page metadata
69+
70+
##### Lower Priority (Complex Types)
71+
7.**formula** - Filter by formula results
72+
- Nested filter structure based on formula return type
73+
- Sub-filters: `checkbox`, `date`, `number`, `string` (using rich_text conditions)
74+
- Use case: Filter by computed values
75+
- API docs: lines 186-210
76+
77+
8.**rollup** - Filter by rollup aggregate values
78+
- Complex nested filter structure
79+
- For arrays: `any`, `every`, `none` (with nested type filters)
80+
- For aggregates: `date` or `number` (with corresponding filter conditions)
81+
- Use case: Filter by aggregated relation data
82+
- API docs: lines 334-405
83+
84+
### 📋 Implementation Plan
85+
86+
#### Phase 1: Core Missing Types (High Priority)
87+
Implement the four most commonly used missing types:
88+
1. Relation filters
89+
2. People filters
90+
3. Status filters
91+
4. Unique ID filters
92+
93+
These follow the same DSL patterns as existing filters and have straightforward implementations.
94+
95+
#### Phase 2: Simple Utility Type
96+
5. Files filters (very simple, only is_empty/is_not_empty)
97+
98+
#### Phase 3: Special Cases
99+
6. Timestamp filters (requires special handling, no property name)
100+
101+
#### Phase 4: Complex Types (if needed)
102+
7. Formula filters (nested structure)
103+
8. Rollup filters (most complex, nested structure with multiple modes)
104+
105+
### 🔧 Implementation Notes
106+
107+
#### Pattern Consistency
108+
All new filter builders should follow the established pattern:
109+
- FilterBuilder extension method (e.g., `fun relation(propertyName: String)`)
110+
- Dedicated builder class (e.g., `RelationFilterBuilder`)
111+
- Condition data class (e.g., `RelationCondition`)
112+
- DataSourceFilter extension in DataSourceQuery.kt
113+
114+
#### Testing Requirements
115+
For each new filter type:
116+
1. Unit tests in `DatabaseQueryFiltersTest.kt` for all operations
117+
2. Builder pattern tests in `DataSourceQueryBuilderTest.kt`
118+
3. JSON serialization validation
119+
4. Integration test examples (if applicable)
120+
121+
#### Documentation Updates
122+
- Update examples in test files
123+
- Add to any relevant documentation
124+
125+
### ✅ Implementation Complete
126+
127+
#### Phase 1-2: Core Missing Types Implemented
128+
All high-priority and medium-priority filter types have been successfully implemented:
129+
130+
1.**Relation filters** - `relation(propertyName)`
131+
- Operations: `contains(pageId)`, `doesNotContain(pageId)`, `isEmpty()`, `isNotEmpty()`
132+
- Use case: Filter by related pages/entities
133+
134+
2.**People filters** - `people(propertyName)`
135+
- Operations: `contains(userId)`, `doesNotContain(userId)`, `isEmpty()`, `isNotEmpty()`
136+
- Use case: Filter by assignees, collaborators, creators
137+
138+
3.**Status filters** - `status(propertyName)`
139+
- Operations: `equals(value)`, `doesNotEqual(value)`, `isEmpty()`, `isNotEmpty()`
140+
- Use case: Filter by workflow status
141+
142+
4.**Unique ID filters** - `uniqueId(propertyName)`
143+
- Operations: `equals(value)`, `doesNotEqual(value)`, `greaterThan(value)`, `lessThan(value)`, `greaterThanOrEqualTo(value)`, `lessThanOrEqualTo(value)`
144+
- Use case: Range queries on auto-incrementing IDs
145+
146+
5.**Files filters** - `files(propertyName)`
147+
- Operations: `isEmpty()`, `isNotEmpty()`
148+
- Use case: Check for attachment presence
149+
150+
#### Implementation Details
151+
152+
**Data Models Added** (`src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQuery.kt`):
153+
- `RelationCondition` - for relation property filters
154+
- `PeopleCondition` - for people property filters
155+
- `StatusCondition` - for status property filters
156+
- `UniqueIdCondition` - for unique_id property filters (uses Int, not UUID)
157+
- `FilesCondition` - for files property filters
158+
159+
**Builder Classes Added** (`src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQueryBuilder.kt`):
160+
- `RelationFilterBuilder` - DSL builder for relation filters
161+
- `PeopleFilterBuilder` - DSL builder for people filters
162+
- `StatusFilterBuilder` - DSL builder for status filters
163+
- `UniqueIdFilterBuilder` - DSL builder for unique_id filters
164+
- `FilesFilterBuilder` - DSL builder for files filters
165+
166+
**FilterBuilder Extensions**:
167+
- Added `relation()`, `people()`, `status()`, `uniqueId()`, `files()` methods to FilterBuilder
168+
169+
**Tests Added**:
170+
- `src/test/kotlin/unit/query/DatabaseQueryFiltersTest.kt` - 7 new unit test cases covering all new filter types
171+
- `src/test/kotlin/integration/NewFilterTypesIntegrationTest.kt` - Comprehensive integration test suite for manual verification
172+
173+
#### Usage Examples
174+
175+
```kotlin
176+
// Filter by relation
177+
dataSourceQuery {
178+
filter {
179+
relation("Project").contains("12345678-1234-1234-1234-123456789abc")
180+
}
181+
}
182+
183+
// Filter by people (assignees)
184+
dataSourceQuery {
185+
filter {
186+
and(
187+
people("Assignee").contains("user-uuid"),
188+
people("Collaborators").isNotEmpty()
189+
)
190+
}
191+
}
192+
193+
// Filter by status
194+
dataSourceQuery {
195+
filter {
196+
status("Status").equals("In Progress")
197+
}
198+
}
199+
200+
// Filter by unique_id (range query)
201+
dataSourceQuery {
202+
filter {
203+
and(
204+
uniqueId("ID").greaterThan(100),
205+
uniqueId("ID").lessThanOrEqualTo(999)
206+
)
207+
}
208+
}
209+
210+
// Filter by files presence
211+
dataSourceQuery {
212+
filter {
213+
files("Attachments").isNotEmpty()
214+
}
215+
}
216+
217+
// Complex query with multiple new filter types
218+
dataSourceQuery {
219+
filter {
220+
and(
221+
title("Task Name").contains("Feature"),
222+
status("Status").equals("Active"),
223+
people("Assignee").isNotEmpty(),
224+
relation("Epic").contains("epic-page-id"),
225+
uniqueId("ID").greaterThan(1),
226+
files("Screenshots").isNotEmpty()
227+
)
228+
}
229+
}
230+
```
231+
232+
#### Testing & Verification
233+
234+
**Unit Tests**: ✅ All passing
235+
- 7 new test cases in `DatabaseQueryFiltersTest.kt`
236+
- Tests for individual filter types and complex combinations
237+
- Run with: `./gradlew test`
238+
239+
**Integration Tests**: ✅ Manually verified against live Notion API
240+
- Created comprehensive test suite with 3-phase workflow:
241+
1. Setup: Creates test databases with all new property types
242+
2. Populate: Creates sample pages with test data
243+
3. Verify: Tests each filter type against real API
244+
- All filter types verified working correctly with live data
245+
- Run with: `./gradlew test --tests "*NewFilterTypesIntegrationTest*Verify*"`
246+
247+
**Build Results**:
248+
- ✅ Code formatting passed
249+
- ✅ Build successful
250+
- ✅ All tests passing
251+
252+
#### Remaining Work (Lower Priority)
253+
- Timestamp filters (special case - no property name, filters by created_time/last_edited_time)
254+
- Formula filters (nested structure based on formula result type)
255+
- Rollup filters (most complex - nested with multiple aggregation modes)
256+
257+
These can be implemented later as needed. The most commonly used filter types are now fully supported.
258+
259+
### 📚 Reference
260+
- Notion API Filter Documentation: `reference/notion-api/documentation/general/07_Filter_Database_Entries.md`
261+
- Implementation: `src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/`
262+
- Unit Tests: `src/test/kotlin/unit/query/DatabaseQueryFiltersTest.kt`
263+
- Integration Tests: `src/test/kotlin/integration/NewFilterTypesIntegrationTest.kt`

src/main/kotlin/it/saabel/kotlinnotionclient/models/datasources/DataSourceQuery.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ data class DataSourceFilter(
8080
val email: PropertyCondition? = null,
8181
@SerialName("phone_number")
8282
val phoneNumber: PropertyCondition? = null,
83+
@SerialName("relation")
84+
val relation: RelationCondition? = null,
85+
@SerialName("people")
86+
val people: PeopleCondition? = null,
87+
@SerialName("status")
88+
val status: StatusCondition? = null,
89+
@SerialName("unique_id")
90+
val uniqueId: UniqueIdCondition? = null,
91+
@SerialName("files")
92+
val files: FilesCondition? = null,
8393
// Compound conditions
8494
@SerialName("and")
8595
val and: List<DataSourceFilter>? = null,
@@ -234,3 +244,79 @@ data class CheckboxCondition(
234244
@SerialName("does_not_equal")
235245
val doesNotEqual: Boolean? = null,
236246
)
247+
248+
/**
249+
* Relation property condition for filtering by related pages.
250+
*/
251+
@Serializable
252+
data class RelationCondition(
253+
@SerialName("contains")
254+
val contains: String? = null, // UUID
255+
@SerialName("does_not_contain")
256+
val doesNotContain: String? = null, // UUID
257+
@SerialName("is_empty")
258+
val isEmpty: Boolean? = null,
259+
@SerialName("is_not_empty")
260+
val isNotEmpty: Boolean? = null,
261+
)
262+
263+
/**
264+
* People property condition for filtering by users.
265+
* Also applies to created_by and last_edited_by property types.
266+
*/
267+
@Serializable
268+
data class PeopleCondition(
269+
@SerialName("contains")
270+
val contains: String? = null, // UUID
271+
@SerialName("does_not_contain")
272+
val doesNotContain: String? = null, // UUID
273+
@SerialName("is_empty")
274+
val isEmpty: Boolean? = null,
275+
@SerialName("is_not_empty")
276+
val isNotEmpty: Boolean? = null,
277+
)
278+
279+
/**
280+
* Status property condition.
281+
*/
282+
@Serializable
283+
data class StatusCondition(
284+
@SerialName("equals")
285+
val equals: String? = null,
286+
@SerialName("does_not_equal")
287+
val doesNotEqual: String? = null,
288+
@SerialName("is_empty")
289+
val isEmpty: Boolean? = null,
290+
@SerialName("is_not_empty")
291+
val isNotEmpty: Boolean? = null,
292+
)
293+
294+
/**
295+
* Unique ID property condition for filtering by auto-incrementing IDs.
296+
*/
297+
@Serializable
298+
data class UniqueIdCondition(
299+
@SerialName("equals")
300+
val equals: Int? = null,
301+
@SerialName("does_not_equal")
302+
val doesNotEqual: Int? = null,
303+
@SerialName("greater_than")
304+
val greaterThan: Int? = null,
305+
@SerialName("less_than")
306+
val lessThan: Int? = null,
307+
@SerialName("greater_than_or_equal_to")
308+
val greaterThanOrEqualTo: Int? = null,
309+
@SerialName("less_than_or_equal_to")
310+
val lessThanOrEqualTo: Int? = null,
311+
)
312+
313+
/**
314+
* Files property condition for checking file attachment presence.
315+
*/
316+
@Serializable
317+
data class FilesCondition(
318+
@SerialName("is_empty")
319+
val isEmpty: Boolean? = null,
320+
@SerialName("is_not_empty")
321+
val isNotEmpty: Boolean? = null,
322+
)

0 commit comments

Comments
 (0)