Skip to content

[Feature]: Date range picker on Knowledge page #2346

Open

Description

@Wallgau

Feature Area

Knowledge Filters (partitions, document filtering)

Problem Description

On /knowledge, users can search and filter by source, type, and owner, but not by when a document was created or ingested. Finding “files from last week” or “everything ingested in March” means paging through the grid.

Proposed Solution

Add a date-range picker to the Knowledge toolbar (next to KnowledgeSearchBar / the existing filter controls). Selecting a range should re-query the file list; clearing it should restore the unfiltered list.

Date field (decide in implementation, default below):

Default: created_time (OpenSearch date on chunks; already set by connectors).
Alternative: modified_time, or ingest time if we add/persist one for local uploads.
Inclusive range: start 00:00:00 local → end 23:59:59.999 local, sent to the API as UTC ISO-8601.

Documents with no timestamp should be excluded when a range is active (or listed under a documented “unknown date” behavior — pick one and test it).

Scope
In

Range picker UI on /knowledge (from + to, clear, optional presets: Last 7 days, Last 30 days, This month).
GET /files (and /v2/files if it shares the handler) query params, e.g. created_after / created_before.
OpenSearch range filter in FileServiceV2.list_files so pagination/total stay correct.
Wire through useListFiles and reset page/cursor when the range changes (same pattern as buildFilterPageResetKey).
Empty state when nothing matches the range.
Unit tests for the API filter; Playwright coverage on the Knowledge page.
Out

Saving the range as part of a Knowledge Filter (can be a follow-up).
Chat / retrieval date filtering.
Changing how connectors write created_time / modified_time.

Use Case

As a user, I want to pick a start and end date on the Knowledge page so the file grid only shows documents in that range.

Alternatives Considered

No response

Priority

Nice to have

Additional Context

No response

Contribution

  • I would be willing to help implement this feature.
  • I can help test this feature once implemented.

Checklist

  • I have searched existing issues and discussions to ensure this feature hasn't been requested before.

