Skip to content

Commit 704c2dc

Browse files
authored
Merge pull request #33 from linuxfoundation/adesilva/add-page-size-param
Add page_size query parameter to query-resources
2 parents f46383f + 6d2bbfd commit 704c2dc

File tree

20 files changed

+231
-63
lines changed

20 files changed

+231
-63
lines changed

CLAUDE.md

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,36 @@ Environment variables control implementation selection:
111111
- Integration tests can switch between real and mock implementations
112112
- Test files follow `*_test.go` pattern alongside implementation files
113113

114-
<<<<<<< HEAD
115114
## API Features
116115

116+
### Page Size
117+
118+
The `query-resources` endpoint supports a `page_size` query parameter to control how many documents are returned per page.
119+
120+
**Query Parameter:**
121+
122+
- `page_size` (int, optional): Number of results per page (min: 1, max: 1000, default: 50)
123+
124+
**Examples:**
125+
126+
```bash
127+
# Custom page size
128+
GET /query/resources?v=1&type=project&page_size=20
129+
130+
# Combined with other filters
131+
GET /query/resources?v=1&type=project&tags=active&date_field=updated_at&date_from=2025-01-01&page_size=100
132+
```
133+
134+
**Interaction with post-query filters:** `cel_filter` and access control checks are applied after OpenSearch returns results, so a page may return fewer than `page_size` results.
135+
136+
**Implementation Details:**
137+
138+
- Goa design: `design/types.go` (`Sortable` type), `design/query-svc.go` (HTTP param)
139+
- Converter: `cmd/service/converters.go` (`payloadToCriteria()` passes `p.PageSize` to criteria)
140+
- Domain model: `internal/domain/model/search_criteria.go` (`PageSize` field)
141+
- OpenSearch pagination: `internal/infrastructure/opensearch/client.go` (page token generated when `len(hits) == pageSize`)
142+
- Constants: `pkg/constants/query.go` (`DefaultPageSize`, `MaxPageSize`)
143+
117144
### Date Range Filtering
118145

119146
The query service supports filtering resources by date ranges on fields within the `data` object.
@@ -154,17 +181,13 @@ GET /query/resources?v=1&type=project&tags=active&date_field=updated_at&date_fro
154181
- OpenSearch query: `internal/infrastructure/opensearch/template.go` (range query with gte/lte)
155182
- API design: `design/query-svc.go` (Goa design specification)
156183
- Test coverage: `cmd/service/converters_test.go` (17 comprehensive test cases)
157-
=======
158-
## CEL Filter Feature
159184

160-
The service supports Common Expression Language (CEL) filtering for post-query resource filtering.
185+
### CEL Filter
161186

162-
### Overview
187+
The service supports Common Expression Language (CEL) filtering for post-query resource filtering.
163188

164189
CEL filtering allows API consumers to filter resources on arbitrary data fields using a safe, non-Turing complete expression language. The filter is applied after the OpenSearch query but before access control checks.
165190

166-
### Implementation Details
167-
168191
**Location**: `internal/infrastructure/filter/cel_filter.go`
169192

170193
**Key Components**:
@@ -178,37 +201,21 @@ CEL filtering allows API consumers to filter resources on arbitrary data fields
178201
- Filters resources before access control checks
179202
- Reduces number of access control checks needed
180203

181-
### Available Variables in CEL Expressions
204+
**Available Variables in CEL Expressions:**
182205

183206
- `data` (map): Resource data object
184207
- `resource_type` (string): Resource type
185208
- `id` (string): Resource ID
186209

187210
Note: `type` is a reserved word in CEL, so we use `resource_type` instead.
188211

189-
### Example Usage
212+
**Example Usage:**
190213

