Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .kiro/specs/event-explorer-filters/.config.kiro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"specId": "9cc9bcb6-936c-486e-9b84-7647b17281e6", "workflowType": "requirements-first", "specType": "feature"}
155 changes: 155 additions & 0 deletions .kiro/specs/event-explorer-filters/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Requirements Document

## Introduction

This feature enhances the Event Explorer page in the Notify-Chain Web3 dashboard with an advanced search and multi-filter system. The current filtering supports a single free-text search, a single-select contract address dropdown, and a single-select event type dropdown with no debounce. The enhancement introduces debounced search, a multi-select event type filter (using the `type` field on `BlockchainEvent`), a date range filter against the `receivedAt` timestamp, a clear-all action, an improved empty state, and ensures performance, responsive layout, and accessibility standards are met.

## Glossary

- **EventExplorer**: The Event Explorer page component (`EventExplorerPage`) that renders the filter bar, event table, pagination, and empty state.
- **EventFiltersBar**: The React component that renders all filter controls (search input, contract filter, event type multi-select, date range inputs, clear-all button).
- **FilterEngine**: The `filterEvents` utility function in `eventData.ts` that applies all active filters to the in-memory event list.
- **EventStore**: The Zustand store (`eventStore.ts`) that holds `events`, `filters`, loading state, and all filter-mutation actions.
- **BlockchainEvent**: The TypeScript interface representing a single on-chain event, containing `eventId`, `contractAddress`, `eventName`, `ledger`, `type`, `topic`, `value`, `txHash`, and `receivedAt`.
- **EventFilters**: The TypeScript interface that holds all active filter state: `search`, `contractAddress`, `eventTypes` (set of selected type strings), `startDate`, and `endDate`.
- **DebounceInterval**: The 300 ms delay applied to the search input before the filter state is updated in the EventStore.
- **ReceivedAt**: The Unix millisecond timestamp field on `BlockchainEvent` used for date range filtering.
- **StartOfDayUTC**: Midnight (00:00:00.000 UTC) on the selected start date, expressed as a Unix ms timestamp.
- **EndOfDayUTC**: 23:59:59.999 UTC on the selected end date, expressed as a Unix ms timestamp.
- **ClearAllAction**: The Zustand store action that resets all filter fields to their default (empty/all) values in a single atomic update.

---

## Requirements

### Requirement 1: Debounced Contract ID Text Search

**User Story:** As a blockchain analyst, I want a debounced text search across event identifiers, so that the filter does not re-execute on every keystroke and the UI remains responsive during fast typing.

#### Acceptance Criteria

1. THE EventFiltersBar SHALL render a text input with a visible label for the search field.
2. WHEN the user types in the search input, THE EventFiltersBar SHALL update the visible input value immediately without delay.
3. WHEN the user stops typing for 300 ms, THE EventStore SHALL update the `search` field in `EventFilters` with the current input value.
4. WHEN the user resumes typing before 300 ms have elapsed, THE EventFiltersBar SHALL reset the debounce timer and not dispatch a store update.
5. WHEN the `search` filter is non-empty, THE FilterEngine SHALL include only events whose `contractAddress`, `eventId`, `eventName`, or `txHash` contains the search string (case-insensitive substring match).
6. WHEN the `search` filter is empty, THE FilterEngine SHALL apply no search restriction and return events matching the remaining active filters.

---

### Requirement 2: Event Type Multi-Select Filter

**User Story:** As a blockchain analyst, I want to select multiple event types simultaneously, so that I can view events of more than one type in a single filtered result set.

#### Acceptance Criteria

1. THE EventFiltersBar SHALL render a multi-select control with one toggle per distinct `type` value present in the loaded `BlockchainEvent` set (values: `contract`, `system`, `diagnostic`).
2. WHEN no event types are selected, THE FilterEngine SHALL apply no type restriction and return events matching the remaining active filters.
3. WHEN one or more event types are selected, THE FilterEngine SHALL include only events whose `type` field matches any of the selected types (OR logic within the type set).
4. WHEN an event type toggle is activated, THE EventStore SHALL add that type to the `eventTypes` set in `EventFilters`.
5. WHEN an active event type toggle is deactivated, THE EventStore SHALL remove that type from the `eventTypes` set in `EventFilters`.
6. THE EventFiltersBar SHALL visually distinguish selected type toggles from unselected ones.

