Skip to content

Add query parameter support for filter deep-linking#192

Open
Copilot wants to merge 5 commits intomasterfrom
copilot/add-query-parameters-to-filtervalues
Open

Add query parameter support for filter deep-linking#192
Copilot wants to merge 5 commits intomasterfrom
copilot/add-query-parameters-to-filtervalues

Conversation

Copy link

Copilot AI commented Jan 26, 2026

Enables linking to serilog-ui with pre-applied filters via URL query parameters. Previously, filter state existed only in-memory and was lost on page reload. External systems and team members can now share direct links to specific logs or timeframes.

Changes

Core Implementation

  • queryParams.ts: Parser/serializer for URL ↔ SearchForm conversion with validation
    • Supports parameter aliases (from/startDate, to/till/endDate, key/table)
    • Omits default values for clean URLs
  • useQueryParamSync.ts: React hook for bidirectional form ↔ URL sync
    • Initializes filters from URL on mount
    • Updates URL on form changes with ref-based deduplication to prevent infinite loops
  • Index.tsx: Enabled sync hook

Tests

  • 12 unit tests covering parse/serialize edge cases and validation

Documentation

  • README section with parameter reference and examples

Example Usage

# Link to errors in specific timeframe
/serilog-ui?level=Error&startDate=2024-01-25T00:00:00Z&endDate=2024-01-26T00:00:00Z

# Link to specific table with search
/serilog-ui?table=ProductionLogs&search=payment

# URLs update automatically as filters change

Supported parameters: table/key, level, search, startDate/from, endDate/to/till, sortOn, sortBy, page, count/entriesPerPage

Original prompt

This section details on the original issue you should resolve

<issue_title>Query parameters for the filtervalues</issue_title>
<issue_description>Is your feature request related to a problem? Please describe.

We also have an external system showing debug info, and it would be cool to be able to generate a link to serilog-ui for specific logs or timeframes so we can have minimal and easy 'integration'.

Describe the solution you'd like
A clear and concise description of what you want to happen.

Allow for the filter options to be set by query parameters, for example;

/serilog-ui?q={table: '...', fromDt: ..., tillDt: ...}

In that sample you'd just load a filters object in the frontend and bob's your uncle.

Else you can map query params;
/serilog-ui?from=...&till=...

It'd be nice to be able to link to individual log lines via a trace id or something - but that'd be a second step.
Above should be low-effort.

The FE could also actively set the query params so that sharing would be easy between devs.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

There is no alternative to link to a certain log line or timeframe at this time

Additional context
Add any other context or screenshots about the feature request here.
</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Add query parameters for filter values in serilog-ui Add query parameter support for filter deep-linking Jan 26, 2026
Copilot AI requested a review from mo-esmp January 26, 2026 07:37
@sommmen
Copy link
Contributor

sommmen commented Mar 3, 2026

Great how this seems to have just one-shotted this but i cant comment on react/next a lot here.

@mo-esmp
Copy link
Member

mo-esmp commented Mar 3, 2026

@followynne It would be great if you could review the React code when you have time.

@mo-esmp mo-esmp marked this pull request as ready for review March 3, 2026 08:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds deep-linking support by synchronizing Serilog UI filter state with URL query parameters, so filtered views can be shared/bookmarked and survive reloads.

Changes:

  • Added query param parsing/serialization utilities for SearchForm with alias support.
  • Added a React hook to initialize filters from the URL and to update the URL as filters change.
  • Added unit tests and README documentation for supported parameters.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/Serilog.Ui.Web/src/app/util/queryParams.ts Implements URL query parsing/serialization for filter state.
