Skip to content

Pull Request: UI & Test Improvements with SSE/Event Handling Fixes - #70

Open
PaulJouvanceau wants to merge 96 commits into
opensvc:mainfrom
PaulJouvanceau:dev
Open

Pull Request: UI & Test Improvements with SSE/Event Handling Fixes #70
PaulJouvanceau wants to merge 96 commits into
opensvc:mainfrom
PaulJouvanceau:dev

Conversation

@PaulJouvanceau

Copy link
Copy Markdown
Contributor

Summary
This PR introduces a wide range of improvements across the application, including UI fixes, new features, test refactoring, and bug fixes related to SSE connections, event handling, and authentication.

Changes
UI & UX
Safari compatibility: Fixed badge overflow and aligned badges to the bottom of stat cards

Action menus: Increased z-index of all action menus to appear above EventLogs panel

Destructive actions: Highlighted destructive actions in red and moved them to the bottom of action lists

StatCard: Added dynamic height with configurable max subtitle height

Mobile/scroll: Resolved iOS scroll issues by centralizing scroll in the main container; fixed logs panel resize on iOS and close button visibility

Navigation: Replaced user icon with tag icon for Kinds route; fixed duplicate navigation on Nodes grid by stopping event propagation

Grid2: Replaced undefined Grid2 with Grid for proper mobile rendering

Auth: Show spinner in AuthChoice while authInfo is loading

Features
Added replace mode to setInstanceStatuses and used it on initial fetch to prevent stale data accumulation

Added bottomSpacing prop to LogsViewer for configurable bottom spacing

Added fallback fetch and dynamic configNode switching in ObjectDetails

Reactive config refresh with fallback loading state

Instances in ObjectDetail are now sorted alphabetically

Bug Fixes
SSE/Events: Added objectName parameter to startEventReception for path fix; fixed objectName usage; closed SSE connection on ClusterOverview unmount to prevent memory leaks

OIDC: Use stable base path for OIDC callback URLs instead of deriving from window.location

Auth: Debounced auth check on page resume to prevent redundant token validation

Events: Debounced localStorage persistence and avoided O(n) instance scanning for performance

Heartbeats: Improved GridHeartbeats click handling

Buffer: Reduced debug logging verbosity

Config: Added useConfig and improved eventSourceManager configuration

Test Refactoring & Coverage
Refactored and optimized numerous test suites for clarity, maintainability, and execution speed without reducing coverage:

ObjectDetails.test

eventSourceManager

Objects tests

App.test.jsx

useEventStore (branch coverage for buffer and Safari)

Heartbeats (consolidated, reduced duplication)

Login test file

LogsViewer test suite

NodesTable tests

NavBar tests

Namespaces unit tests

AuthProvider (fixed timing/isolation issues)

WhoAmI tests

ClusterOverview tests (fixed mock alignment)

DarkModeContext (added comprehensive unit tests)

ConfigSection (improved coverage and structure)

Additional test improvements:

Added shared helpers and parameterization for KeysSection tests

Used real MUI RadioGroup/FormControlLabel in KeysSection tests

Fixed flaky action tests in Objects component

Misc
Deleted patch action (cleanup)

Improved GridNodes click handling

… cards

- StatCard: add `dynamicHeight` prop (default false) to toggle between fixed height (240px) and flexible height (auto with min-height 240px)
- StatCard: use flex layout with `justifyContent: flex-end` to push subtitle content (badges) to the bottom of the card
- StatCard: wrap subtitle in inner div with `overflowY: auto` and `maxHeight: 100%` to enable scrolling when many badges are present; add `minHeight: 0` for Safari overflow compatibility
- GridNamespaces & GridKinds: set `dynamicHeight={true}` to allow their cards to grow vertically when needed
- Other grids (Nodes, Objects, Heartbeats, Pools, Networks) keep fixed height (240px)
- NamespaceChip and KindChip retain absolute positioning for status indicators (colored circles) to preserve original design

This resolves the Safari-specific issue where badges would overflow outside the cards and ensures all badge content stays within the card boundaries. Badges are now consistently aligned at the bottom of each card, with scrollbars appearing only when necessary.
- Group mocks and helpers for better readability
- Add reusable helpers (renderReadySvc, executeObjectAction, mockNetworkFailure, captureSubscription)
- Replace repetitive test blocks with test.each and describe.each
- Consolidate console and action error handling tests
- Remove redundant assertions and improve overall test structure
…nability

