feat: add CRUD REST API for saved searches (/api/v2/saved-searches)#2544
feat: add CRUD REST API for saved searches (/api/v2/saved-searches)#2544ankitcharolia wants to merge 14 commits into
Conversation
Adds GET/POST /api/v2/saved-searches and GET/PUT/DELETE /api/v2/saved-searches/:id to the external API. The SavedSearch mongoose model and toExternalJSON() already exist; this PR adds only the missing router and index registration. Closes hyperdxio#2543
|
@ankitcharolia is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
🦋 Changeset detectedLatest commit: 7391f48 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Greptile SummaryThis PR adds saved-search CRUD support to the external API. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (14): Last reviewed commit: "Merge branch 'main' into feat/saved-sear..." | Re-trigger Greptile |
Adds a Source.findOne({ _id: sourceId, team: teamId }) check to both
POST and PUT handlers, returning 400 if the source does not exist or
belongs to another team. Also adds test coverage for cross-team and
non-existent sourceId scenarios.
Addresses greptile review comments on unchecked source ownership.
Deep Review✅ No critical issues found. No P0/P1 ship-blockers: every handler scopes queries to the authenticated team, input is length-bounded via zod, no injection or auth-bypass path exists, and the loose 🟡 P2 — recommended
🔵 P3 nitpicks (7)
Reviewers (12): correctness, security, adversarial, api-contract, reliability, kieran-typescript, testing, maintainability, project-standards, performance, agent-native, learnings-researcher. Testing gaps:
Notes: |
- DELETE now routes through the deleteSavedSearch controller so alerts that
reference the search are cascade-deleted instead of orphaned
- create/update reuse controllers / set updatedBy so createdBy/updatedBy audit
metadata is persisted consistently with the internal API
- body schema defaults where/select/tags to empty values (matching the
internal invariant) and reuses the shared whereLanguage enum; accepts filters
- PUT now has deterministic full-replace semantics (omitted optional fields are
cleared) and is documented + tested
- define OpenAPI component schemas (fixes dangling CreateSavedSearchRequest
$ref) and register the Saved Searches swagger tag
- sourceId-not-found now returns { message } for a consistent error contract;
extracted assertOwnedSource helper
- add changeset for @hyperdx/api
- add tests: zod limits, whereLanguage enum rejection, invalid-id 400 on
PUT/DELETE, PUT field-omission semantics, alert cascade, empty delete body
…t filter schema Addresses follow-up greptile review: - add filters create/persist/GET round-trip test + malformed-filters rejection - add PUT test asserting explicitly-provided whereLanguage/orderBy/filters persist ($set branch) - assert GET list is sorted by name ascending across multiple documents - define a SavedSearchFilter OpenAPI component matching FilterSchema and reference it from the filters arrays instead of a bare 'type: object'
…d 403 docs Addresses latest greptile P2/P3 review: - filters now use a local boundedFilterSchema with .max(8192) on all string fields so 100 filters can't persist multi-MB documents - removed dead updateSavedSearch import - renamed assertOwnedSource → isSourceOwnedByTeam (predicate naming) - added '403' response to all OpenAPI endpoint blocks - new tests: sql_ast filter round-trip, PUT clears previously-set filters, select/orderBy/tag-length/filter-count negative validation
…s non-empty, add filter-length tests Addresses latest greptile P2: - fix \\ → in four 403 JSDoc blocks so openapi.json generates valid JSON References instead of unresolvable escaped strings - add .refine() to bodySchema requiring whereLanguage when where is non-empty, preventing downstream renderWhere from defaulting a Lucene expression to SQL - add rejection tests for over-length filter condition/left/right (8192 cap) and for non-empty where submitted without whereLanguage
…im, existence-first PUT P2 fixes: - Extracted replaceSavedSearch controller (mirrors updateConnection unsetFields pattern) so PUT no longer hand-rolls / inline - Added maxLength/maxItems to all OpenAPI request/response schemas matching the zod bounds (name: 1024, where: 8192, select: 4096, orderBy: 1024, tags: 50×32, filters: 100) - Documented full-replace semantics prominently and sourceId-change caveat P3 fixes: - PUT now checks saved-search existence before sourceId ownership (404 before 400) - Added .trim() to name so whitespace-only names are rejected (parity with internal) - Added 'Required when where is non-empty' to whereLanguage OpenAPI description - Documented sourceId-repoint risk in UpdateSavedSearchRequest - Added alert cross-reference guidance to SavedSearch.teamId description New tests: - whitespace-only name rejected - PUT rejects non-empty where without whereLanguage - PUT persists filters: [] as empty array (not unset) - PUT returns 404 before 400 when search doesn't exist (existence-first)
…CLEARABLE, minLength, tests P2 fixes: - DELETE cascade: alerts are now deleted before the search document so a partial failure doesn't orphan alerts (search still exists for retry) - boundedFilterSchema drift: CLEARABLE_ON_REPLACE is now typed as (keyof BodyOutput)[] so a field rename breaks compilation - replaceSavedSearch keeps typed fieldsToSet for compile-time checking P3 fixes: - whereLanguage now uses SearchConditionTrimmedLanguageSchema (excludes promql) preventing un-renderable PromQL queries against log/trace sources - Added minLength: 1 to name in all three OpenAPI schemas - Improved clears-filters assertion to distinguish unset from empty-array - OpenAPI whereLanguage enum updated to [lucene, sql] only New tests: - createdBy/createdAt preserved across PUT replace - whereLanguage: 'promql' rejected on PUT (400)
|
Hey @ankitcharolia - thank you for the contribution! Unfortunately it seems like a team member was concurrently working on the same thing in #2586 as part of ongoing terraform-provider work. We will likely move ahead with #2586 and close this PR, but if you have any thoughts on #2586 or think it will not cover your use-case, please let us know or feel free to comment there. |
Summary
Adds full CRUD endpoints for saved searches to the external API (
/api/v2/saved-searches), enabling infrastructure-as-code tooling to provision saved searches programmatically.The
SavedSearchmongoose model andtoExternalJSON()method already existed — only the router and its registration were missing.New endpoints:
/api/v2/saved-searches/api/v2/saved-searches/api/v2/saved-searches/:id/api/v2/saved-searches/:id/api/v2/saved-searches/:idScreenshots or video
/api/v2/saved-searches→ 404GET /api/v2/saved-searches→{ data: [...] }How to test on Vercel preview
Steps:
curl -H 'Authorization: Bearer <token>' <preview-url>/api/v2/saved-searches— should return{ "data": [] }name,sourceId, and optionalwhere/whereLanguage/select/orderBy/tagssavedSearchIdReferences
packages/api/src/routers/external-api/v2/connections.tspackages/api/src/routers/external-api/__tests__/savedSearches.test.ts