Skip to content

Commit 28f6355

Browse files
committed
feat: add resource search querying api design
1 parent ff18c2d commit 28f6355

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed

docs/api-design.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ The Search API will initially offer these new API resources to end-users:
2929
indexed and which fields within the resource are applicable to indexing
3030
- **ResourceSearchQuery** allows users to execute field filtering and full-text
3131
searching capabilities across all indexed resources
32+
- **ResourceFacetQuery** allows users to retrieve facet counts for building
33+
filter UIs, independent of search results
3234

3335

3436
> [!IMPORTANT]
@@ -146,3 +148,189 @@ Conditions are re-evaluated when resources change. If a resource no longer
146148
satisfies any condition (e.g., it transitions from Ready to NotReady), it will
147149
be removed from the search index. Similarly, resources that begin satisfying
148150
a condition after an update will be added to the index.
151+
152+
### Resource search queries
153+
154+
The `ResourceSearchQuery` resource allows users to execute searches across all
155+
indexed resources. It supports full-text search across searchable fields and
156+
field-based filtering using CEL expressions.
157+
158+
```yaml
159+
apiVersion: search.miloapis.com/v1alpha1
160+
kind: ResourceSearchQuery
161+
metadata:
162+
name: find-production-deployments
163+
spec:
164+
# Full-text search string. Searches all fields marked as searchable in the
165+
# applicable index policies.
166+
query: "nginx frontend"
167+
168+
# CEL expressions for field-based filtering. Only fields marked as filterable
169+
# can be used. Each expression supports full boolean logic (&&, ||). Multiple
170+
# filters are combined with OR logic.
171+
filters:
172+
- # Identifier for the filter. Used in error messages and for documentation.
173+
name: prod-high-replica
174+
# CEL expression that must evaluate to a boolean.
175+
expression: 'metadata.namespace == "production" && spec.replicas >= 2'
176+
- name: staging-any
177+
expression: 'metadata.namespace == "staging"'
178+
179+
# Limit search to specific resource types. When empty, searches all indexed
180+
# resource types.
181+
resourceTypes:
182+
- group: apps
183+
kind: Deployment
184+
185+
# Maximum results per page (default: 10, max: 100).
186+
limit: 25
187+
188+
# Pagination cursor from a previous query response.
189+
continue: ""
190+
191+
# Explicit sort ordering. When omitted, results are ordered by relevance
192+
# score for full-text queries.
193+
sort:
194+
# Field path to sort by (must be marked as filterable).
195+
field: metadata.creationTimestamp
196+
# Sort direction, either "asc" or "desc".
197+
order: desc
198+
199+
status:
200+
# Array of matched resources. Each result contains the full resource object
201+
# as it exists in the cluster.
202+
results:
203+
- resource:
204+
apiVersion: apps/v1
205+
kind: Deployment
206+
metadata:
207+
name: nginx-frontend
208+
namespace: production
209+
# ... full resource
210+
211+
# Pagination cursor for the next page. Present only when more results exist.
212+
# Pass this value to the continue field in a subsequent query to fetch the
213+
# next page.
214+
continue: "eyJvZmZzZXQiOjI1fQ=="
215+
216+
# Approximate total number of matching resources across all pages. This is
217+
# an estimate and may change as resources are added or removed.
218+
estimatedTotalHits: 142
219+
```
220+
221+
#### Supported filter operations
222+
223+
Each filter expression accepts CEL syntax with the following supported
224+
operations:
225+
226+
| Operation | Example |
227+
|-----------|---------|
228+
| Equality | `metadata.namespace == "production"` |
229+
| Inequality | `metadata.namespace != "default"` |
230+
| Comparison | `spec.replicas >= 2` |
231+
| List membership | `metadata.namespace in ["prod", "staging"]` |
232+
| Prefix | `metadata.name.startsWith("api-")` |
233+
| Substring | `metadata.name.contains("api")` |
234+
| Field existence | `has(metadata.annotations["description"])` |
235+
| AND | `expr1 && expr2` |
236+
| OR | `expr1 \|\| expr2` |
237+
| Grouping | `(expr1 \|\| expr2) && expr3` |
238+
239+
### Resource facet queries
240+
241+
Facets provide aggregated counts of unique values for specified fields across
242+
matching resources. When you request a facet on a field like `metadata.namespace`,
243+
the search service returns each unique namespace along with the number of
244+
resources in that namespace. For example, faceting on `metadata.namespace` and
245+
`kind` might return:
246+
247+
```
248+
Namespace Kind
249+
├── production (89) ├── Deployment (78)
250+
├── staging (42) ├── Service (64)
251+
└── development (11) └── ConfigMap (23)
252+
```
253+
254+
This enables building dynamic filter interfaces that show users what options
255+
exist, how many results each option returns, and which filters would return no
256+
results. Facets are computed against the filtered result set—if a user has
257+
already filtered to `kind = Deployment`, the namespace facet counts will reflect
258+
only Deployments.
259+
260+
The `ResourceFacetQuery` resource retrieves facet counts independently from
261+
search results. This separation allows populating filter UIs before a user has
262+
entered a search query, building browse interfaces that show category breakdowns
263+
without individual results, or fetching facets separately for performance.
264+
265+
```yaml
266+
apiVersion: search.miloapis.com/v1alpha1
267+
kind: ResourceFacetQuery
268+
metadata:
269+
name: explore-deployments
270+
spec:
271+
# CEL expressions for scoping facet computation. Only fields marked as
272+
# filterable can be used. Each expression supports full boolean logic (&&, ||).
273+
# Multiple filters are combined with OR logic.
274+
filters:
275+
- # CEL expression that must evaluate to a boolean.
276+
expression: 'metadata.namespace == "production"'
277+
278+
# Limit facet computation to specific resource types. When empty, computes
279+
# facets across all indexed resource types.
280+
resourceTypes:
281+
- group: apps
282+
kind: Deployment
283+
284+
# Fields to compute facets for. Only fields marked as facetable in the
285+
# applicable index policies can be used.
286+
facets:
287+
- # The field path to aggregate on.
288+
field: metadata.namespace
289+
- field: metadata.labels["app"]
290+
# Maximum number of unique values to return (default: 10).
291+
limit: 20
292+
- field: kind
293+
294+
status:
295+
# Array of facet results, one per requested facet field. Order matches the
296+
# request. Each facet contains value/count pairs sorted by count descending.
297+
# The response includes only the top N values per field (controlled by limit).
298+
facets:
299+
- # The field path that was aggregated.
300+
field: metadata.namespace
301+
# Array of unique values and their counts.
302+
values:
303+
- # The unique field value.
304+
value: production
305+
# Number of matching resources with this value.
306+
count: 89
307+
- value: staging
308+
count: 42
309+
- value: development
310+
count: 11
311+
- field: metadata.labels["app"]
312+
values:
313+
- value: nginx
314+
count: 34
315+
- value: redis
316+
count: 28
317+
- field: kind
318+
values:
319+
- value: Deployment
320+
count: 78
321+
- value: Service
322+
count: 64
323+
324+
# Total number of resources matching the filters. Useful for displaying
325+
# "X resources found" in the UI.
326+
totalResources: 142
327+
```
328+
329+
### Future considerations
330+
331+
The following features may be added in future versions:
332+
333+
- **Highlighting**: Return matched terms with highlight markers for display
334+
- **Relevance scores**: Include match scores in results for ranking visibility
335+
- **Disjunctive facets**: Option in `ResourceFacetQuery` to compute facet counts
336+
as if that facet's filter was not applied, enabling multi-select filter UIs

0 commit comments

Comments
 (0)