191-
```go
192-
// API call
214+
```bash
193215
GET /query/resources?type=project&cel_filter=data.slug == "tlf"
194-
195-
// Expression is evaluated against each resource after OpenSearch query
196-
// Only matching resources proceed to access control checks
197-
```
198-
199-
### Adding CEL Filter Tests
200-
201-
When writing tests that involve resource search:
202-
203-
```go
204-
// Use MockResourceFilter for testing
205-
mockFilter := mock.NewMockResourceFilter()
206-
207-
// Pass to service constructor
208-
service := service.NewResourceSearch(mockSearcher, mockAccessChecker, mockFilter)
209216
```
210217

211-
### Common CEL Operations
218+
**Common CEL Operations:**
212219

213220
- Equality: `data.status == "active"`
214221
- Comparison: `data.priority > 5`
@@ -217,14 +224,13 @@ service := service.NewResourceSearch(mockSearcher, mockAccessChecker, mockFilter
217224
- List membership: `data.category in ["security", "networking"]`
218225
- Field existence: `has(data.archived)`
219226

220-
### Performance Considerations
227+
**Performance Considerations:**
221228

222229
- Compiled CEL programs are cached (100 max entries, 5-minute TTL)
223230
- Each resource evaluation has 100ms timeout
224231
- Post-query filtering means pagination may return fewer results than page size
225232
- For best performance, use specific OpenSearch criteria first, then CEL for refinement
226233

227-
### Important Limitations
234+
**Important Limitations:**
228235

229-
**Pagination**: CEL filters apply only to results from each OpenSearch page. If the target resource is not in the first page of OpenSearch results, it won't be found even if it matches the CEL filter. Always use specific primary search criteria (`type`, `name`, `parent`) to narrow OpenSearch results first.
230-
>>>>>>> 3e45fc4d33aba656a5abe1c3df0d3f2bd0fd6be7
236+
CEL filters apply only to results from each OpenSearch page. If the target resource is not in the first page of OpenSearch results, it won't be found even if it matches the CEL filter. Always use specific primary search criteria (`type`, `name`, `parent`) to narrow OpenSearch results first.

charts/lfx-v2-query-service/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ apiVersion: v2
55
name: lfx-v2-query-service
66
description: LFX Platform V2 Query Service chart
77
type: application
8-
version: 0.4.10
8+
version: 0.4.11
99
appVersion: "latest"

cmd/service/converters.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (s *querySvcsrvc) payloadToCriteria(ctx context.Context, p *querysvc.QueryR
9898
CelFilter: p.CelFilter,
9999
SortBy: p.Sort,
100100
PageToken: p.PageToken,
101-
PageSize: constants.DefaultPageSize,
101+
PageSize: p.PageSize,
102102
}
103103
switch p.Sort {
104104
case "name_asc":

cmd/service/converters_test.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ func TestPayloadToCriteria(t *testing.T) {
3535
{
3636
name: "basic payload conversion",
3737
payload: &querysvc.QueryResourcesPayload{
38-
Name: stringPtr("test-project"),
39-
Type: stringPtr("project"),
40-
Tags: []string{"active", "governance"},
38+
Name: stringPtr("test-project"),
39+
Type: stringPtr("project"),
40+
Tags: []string{"active", "governance"},
41+
PageSize: constants.DefaultPageSize,
4142
},
4243
expectedCriteria: model.SearchCriteria{
4344
Name: stringPtr("test-project"),
@@ -50,8 +51,9 @@ func TestPayloadToCriteria(t *testing.T) {
5051
{
5152
name: "payload with parent",
5253
payload: &querysvc.QueryResourcesPayload{
53-
Parent: stringPtr("parent-id"),
54-
Name: stringPtr("child-resource"),
54+
Parent: stringPtr("parent-id"),
55+
Name: stringPtr("child-resource"),
56+
PageSize: constants.DefaultPageSize,
5557
},
5658
expectedCriteria: model.SearchCriteria{
5759
Name: stringPtr("child-resource"),
@@ -63,8 +65,9 @@ func TestPayloadToCriteria(t *testing.T) {
6365
{
6466
name: "payload with sorting - name_asc",
6567
payload: &querysvc.QueryResourcesPayload{
66-
Name: stringPtr("test"),
67-
Sort: "name_asc",
68+
Name: stringPtr("test"),
69+
Sort: "name_asc",
70+
PageSize: constants.DefaultPageSize,
6871
},
6972
expectedCriteria: model.SearchCriteria{
7073
Name: stringPtr("test"),
@@ -77,8 +80,9 @@ func TestPayloadToCriteria(t *testing.T) {
7780
{
7881
name: "payload with sorting - name_desc",
7982
payload: &querysvc.QueryResourcesPayload{
80-
Name: stringPtr("test"),
81-
Sort: "name_desc",
83+
Name: stringPtr("test"),
84+
Sort: "name_desc",
85+
PageSize: constants.DefaultPageSize,
8286
},
8387
expectedCriteria: model.SearchCriteria{
8488
Name: stringPtr("test"),
@@ -91,8 +95,9 @@ func TestPayloadToCriteria(t *testing.T) {
9195
{
9296
name: "payload with sorting - updated_asc",
9397
payload: &querysvc.QueryResourcesPayload{
94-
Name: stringPtr("test"),
95-
Sort: "updated_asc",
98+
Name: stringPtr("test"),
99+
Sort: "updated_asc",
100+
PageSize: constants.DefaultPageSize,
96101
},
97102
expectedCriteria: model.SearchCriteria{
98103
Name: stringPtr("test"),
@@ -105,8 +110,9 @@ func TestPayloadToCriteria(t *testing.T) {
105110
{
106111
name: "payload with sorting - updated_desc",
107112
payload: &querysvc.QueryResourcesPayload{
108-
Name: stringPtr("test"),
109-
Sort: "updated_desc",
113+
Name: stringPtr("test"),
114+
Sort: "updated_desc",
115+
PageSize: constants.DefaultPageSize,
110116
},
111117
expectedCriteria: model.SearchCriteria{
112118
Name: stringPtr("test"),
@@ -116,18 +122,33 @@ func TestPayloadToCriteria(t *testing.T) {
116122
},
117123
expectedError: false,
118124
},
125+
{
126+
name: "payload with explicit page_size",
127+
payload: &querysvc.QueryResourcesPayload{
128+
Name: stringPtr("test"),
129+
PageSize: 20,
130+
},
131+
expectedCriteria: model.SearchCriteria{
132+
Name: stringPtr("test"),
133+
PageSize: 20,
134+
},
135+
expectedError: false,
136+
},
119137
{
120138
name: "payload with invalid page token",
121139
payload: &querysvc.QueryResourcesPayload{
122140
Name: stringPtr("test"),
123141
PageToken: stringPtr("invalid-token"),
142+
PageSize: constants.DefaultPageSize,
124143
},
125144
expectedCriteria: model.SearchCriteria{}, // Will be empty due to error
126145
expectedError: true,
127146
},
128147
{
129-
name: "empty payload",
130-
payload: &querysvc.QueryResourcesPayload{},
148+
name: "empty payload",
149+
payload: &querysvc.QueryResourcesPayload{
150+
PageSize: constants.DefaultPageSize,
151+
},
131152
expectedCriteria: model.SearchCriteria{
132153
PageSize: constants.DefaultPageSize,
133154
},

design/query-svc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ var _ = dsl.Service("query-svc", func() {
101101
dsl.Param("cel_filter")
102102
dsl.Param("sort")
103103
dsl.Param("page_token")
104+
dsl.Param("page_size")
104105
dsl.Header("bearer_token:Authorization")
105106
dsl.Response(dsl.StatusOK, func() {
106107
dsl.Header("cache_control:Cache-Control")

design/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ var Sortable = dsl.Type("Sortable", func() {
2828
dsl.Attribute("page_token", dsl.String, "Opaque token for pagination", func() {
2929
dsl.Example("****")
3030
})
31+
dsl.Attribute("page_size", dsl.Int, "Number of results per page", func() {
32+
dsl.Minimum(1)
33+
dsl.Maximum(1000)
34+
dsl.Default(50)
35+
dsl.Example(20)
36+
})
3137
})
3238

3339
var Resource = dsl.Type("Resource", func() {

gen/http/cli/lfx_v2_query_service/cli.go

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)