---

### Requirement 3: Date Range Filter

**User Story:** As a blockchain analyst, I want to filter events by a date range, so that I can focus on events that occurred within a specific time window.

#### Acceptance Criteria

1. THE EventFiltersBar SHALL render a Start Date input and an End Date input using native HTML date inputs.
2. WHEN a Start Date is set and no End Date is set, THE FilterEngine SHALL include only events whose `receivedAt` is greater than or equal to the StartOfDayUTC of the selected start date.
3. WHEN an End Date is set and no Start Date is set, THE FilterEngine SHALL include only events whose `receivedAt` is less than or equal to the EndOfDayUTC of the selected end date.
4. WHEN both a Start Date and End Date are set, THE FilterEngine SHALL include only events whose `receivedAt` falls within the inclusive range from StartOfDayUTC to EndOfDayUTC.
5. IF a Start Date and End Date are both set and the Start Date is after the End Date, THEN THE FilterEngine SHALL return zero events for the date filter criterion.
6. WHEN neither Start Date nor End Date is set, THE FilterEngine SHALL apply no date restriction.
7. THE EventFiltersBar SHALL render visible labels for both the Start Date and End Date inputs.

---

### Requirement 4: Additive AND Filter Logic

**User Story:** As a blockchain analyst, I want all active filters to apply simultaneously, so that I can narrow results precisely using multiple criteria at once.

#### Acceptance Criteria

1. WHEN multiple filters are active, THE FilterEngine SHALL include only events that satisfy every active filter condition simultaneously (AND logic across filter dimensions).
2. THE FilterEngine SHALL apply the search filter, contract address filter, event type filter, and date range filter as independent, composable predicates.
3. WHEN a filter field holds its default value (`search` is empty, `contractAddress` is `'all'`, `eventTypes` is empty, `startDate` and `endDate` are empty), THE FilterEngine SHALL treat that filter as inactive and not exclude events based on it.

---

### Requirement 5: Clear All Filters

**User Story:** As a blockchain analyst, I want a single button to reset all filters at once, so that I can return to the full unfiltered event list without manually resetting each control.

#### Acceptance Criteria

1. THE EventFiltersBar SHALL render a visible "Clear filters" button.
2. WHEN the "Clear filters" button is activated, THE EventStore SHALL atomically reset `search` to `''`, `contractAddress` to `'all'`, `eventTypes` to an empty set, `startDate` to `''`, and `endDate` to `''` via the ClearAllAction.
3. WHEN the ClearAllAction is dispatched, THE EventExplorer SHALL reset the current page to 1.
4. WHEN all filters are already at their default values, THE EventFiltersBar SHALL render the "Clear filters" button in a visually disabled state.

---

### Requirement 6: Empty State for Zero Filtered Results

**User Story:** As a blockchain analyst, I want a clear message when my filters return no results, so that I understand the list is empty due to active filters rather than a data loading problem.

#### Acceptance Criteria

1. WHEN the FilterEngine returns zero events and at least one filter is active, THE EventExplorer SHALL render a heading containing "No matching events found".
2. WHEN the FilterEngine returns zero events and at least one filter is active, THE EventExplorer SHALL render body text containing "Try adjusting or clearing your filters to see more results."
3. WHEN the FilterEngine returns zero events and no filters are active, THE EventExplorer SHALL render the existing "No events found" empty state.
4. THE EventExplorer SHALL render the empty state region with `role="status"` and `aria-live="polite"` so assistive technologies announce the state change.

---

### Requirement 7: Performance

**User Story:** As a user browsing the Event Explorer, I want filter operations to complete without perceptible lag even when thousands of events are loaded, so that interaction remains smooth.

#### Acceptance Criteria

1. THE EventExplorer SHALL derive the filtered event list using `useMemo`, recomputing only when `events` or `filters` change.
2. WHEN the user types in the search input, THE EventExplorer SHALL not recompute the filtered list until the DebounceInterval has elapsed.
3. WHEN any filter changes, THE EventExplorer SHALL not trigger a full-page reload or re-fetch of events.

---

### Requirement 8: Responsive Filter Panel Layout

**User Story:** As a user on a mobile device, I want the filter controls to stack vertically, so that they are accessible and usable on small screens.

#### Acceptance Criteria

