Skip to content

feat(sdk): add sorting + RQL infinite loading to views-new invoices#1553

Open
paanSinghCoder wants to merge 10 commits intomainfrom
feat/add-sorting-and-pagination-invoices
Open

feat(sdk): add sorting + RQL infinite loading to views-new invoices#1553
paanSinghCoder wants to merge 10 commits intomainfrom
feat/add-sorting-and-pagination-invoices

Conversation

@paanSinghCoder
Copy link
Copy Markdown
Contributor

@paanSinghCoder paanSinghCoder commented Apr 20, 2026

Summary

  • Swap views-new billing `Invoices` component from the plain `listInvoices` RPC to `FrontierServiceQueries.searchOrganizationInvoices` with `useInfiniteQuery` (server-mode pagination).
  • Enable server-side sorting on Date, Status, and Amount columns via `<DataTable.Toolbar />` + DisplaySettings.
  • Default sort: `createdAt desc` (matching the admin invoices view).
  • `listInvoices` call remains in `billing-view.tsx` to feed `PaymentIssue` (which needs `dueDate`/`hostedUrl` not present in the RQL response shape).
  • Duplicate `connect-pagination.ts` and `transform-query.ts` into `sdk/react/utils/` (kept independent from `sdk/admin/utils/` — the admin and react entrypoints deploy separately, so utilities can't be shared via direct imports).
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`:

  • `effectiveAt` (with draft → `dueDate` fallback) → `createdAt`
  • `hostedUrl` → `invoiceLink`
    Other fields (`state`, `amount`, `currency`) map 1:1.

Depends on

Test plan

  • `pnpm run build` (web turbo) passes
  • `docker-compose build` succeeds against the local proton tarball
  • Manual: change sort via DisplaySettings; Reset to default snaps back to createdAt desc
  • Manual: scroll triggers `fetchNextPage` (infinite loading)
  • Manual: PaymentIssue banner still fires for past-due subscriptions (uses the untouched `listInvoices` call)

🤖 Generated with Claude Code

paanSinghCoder and others added 2 commits April 17, 2026 16:42
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>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Error Error Apr 20, 2026 8:56am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 20, 2026

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features
    • Invoice list now supports server-driven pagination with load-more capability.
    • Enhanced invoice status column with grouping indicators, sorting, and hide/show options.
    • Improved error handling when invoice retrieval fails.

Walkthrough

The 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 useInfiniteQuery instead of relying on parent props.

Changes

Cohort / File(s) Summary
Build Configuration
Makefile
Updated PROTON_COMMIT variable to new hash, affecting protobuf archive revision used during proto target build.
React Utilities
web/sdk/react/utils/connect-pagination.ts, web/sdk/react/utils/transform-query.ts
Added new modules: connect-pagination.ts exports pagination utilities for RQL infinite queries (DEFAULT_PAGE_SIZE, getConnectNextPageParam, getGroupCountMapFromFirstPage); transform-query.ts exports DataTableQuery-to-RQLRequest conversion with field name mapping and filter/sort transformation.
Constants
web/sdk/react/utils/constants.ts
Extended INVOICE_STATES to include DRAFT, VOID, and UNCOLLECTIBLE invoice statuses.
Billing Components
web/sdk/react/views-new/billing/billing-view.tsx, web/sdk/react/views-new/billing/components/invoices.tsx
BillingView no longer passes invoice props to Invoices. Invoices component refactored to fetch data internally via useInfiniteQuery, manage DataTableQuery state, apply RQL transformations, and handle pagination/error states independently.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • rsbh
  • rohanchkrabrty

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coveralls
Copy link
Copy Markdown

coveralls commented Apr 20, 2026

Coverage Report for CI Build 24657517656

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage remained the same at 42.257%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 36962
Covered Lines: 15619
Line Coverage: 42.26%
Coverage Strength: 11.85 hits per line

💛 - Coveralls

Base automatically changed from refactor/search-org-invoices-to-frontier-service to main April 20, 2026 08:53
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between cd1a62d and 1b8a353.

📒 Files selected for processing (6)
  • Makefile
  • web/sdk/react/utils/connect-pagination.ts
  • web/sdk/react/utils/constants.ts
  • web/sdk/react/utils/transform-query.ts
  • web/sdk/react/views-new/billing/billing-view.tsx
  • web/sdk/react/views-new/billing/components/invoices.tsx

Comment on lines +31 to +34
// 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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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;

Comment on lines +36 to +41
const DEFAULT_SORT: DataTableSort = { name: 'createdAt', order: 'desc' };

const INITIAL_QUERY: DataTableQuery = {
offset: 0,
limit: DEFAULT_PAGE_SIZE
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n web/sdk/react/views-new/billing/components/invoices.tsx | head -100

Repository: 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.ts

Repository: raystack/frontier

Length of output: 4223


🏁 Script executed:

rg -l "DataTable" --type tsx --type ts web/sdk/react/ | head -10

Repository: raystack/frontier

Length of output: 88


🏁 Script executed:

rg -l "DataTable" web/sdk/react/ | grep -E "\.(tsx?|jsx?)$" | head -10

Repository: raystack/frontier

Length of output: 565


🏁 Script executed:

rg -A 20 "const INITIAL_QUERY" web/sdk/react/ | head -80

Repository: raystack/frontier

Length of output: 1781


🏁 Script executed:

rg -B 5 -A 15 "defaultSort" web/sdk/react/ | head -100

Repository: 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.tsx

Repository: raystack/frontier

Length of output: 43


🏁 Script executed:

rg "useEffect\|useMemo" web/sdk/react/views-new/billing/components/invoices.tsx -A 3

Repository: 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 -5

Repository: 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 -5

Repository: 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.

Suggested change
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]
};

Comment on lines +173 to +180
} = useInfiniteQuery(
FrontierServiceQueries.searchOrganizationInvoices,
{ id: organizationId, query },
{
enabled: !!organizationId,
pageParamKey: 'query',
getNextPageParam: lastPage =>
getConnectNextPageParam(lastPage, { query }, 'organizationInvoices'),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the backend implementation of SearchOrganizationInvoices
fd -t f 'service\.go$' | grep -i invoice | head -20

Repository: 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 2

Repository: 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 2

Repository: 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 -80

Repository: 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 -5

Repository: 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 -3

Repository: 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 -150

Repository: 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 -300

Repository: 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',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lets use created_at here

limit: DEFAULT_PAGE_SIZE
};

const TRANSFORM_OPTIONS = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we can remove this

isLoading: boolean;
invoices: Invoice[];
}
const DEFAULT_SORT: DataTableSort = { name: 'createdAt', order: 'desc' };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lets use created_at here

<DataTable.Content
emptyState={noDataChildren}
/>
<Flex direction="column" style={{ width: '100%' }}>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use width props

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.

3 participants