- Extract `getHandler` helper to reduce repetitive mock access patterns
- Consolidate multiple similar test cases into single, data-driven tests
- Simplify `afterEach` state restoration (use `Object.assign` for console)
- Remove redundant assertions and duplicated coverage scenarios
- Improve test naming and grouping for better readability
- Extract a shared `setup` helper and a `defaultState` object to eliminate
  repetitive mock and state initialization
- Add utility functions (`waitForLoad`, `selectFilter`, `selectRow`,
  `openActionsMenu`, `clickMenuItem`, `confirmDialog`) to reduce boilerplate
- Parametrize filter tests using `test.each` instead of duplicating test cases
- Group related tests into logical `describe` blocks (filtering, actions,
  sorting, etc.)
- Remove redundant or overly specific tests while preserving coverage
- Simplify console.error suppression and mock lifecycle management
…etch

- Extend setInstanceStatuses with optional 'replace' parameter to fully
  overwrite instance data for a path instead of merging incrementally.
- Use replace=true in fetchInitialObjectData to replace stale instance
  statuses with fresh API data, eliminating display of obsolete nodes.
- Simplify nodesList computation to directly depend on objectInstanceStatus.
…m of menus

- Reorder OBJECT_ACTIONS: move 'delete' and 'purge' to the end, add color:'red'
- Reorder NODE_ACTIONS: move 'delete' to the end, add color:'red'

Apply red styling and bottom positioning in all menus:
- Objects.js: global actions menu and per-row actions menu
- HeaderSection.js: object actions menu (Popper)
- NodeRow.jsx: per-node actions menu
- NodesTable.jsx: batch actions menu on selected nodes
- ObjectDetail.js: instance actions and resource actions menus
- ObjectInstanceView.js: instance actions and resource actions menus

All destructive actions (purge, delete) now appear in red (error.main)
and are consistently placed at the bottom of action lists.
…gger

- Set zIndex: 10000 on all Menu and Popper components across the app
- EventLogger button has zIndex: 9999, menus now properly overlay it
- Affected files: Objects.js, HeaderSection.js, NodeRow.jsx, NodesTable.jsx,
  ObjectDetail.js, ObjectInstanceView.js

Previously, action menus were hidden behind the EventLogger button.
Now all action menus (global, per-object, per-node, instance, resource)
display correctly on top.
Added .sort() to nodesList useMemo to display instance cards in alphabetical order.
Refactor App.test.jsx for brevity and correctness without reducing coverage.

Optimizations:
- Extract setupBasicAuth(), setupOidcAuth(), and renderApp() helpers to
  eliminate repeated setup code across tests
- Collapse protected and unprotected route tests into two test.each blocks
- Merge redundant tests covering the same code paths from different angles
- Combine multi-assertion tests where behavior belongs to a single concern

Bug fixes:
- Mock logger.js directly instead of spying on console.* methods; logger
  captures a reference to console at import time so jest.spyOn patches
  arrive too late and assertions see 0 calls
- Replace jest.clearAllMocks() / mockReset() with mockClear() in beforeEach;
  mockReset strips mock implementations (including jest.mock factories and
  spy mockImplementation) causing oidcConfiguration and consoleSpy to lose
  their behavior between tests
- Replace mockImplementationOnce(() => null) on the authInfo hook with a
  persistent mockImplementation for the duration of the test; the hook is
  called on every re-render so a one-shot mock is exhausted before the
  effect under test fires
- Add missing branch coverage for handleCheckAuthOnResume: OIDC resume with
  no token on focus, OIDC resume with null userManager, silent renew
  returning a still-expired user, and the warn log on that path
- Simplify storage event test to assert on localStorage.getItem instead of
  filtering console.debug call history, removing fragile coupling to logger
  internals
…ltering

Previously, ObjectDetail manually constructed event filter strings with
path=... but these were invalidated by createQueryString, causing SSE to
receive events for all objects. Now startEventReception accepts an optional
objectName parameter that is correctly handled by createQueryString to
generate proper path-specific filters.

- eventSourceManager.jsx: startEventReception now takes a third argument
  objectName and passes it to configureEventSource.
- ObjectDetail.jsx: simplified useEffect to call startEventReception with
  decodedObjectName instead of manually building filter strings.
…InstanceConfigDeleted handling

- Add refreshTrigger prop to ConfigSection to force config refetch after external updates (SSE)
- Move config fetching logic from ObjectDetails to ConfigSection, reducing parent complexity
- Implement fallback data fetch with 5s timeout to avoid indefinite loading state
- Auto-select initial config node based on first node with encap/resources
- Handle InstanceConfigDeleted events to clean up instance config/monitor data
- Introduce pendingDeletes and removeInstanceFromObject in useEventStore
- Add RESET action and forceBypassThrottle flag in useConfig hook
- Improve URL encoding and add useCache parameter for SSE connections
…from current page path

Previously, the base URL was computed by stripping the last segment of
window.location.pathname, which caused redirect_uri and other callback
URLs to vary depending on the current page (e.g., /objects/auth-callback
when starting from an object detail page). This led to invalid callback
routes and "Not Found" errors from the OIDC provider.

Now the base path is dynamically extracted by matching the "/ui" prefix
in the pathname. This ensures all OIDC URIs are consistently rooted at
the application's sub-path, regardless of the page the user is on.
- Implement fallback data fetching via HTTP when no SSE events arrive within 5 seconds
- Auto-select initial configNode based on encap resources and handle switching when the node disappears
- Encode URI components in API URLs to prevent issues with special characters
- Subscribe to instanceConfig store changes and warn if unsubscribe is not a function
- Add tests: fallback fetch (success and failure), configNode switching, drawer max width, console action without rid, improved lifecycle assertions
- Added tests for removePendingDelete action
- Added tests for removeInstanceFromObject covering all three state keys and partial/no-op cases
- Added tests for setInstanceStatuses with replace=true, including replace with empty nodes, inherited properties, and shallow equal rejection
- Added tests for setConfigUpdated with missing namespace, cluster kind, and missing path
- Fixed existing assertions to expect empty objects instead of undefined after deleting the last instance from an object path
…ri optimizations

- Add tests for pending deletes filtering buffered instanceStatus and instanceConfig
- Add test for configUpdated event with missing name/node (fallback to warning)
- Add Safari-specific isolated module tests (setTimeout vs requestAnimationFrame, BATCH_SIZE=150)
- Fix mocking of default exports in Safari test suite
- Correct expected buffer state when pending delete removes buffered instanceStatus
…ed tests

Extract reusable mock factory (buildFetchMock), rendering and dialog open
helpers, and replace repetitive tests with test.each for auth token errors,
dialog close (cancel/Escape), and CRUD success/error paths.
…ght and custom scrollbars

- Introduce `maxSubtitleHeight` prop (default 160) to limit the subtitle area when `dynamicHeight` is enabled.
- Adjust flex layout so dynamic cards have a smaller min-height (120px) and non-flex subtitle area.
- Add thin custom scrollbar styling for the subtitle container to improve overflow handling.
- Apply `dynamicHeight` and `maxSubtitleHeight={220}` to GridNamespaces and GridKinds components for better space utilisation.
…overage loss

Merge multiple isolated test cases into comprehensive scenarios (all states,
filtering, sorting loops), introduce a createStream helper, and reduce
the number of render cycles. This significantly improves test suite performance
while keeping full coverage.
Replace mocked RadioGroup and FormControlLabel with actual MUI components
to fix interaction issues with radio inputs. Simplify other mocked components
and refactor helpers for clarity and reliability.
…rity

- Introduce `buildStatus` helper to centralize mock data construction
- Replace repetitive test blocks with `test.each` for state icons, URL filters, and sorting columns
- Consolidate mock implementations and clean up unused imports
- Add new tests for state column sorting and responsive filter visibility
- Improve test structure with clear section separators and better assertion patterns
- Simplify auth/cleanup tests and remove obsolete mocking of logger functions
- Introduce setupLogin and fillForm helpers to reduce boilerplate
- Merge input change and submit button disable checks into a single test
- Combine invalid token decode cases into one test
- Keep all code paths covered with fewer lines of code
…ertions

- Condense test file while preserving full code coverage
- Fix 404 error handling tests to use rejected fetch, matching real error paths
- Fix malformed JSON test to correctly match displayed substring
- Reduce test file size by ~50% using parametrized tests and cleanup
- Maintain all branch coverage for the store logic
- Extract common helpers (confirmDialog, withConsoleAction) to reduce duplication
- Consolidate parameterized tests for drawer resize and instanceConfig error handling
- Reorganize code with clear section comments for better navigation
- Introduce BASE_FNS factory to avoid repetitive mock function definitions
- Improve test descriptions and structure for clarity
- Remove redundant test code
- Added DEBUG_BUFFERS flag (default false) to suppress verbose buffer
  debug logs via debugLog function, keeping only essential logs.
- Updated test to verify needsFlush behavior without relying on
  disabled debug log messages.
…pagation on Frozen chip

Clicking the "Frozen" chip inside the Nodes grid triggered both the chip's onClick and the parent StatCard's onClick, causing two concurrent navigation attempts and requiring multiple clicks to open the page. Added stopPropagation in the chip's handler to ensure only a single navigation call is made.
Removed the onClick stopPropagation handler from the subtitle container
in StatCard. Previously, clicking the central area of the card (between
chips) was blocked because the container captured the event and stopped
propagation, requiring multiple clicks to navigate. Now clicks on empty
areas propagate to the parent Paper and trigger navigation as intended.
Chips still prevent propagation via their own handlers.
Covered the following previously uncovered code paths:
- Encapsulated resource logs (getLogPaddingLeft when isEncap=true).
- Mobile view stopPropagation Box click (xs/sm breakpoint).
- Empty instance action triggering warn and dialog reset.
- Resource type resolution when resource is removed (return type;).
Fixed test selectors for multiple elements and dialog cleanup.
…nd shared helpers

- Extract reusable helpers (clickHeader, makeMany, setScroll, getScrollContainer)
- Merge individual status-rendering tests into a single comprehensive test
- Use test.each for filter options, global state values, sorting sequences,
  row menu visibility, object-name resolution, and chip toggle scenarios
- Preserve full branch coverage while significantly shortening the file
- Replace spyOn with manual getItem mock to avoid type issues.
- Use factory mock for useEventStore to expose getState.
- Add jest-dom import for custom matchers.
- Introduce mock() helper to safely cast mocks.
- Replace toHaveNoViolations with violations length check.
- Remove redundant awaits on synchronous selectRow calls.
- Use act without await for chip toggle test.
- Switch from require to import for useLocation, useNavigate, useMediaQuery.
…ized tests, and extended coverage

- Introduce `createES()` and `fire()` helpers to reduce boilerplate and improve readability
- Merge redundant NodeStatusUpdated, NodeMonitorUpdated, and NodeStatsUpdated tests into a single parameterized test
- Add comprehensive tests for edge cases:
  * ObjectStatusUpdated merging and missing fields
  * InstanceStatusUpdated with labels.path, missing fields, and skip-equal logic
  * DaemonHeartbeatUpdated with labels.node
  * InstanceConfigUpdated with multiple nodes, missing name/node, and pendingDeletes filtering
  * JSON parse errors during configUpdated flush
  * Flush rescheduling when MIN_FLUSH_INTERVAL not elapsed
  * Events arriving during an active flush (`needsFlush`)
- Extend buffer management tests for Safari-specific behaviour (BATCH_SIZE = 150, setTimeout instead of rAF)
- Improve `isEqual` tests with a parameterized table covering primitives, objects, arrays, and type mismatches
- Use `jest.mocked()` for type-safe mock access and avoid “Unresolved variable” warnings
- Add tests for `_cleanup` error handling in both main and logger EventSource
- Ensure all timers are properly advanced and reset in `afterEach`

The suite now provides full branch coverage for the eventSourceManager module.
- Add `void` operator to explicitly ignore unawaited promises (fetchConfig, fetchKeywords, fetchExistingParams)
- Rename unused `event` parameters to `_event` in Autocomplete onChange handlers to suppress TS6133
- Replace `freeSolo={true}` with shorthand `freeSolo` to satisfy type expectations
- Enhance `getOptionLabel` in the first Autocomplete to handle both string and object options
- Guard `newValue` with fallback array before calling `.map()` to avoid potential type errors

No functional changes; only warning fixes and type improvements.
…handling

- Added JSDoc type annotations for useState, useRef, and popper modifiers
- Added error logging (.catch) for async actions (postNodeAction, postObjectAction, postConsoleAction, clipboard copy)
- Removed unused `Menu` import and unused `isMounted` ref
- Wrapped fetchFallbackData call in setTimeout with error catch
- Improved type safety with explicit type casting for refs and state
- Update ajv to 8.17.2 (fixes ReDoS vulnerability)
- Update axios to 1.9.0 (fixes multiple security issues)
- Update react-router-dom to 7.18.1 (fixes CSRF, XSS, RCE vulnerabilities)
- Add @jest/globals for improved test typings
- Add @typescript-eslint/eslint-plugin and parser for modern linting
- Add eslint-plugin-import and eslint-plugin-jsx-a11y for stricter rules
- Update eslint to 9.39.5 and eslint-plugin-react to 7.37.5
- Remove deprecated eslint-config-next and eslint-config-react-app
- Remove obsolete eslintConfig section
- Add glob to devDependencies (required by some tooling)
- Add resolutions and overrides to enforce secure versions of transitive dependencies (fast-uri, follow-redirects, form-data, yaml, react-router)
…le toggle

- Changed filter visibility logic to use !isMobile || showFilters, so filters are always displayed on non-mobile screens.
- Added unmountOnExit to Collapse for proper DOM removal on mobile, fixing failing test.
- Adjusted showFilters initial state to false on mobile, true otherwise.
The test would sometimes fail because the second log was delivered before
the pause effect ran, causing a false negative in the “not in document”
assertion. The initial stream now contains only one log to eliminate the
race condition while still covering the abort path during reading.
- Add tests for handleDialogConfirm outer .catch branches (batch, node, object actions)
- Add test for instanceConfig subscription not triggering snackbar when configNode is null
- Add test for batch menu hiding freeze when all selected nodes are frozen
- Add test for instance-level logs drawer title
- Add test for resource console trigger via tooltip to exercise error paths
- Add test for fetchFallbackData rejection logging
Reduce test file size without lowering coverage
by merging near-identical test cases into parameterized test.each
blocks and extracting shared setup into helpers.

- Merge the 3 handleDialogConfirm rejection tests (batch node,
  individual node, object action) into a single test.each covering
  postNodeAction/postObjectAction error logging.
- Merge the 2 instanceConfig subscription tests (fires vs. does not
  fire snackbar) into one test.each.
- Merge the 2 batch-menu freeze visibility tests (mixed vs. fully
  frozen nodes) into one test.each.
- Merge the 2 best-effort resource-console-trigger tests (action
  button vs. tooltip title) into one test.each, extracting a shared
  findAndOpenResourceConsoleDialog helper.
- Add setStoreState() helper to replace the repeated
  useEventStore.mockImplementation/getState.mockReturnValue pairing
  used throughout the file.
- Add emptyState() factory to remove duplicated empty-state object
  literals (fallback fetch suite and others).

No test cases were removed; all assertions, error paths, and
conditional branches previously covered are preserved.
Add tests for previously uncovered lines:
- decodeToken returns null for invalid base64 payload (atob failure)
- decodeToken returns null for valid base64 but non-JSON payload
- Login form submission via Enter with empty fields shows error
- refreshToken handles missing refreshTokenExpiration (no expiration set)
- refreshToken stores new refresh token and its expiration when provided
- refreshToken clears refreshTokenExpiration when new refresh token has no exp
- Login when tokens have no exp: verify removeItem calls for tokenExpiration and refreshTokenExpiration
- refreshToken when access token has no exp: verify removeItem call for tokenExpiration

Also adds spy on localStorage.removeItem to assert cleanup calls.
- Export areStatusDotPropsEqual to allow direct unit testing of the memo comparison function.
- Add tests for navigation with down, warn, n/a status clicks.
- Add sorting tests for all columns (Up, Down, Warn, N/A, Total) in ascending order.
- Fix Namespace descending sort test to work with useDeferredValue by using a single click and waitFor.
- Add unit tests for areStatusDotPropsEqual with equal, different status, and different count.
- Ensure all sorting tests wait for deferred updates to reflect in the DOM.
- Remove isolated NamespaceTableRow test (coverage achieved via Namespaces component tests).
- Add istanbul ignore comments for defensive/unreachable code paths
  in ConfigSection.jsx
- Update Autocomplete mock to always call getOptionLabel
- Add test for network error in handleAddParams
…ns/suffixes when adding parameters

- Allow selecting existing sections or indexed section suffixes in the "Add parameters" flow
  using free-solo Autocomplete fields, while still enabling free text input for new sections.
…ents

- Add test cases for:
  * ObjectDeleted filtering with non-matching _rawEvent
  * CONNECTION events bypassing objectName filter
  * Subscription dialog updating when eventTypes change
  * Page events subscribe action with existing subscriptions
  * Rapid resize moves and timeout cleanup
  * Early unmount cleanup of initialLoading timer
  * Infinite scroll loading more logs
- Insert `/* istanbul ignore next */` comments to exclude branches that are
  difficult or impractical to cover in unit tests (syntax highlighting,
  resize scroll logic, Drawer slotProps style, and non-page chip style).
…router 8.3.0

- Replace all Jest test files with Vitest equivalents (vi.fn, vi.mock, vi.hoisted)
- Update package.json: add vitest, remove jest/babel-jest/ts-jest/etc, update scripts
- Add setupTests.js with vitest globals, jest alias, vitest-axe, polyfills
- Fix hoisted variable patterns for vi.mock factories
- Correct mock exports (default logger, eventSourceManager DEFAULT_FILTERS)
- Fix useNavigate mock pattern with vi.hoisted
- Add missing useMediaQuery import in several test files
- Adapt Objects test suite to current UI (no node columns, no filters toggle)
- Extend TextField mock in ActionDialogManager test for data-testid generation
- Upgrade to React 19 and react-router 8.3.0 to address security vulnerabilities
- Replace jest-axe with vitest-axe and update axe setup
- Misc test fixes: async timer flushing, console spy cleanup, dialog selectors
- Replace '@v1' with the commit hash for security (pinning).
- Correct 'files' syntax to properly attach dist/index.html to releases.
Add a safety check before fetching the .well-known/openid-configuration
to block requests to internal or malicious endpoints.

- Validate issuer URL scheme (HTTPS only, except localhost for dev)
- Reject URLs with embedded credentials
- Block IP addresses (IPv4 and IPv6) to prevent internal network access
- Disable HTTP redirects in fetch (`redirect: 'error'`)
- Keep the try/catch flow intact so existing tests for malformed URLs
  still pass

This closes the SSRF vector reported by SAST.
- Cover rendering of table headers, row counts, and status icon colors
- Verify navigation on row click and individual status cell clicks (up, down, warn, unprovisioned)
- Test event source lifecycle (start on mount, close on unmount, skip without token)
- Validate autocomplete filter behavior and URL parameter synchronization
- Check sorting by all columns (kind, up, down, warn, unprovisioned, total) in both directions
- Test infinite scroll (load more items, prevent duplicate loads, cleanup)
- Ensure correct messages for empty state and filter mismatch
- Fix sort test assertions for equal values by sorting expected subsets
Add unit tests for isSafeIssuerUrl edge cases (HTTP, credentials,
IPv4, private IPs, IPv6, missing host) and /ui base path handling.
This covers previously uncovered branches and increases line/branch
coverage in the OIDC configuration module.
- Add index.test.jsx covering the app bootstrap logic:
  - logs an error and skips rendering when #root is missing
  - creates the React root and renders the app tree when #root exists
  - renders the tree wrapped in React.StrictMode
  - resolves the router basename to "/ui" or "/" based on pathname
- Mock react-dom/client, react-router-dom, App, MUI theming, colors,
  main.css, logger, and DarkModeContext to isolate index.jsx from its
  dependencies
- Export getDesignTokens from index.jsx so the light/dark palette
  logic can be unit tested directly, since production code only ever
  invokes it with 'light' at import time
- Add tests for getDesignTokens covering both the light and dark
  palette branches, including mode-specific keys (text for dark,
  contrastText for light)
…anceStatus

Add test cases for:
- Returning and caching default empty data when instanceStatus is missing
- Maintaining last known data when instanceStatus disappears (prevDataRef not null)
- Frozen logic for zero date, missing frozen_at, and valid frozen date
- Monitor state handling when state is 'idle' or monitor is missing entirely
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.

1 participant