1. WHILE the viewport width is below 768 px, THE EventFiltersBar SHALL render all filter controls in a single-column vertical stack.
2. WHILE the viewport width is 768 px or greater, THE EventFiltersBar SHALL render filter controls in a horizontal row layout.

---

### Requirement 9: Accessibility

**User Story:** As a user relying on assistive technology, I want all filter controls to be labelled and keyboard navigable, so that I can use the Event Explorer without a mouse.

#### Acceptance Criteria

1. THE EventFiltersBar SHALL associate every filter input with a visible `<label>` element using `htmlFor`/`id` pairing or equivalent accessible labelling.
2. THE EventFiltersBar SHALL expose all filter controls as keyboard-focusable and operable via standard keyboard interactions (Tab, Enter, Space).
3. WHEN the filtered event count changes, THE EventExplorer SHALL update an `aria-live="polite"` status region with the current result count so assistive technologies announce the change.
4. WHEN the empty state is rendered, THE EventExplorer SHALL expose it via `role="status"` and `aria-live="polite"` so assistive technologies announce the absence of results.

---

### Requirement 10: Filter Correctness Properties

**User Story:** As a developer maintaining the FilterEngine, I want automated property-based tests covering key correctness invariants, so that regressions are caught early.

#### Acceptance Criteria

1. FOR ALL non-empty search strings `s`, applying the search filter SHALL produce a result set that is a subset of or equal to the unfiltered set (monotone reduction property).
2. FOR ALL combinations of active filters, applying the full filter pipeline twice to the same input SHALL produce the same result as applying it once (idempotence property).
3. FOR ALL event lists and filter states where no filters are active, THE FilterEngine SHALL return all input events unchanged.
4. WHEN a date range `[startDate, endDate]` is applied where `startDate > endDate`, THE FilterEngine SHALL return zero events.
5. FOR ALL event lists, applying a single-type selection to `eventTypes` SHALL return a subset where every event's `type` field equals the selected type.
141 changes: 141 additions & 0 deletions dashboard/.kiro/specs/event-explorer-filters/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Requirements: Event Explorer Advanced Filters

## Overview

Enhance the Event Explorer page with an advanced, multi-dimensional filter panel. Users must be able to combine a debounced contract-ID text search, a multi-select event-type filter, and a date-range picker to instantly isolate smart contract events — all without a page reload.

---

## Requirements

### 1. Debounced Contract ID / Text Search

**User Story:** As a user, I want to type a contract address or keyword into a search field and see results update automatically, so that I can locate specific events without the UI freezing on every keystroke.

#### Acceptance Criteria

- 1.1 A text input labelled "Search" accepts free-text and searches across `contractAddress`, `eventId`, `eventName`, and `txHash` fields.
- 1.2 The visible input value updates on every keystroke (controlled input); the filter state in the store updates only after 300 ms of inactivity (debounce).
- 1.3 Clearing the input resets the search filter immediately (on clear/empty, the debounce fires at once or the value is applied on blur-clear).
- 1.4 Typing rapidly (e.g., 10 characters in 200 ms) triggers the store update exactly **once** after the 300 ms window, not once per character.

---

### 2. Event Type Multi-Select Filter

**User Story:** As a user, I want to filter events by one or more event-type classifications simultaneously, so that I can compare or isolate multiple categories at once.

#### Acceptance Criteria

- 2.1 The filter panel exposes the distinct values of `BlockchainEvent.type` (e.g., `contract`, `system`, `diagnostic`) as individually toggleable controls (button group or multi-select).
- 2.2 When no type is selected, all events are shown (equivalent to "All types").
- 2.3 When one or more types are selected, only events whose `type` matches **any** selected value are shown (OR logic within the type set).
- 2.4 Selecting and deselecting a type updates the filtered list immediately (subject to the overall AND logic with other active filters).
- 2.5 The current selection is visually indicated (e.g., active/pressed button state or checked option).

---

### 3. Date Range Filter

**User Story:** As a user, I want to specify a start date and/or end date so that only events received within that window are shown.

#### Acceptance Criteria

- 3.1 The filter panel includes a "From" date input and a "To" date input (native `<input type="date">` is acceptable).
- 3.2 The "From" date is treated as the **start of that calendar day in UTC** (00:00:00.000 UTC).
- 3.3 The "To" date is treated as the **end of that calendar day in UTC** (23:59:59.999 UTC).
- 3.4 Setting only "From" shows all events from that date onwards.
- 3.5 Setting only "To" shows all events up to and including that date.
- 3.6 Setting both constrains events to the inclusive range [start-of-From, end-of-To].
- 3.7 If "From" is set to a date **after** "To", the result set is empty and a validation hint is shown (e.g., "Start date must be before end date").
- 3.8 Clearing either date input removes that bound immediately.

---

### 4. Additive AND Filter Logic

**User Story:** As a user, I want all my active filters to work together, so that I always see events that match every criteria I've set.

#### Acceptance Criteria

- 4.1 An event appears in the results only when it passes **all** active filters simultaneously (search AND selected event types AND date range).
- 4.2 Adding a new filter dimension never widens the result set — it can only narrow or maintain it.
- 4.3 The existing `filterEvents` utility is extended (not replaced) to accept the new filter dimensions.

---

### 5. Clear All Filters

**User Story:** As a user, I want a single button to reset all filters at once, so that I can return to the full event list without manually clearing each control.

#### Acceptance Criteria

- 5.1 A "Clear filters" button is visible in the filter panel whenever **any** filter deviates from its default state.
- 5.2 Clicking it resets: search text to `""`, contractAddress to `"all"`, event type selection to none/all, startDate and endDate to empty.
- 5.3 The pagination page resets to 1 on clear.
- 5.4 The button is hidden (or disabled) when all filters are already at their defaults.

---

### 6. Empty State

**User Story:** As a user, when my filter combination returns no results, I want a clear message explaining what happened and how to recover.

#### Acceptance Criteria

- 6.1 When the filtered result set is empty, the table/card area is replaced by an empty-state section.
- 6.2 The empty state reads: heading **"No matching events found"** and body **"Try adjusting or clearing your filters to see more results."**
- 6.3 The empty-state section uses `role="status"` and `aria-live="polite"` so screen readers announce it.
- 6.4 A shortcut "Clear filters" link/button inside the empty state triggers the same reset as Requirement 5.

---

### 7. Performance

**User Story:** As a user, I expect filtering to feel instant even with thousands of events loaded.

#### Acceptance Criteria

- 7.1 The derived filtered event list is computed via `useMemo`, re-evaluating only when `events` or any filter value changes.
- 7.2 Filter state changes never trigger a network request or a full page reload.
- 7.3 The debounce on the text search input (Requirement 1) prevents redundant re-computations from rapid typing.

---

### 8. Responsive Layout

**User Story:** As a user on a mobile device, I want the filter controls to be usable and not overflow the screen.

#### Acceptance Criteria

- 8.1 On viewports narrower than 768 px the filter controls stack into a single vertical column.
- 8.2 On viewports 768 px and wider the controls lay out horizontally in a single row or a compact grid.
- 8.3 No filter control overflows or clips its label at any tested breakpoint.

---

### 9. Accessibility

**User Story:** As a keyboard or screen-reader user, I want to fully operate all filter controls without a mouse.

#### Acceptance Criteria

- 9.1 Every filter input has a programmatically associated `<label>` (via `htmlFor`/`id` pairing).
- 9.2 The result count (e.g., "Showing 1–12 of 47 events") updates in an `aria-live="polite"` region.
- 9.3 The empty-state region is announced by screen readers when it appears (see 6.3).
- 9.4 All interactive controls (inputs, selects, buttons) are reachable and operable via keyboard Tab / Enter / Space navigation.
- 9.5 The filter panel passes an automated axe accessibility audit with zero violations.

---

### 10. Test Coverage

**User Story:** As a developer, I want automated tests to verify all new filter behaviour so that regressions are caught immediately.

#### Acceptance Criteria

- 10.1 **Debounce test:** typing 5+ characters rapidly triggers the store's search action exactly once after the debounce window, not on each keystroke.
- 10.2 **Multi-filter intersection test:** seeding the store with a known event set and applying a Contract ID + Event Type + Date Range combination returns only the records that satisfy all three criteria.
- 10.3 **Clear All test:** after applying at least two filters, clicking "Clear filters" causes the rendered list to return to the full unfiltered count.
- 10.4 **Empty state test:** a filter combination that matches zero events renders the "No matching events found" heading.
- 10.5 **Accessibility test:** the filter panel passes `jest-axe` with no violations.
Loading