src/Serilog.Ui.Web/src/app/hooks/useQueryParamSync.ts Synchronizes form state with URL query parameters.
src/Serilog.Ui.Web/src/app/components/Index.tsx Enables query-param synchronization hook at app entry.
src/Serilog.Ui.Web/src/tests/util/queryParams.spec.ts Adds unit coverage for parse/serialize behavior.
README.md Documents supported query parameters and examples.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +98 to +109
| Parameter | Aliases | Description | Example |
|-----------|---------|-------------|---------|
| `table` | `key` | The log table/database to query | `?table=MyLogs` |
| `level` | - | Filter by log level | `?level=Error` |
| `search` | - | Search text filter | `?search=exception` |
| `startDate` | `from` | Start date/time (ISO 8601) | `?startDate=2024-01-01T00:00:00Z` |
| `endDate` | `to`, `till` | End date/time (ISO 8601) | `?endDate=2024-01-02T00:00:00Z` |
| `sortOn` | - | Property to sort by | `?sortOn=Timestamp` |
| `sortBy` | - | Sort direction (`Asc` or `Desc`) | `?sortBy=Desc` |
| `page` | - | Page number | `?page=2` |
| `count` | `entriesPerPage` | Number of entries per page | `?count=50` |

Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README’s markdown table is malformed (each row starts with ||), which renders as an extra empty column in most markdown parsers. Use single leading/trailing | for each row so the table formats correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +193 to +203
it('includes table even if empty string', () => {
const form: SearchForm = {
...baseForm,
table: '',
};

const result = serializeSearchParams(form);

// Empty string should not be included
expect(result.get('table')).toBeNull();
});
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case name contradicts its assertions: it says "includes table even if empty string" but the expectation verifies the table param is omitted when table is an empty string. Rename the test (and/or adjust the inline comment) to match the behavior being asserted.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +23
// Initialize form from URL params on mount
useEffect(() => {
if (isInitialized.current || !tableKeys || !isArrayGuard(tableKeys)) return;

const urlParams = parseSearchParams(searchParams);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useQueryParamSync only applies searchParams to the form once (guarded by isInitialized). This means browser back/forward navigation or manually edited query params after initial load will not update the form, despite the hook intending bidirectional sync. Consider handling subsequent searchParams changes when they differ from lastParamsRef.current by re-parsing and updating the form (while still using the ref to avoid loops).

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +73
if (entriesPerPage) {
const count = parseInt(entriesPerPage, 10);
// Validate it's a positive number
if (!isNaN(count) && count > 0) {
result.entriesPerPage = entriesPerPage;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entriesPerPage validation parses the value but then stores the original string. This allows non-integer strings like "20.5" or "10abc" to pass (because parseInt succeeds) and then propagate to the form/URL, even though the UI only supports specific numeric string options (e.g. 10/25/50/100). Consider validating the raw string strictly (digits-only) and/or normalizing to count.toString() and restricting to supported values.

Suggested change
if (entriesPerPage) {
const count = parseInt(entriesPerPage, 10);
// Validate it's a positive number
if (!isNaN(count) && count > 0) {
result.entriesPerPage = entriesPerPage;
if (entriesPerPage && /^[0-9]+$/.test(entriesPerPage)) {
const count = parseInt(entriesPerPage, 10);
// Validate it's a positive integer and normalize the value
if (!isNaN(count) && count > 0) {
result.entriesPerPage = count.toString();

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +35
// Ensure we have at least one table key
if (tableKeys.length === 0) return;

const tableKeysDefaultValue = tableKeys[0];

// Validate and apply URL params to the form
Object.entries(urlParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
// Special handling for table - ensure it's valid
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isArrayGuard(tableKeys) already guarantees tableKeys.length > 0 (it checks value?.length > 0), so the later if (tableKeys.length === 0) return; is redundant and can be removed to simplify the initialization logic.

Suggested change
// Ensure we have at least one table key
if (tableKeys.length === 0) return;
const tableKeysDefaultValue = tableKeys[0];
// Validate and apply URL params to the form
Object.entries(urlParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
// Special handling for table - ensure it's valid
const tableKeysDefaultValue = tableKeys[0];
// Validate and apply URL params to the form
Object.entries(urlParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
// Special handling for table - ensure it's valid
Object.entries(urlParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
// Special handling for table - ensure it's valid

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Query parameters for the filtervalues

4 participants