[PULSE-13] Org Audit Log UI with advanced filtering #10

Merged
uz1mani merged 9 commits from staging into main 2026-02-05 13:53:56 +00:00
uz1mani commented 2026-02-05 12:58:11 +00:00 (Migrated from github.com)

Work Item

PULSE-13

Summary

  • Added a new "Audit log" tab to the Organization Settings page.
  • Implemented a responsive data table to display immutable audit log entries.
  • Built an advanced filtering interface allowing users to search by Log ID, Action, and Date Range.

Changes

  • Components:
    • OrganizationSettings.tsx: Added the Audit Log tab, data table, and filter grid logic.
    • lib/api/audit.ts: Created API client for fetching and filtering audit logs.
  • Features:
    • Displays Actor, Action, Resource, and Time for every event.
    • Supports filtering by specific 6-character Log IDs.
    • Removed unused "Resource ID" column from the view to reduce clutter.

Test Plan

[ ] Navigate to Organization Settings -> Audit log.
[ ] Verify the table loads with recent events.
[ ] Test the "Log ID" filter with a known ID (e.g., 8a2b3c).
[ ] Test the "Action" filter (e.g., site_created) and Date Range inputs.
[ ] Verify that pagination works correctly.

## Work Item PULSE-13 ## Summary - Added a new "Audit log" tab to the Organization Settings page. - Implemented a responsive data table to display immutable audit log entries. - Built an advanced filtering interface allowing users to search by Log ID, Action, and Date Range. ## Changes - **Components:** - `OrganizationSettings.tsx`: Added the Audit Log tab, data table, and filter grid logic. - `lib/api/audit.ts`: Created API client for fetching and filtering audit logs. - **Features:** - Displays Actor, Action, Resource, and Time for every event. - Supports filtering by specific 6-character Log IDs. - Removed unused "Resource ID" column from the view to reduce clutter. ## Test Plan [ ] Navigate to Organization Settings -> Audit log. [ ] Verify the table loads with recent events. [ ] Test the "Log ID" filter with a known ID (e.g., `8a2b3c`). [ ] Test the "Action" filter (e.g., `site_created`) and Date Range inputs. [ ] Verify that pagination works correctly.
greptile-apps[bot] commented 2026-02-05 13:00:11 +00:00 (Migrated from github.com)

Greptile Overview

Greptile Summary

Added a comprehensive audit log feature to Organization Settings with filtering and pagination capabilities. The implementation uses a trigger-based approach to coordinate filter changes and pagination, ensuring proper data fetching without loops. All previously identified issues have been addressed:

  • Filter changes now properly trigger data reload using auditFetchTrigger state
  • Tab navigation syncs with URL query parameters via handleTabChange
  • Infinite loop resolved by removing loadAudit from debounced effect dependencies
  • Double API calls eliminated by merging pagination logic into single effect
  • Standard API client with token refresh now used via legacy endpoint support
  • Unnecessary useEffect for ref updates removed

The audit log displays actor, action, resource type, and timestamp for each entry with support for filtering by Log ID, action type, and date range.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk after addressing previous review feedback
  • The implementation demonstrates thorough attention to previous feedback with all major issues resolved. State management uses refs and triggers appropriately to avoid re-render issues. API integration follows existing patterns with proper error handling.
  • No files require special attention - all previous issues have been addressed

Important Files Changed

Filename Overview
components/settings/OrganizationSettings.tsx Added audit log tab with filter UI and pagination; refactored state management using refs and trigger mechanism
lib/api/audit.ts New API client for audit log with proper defensive handling of response data
lib/api/client.ts Modified to support legacy /api/ endpoints alongside /api/v1 endpoints

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as OrganizationSettings
    participant Filter as Filter State
    participant Trigger as auditFetchTrigger
    participant API as lib/api/audit
    participant Client as lib/api/client
    participant Backend as /api/audit

    User->>UI: Navigate to Audit log tab
    UI->>API: getAuditLog(params)
    API->>Client: apiRequest('/api/audit?...')
    Client->>Backend: GET /api/audit (with cookies)
    Backend-->>Client: { entries: [...], total: N }
    Client-->>API: Response data
    API-->>UI: { entries, total }
    UI->>UI: Display audit table

    User->>Filter: Change filter (action/logId/date)
    Filter->>Filter: Update filter state
    Note over Filter: 500ms debounce timer
    Filter->>Trigger: setAuditPage(0)
    Filter->>Trigger: setAuditFetchTrigger(prev + 1)
    Trigger->>UI: Trigger useEffect
    UI->>API: getAuditLog(params with filters)
    API->>Client: apiRequest('/api/audit?...')
    Client->>Backend: GET /api/audit?action=X&log_id=Y (with cookies)
    Backend-->>Client: Filtered results
    Client-->>API: Response data
    API-->>UI: { entries, total }
    UI->>UI: Display filtered results

    User->>UI: Click pagination (Next/Previous)
    UI->>UI: setAuditPage(newPage)
    UI->>API: getAuditLog(params with new offset)
    API->>Client: apiRequest('/api/audit?offset=...')
    Client->>Backend: GET /api/audit (with cookies)
    alt Token expired (401)
        Backend-->>Client: 401 Unauthorized
        Client->>Client: Detect 401, start refresh
        Client->>Backend: POST /api/auth/refresh (via /api/auth/refresh route)
        Backend-->>Client: New tokens (HttpOnly cookies)
        Client->>Backend: Retry GET /api/audit (with new cookies)
        Backend-->>Client: { entries: [...], total: N }
    else Token valid
        Backend-->>Client: { entries: [...], total: N }
    end
    Client-->>API: Response data
    API-->>UI: { entries, total }
    UI->>UI: Display paginated results

    User->>UI: Click Clear Filters
    UI->>Filter: Reset all filters to ''
    UI->>Trigger: setAuditPage(0)
    UI->>Trigger: setAuditFetchTrigger(prev + 1)
    Trigger->>UI: Trigger useEffect
    UI->>API: getAuditLog(params without filters)
    API->>Client: apiRequest('/api/audit')
    Client->>Backend: GET /api/audit (with cookies)
    Backend-->>Client: All entries (unfiltered)
    Client-->>API: Response data
    API-->>UI: { entries, total }
    UI->>UI: Display all results
<h2>Greptile Overview</h2> <h3>Greptile Summary</h3> Added a comprehensive audit log feature to Organization Settings with filtering and pagination capabilities. The implementation uses a trigger-based approach to coordinate filter changes and pagination, ensuring proper data fetching without loops. All previously identified issues have been addressed: - Filter changes now properly trigger data reload using `auditFetchTrigger` state - Tab navigation syncs with URL query parameters via `handleTabChange` - Infinite loop resolved by removing `loadAudit` from debounced effect dependencies - Double API calls eliminated by merging pagination logic into single effect - Standard API client with token refresh now used via legacy endpoint support - Unnecessary `useEffect` for ref updates removed The audit log displays actor, action, resource type, and timestamp for each entry with support for filtering by Log ID, action type, and date range. <h3>Confidence Score: 4/5</h3> - This PR is safe to merge with minimal risk after addressing previous review feedback - The implementation demonstrates thorough attention to previous feedback with all major issues resolved. State management uses refs and triggers appropriately to avoid re-render issues. API integration follows existing patterns with proper error handling. - No files require special attention - all previous issues have been addressed <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | components/settings/OrganizationSettings.tsx | Added audit log tab with filter UI and pagination; refactored state management using refs and trigger mechanism | | lib/api/audit.ts | New API client for audit log with proper defensive handling of response data | | lib/api/client.ts | Modified to support legacy /api/ endpoints alongside /api/v1 endpoints | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant User participant UI as OrganizationSettings participant Filter as Filter State participant Trigger as auditFetchTrigger participant API as lib/api/audit participant Client as lib/api/client participant Backend as /api/audit User->>UI: Navigate to Audit log tab UI->>API: getAuditLog(params) API->>Client: apiRequest('/api/audit?...') Client->>Backend: GET /api/audit (with cookies) Backend-->>Client: { entries: [...], total: N } Client-->>API: Response data API-->>UI: { entries, total } UI->>UI: Display audit table User->>Filter: Change filter (action/logId/date) Filter->>Filter: Update filter state Note over Filter: 500ms debounce timer Filter->>Trigger: setAuditPage(0) Filter->>Trigger: setAuditFetchTrigger(prev + 1) Trigger->>UI: Trigger useEffect UI->>API: getAuditLog(params with filters) API->>Client: apiRequest('/api/audit?...') Client->>Backend: GET /api/audit?action=X&log_id=Y (with cookies) Backend-->>Client: Filtered results Client-->>API: Response data API-->>UI: { entries, total } UI->>UI: Display filtered results User->>UI: Click pagination (Next/Previous) UI->>UI: setAuditPage(newPage) UI->>API: getAuditLog(params with new offset) API->>Client: apiRequest('/api/audit?offset=...') Client->>Backend: GET /api/audit (with cookies) alt Token expired (401) Backend-->>Client: 401 Unauthorized Client->>Client: Detect 401, start refresh Client->>Backend: POST /api/auth/refresh (via /api/auth/refresh route) Backend-->>Client: New tokens (HttpOnly cookies) Client->>Backend: Retry GET /api/audit (with new cookies) Backend-->>Client: { entries: [...], total: N } else Token valid Backend-->>Client: { entries: [...], total: N } end Client-->>API: Response data API-->>UI: { entries, total } UI->>UI: Display paginated results User->>UI: Click Clear Filters UI->>Filter: Reset all filters to '' UI->>Trigger: setAuditPage(0) UI->>Trigger: setAuditFetchTrigger(prev + 1) Trigger->>UI: Trigger useEffect UI->>API: getAuditLog(params without filters) API->>Client: apiRequest('/api/audit') Client->>Backend: GET /api/audit (with cookies) Backend-->>Client: All entries (unfiltered) Client-->>API: Response data API-->>UI: { entries, total } UI->>UI: Display all results ```
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-05 13:00:17 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>2 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:00:16 +00:00

Tab state desync

activeTab is initialized from searchParams, and there’s an effect to sync when the URL changes, but clicking the sidebar buttons only calls setActiveTab(...) and never updates ?tab= in the URL. This means the URL can remain on a different tab, breaking deep-linking/back/forward navigation consistency (e.g., refresh brings you back to the old tab from the URL, not the one you clicked).

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 44:47

Comment:
**Tab state desync**

`activeTab` is initialized from `searchParams`, and there’s an effect to sync when the URL changes, but clicking the sidebar buttons only calls `setActiveTab(...)` and never updates `?tab=` in the URL. This means the URL can remain on a different tab, breaking deep-linking/back/forward navigation consistency (e.g., refresh brings you back to the old tab from the URL, not the one you clicked).

How can I resolve this? If you propose a fix, please make it concise.
**Tab state desync** `activeTab` is initialized from `searchParams`, and there’s an effect to sync when the URL changes, but clicking the sidebar buttons only calls `setActiveTab(...)` and never updates `?tab=` in the URL. This means the URL can remain on a different tab, breaking deep-linking/back/forward navigation consistency (e.g., refresh brings you back to the old tab from the URL, not the one you clicked). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 44:47 Comment: **Tab state desync** `activeTab` is initialized from `searchParams`, and there’s an effect to sync when the URL changes, but clicking the sidebar buttons only calls `setActiveTab(...)` and never updates `?tab=` in the URL. This means the URL can remain on a different tab, breaking deep-linking/back/forward navigation consistency (e.g., refresh brings you back to the old tab from the URL, not the one you clicked). How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:00:15 +00:00

Filters don't trigger reload

In the Audit Log tab, changing auditActionFilter, auditLogIdFilter, auditStartDate, or auditEndDate does not actually refetch data because loadAudit() is only invoked by the useEffect that depends on activeTab/currentOrgId/loadAudit (and loadAudit’s identity doesn’t change when its deps change). As a result, the UI shows updated filter inputs but still displays stale table data until the user changes tabs or pagination. Consider invoking loadAudit() when any filter changes (and resetting auditPage as needed).

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 169:196

Comment:
**Filters don't trigger reload**

In the Audit Log tab, changing `auditActionFilter`, `auditLogIdFilter`, `auditStartDate`, or `auditEndDate` does not actually refetch data because `loadAudit()` is only invoked by the `useEffect` that depends on `activeTab/currentOrgId/loadAudit` (and `loadAudit`’s identity doesn’t change when its deps change). As a result, the UI shows updated filter inputs but still displays stale table data until the user changes tabs or pagination. Consider invoking `loadAudit()` when any filter changes (and resetting `auditPage` as needed).

How can I resolve this? If you propose a fix, please make it concise.
**Filters don't trigger reload** In the Audit Log tab, changing `auditActionFilter`, `auditLogIdFilter`, `auditStartDate`, or `auditEndDate` does not actually refetch data because `loadAudit()` is only invoked by the `useEffect` that depends on `activeTab/currentOrgId/loadAudit` (and `loadAudit`’s identity doesn’t change when its deps change). As a result, the UI shows updated filter inputs but still displays stale table data until the user changes tabs or pagination. Consider invoking `loadAudit()` when any filter changes (and resetting `auditPage` as needed). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 169:196 Comment: **Filters don't trigger reload** In the Audit Log tab, changing `auditActionFilter`, `auditLogIdFilter`, `auditStartDate`, or `auditEndDate` does not actually refetch data because `loadAudit()` is only invoked by the `useEffect` that depends on `activeTab/currentOrgId/loadAudit` (and `loadAudit`’s identity doesn’t change when its deps change). As a result, the UI shows updated filter inputs but still displays stale table data until the user changes tabs or pagination. Consider invoking `loadAudit()` when any filter changes (and resetting `auditPage` as needed). How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:02:43 +00:00
uz1mani (Migrated from github.com) commented 2026-02-05 13:02:42 +00:00

Created a handleTabChange function that updates both the local activeTab state and the URL query parameter (?tab=...).
Updated all sidebar navigation buttons to use this handler, ensuring that clicking a tab updates the URL, preserving state across refreshes and navigation.

Created a handleTabChange function that updates both the local activeTab state and the URL query parameter (?tab=...). Updated all sidebar navigation buttons to use this handler, ensuring that clicking a tab updates the URL, preserving state across refreshes and navigation.
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:02:52 +00:00
uz1mani (Migrated from github.com) commented 2026-02-05 13:02:52 +00:00

Introduced a debounced useEffect that listens to changes in auditActionFilter, auditLogIdFilter, auditStartDate, and auditEndDate.
When any filter changes, it now waits 500ms (debounce) and then automatically triggers loadAudit() and resets the page to 0.
Refactored loadAudit to use a useRef for filter values, ensuring the function identity remains stable while always accessing the latest state. This prevents excessive re-renders while ensuring data freshness.

Introduced a debounced useEffect that listens to changes in auditActionFilter, auditLogIdFilter, auditStartDate, and auditEndDate. When any filter changes, it now waits 500ms (debounce) and then automatically triggers loadAudit() and resets the page to 0. Refactored loadAudit to use a useRef for filter values, ensuring the function identity remains stable while always accessing the latest state. This prevents excessive re-renders while ensuring data freshness.
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-05 13:07:21 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>2 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] commented 2026-02-05 13:07:22 +00:00 (Migrated from github.com)
Additional Comments (2)

components/settings/OrganizationSettings.tsx
URL tab state desync

Sidebar tab buttons call setActiveTab(...) directly (e.g. onClick={() => setActiveTab('general')}), but nothing updates the ?tab= query param. Since activeTab is initialized/synced from useSearchParams() (components/settings/OrganizationSettings.tsx:44-47 and :155-160), the URL and UI can drift (refresh/back/forward can land on a different tab than the one last clicked). You likely want a single tab-change handler that updates both state and the URL, and use it for all tab buttons.

Also appears at: components/settings/OrganizationSettings.tsx:333-356 (members/billing/audit buttons).

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 321:323

Comment:
**URL tab state desync**

Sidebar tab buttons call `setActiveTab(...)` directly (e.g. `onClick={() => setActiveTab('general')}`), but nothing updates the `?tab=` query param. Since `activeTab` is initialized/synced from `useSearchParams()` (`components/settings/OrganizationSettings.tsx:44-47` and `:155-160`), the URL and UI can drift (refresh/back/forward can land on a different tab than the one last clicked). You likely want a single tab-change handler that updates both state and the URL, and use it for all tab buttons.

Also appears at: components/settings/OrganizationSettings.tsx:333-356 (members/billing/audit buttons).

How can I resolve this? If you propose a fix, please make it concise.

components/settings/OrganizationSettings.tsx
Filter + pagination inconsistency

loadAudit() computes offset from auditPage (offset: auditPage * auditPageSize). When a user changes filters while on a later page, auditPage is not reset to 0, so the request can legitimately return an empty page even though matching results exist on earlier pages. This will happen in normal use (e.g., go to page 3 then narrow Action). Consider resetting auditPage to 0 on any filter change (not just when clicking “Clear Filters”).

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 169:176

Comment:
**Filter + pagination inconsistency**

`loadAudit()` computes `offset` from `auditPage` (`offset: auditPage * auditPageSize`). When a user changes filters while on a later page, `auditPage` is not reset to 0, so the request can legitimately return an empty page even though matching results exist on earlier pages. This will happen in normal use (e.g., go to page 3 then narrow `Action`). Consider resetting `auditPage` to 0 on any filter change (not just when clicking “Clear Filters”).


How can I resolve this? If you propose a fix, please make it concise.
<details> <summary>Additional Comments (2)</summary> **`components/settings/OrganizationSettings.tsx`** **URL tab state desync** Sidebar tab buttons call `setActiveTab(...)` directly (e.g. `onClick={() => setActiveTab('general')}`), but nothing updates the `?tab=` query param. Since `activeTab` is initialized/synced from `useSearchParams()` (`components/settings/OrganizationSettings.tsx:44-47` and `:155-160`), the URL and UI can drift (refresh/back/forward can land on a different tab than the one last clicked). You likely want a single tab-change handler that updates both state and the URL, and use it for all tab buttons. Also appears at: components/settings/OrganizationSettings.tsx:333-356 (members/billing/audit buttons). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 321:323 Comment: **URL tab state desync** Sidebar tab buttons call `setActiveTab(...)` directly (e.g. `onClick={() => setActiveTab('general')}`), but nothing updates the `?tab=` query param. Since `activeTab` is initialized/synced from `useSearchParams()` (`components/settings/OrganizationSettings.tsx:44-47` and `:155-160`), the URL and UI can drift (refresh/back/forward can land on a different tab than the one last clicked). You likely want a single tab-change handler that updates both state and the URL, and use it for all tab buttons. Also appears at: components/settings/OrganizationSettings.tsx:333-356 (members/billing/audit buttons). How can I resolve this? If you propose a fix, please make it concise. ````` </details> --- **`components/settings/OrganizationSettings.tsx`** **Filter + pagination inconsistency** `loadAudit()` computes `offset` from `auditPage` (`offset: auditPage * auditPageSize`). When a user changes filters while on a later page, `auditPage` is not reset to 0, so the request can legitimately return an empty page even though matching results exist on earlier pages. This will happen in normal use (e.g., go to page 3 then narrow `Action`). Consider resetting `auditPage` to 0 on any filter change (not just when clicking “Clear Filters”). <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 169:176 Comment: **Filter + pagination inconsistency** `loadAudit()` computes `offset` from `auditPage` (`offset: auditPage * auditPageSize`). When a user changes filters while on a later page, `auditPage` is not reset to 0, so the request can legitimately return an empty page even though matching results exist on earlier pages. This will happen in normal use (e.g., go to page 3 then narrow `Action`). Consider resetting `auditPage` to 0 on any filter change (not just when clicking “Clear Filters”). How can I resolve this? If you propose a fix, please make it concise. ````` </details> </details>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-05 13:28:19 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

2 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

<sub>2 files reviewed, 3 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -153,6 +183,46 @@ export default function OrganizationSettings() {
}
}, [activeTab, currentOrgId, loadSubscription, loadInvoices])
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:28:16 +00:00

infinite loop risk: loadAudit in dependency array

since loadAudit depends on auditPage (line 208), changing filters triggers the debounced effect, which calls loadAudit() and resets auditPage to 0 (line 215). this causes loadAudit to be recreated, potentially re-triggering the effect

remove loadAudit from dependencies since you're already using filtersRef to access latest values:

  useEffect(() => {
    if (activeTab !== 'audit') return
    
    const timer = setTimeout(() => {
        setAuditPage(0)
        loadAudit()
    }, 500)
    return () => clearTimeout(timer)
  }, [auditActionFilter, auditLogIdFilter, auditStartDate, auditEndDate, activeTab])
Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 211:219

Comment:
infinite loop risk: `loadAudit` in dependency array

since `loadAudit` depends on `auditPage` (line 208), changing filters triggers the debounced effect, which calls `loadAudit()` and resets `auditPage` to 0 (line 215). this causes `loadAudit` to be recreated, potentially re-triggering the effect

remove `loadAudit` from dependencies since you're already using `filtersRef` to access latest values:

```suggestion
  useEffect(() => {
    if (activeTab !== 'audit') return
    
    const timer = setTimeout(() => {
        setAuditPage(0)
        loadAudit()
    }, 500)
    return () => clearTimeout(timer)
  }, [auditActionFilter, auditLogIdFilter, auditStartDate, auditEndDate, activeTab])
```

How can I resolve this? If you propose a fix, please make it concise.
infinite loop risk: `loadAudit` in dependency array since `loadAudit` depends on `auditPage` (line 208), changing filters triggers the debounced effect, which calls `loadAudit()` and resets `auditPage` to 0 (line 215). this causes `loadAudit` to be recreated, potentially re-triggering the effect remove `loadAudit` from dependencies since you're already using `filtersRef` to access latest values: ```suggestion useEffect(() => { if (activeTab !== 'audit') return const timer = setTimeout(() => { setAuditPage(0) loadAudit() }, 500) return () => clearTimeout(timer) }, [auditActionFilter, auditLogIdFilter, auditStartDate, auditEndDate, activeTab]) ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 211:219 Comment: infinite loop risk: `loadAudit` in dependency array since `loadAudit` depends on `auditPage` (line 208), changing filters triggers the debounced effect, which calls `loadAudit()` and resets `auditPage` to 0 (line 215). this causes `loadAudit` to be recreated, potentially re-triggering the effect remove `loadAudit` from dependencies since you're already using `filtersRef` to access latest values: ```suggestion useEffect(() => { if (activeTab !== 'audit') return const timer = setTimeout(() => { setAuditPage(0) loadAudit() }, 500) return () => clearTimeout(timer) }, [auditActionFilter, auditLogIdFilter, auditStartDate, auditEndDate, activeTab]) ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:28:17 +00:00

double API call when page changes

when user clicks pagination, setAuditPage triggers this effect AND the debounced filter effect (211-219) since loadAudit is in both dependency arrays. causes 2 API calls

add auditPage to the debounced effect's dependencies and remove this separate effect

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 221:225

Comment:
double API call when page changes

when user clicks pagination, `setAuditPage` triggers this effect AND the debounced filter effect (211-219) since `loadAudit` is in both dependency arrays. causes 2 API calls

add `auditPage` to the debounced effect's dependencies and remove this separate effect

How can I resolve this? If you propose a fix, please make it concise.
double API call when page changes when user clicks pagination, `setAuditPage` triggers this effect AND the debounced filter effect (211-219) since `loadAudit` is in both dependency arrays. causes 2 API calls add `auditPage` to the debounced effect's dependencies and remove this separate effect <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 221:225 Comment: double API call when page changes when user clicks pagination, `setAuditPage` triggers this effect AND the debounced filter effect (211-219) since `loadAudit` is in both dependency arrays. causes 2 API calls add `auditPage` to the debounced effect's dependencies and remove this separate effect How can I resolve this? If you propose a fix, please make it concise. ````` </details>
@@ -0,0 +1,47 @@
/**
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:28:18 +00:00

auditFetch bypasses the standard client's token refresh

lib/api/client.ts has automatic token refresh on 401 (lines 103-173). this custom fetch duplicates error handling but lacks that retry mechanism. if the access token expires, audit log calls fail instead of auto-refreshing

use apiRequest from client.ts instead (note: may need to adjust endpoint format to match)

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/api/audit.ts
Line: 32:55

Comment:
`auditFetch` bypasses the standard client's token refresh

`lib/api/client.ts` has automatic token refresh on 401 (lines 103-173). this custom fetch duplicates error handling but lacks that retry mechanism. if the access token expires, audit log calls fail instead of auto-refreshing

use `apiRequest` from `client.ts` instead (note: may need to adjust endpoint format to match)

How can I resolve this? If you propose a fix, please make it concise.
`auditFetch` bypasses the standard client's token refresh `lib/api/client.ts` has automatic token refresh on 401 (lines 103-173). this custom fetch duplicates error handling but lacks that retry mechanism. if the access token expires, audit log calls fail instead of auto-refreshing use `apiRequest` from `client.ts` instead (note: may need to adjust endpoint format to match) <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: lib/api/audit.ts Line: 32:55 Comment: `auditFetch` bypasses the standard client's token refresh `lib/api/client.ts` has automatic token refresh on 401 (lines 103-173). this custom fetch duplicates error handling but lacks that retry mechanism. if the access token expires, audit log calls fail instead of auto-refreshing use `apiRequest` from `client.ts` instead (note: may need to adjust endpoint format to match) How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:32:37 +00:00
@@ -0,0 +1,47 @@
/**
uz1mani (Migrated from github.com) commented 2026-02-05 13:32:36 +00:00

Replaced the custom auditFetch with the standard apiRequest from lib/api/client.ts, which includes automatic token refresh logic.
Crucial Update in client.ts: Modified apiRequest to support legacy endpoints starting with /api/ (like /api/audit) without double-prepending /api/v1, ensuring compatibility with the backend.

Replaced the custom auditFetch with the standard apiRequest from lib/api/client.ts, which includes automatic token refresh logic. Crucial Update in client.ts: Modified apiRequest to support legacy endpoints starting with /api/ (like /api/audit) without double-prepending /api/v1, ensuring compatibility with the backend.
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:32:49 +00:00
@@ -153,6 +183,46 @@ export default function OrganizationSettings() {
}
}, [activeTab, currentOrgId, loadSubscription, loadInvoices])
uz1mani (Migrated from github.com) commented 2026-02-05 13:32:49 +00:00

Fix:
Introduced a auditFetchTrigger state to reliably trigger fetches when filters change, even if the page number remains 0.
Debounced Effect: Removed loadAudit from dependencies (fixing the loop). Now it only resets the page and increments the trigger.
Fetch Effect: Merged the pagination logic. It now listens to loadAudit (which changes on page change) AND auditFetchTrigger (which changes on filter change).

Fix: Introduced a auditFetchTrigger state to reliably trigger fetches when filters change, even if the page number remains 0. Debounced Effect: Removed loadAudit from dependencies (fixing the loop). Now it only resets the page and increments the trigger. Fetch Effect: Merged the pagination logic. It now listens to loadAudit (which changes on page change) AND auditFetchTrigger (which changes on filter change).
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:32:58 +00:00
@@ -153,6 +183,46 @@ export default function OrganizationSettings() {
}
}, [activeTab, currentOrgId, loadSubscription, loadInvoices])
uz1mani (Migrated from github.com) commented 2026-02-05 13:32:58 +00:00

Fix:
Introduced a auditFetchTrigger state to reliably trigger fetches when filters change, even if the page number remains 0.
Debounced Effect: Removed loadAudit from dependencies (fixing the loop). Now it only resets the page and increments the trigger.
Fetch Effect: Merged the pagination logic. It now listens to loadAudit (which changes on page change) AND auditFetchTrigger (which changes on filter change).

Fix: Introduced a auditFetchTrigger state to reliably trigger fetches when filters change, even if the page number remains 0. Debounced Effect: Removed loadAudit from dependencies (fixing the loop). Now it only resets the page and increments the trigger. Fetch Effect: Merged the pagination logic. It now listens to loadAudit (which changes on page change) AND auditFetchTrigger (which changes on filter change).
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-05 13:36:35 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

<sub>2 files reviewed, 2 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -77,6 +79,34 @@ export default function OrganizationSettings() {
const [orgSlug, setOrgSlug] = useState('')
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:36:33 +00:00

this useEffect is unnecessary - refs can be updated directly during render without causing issues

  // Update refs when state changes (no useEffect needed)
  filtersRef.current = {
    action: auditActionFilter,
    logId: auditLogIdFilter,
    startDate: auditStartDate,
    endDate: auditEndDate
  }
Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 103:110

Comment:
this `useEffect` is unnecessary - refs can be updated directly during render without causing issues

```suggestion
  // Update refs when state changes (no useEffect needed)
  filtersRef.current = {
    action: auditActionFilter,
    logId: auditLogIdFilter,
    startDate: auditStartDate,
    endDate: auditEndDate
  }
```

How can I resolve this? If you propose a fix, please make it concise.
this `useEffect` is unnecessary - refs can be updated directly during render without causing issues ```suggestion // Update refs when state changes (no useEffect needed) filtersRef.current = { action: auditActionFilter, logId: auditLogIdFilter, startDate: auditStartDate, endDate: auditEndDate } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 103:110 Comment: this `useEffect` is unnecessary - refs can be updated directly during render without causing issues ```suggestion // Update refs when state changes (no useEffect needed) filtersRef.current = { action: auditActionFilter, logId: auditLogIdFilter, startDate: auditStartDate, endDate: auditEndDate } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-05 13:36:32 +00:00

if filters are already empty and auditPage is 0, clicking Clear Filters won't trigger a refetch (no state changes = no debounced effect)

                    <Button
                        variant="ghost"
                        onClick={() => {
                            setAuditLogIdFilter('')
                            setAuditActionFilter('')
                            setAuditStartDate('')
                            setAuditEndDate('')
                            setAuditPage(0)
                            setAuditFetchTrigger(prev => prev + 1)
                        }}
                        disabled={isLoadingAudit}
                    >
                        Clear Filters
                    </Button>
Prompt To Fix With AI
This is a comment left during a code review.
Path: components/settings/OrganizationSettings.tsx
Line: 876:889

Comment:
if filters are already empty and `auditPage` is 0, clicking Clear Filters won't trigger a refetch (no state changes = no debounced effect)

```suggestion
                    <Button
                        variant="ghost"
                        onClick={() => {
                            setAuditLogIdFilter('')
                            setAuditActionFilter('')
                            setAuditStartDate('')
                            setAuditEndDate('')
                            setAuditPage(0)
                            setAuditFetchTrigger(prev => prev + 1)
                        }}
                        disabled={isLoadingAudit}
                    >
                        Clear Filters
                    </Button>
```

How can I resolve this? If you propose a fix, please make it concise.
if filters are already empty and `auditPage` is 0, clicking Clear Filters won't trigger a refetch (no state changes = no debounced effect) ```suggestion <Button variant="ghost" onClick={() => { setAuditLogIdFilter('') setAuditActionFilter('') setAuditStartDate('') setAuditEndDate('') setAuditPage(0) setAuditFetchTrigger(prev => prev + 1) }} disabled={isLoadingAudit} > Clear Filters </Button> ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: components/settings/OrganizationSettings.tsx Line: 876:889 Comment: if filters are already empty and `auditPage` is 0, clicking Clear Filters won't trigger a refetch (no state changes = no debounced effect) ```suggestion <Button variant="ghost" onClick={() => { setAuditLogIdFilter('') setAuditActionFilter('') setAuditStartDate('') setAuditEndDate('') setAuditPage(0) setAuditFetchTrigger(prev => prev + 1) }} disabled={isLoadingAudit} > Clear Filters </Button> ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:39:46 +00:00
@@ -77,6 +79,34 @@ export default function OrganizationSettings() {
const [orgSlug, setOrgSlug] = useState('')
uz1mani (Migrated from github.com) commented 2026-02-05 13:39:46 +00:00

Issue: Unnecessary useEffect for updating filtersRef.
Fix: Removed the useEffect and now updating filtersRef.current synchronously during the render cycle.
Why: Refs can be safely updated during render for this use case, avoiding an extra effect hook and simplifying the component lifecycle.

Issue: Unnecessary useEffect for updating filtersRef. Fix: Removed the useEffect and now updating filtersRef.current synchronously during the render cycle. Why: Refs can be safely updated during render for this use case, avoiding an extra effect hook and simplifying the component lifecycle.
uz1mani (Migrated from github.com) reviewed 2026-02-05 13:39:55 +00:00
uz1mani (Migrated from github.com) commented 2026-02-05 13:39:55 +00:00

Issue: "Clear Filters" button doesn't trigger a refetch if filters are already empty and page is 0 (no state change).
Fix: Added setAuditFetchTrigger(prev => prev + 1) to the onClick handler.
Why: This forces the useEffect dependent on auditFetchTrigger to run, ensuring the audit log reloads even if filter state values remain unchanged.

Issue: "Clear Filters" button doesn't trigger a refetch if filters are already empty and page is 0 (no state change). Fix: Added setAuditFetchTrigger(prev => prev + 1) to the onClick handler. Why: This forces the useEffect dependent on auditFetchTrigger to run, ensuring the audit log reloads even if filter state values remain unchanged.
Sign in to join this conversation.
No description provided.