feat: add search query endpoint with request examples - #34
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an OpenAPI definition for a new beta search endpoint modeled after Microsoft Graph Search, including request/response schemas and multiple example payloads to document common search scenarios.
Changes:
- Introduces
POST /v1beta1/search/querywith detailed description and request/response examples. - Adds search-related schemas (
searchRequest,searchQuery,searchResponse, aggregation/bucket schemas) undercomponents/schemas. - Wires the new endpoint to the new schemas via
$refto keep the spec structured.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@dschmidt Since the search endpoint returns plain drive items I'm wondering how we would handle the additional information the results are currently being enriched with (see https://github.com/opencloud-eu/opencloud/blob/main/services/webdav/pkg/service/v0/search.go#L150-L263 for the current state). Things like the tags could be derived from the arbitrary metadata (which currently isn't exposed afaict), but do you have an idea how we would handle the |
|
Very good questions, thanks! I assume
Although this introduces user specific data to the driveItem, We could introduce a custom prop like |
|
The thumbnails relationship is already in the spec, it might still need implementation tho 😅 |
|
Aha, I just realized the follow endpoints map to setting the favorite state... so just @libre.graph.following or @libre.graph.user.following on the driveItem? If you agree, I can send another PR for that, to keep this one scoped |
|
#37 👀 |
|
What's also potentially missing: short permissions/allowed actions without loading all shares Not sure how we want to do that? Thoughts? |
we introduced @libre.graph.permissions.roles.allowedValues and @libre.graph.permissions.actions.allowedValues to let the web ui build the sharing dialog. see https://docs.opencloud.eu/docs/dev/server/apis/http/graph/permissions for what that looks like or just monitor your browsers network tab when opening the sharing dialog in the web ui. Let me know if that covers what you have in mind |
Adds an optional, `$select`-gated instance annotation on `driveItem` that carries the list of libre.graph actions the caller is allowed to perform on the item. Mirrors the annotation of the same name on the `/permissions` endpoint so clients (e.g. a sharing dialog in a search/listing UI) can get the effective-actions view inline without a separate round-trip per item. - Adds `@libre.graph.permissions.actions.allowedValues` to the `driveItem` schema, marked read-only and documented as only populated when requested. - Adds a reusable `driveItemSelect` component parameter with a narrow enum, following the same pattern as PR #38 (feat/download-url). When both land, the enum values merge. - Wires the new parameter to `GetDriveItem`. Rationale discussed in #34.
|
@butonic @aduffeck do we want to hold this back until the additional properties are spec'ed and merged or should we progress here independently? They are not hard to add later and merging this would unblock further work on the search service. edit: Then again, they are "half approved". If we can settle on the |
Adds an optional, `$select`-gated instance annotation on `driveItem` that carries the list of libre.graph actions the caller is allowed to perform on the item. Mirrors the annotation of the same name on the `/permissions` endpoint so clients (e.g. a sharing dialog in a search/listing UI) can get the effective-actions view inline without a separate round-trip per item. - Adds `@libre.graph.permissions.actions.allowedValues` to the `driveItem` schema, marked read-only and documented as only populated when requested. - Adds a reusable `driveItemSelect` component parameter with a narrow enum, following the same pattern as PR #38 (feat/download-url). When both land, the enum values merge. - Wires the new parameter to `GetDriveItem`. Rationale discussed in #34.
|
Implementations for the issues raised above:
I think with those in place we can fully replace the WebDAV search... |
|
I've found more missing properties that WebDAV PROPFIND provides but driveItems don't cover yet. Spec PRs:
Together with the already merged following/tags/allowedValues annotations these cover everything web currently reads from PROPFIND (share indicators, lock state, processing state) afaict |
d0dceba to
12d8885
Compare
Added a new search endpoint for querying resources with detailed request and response structures, including examples for various search scenarios. Based on the MS Graph Search Api of course: https://learn.microsoft.com/en-us/graph/api/resources/search-api-overview?view=graph-rest-1.0
The MS Graph token is opaque and varies by bucket type (hex-encoded for terms, range(...) for ranges, with different quoting rules). Rather than commit to that contract, omit the field and let clients construct filters directly from field + key (or the original range definition).
Adds `subAggregations` as an optional array on both the request (`aggregationOption`) and response (`searchBucket`) sides of the search aggregation spec. Lets callers nest term aggregations, e.g. "group by audio.artist, then by audio.album", evaluated in a single request. Explicit libregraph extension beyond MS Graph. Bleve backend emulates via client-side fold over matched hits; OpenSearch would translate to native composite aggregations.
Adds `metricKind` (sum|min|max) to aggregationOption and mirrors `value` + `metricKind` on searchAggregation. When set, the aggregation returns a scalar rather than a bucket list; `size` and `bucketDefinition` are ignored. Libregraph extension, not present in MS Graph. Composable with subAggregations: a metric appears alongside term sub-aggregations inside a parent bucket's subAggregations list, just like in Elasticsearch. The most common use is "for each term bucket, compute a scalar over a numeric field in that bucket's docs", e.g. sum(audio.duration) per audio.album gives total album runtime. Intentionally omits avg/cardinality: avg needs split (sum, count) transport to merge correctly across shards and lands in a follow-up; cardinality is already derivable from a terms sub-aggregation's bucket count.
Adds `avg` as a permitted value on `aggregationOption.metricKind` and `searchAggregation.metricKind`. Distinct from sum/min/max because an average of two averages isn't mergeable: the backend has to carry (sum, count) internally per bucket and only collapse to the scalar `value` at the outermost merge. Wire shape stays symmetrical with sum/min/max: callers still just read `value` off the response; the (sum, count) bookkeeping is entirely service-side.
Term buckets carry the key as a quoted, ǂǂ-prefixed hex token, range
buckets a range(from,to) expression, both as issued by MS Graph.
aggregationFilters only accept server-issued tokens in the
{field}:{aggregationFilterToken} form, optionally combined with or(...).
12d8885 to
8f05719
Compare
…graph. subAggregations, metricKind and value are not part of the MS Graph resource types; the annotation prefix marks them like the other libregraph additions. Enum values stay unprefixed.
…egations
Mirrors the bucketDefinition / buckets pair: @libre.graph.metricDefinition
{ kind } on aggregationOption, @libre.graph.metric { kind, value } on
searchAggregation.
Space after the comma and to="le" on an open upper bound, so the issued tokens are byte-identical to MS Graph; whitespace is optional when parsing.
Add a $expand query parameter to /v1beta1/search/query so clients can pull the thumbnails relationship inline on each hit's driveItem, the graph equivalent of the WebDAV report's has-preview. Libregraph extension: MS Graph search has no $expand. Also switch the aggregation examples to Saxon - And the Bands Played On.
Split the single response example into two keyed to match the request examples: a size:0 facets-only response (empty hits, buckets with filter tokens) and a token drilldown whose total equals the Saxon bucket count.
…tch the rest of the spec
Added a new search endpoint for querying resources with detailed request and response structures, including examples for various search scenarios.
Based on the MS Graph Search Api of course:
https://learn.microsoft.com/en-us/graph/api/resources/search-api-overview?view=graph-rest-1.0
I'm also willing to implement that endpoint: opencloud-eu/opencloud#3211
Libregraph extensions beyond MS Graph
Everything follows the MS Graph resource types under the same names, except these additions, which carry the
@libre.graph.prefix like the other libregraph annotations:@libre.graph.subAggregationsonaggregationOptionandsearchBucket: nested aggregations, evaluated in a single request@libre.graph.metricDefinition({ kind }, kind beingsum/min/max/avg) onaggregationOption, mirroringbucketDefinition, with a@libre.graph.metric({ kind, value }) onsearchAggregationinstead ofbucketsExample request with a sub-aggregation and a metric:
{ "requests": [{ "entityTypes": ["driveItem"], "query": { "queryString": "mediatype:audio" }, "size": 0, "aggregations": [{ "field": "audio.artist", "size": 10, "bucketDefinition": { "sortBy": "count", "isDescending": true }, "@libre.graph.subAggregations": [ { "field": "audio.album", "size": 5, "bucketDefinition": { "sortBy": "count" } }, { "field": "audio.duration", "@libre.graph.metricDefinition": { "kind": "sum" } } ] }] }] }And the matching response:
{ "value": [{ "searchTerms": ["mediatype:audio"], "hitsContainers": [{ "hits": [], "total": 142, "moreResultsAvailable": false, "aggregations": [{ "field": "audio.artist", "buckets": [{ "key": "Saxon", "count": 42, "aggregationFilterToken": "\"ǂǂ5361786f6e\"", "@libre.graph.subAggregations": [ { "field": "audio.album", "buckets": [ { "key": "Power & the Glory", "count": 26, "aggregationFilterToken": "\"ǂǂ506f77657220262074686520476c6f7279\"" }, { "key": "Wheels of Steel", "count": 5, "aggregationFilterToken": "\"ǂǂ576865656c73206f6620537465656c\"" } ] }, { "field": "audio.duration", "@libre.graph.metric": { "kind": "sum", "value": 16073000 } } ] }] }] }] }] }aggregationFilterToken
searchBucket.aggregationFilterTokenuses the MS Graph encoding: terms buckets carry the key as a quoted,ǂǂ-prefixed hex token, range buckets arange(from, to)expression withmin/maxfor open bounds andto="le"on an open upper bound, spelled exactly like MS Graph does (whitespace is optional when parsing).aggregationFiltersonly accept server-issued tokens in the{field}:{aggregationFilterToken}form, optionally combined withor(...). The filter matches the bucket key exactly and case-sensitively, so the narrowed result set is the set of matches counted in the bucket.