Activity

  1. coderabbitai commented on Sep 9, 2026

    @coderabbitai
    Contributor

    Coding Plan

    Summary

    • Add created_after/created_before to the list endpoints and translate them into a single OpenSearch range clause on created_time inside FileServiceV2._build_filter_query, reusing the existing shared query so totals and pagination stay consistent.
    • Build a new date-range picker on the existing Radix Popover with react-day-picker, store the range as toolbar-level context state (not part of a saved filter), and convert local day boundaries to inclusive UTC ISO-8601 bounds.
    • Thread the bounds through useListFiles, include them in the pagination reset key, and keep them out of the search-mode switch so the filter stays confined to the list path and does not affect chat/agent retrieval.
    • Cover the API filter with direct query-builder and handler-forwarding unit tests, and cover the Knowledge page with a Playwright test for applying and clearing the range.
    Design Choices
    Design Choice 1: Which date field backs the filter?

    Options Considered:

    1. Use created_time. This matches the ticket default. Documents without created_time (local uploads and legacy chunks) are excluded when a range is active.
    2. Use indexed_time. This includes local uploads because the field is always present, but it represents ingest time, not document creation time.

    Chosen Option: 1

    Rationale: The ticket names created_time as the default and states that documents with no timestamp must be excluded when a range is active. This stays in scope and requires no changes to how connectors or uploads write timestamps.

    Design Choice 2: Does the date filter apply to semantic search mode?

    Options Considered:

    1. Apply the date filter only in list mode (useListFiles/GET /files). Keep the date range as separate toolbar state, not part of parsedFilterData, so it never routes through search_service.
    2. Thread the date range through both list mode and search mode. This requires changes to search_service.py, which also affects chat and agent retrieval.

    Chosen Option: 1

    Rationale: The ticket explicitly excludes chat/retrieval date filtering. search_service.search() is shared with the agent, so modifying it would leak date filtering into retrieval. Confining the filter to the list path keeps the change in scope.

    Design Choice 3: How is the range picker UI built, given no date library exists?

    Options Considered:

    1. Add react-day-picker and build a shadcn-style Calendar on top of the existing Radix Popover. This gives a standard two-month range calendar and presets.
    2. Hand-roll two native <input type="date"> controls inside a Popover. This avoids a new dependency but gives a weaker range UX.

    Chosen Option: 1

    Rationale: react-day-picker is the standard companion for the existing Radix Popover pattern and provides proper range selection and preset support with minimal custom code.

    💡 User Tips

    Regenerate the plan with different choices with @coderabbitai <feedback>.

    Implementation Steps

    Phase 1: Backend date-range filter

    This phase adds the created_after/created_before parameters to the list endpoints and translates them into an OpenSearch range clause. The clause joins the existing bool.filter list, so totals, composite-aggregation pagination, and the chunk-count path all scope to the same filtered set automatically.

    Task 1: Add a reusable date-range query helper

    Provide a single, testable builder for a range clause on a date field.

    • Create build_date_range_query(field, gte=None, lte=None) in src/utils/opensearch_queries.py, following the style of the existing filename builders.
    • Return a {"range": {field: {...}}} dict. Include gte only when created_after is set and lte only when created_before is set.
    • Return None when both bounds are absent, so callers can skip appending an empty clause.
    Task 2: Apply the range clause in FileServiceV2

    Thread the new bounds through the query builder and the list path.

    • Update FileServiceV2._build_filter_query in src/services/file_service_v2.py to accept created_after and created_before.
    • Use the helper from Task 1.1 to append the range clause on created_time to filter_clauses.
    • Add created_after and created_before parameters to FileServiceV2.list_files and pass them to _build_filter_query.
    • Do not change _get_file_count, _build_composite_aggregation, or the chunk-count path; they already reuse the same query, so totals and pagination stay correct.
    Task 3: Add the API query parameters

    Expose the new bounds on both the internal and public list handlers.

    • Add created_after: str | None = Query(None) and created_before: str | None = Query(None) to list_files in src/api/files.py and forward them to FileServiceV2.list_files.
    • Mirror the same parameters in the /v2/files wrapper in src/api/v2/files.py so the internal and public signatures stay in parity.
    • Treat the values as opaque ISO-8601 strings and pass them through unchanged. Do not add server-side timezone conversion.
    🤖 Prompt for AI agents
    Implement the backend date-range filter for the file list endpoints. Goal: add
    `created_after`/`created_before` support and translate them into an OpenSearch
    `range` clause that joins the existing `bool.filter` list.
    
    - Create `build_date_range_query(field, gte=None, lte=None)` in
    `src/utils/opensearch_queries.py`, following the style of the existing filename
    builders. Return `{"range": {field: {...}}}`, include `gte` only when
    `created_after` is set and `lte` only when `created_before` is set, and return
    `None` when both bounds are absent.
    - Update `FileServiceV2._build_filter_query` in
    `src/services/file_service_v2.py` to accept `created_after` and
    `created_before`, and use the new helper to append the `range` clause on
    `created_time` to `filter_clauses`.
    - Add `created_after` and `created_before` parameters to
    `FileServiceV2.list_files` and pass them to `_build_filter_query`. Do not change
    `_get_file_count`, `_build_composite_aggregation`, or the chunk-count path,
    since they already reuse the same `query`.
    - Add `created_after: str | None = Query(None)` and `created_before: str | None
    = Query(None)` to `list_files` in `src/api/files.py` and forward them to
    `FileServiceV2.list_files`.
    - Mirror the same two parameters in the `/v2/files` wrapper in
    `src/api/v2/files.py` to keep the internal and public signatures in parity.
    - Treat the values as opaque ISO-8601 strings and pass them through unchanged.
    Do not add server-side timezone conversion.
    

    Phase 2: Frontend date-range picker and data wiring

    This phase adds the toolbar control, the local-to-UTC conversion, the data-fetch parameters, the pagination reset, and the filter-aware empty state. The date range is toolbar-level state and is not part of a saved Knowledge Filter.

    Task 1: Add local-day-to-UTC conversion helpers

    Convert a selected local calendar day range into inclusive UTC ISO-8601 bounds.

    • Add exported helpers to frontend/lib/time-utils.ts, for example toUtcDayStartISOString(date) and toUtcDayEndISOString(date).
    • Map the start date to local 00:00:00.000 and the end date to local 23:59:59.999, then emit toISOString() (UTC).
    • Use native Date APIs. Do not add a date library for this conversion.
    Task 2: Add date-range state to the filter context

    Store the selected range separately from saved-filter state.

    • Extend KnowledgeFilterContext in frontend/contexts/knowledge-filter-context.tsx with dateRange: { from?: Date; to?: Date } | null and a setDateRange setter.
    • Do not add the date range to ParsedQueryData or parsedFilterData.filters. Saving the range as part of a Knowledge Filter is out of scope.
    • Initialize dateRange to null (unfiltered).
    Task 3: Build the calendar and range picker components

    Add the missing UI primitives on top of the existing Radix Popover.

    • Add react-day-picker to frontend/package.json.
    • Create frontend/components/ui/calendar.tsx as a shadcn-style wrapper over react-day-picker, styled with cn(...) to match existing components/ui conventions.
    • Create frontend/components/knowledge-date-range-filter.tsx. Use Popover + PopoverTrigger + PopoverContent, following frontend/components/filter-icon-popover.tsx.
    • Support a from/to range, a clear control that resets the range to null, and optional presets ("Last 7 days", "Last 30 days", "This month").
    • Read and write the range through useKnowledgeFilter() (dateRange/setDateRange).
    Task 4: Place the picker in the toolbar

    Add the control next to the existing filter controls in both brand layouts.

    • In the non-cloud branch of frontend/app/knowledge/page.tsx, add KnowledgeDateRangeFilter as a flex-shrink-0 item in the toolbar row, near the search input.
    • In the cloud branch, add the control to the <form> row in frontend/components/knowledge-search-bar.tsx, alongside the existing buttons before KnowledgeDropdown.
    Task 5: Wire the range into the list query and pagination reset

    Send the bounds to the list endpoint and reset the cursor when the range changes.

    • Add createdAfter?: string and createdBefore?: string to ListFilesParams in frontend/app/api/queries/useListFiles.ts and serialize them to created_after/created_before.
    • In frontend/app/knowledge/page.tsx, convert dateRange with the Task 2.1 helpers and pass createdAfter/createdBefore into the useListFiles params.
    • Include the converted bounds in buildFilterPageResetKey (or add them as dependencies of the reset useEffect) so the cursor cache clears and currentPage resets to 1 when the range changes.
    • Keep the date range out of hasActiveFilters/isWildcardQuery so that setting a range does not switch the page into semantic search mode.
    Task 6: Make the empty state filter-aware

    Show clear feedback when a range matches no documents.

    • Update the noRowsOverlayComponent in frontend/app/knowledge/page.tsx (both grid branches) to show a "no results for the selected date range" message when a date range is active and serverTotal === 0.
    • Keep the existing "No knowledge" copy for the true-empty-library case (no active range).
    🤖 Prompt for AI agents
    Implement the frontend date-range picker for the Knowledge page toolbar and wire
    it into the list data fetch. Goal: let users select a date range that filters
    the file grid, without affecting saved Knowledge Filters or semantic search
    mode.
    
    - Add exported helpers `toUtcDayStartISOString(date)` and
    `toUtcDayEndISOString(date)` to `frontend/lib/time-utils.ts`. Map the start date
    to local `00:00:00.000` and the end date to local `23:59:59.999`, then emit
    `toISOString()` (UTC). Use native `Date` APIs only; do not add a date library
    for this conversion.
    - Extend `KnowledgeFilterContext` in
    `frontend/contexts/knowledge-filter-context.tsx` with `dateRange: { from?: Date;
    to?: Date } | null` and a `setDateRange` setter, initialized to `null`. Do not
    add the date range to `ParsedQueryData` or `parsedFilterData.filters`.
    - Add `react-day-picker` to `frontend/package.json`. Create
    `frontend/components/ui/calendar.tsx` as a shadcn-style wrapper over
    `react-day-picker`, styled with `cn(...)` to match existing `components/ui`
    conventions.
    - Create `frontend/components/knowledge-date-range-filter.tsx` using `Popover` +
    `PopoverTrigger` + `PopoverContent`, following the pattern in
    `frontend/components/filter-icon-popover.tsx`. Support a from/to range, a clear
    control that resets the range to `null`, and optional presets ("Last 7 days",
    "Last 30 days", "This month"). Read and write the range through
    `useKnowledgeFilter()`.
    - Place `KnowledgeDateRangeFilter` in the toolbar: as a `flex-shrink-0` item
    near the search input in the non-cloud branch of
    `frontend/app/knowledge/page.tsx`, and in the `<form>` row of
    `frontend/components/knowledge-search-bar.tsx` (alongside the existing buttons,
    before `KnowledgeDropdown`) for the cloud branch.
    - Add `createdAfter?: string` and `createdBefore?: string` to `ListFilesParams`
    in `frontend/app/api/queries/useListFiles.ts`, serialized to
    `created_after`/`created_before`. In `frontend/app/knowledge/page.tsx`, convert
    `dateRange` using the new time helpers and pass the converted bounds into the
    `useListFiles` params.
    - Include the converted bounds in `buildFilterPageResetKey` (or as dependencies
    of the reset `useEffect`) so the cursor cache clears and `currentPage` resets to
    1 when the range changes. Keep the date range out of
    `hasActiveFilters`/`isWildcardQuery` so it does not trigger semantic search
    mode.
    - Update `noRowsOverlayComponent` in `frontend/app/knowledge/page.tsx` (both
    grid branches) to show a "no results for the selected date range" message when a
    date range is active and `serverTotal === 0`, while keeping the existing "No
    knowledge" copy for the true-empty-library case.
    

    Phase 3: Tests

    This phase covers the API filter with unit tests and the Knowledge page with Playwright, following existing conventions.

    Task 1: Unit-test the query builder

    Assert the range clause is constructed correctly.

    • Add tests in tests/unit/test_query_acl_filtering.py calling FileServiceV2()._build_filter_query(...) directly (no mocks).
    • Assert a created_after value produces {"range": {"created_time": {"gte": ...}}} in query["bool"]["filter"].
    • Assert both bounds combine into a single range clause with gte and lte.
    • Assert that documents without created_time are excluded by the range clause (documented exclusion behavior).
    Task 2: Unit-test API parameter forwarding

    Confirm the handlers pass the new params to the service.

    • Add tests in tests/unit/api/test_v1_files.py that mock FileServiceV2 and assert call_kwargs["created_after"] and call_kwargs["created_before"] are forwarded, following test_forwards_all_params_to_service.
    • Extend TestSignatureParity so the internal and public list_files signatures include the new parameters.
    Task 3: Add Playwright coverage

    Verify the picker filters the grid and the clear action restores it.

    • Add page-object helpers to frontend/tests/pages/Knowledge.ts, for example setKnowledgeDateRange(from, to) and clearKnowledgeDateRange(), following the "open control → select → close" pattern used by createKnowledgeFilter.
    • Add a spec near frontend/tests/core/knowledge_filter.spec.ts that ingests a file, applies a date range, asserts the expected rows appear or disappear (using getDataRows/getSearchResults), clears the range, and asserts the unfiltered grid returns.
    • Follow the existing idempotent setup/teardown and extended test.setTimeout conventions.
    🤖 Prompt for AI agents
    Implement automated test coverage for the date-range filter, on both the backend
    and the frontend. Goal: verify the query builder, API parameter forwarding, and
    the end-to-end toolbar behavior.
    
    - Add tests in `tests/unit/test_query_acl_filtering.py` that call
    `FileServiceV2()._build_filter_query(...)` directly (no mocks). Assert that a
    `created_after` value produces `{"range": {"created_time": {"gte": ...}}}` in
    `query["bool"]["filter"]`, that both bounds combine into a single `range` clause
    with `gte` and `lte`, and that documents without `created_time` are excluded by
    the range clause.
    - Add tests in `tests/unit/api/test_v1_files.py` that mock `FileServiceV2` and
    assert `call_kwargs["created_after"]` and `call_kwargs["created_before"]` are
    forwarded, following the existing `test_forwards_all_params_to_service` pattern.
    Extend `TestSignatureParity` so the internal and public `list_files` signatures
    include the new parameters.
    - Add page-object helpers to `frontend/tests/pages/Knowledge.ts`, for example
    `setKnowledgeDateRange(from, to)` and `clearKnowledgeDateRange()`, following the
    "open control → select → close" pattern used by `createKnowledgeFilter`.
    - Add a spec near `frontend/tests/core/knowledge_filter.spec.ts` that ingests a
    file, applies a date range, asserts the expected rows appear or disappear (using
    `getDataRows`/`getSearchResults`), clears the range, and asserts the unfiltered
    grid returns. Follow the existing idempotent setup/teardown and extended
    `test.setTimeout` conventions.
    
    Research

    The Knowledge page (frontend/app/knowledge/page.tsx) owns pagination and cursor state. It toggles between list mode (useListFiles → GET /api/files) and semantic search mode (useGetSearchQuery → /api/search) based on isWildcardQuery. Filter state (source/type/owner/data_sources) lives in KnowledgeFilterContext under parsedFilterData.filters. The backend list path uses FastAPI Query(...) params in src/api/files.py, wrapped by API-key handlers in src/api/v2/files.py, and translated into an OpenSearch bool query by FileServiceV2._build_filter_query. The same query drives count, composite-aggregation pagination, and chunk-count aggregation, so filters stay consistent across totals and pages. The frontend has no date library and no calendar component today.


    🚀 Next Steps

    🤖 All AI agent prompts combined
    Task: 1
    
    Implement the backend date-range filter for the file list endpoints. Goal: add
    `created_after`/`created_before` support and translate them into an OpenSearch
    `range` clause that joins the existing `bool.filter` list.
    
    - Create `build_date_range_query(field, gte=None, lte=None)` in
    `src/utils/opensearch_queries.py`, following the style of the existing filename
    builders. Return `{"range": {field: {...}}}`, include `gte` only when
    `created_after` is set and `lte` only when `created_before` is set, and return
    `None` when both bounds are absent.
    - Update `FileServiceV2._build_filter_query` in
    `src/services/file_service_v2.py` to accept `created_after` and
    `created_before`, and use the new helper to append the `range` clause on
    `created_time` to `filter_clauses`.
    - Add `created_after` and `created_before` parameters to
    `FileServiceV2.list_files` and pass them to `_build_filter_query`. Do not change
    `_get_file_count`, `_build_composite_aggregation`, or the chunk-count path,
    since they already reuse the same `query`.
    - Add `created_after: str | None = Query(None)` and `created_before: str | None
    = Query(None)` to `list_files` in `src/api/files.py` and forward them to
    `FileServiceV2.list_files`.
    - Mirror the same two parameters in the `/v2/files` wrapper in
    `src/api/v2/files.py` to keep the internal and public signatures in parity.
    - Treat the values as opaque ISO-8601 strings and pass them through unchanged.
    Do not add server-side timezone conversion.
    ===============================================================================
    
    Task: 2
    
    Implement the frontend date-range picker for the Knowledge page toolbar and wire
    it into the list data fetch. Goal: let users select a date range that filters
    the file grid, without affecting saved Knowledge Filters or semantic search
    mode.
    
    - Add exported helpers `toUtcDayStartISOString(date)` and
    `toUtcDayEndISOString(date)` to `frontend/lib/time-utils.ts`. Map the start date
    to local `00:00:00.000` and the end date to local `23:59:59.999`, then emit
    `toISOString()` (UTC). Use native `Date` APIs only; do not add a date library
    for this conversion.
    - Extend `KnowledgeFilterContext` in
    `frontend/contexts/knowledge-filter-context.tsx` with `dateRange: { from?: Date;
    to?: Date } | null` and a `setDateRange` setter, initialized to `null`. Do not
    add the date range to `ParsedQueryData` or `parsedFilterData.filters`.
    - Add `react-day-picker` to `frontend/package.json`. Create
    `frontend/components/ui/calendar.tsx` as a shadcn-style wrapper over
    `react-day-picker`, styled with `cn(...)` to match existing `components/ui`
    conventions.
    - Create `frontend/components/knowledge-date-range-filter.tsx` using `Popover` +
    `PopoverTrigger` + `PopoverContent`, following the pattern in
    `frontend/components/filter-icon-popover.tsx`. Support a from/to range, a clear
    control that resets the range to `null`, and optional presets ("Last 7 days",
    "Last 30 days", "This month"). Read and write the range through
    `useKnowledgeFilter()`.
    - Place `KnowledgeDateRangeFilter` in the toolbar: as a `flex-shrink-0` item
    near the search input in the non-cloud branch of
    `frontend/app/knowledge/page.tsx`, and in the `<form>` row of
    `frontend/components/knowledge-search-bar.tsx` (alongside the existing buttons,
    before `KnowledgeDropdown`) for the cloud branch.
    - Add `createdAfter?: string` and `createdBefore?: string` to `ListFilesParams`
    in `frontend/app/api/queries/useListFiles.ts`, serialized to
    `created_after`/`created_before`. In `frontend/app/knowledge/page.tsx`, convert
    `dateRange` using the new time helpers and pass the converted bounds into the
    `useListFiles` params.
    - Include the converted bounds in `buildFilterPageResetKey` (or as dependencies
    of the reset `useEffect`) so the cursor cache clears and `currentPage` resets to
    1 when the range changes. Keep the date range out of
    `hasActiveFilters`/`isWildcardQuery` so it does not trigger semantic search
    mode.
    - Update `noRowsOverlayComponent` in `frontend/app/knowledge/page.tsx` (both
    grid branches) to show a "no results for the selected date range" message when a
    date range is active and `serverTotal === 0`, while keeping the existing "No
    knowledge" copy for the true-empty-library case.
    ===============================================================================
    
    Task: 3
    
    Implement automated test coverage for the date-range filter, on both the backend
    and the frontend. Goal: verify the query builder, API parameter forwarding, and
    the end-to-end toolbar behavior.
    
    - Add tests in `tests/unit/test_query_acl_filtering.py` that call
    `FileServiceV2()._build_filter_query(...)` directly (no mocks). Assert that a
    `created_after` value produces `{"range": {"created_time": {"gte": ...}}}` in
    `query["bool"]["filter"]`, that both bounds combine into a single `range` clause
    with `gte` and `lte`, and that documents without `created_time` are excluded by
    the range clause.
    - Add tests in `tests/unit/api/test_v1_files.py` that mock `FileServiceV2` and
    assert `call_kwargs["created_after"]` and `call_kwargs["created_before"]` are
    forwarded, following the existing `test_forwards_all_params_to_service` pattern.
    Extend `TestSignatureParity` so the internal and public `list_files` signatures
    include the new parameters.
    - Add page-object helpers to `frontend/tests/pages/Knowledge.ts`, for example
    `setKnowledgeDateRange(from, to)` and `clearKnowledgeDateRange()`, following the
    "open control → select → close" pattern used by `createKnowledgeFilter`.
    - Add a spec near `frontend/tests/core/knowledge_filter.spec.ts` that ingests a
    file, applies a date range, asserts the expected rows appear or disappear (using
    `getDataRows`/`getSearchResults`), clears the range, and asserts the unfiltered
    grid returns. Follow the existing idempotent setup/teardown and extended
    `test.setTimeout` conventions.
    
    💡 Iterate on the plan with: `@coderabbitai` <feedback>
    Example Feedback
    - `@coderabbitai` You can skip phase 3. Add a simple unit test case for phase 2.
    - `@coderabbitai` For design choice 1 go ahead with option 3 and replan.
    

    Start Coding

  2. self-assigned this
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

enhancement🔵 New feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions