feat(sdk): add sorting + RQL infinite loading to views-new invoices#1553
feat(sdk): add sorting + RQL infinite loading to views-new invoices#1553paanSinghCoder wants to merge 10 commits intomainfrom
Conversation
Move the RPC from AdminService to FrontierService so org admins (not only platform superusers) can list their own org's invoices. Matches the gate pattern already used by FrontierService/ListInvoices (UpdatePermission on the org namespace). Superusers still pass via the standard interceptor bypass. - Bump PROTON_COMMIT to pick up the proto move (raystack/proton#476). - Regenerate proto/v1beta1 via `make proto`. - Swap authorization.go entry from IsSuperUser to IsAuthorized(org, UpdatePermission). - Switch the admin dashboard frontend from AdminServiceQueries to FrontierServiceQueries; request/response shape is unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughThe PR updates the Proton commit reference in the Makefile, introduces new React utilities for RQL-based pagination and query transformation, and refactors the Invoices component to manage invoice data fetching internally via Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report for CI Build 24657517656Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage remained the same at 42.257%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
…c0931e4ca9b087de8cd in package.json and pnpm-lock.yaml
…feat/add-sorting-and-pagination-invoices
…931e4ca9b087de8cd in package.json and pnpm-lock.yaml
…feat/add-sorting-and-pagination-invoices
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 822a8b82-d707-4637-b968-776c4e14c15c
📒 Files selected for processing (6)
Makefileweb/sdk/react/utils/connect-pagination.tsweb/sdk/react/utils/constants.tsweb/sdk/react/utils/transform-query.tsweb/sdk/react/views-new/billing/billing-view.tsxweb/sdk/react/views-new/billing/components/invoices.tsx
| // Use pagination info from response to determine next page | ||
| const pagination = lastPage.pagination; | ||
| const currentOffset = pagination?.offset || 0; | ||
| const limit = pagination?.limit || DEFAULT_PAGE_SIZE; |
There was a problem hiding this comment.
Preserve valid pagination values when falling back.
offset and limit are numeric fields, so || treats valid 0 values as missing. Prefer nullish fallback and use the request query as the next fallback before DEFAULT_PAGE_SIZE.
Proposed fix
- const currentOffset = pagination?.offset || 0;
- const limit = pagination?.limit || DEFAULT_PAGE_SIZE;
+ const currentOffset = pagination?.offset ?? queryParams.query.offset ?? 0;
+ const limit = pagination?.limit ?? queryParams.query.limit ?? DEFAULT_PAGE_SIZE;| const DEFAULT_SORT: DataTableSort = { name: 'createdAt', order: 'desc' }; | ||
|
|
||
| const INITIAL_QUERY: DataTableQuery = { | ||
| offset: 0, | ||
| limit: DEFAULT_PAGE_SIZE | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n web/sdk/react/views-new/billing/components/invoices.tsx | head -100Repository: raystack/frontier
Length of output: 3545
🏁 Script executed:
cat -n web/sdk/react/views-new/billing/components/invoices.tsx | sed -n '100,250p'Repository: raystack/frontier
Length of output: 4321
🏁 Script executed:
cat -n web/sdk/react/utils/transform-query.tsRepository: raystack/frontier
Length of output: 4223
🏁 Script executed:
rg -l "DataTable" --type tsx --type ts web/sdk/react/ | head -10Repository: raystack/frontier
Length of output: 88
🏁 Script executed:
rg -l "DataTable" web/sdk/react/ | grep -E "\.(tsx?|jsx?)$" | head -10Repository: raystack/frontier
Length of output: 565
🏁 Script executed:
rg -A 20 "const INITIAL_QUERY" web/sdk/react/ | head -80Repository: raystack/frontier
Length of output: 1781
🏁 Script executed:
rg -B 5 -A 15 "defaultSort" web/sdk/react/ | head -100Repository: raystack/frontier
Length of output: 8153
🏁 Script executed:
rg -B 10 'mode="server"' web/sdk/react/ | grep -A 10 "INITIAL_QUERY\|defaultSort"Repository: raystack/frontier
Length of output: 225
🏁 Script executed:
rg -B 5 -A 10 "onTableQueryChange.*mount\|useEffect.*onTableQueryChange\|useEffect.*defaultSort" web/sdk/react/Repository: raystack/frontier
Length of output: 43
🏁 Script executed:
rg -B 5 -A 10 "useEffect" web/sdk/react/views-new/billing/components/invoices.tsxRepository: raystack/frontier
Length of output: 43
🏁 Script executed:
rg "useEffect\|useMemo" web/sdk/react/views-new/billing/components/invoices.tsx -A 3Repository: raystack/frontier
Length of output: 43
🏁 Script executed:
cat web/sdk/react/views-new/billing/components/invoices.tsx | grep -A 3 "useEffect\|useMemo"Repository: raystack/frontier
Length of output: 545
🏁 Script executed:
cd web && find . -name "package.json" -o -name "package-lock.json" -o -name "yarn.lock" | head -5Repository: raystack/frontier
Length of output: 177
🏁 Script executed:
cat web/sdk/react/package.json | grep -A 5 -B 5 "apsara"Repository: raystack/frontier
Length of output: 43
🏁 Script executed:
find web/sdk/react -name "*.json" -exec grep -l "apsara" {} \;Repository: raystack/frontier
Length of output: 43
🏁 Script executed:
find web/sdk/react -type f -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "defaultSort\|onTableQueryChange" 2>/dev/null | head -5Repository: raystack/frontier
Length of output: 43
Seed the server query with the default sort.
defaultSort configures the table UI, but the initial RQL request is built from INITIAL_QUERY, which currently has no sort. The transformDataTableQueryToRQLRequest function does not apply a default sort—it only transforms what exists in query.sort. This means the first server page loads without any sort order, requiring a user interaction (column click) to trigger onTableQueryChange and add sorting. Add the default sort to INITIAL_QUERY so the first server request is deterministically createdAt desc.
Proposed fix
const INITIAL_QUERY: DataTableQuery = {
offset: 0,
- limit: DEFAULT_PAGE_SIZE
+ limit: DEFAULT_PAGE_SIZE,
+ sort: [DEFAULT_SORT]
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const DEFAULT_SORT: DataTableSort = { name: 'createdAt', order: 'desc' }; | |
| const INITIAL_QUERY: DataTableQuery = { | |
| offset: 0, | |
| limit: DEFAULT_PAGE_SIZE | |
| }; | |
| const DEFAULT_SORT: DataTableSort = { name: 'createdAt', order: 'desc' }; | |
| const INITIAL_QUERY: DataTableQuery = { | |
| offset: 0, | |
| limit: DEFAULT_PAGE_SIZE, | |
| sort: [DEFAULT_SORT] | |
| }; |
| } = useInfiniteQuery( | ||
| FrontierServiceQueries.searchOrganizationInvoices, | ||
| { id: organizationId, query }, | ||
| { | ||
| enabled: !!organizationId, | ||
| pageParamKey: 'query', | ||
| getNextPageParam: lastPage => | ||
| getConnectNextPageParam(lastPage, { query }, 'organizationInvoices'), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the backend implementation of SearchOrganizationInvoices
fd -t f 'service\.go$' | grep -i invoice | head -20Repository: raystack/frontier
Length of output: 311
🏁 Script executed:
# Also search for the RQL query construction and any filtering logic
rg 'SearchOrganizationInvoices|searchOrganizationInvoices' -t go -A 5 -B 2Repository: raystack/frontier
Length of output: 37957
🏁 Script executed:
# Find the billing invoice service implementation
fd -t f -p 'billing/invoice' | grep -E '\.go$'Repository: raystack/frontier
Length of output: 146
🏁 Script executed:
# Look for nonzeroAmountOnly or zero-amount filtering logic in the codebase
rg 'nonzeroAmountOnly|zero.*amount|zeroAmount' -t go -B 2 -A 2Repository: raystack/frontier
Length of output: 6896
🏁 Script executed:
# Read the SearchOrganizationInvoices implementation
sed -n '/func (h \*ConnectHandler) SearchOrganizationInvoices/,/^func /p' internal/api/v1beta1connect/organization_invoices.go | head -80Repository: raystack/frontier
Length of output: 2330
🏁 Script executed:
# Also check the orginvoices service to see if there's default filtering
fd -t f 'service.go' -path '*/orginvoices/*' | head -5Repository: raystack/frontier
Length of output: 229
🏁 Script executed:
# Look at the old billing-view.tsx to see how the old query was constructed
fd -t f 'billing-view.tsx' | head -3Repository: raystack/frontier
Length of output: 109
🏁 Script executed:
# Find and read the orginvoices service
fd -t f -p 'core/aggregates/orginvoices' '\.go$'Repository: raystack/frontier
Length of output: 153
🏁 Script executed:
# Read the orginvoices service
cat core/aggregates/orginvoices/service.go | head -150Repository: raystack/frontier
Length of output: 1437
🏁 Script executed:
# Check the old billing-view.tsx
cat web/sdk/react/views-new/billing/billing-view.tsx | head -300Repository: raystack/frontier
Length of output: 4483
🏁 Script executed:
# Read invoices.tsx around lines 173-180 to see the full context and how query is initialized
cat -n web/sdk/react/views-new/billing/components/invoices.tsx | sed -n '1,200p'Repository: raystack/frontier
Length of output: 6762
Add an RQL filter to exclude zero-amount invoices from the search query.
The old listInvoices path used nonzeroAmountOnly: true, but SearchOrganizationInvoices has no equivalent parameter and the backend applies no default filtering. The initial RQL query passed to the search request (lines 159–164) contains only pagination parameters, so zero-amount invoices will be displayed.
Either add an amount filter clause to the initial query or implement a nonzero_amount_only parameter in the SearchOrganizationInvoicesRequest proto and backend handler.
| : (getValue() as TimeStamp); | ||
| const timestamp = value || row?.original?.createdAt; | ||
| const date = timestampToDayjs(timestamp); | ||
| accessorKey: 'createdAt', |
| limit: DEFAULT_PAGE_SIZE | ||
| }; | ||
|
|
||
| const TRANSFORM_OPTIONS = { |
| isLoading: boolean; | ||
| invoices: Invoice[]; | ||
| } | ||
| const DEFAULT_SORT: DataTableSort = { name: 'createdAt', order: 'desc' }; |
| <DataTable.Content | ||
| emptyState={noDataChildren} | ||
| /> | ||
| <Flex direction="column" style={{ width: '100%' }}> |
Summary
Screen.Recording.2026-04-20.at.1.14.52.PM.mov
Field mapping
The RQL response (`OrganizationInvoice`) has a trimmed projection vs the full `Invoice`:
Other fields (`state`, `amount`, `currency`) map 1:1.
Depends on
Test plan
🤖 Generated with Claude Code