@@ -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
146148satisfies any condition (e.g., it transitions from Ready to NotReady), it will
147149be removed from the search index. Similarly, resources that begin satisfying
148150a 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