-
Notifications
You must be signed in to change notification settings - Fork 7
Keep the payout fields, not the whole entries, for the wallet total #1571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||
| import { getAccountPostsQueryOptions } from "@ecency/sdk"; | ||||||||||
| import { queryOptions } from "@tanstack/react-query"; | ||||||||||
| import type { Entry } from "@/entities"; | ||||||||||
|
|
||||||||||
| /** The two fields the pending-earnings total is computed from. */ | ||||||||||
| export interface PendingPayout { | ||||||||||
| payout_at: string; | ||||||||||
| pending_payout_value: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const RECENT_LIMIT = 20; | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Recent posts or comments, reduced to the payout fields on arrival. | ||||||||||
| * | ||||||||||
| * The wallet total reads `payout_at` and `pending_payout_value` and nothing | ||||||||||
| * else, but the bridge answers with whole entries: measured across four active | ||||||||||
| * accounts, the two calls this component makes retain about 660 KB each time | ||||||||||
| * the wallet is opened, roughly 100 KB of post bodies and 500 KB of voter | ||||||||||
| * records, to produce one number. Projecting on arrival keeps 2.8 KB of it. | ||||||||||
| * | ||||||||||
| * The wire cost is unchanged: the bridge sends what it sends, and only an | ||||||||||
| * endpoint of our own or a field on the account could avoid that. What this | ||||||||||
| * buys is retained memory, which is the thing that bounds how many renderer | ||||||||||
| * replicas fit on a host. | ||||||||||
| * | ||||||||||
| * The key carries its own marker. `accountPostsPage` is read by the waves | ||||||||||
| * composer and the decks user column, which need whole entries, and handing | ||||||||||
| * either of them a projected row would be the fault that issue #1556 was. | ||||||||||
| */ | ||||||||||
| export function pendingPayoutsQueryOptions(username: string, sort: "posts" | "comments") { | ||||||||||
| const base = getAccountPostsQueryOptions(username, sort, "", "", RECENT_LIMIT, ""); | ||||||||||
| const fetchEntries = base.queryFn as (ctx?: unknown) => Promise<Entry[] | null | undefined>; | ||||||||||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||||||||||
|
|
||||||||||
| // Built rather than spread: spreading carries the SDK's own `Entry[]` result | ||||||||||
| // type, which this deliberately narrows. | ||||||||||
| return queryOptions({ | ||||||||||
| queryKey: [...base.queryKey, "pending-payouts"], | ||||||||||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||||||||||
| queryFn: async (): Promise<PendingPayout[]> => { | ||||||||||
| const entries = (await fetchEntries()) ?? []; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When these wallet queries are cancelled because the user navigates away, changes profiles, or code calls Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, and fixed in 8994fdf. The wrapper now takes React Query's context and passes it straight through, so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the wallet unmounts during either request, React Query aborts its signal, but this wrapper calls
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 8994fdf, along the lines of the suggestion. Added a test asserting the exact context object reaches the SDK query function and that its
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||||||||||
| return entries.map((entry) => ({ | ||||||||||
| payout_at: entry.payout_at, | ||||||||||
| pending_payout_value: entry.pending_payout_value | ||||||||||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||||||||||
| })); | ||||||||||
| } | ||||||||||
| }); | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { describe, expect, it, vi } from "vitest"; | ||
| import { QueryKeys } from "@ecency/sdk"; | ||
| import type { Entry } from "@/entities"; | ||
|
|
||
| const fetchSpy = vi.hoisted(() => vi.fn()); | ||
|
|
||
| vi.mock("@ecency/sdk", async () => { | ||
| const actual = await vi.importActual<Record<string, unknown>>("@ecency/sdk"); | ||
| const { QueryKeys: keys } = actual as { QueryKeys: typeof QueryKeys }; | ||
| return { | ||
| ...actual, | ||
| getAccountPostsQueryOptions: ( | ||
| username: string, | ||
| sort: string, | ||
| a: string, | ||
| p: string, | ||
| limit: number, | ||
| observer: string | ||
| ) => ({ | ||
| queryKey: keys.posts.accountPostsPage(username, sort, a, p, limit, observer), | ||
| queryFn: fetchSpy | ||
| }) | ||
| }; | ||
| }); | ||
|
|
||
| import { pendingPayoutsQueryOptions } from "@/app/(dynamicPages)/profile/[username]/wallet/_components/pending-payouts-query"; | ||
|
|
||
| function entry(overrides: Partial<Entry> = {}): Entry { | ||
| return { | ||
| author: "alice", | ||
| permlink: "p", | ||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||
| body: "a very long post body that the wallet total has no use for", | ||
| active_votes: Array.from({ length: 400 }, (_, i) => ({ voter: `v${i}`, rshares: 1_000_000 })), | ||
| payout_at: "2026-08-27T00:00:00", | ||
| pending_payout_value: "1.234 HBD", | ||
| ...overrides | ||
| } as unknown as Entry; | ||
| } | ||
|
|
||
| /** | ||
| * The wallet total reads two fields and the bridge answers with whole entries, | ||
| * about 660 KB per wallet view across the two calls, mostly voter records. | ||
| */ | ||
| describe("pendingPayoutsQueryOptions", () => { | ||
| it("keeps only the payout fields", async () => { | ||
| fetchSpy.mockResolvedValueOnce([entry(), entry({ pending_payout_value: "2.000 HBD" })]); | ||
| const rows = await pendingPayoutsQueryOptions("alice", "posts").queryFn(); | ||
|
|
||
| expect(rows).toEqual([ | ||
| { payout_at: "2026-08-27T00:00:00", pending_payout_value: "1.234 HBD" }, | ||
| { payout_at: "2026-08-27T00:00:00", pending_payout_value: "2.000 HBD" } | ||
| ]); | ||
| for (const row of rows) { | ||
| expect(row).not.toHaveProperty("body"); | ||
| expect(row).not.toHaveProperty("active_votes"); | ||
| } | ||
| }); | ||
|
|
||
| it("does not answer under the key that whole-entry readers use", () => { | ||
| // The waves composer and the decks user column read accountPostsPage and | ||
| // need whole entries; a projected row there would be issue #1556 again. | ||
| const shared = QueryKeys.posts.accountPostsPage("alice", "posts", "", "", 20, ""); | ||
| const projected = pendingPayoutsQueryOptions("alice", "posts").queryKey; | ||
|
|
||
| expect(projected).not.toEqual(shared); | ||
| expect(projected.slice(0, shared.length)).toEqual(shared); | ||
| expect(projected[projected.length - 1]).toBe("pending-payouts"); | ||
| }); | ||
|
|
||
| it("survives an empty or missing answer", async () => { | ||
| fetchSpy.mockResolvedValueOnce(null); | ||
| await expect(pendingPayoutsQueryOptions("alice", "comments").queryFn()).resolves.toEqual([]); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.