Skip to content

Commit 54601c0

Browse files
authored
refactor: delete total_count and remaining_count from pagination results (#117)
## Description <!-- Provide a brief description of your changes --> **Note:** PR titles should follow [Conventional Commits](https://www.conventionalcommits.org/) format (e.g., `feat(devbox): add support for custom env vars` or `fix(snapshot): resolve pagination issue`) as they are used for automatic release notes generation. ## Type of Change <!-- Mark the relevant option with an 'x' --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update - [x] Code refactoring - [x] Performance improvement - [ ] Test updates ## Related Issues <!-- Link to related issues using #issue-number --> Closes # ## Changes Made - Make our TUI pagination and CLI list subcommand no longer use `total_count` and `remaining_count` from our API since it will be deprecated - Make our TUI pagination keep track of # of items seen so far (when scrolling to previous page, it shouldn't decrease). However, this isn't necessarily equal to total number of items that exist - Add `--limit` option to `list` subcommands of `blueprints` and `snapshots` - Breaking: these subcommands only list 20 by default (just like the other ones) instead of all ## Testing <!-- Describe how you tested your changes --> - [x] I have tested locally - [ ] I have added/updated tests - [x] All existing tests pass ## Checklist - [x] My code follows the code style of this project - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published ## Screenshots (if applicable) <!-- Add screenshots to help explain your changes --> ## Additional Notes <!-- Any additional information that reviewers should know -->
1 parent 08a67c7 commit 54601c0

19 files changed

Lines changed: 476 additions & 174 deletions

src/commands/blueprint/list.tsx

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Operation } from "../../components/OperationsMenu.js";
1515
import { ActionsPopup } from "../../components/ActionsPopup.js";
1616
import { formatTimeAgo } from "../../components/ResourceListView.js";
1717
import { SearchBar } from "../../components/SearchBar.js";
18-
import { output, outputError } from "../../utils/output.js";
18+
import { output, outputError, parseLimit } from "../../utils/output.js";
1919
import { getBlueprintUrl } from "../../utils/url.js";
2020
import { colors } from "../../utils/theme.js";
2121
import { getStatusDisplay } from "../../components/StatusBadge.js";
@@ -148,7 +148,7 @@ const ListBlueprintsUI = ({
148148
const result = {
149149
items: pageBlueprints,
150150
hasMore: page.has_more || false,
151-
totalCount: page.total_count || pageBlueprints.length,
151+
totalCount: pageBlueprints.length,
152152
};
153153

154154
return result;
@@ -179,7 +179,7 @@ const ListBlueprintsUI = ({
179179
!executingOperation &&
180180
!showDeleteConfirm &&
181181
!search.searchMode,
182-
deps: [PAGE_SIZE, search.submittedSearchQuery],
182+
deps: [search.submittedSearchQuery],
183183
});
184184

185185
// Memoize columns array
@@ -624,10 +624,18 @@ const ListBlueprintsUI = ({
624624
bindings: {
625625
up: () => {
626626
if (selectedIndex > 0) setSelectedIndex(selectedIndex - 1);
627+
else if (!loading && !navigating && hasPrev) {
628+
prevPage();
629+
setSelectedIndex(blueprints.length - 1);
630+
}
627631
},
628632
down: () => {
629633
if (selectedIndex < blueprints.length - 1)
630634
setSelectedIndex(selectedIndex + 1);
635+
else if (!loading && !navigating && hasMore) {
636+
nextPage();
637+
setSelectedIndex(0);
638+
}
631639
},
632640
n: goToNextPage,
633641
right: goToNextPage,
@@ -875,7 +883,7 @@ const ListBlueprintsUI = ({
875883
data={blueprints}
876884
keyExtractor={(blueprint: BlueprintListItem) => blueprint.id}
877885
selectedIndex={selectedIndex}
878-
title={`blueprints[${totalCount}]`}
886+
title={`blueprints[${hasMore ? `${totalCount}+` : totalCount}]`}
879887
columns={blueprintColumns}
880888
emptyState={
881889
<Text color={colors.textDim}>
@@ -889,7 +897,7 @@ const ListBlueprintsUI = ({
889897
{!showPopup && (
890898
<Box marginTop={1} paddingX={1}>
891899
<Text color={colors.primary} bold>
892-
{figures.hamburger} {totalCount}
900+
{figures.hamburger} {hasMore ? `${totalCount}+` : totalCount}
893901
</Text>
894902
<Text color={colors.textDim} dimColor>
895903
{" "}
@@ -907,7 +915,8 @@ const ListBlueprintsUI = ({
907915
</Text>
908916
) : (
909917
<Text color={colors.textDim} dimColor>
910-
Page {currentPage + 1} of {totalPages}
918+
Page {currentPage + 1} of{" "}
919+
{hasMore ? `${totalPages}+` : totalPages}
911920
</Text>
912921
)}
913922
</>
@@ -917,7 +926,8 @@ const ListBlueprintsUI = ({
917926
{" "}
918927
</Text>
919928
<Text color={colors.textDim} dimColor>
920-
Showing {startIndex + 1}-{endIndex} of {totalCount}
929+
Showing {startIndex + 1}-{endIndex} of{" "}
930+
{hasMore ? `${totalCount}+` : totalCount}
921931
</Text>
922932
{search.submittedSearchQuery && (
923933
<>
@@ -982,6 +992,7 @@ const ListBlueprintsUI = ({
982992

983993
interface ListBlueprintsOptions {
984994
name?: string;
995+
limit?: string;
985996
output?: string;
986997
}
987998

@@ -992,23 +1003,45 @@ export async function listBlueprints(options: ListBlueprintsOptions = {}) {
9921003
try {
9931004
const client = getClient();
9941005

995-
// Build query params
996-
const queryParams: Record<string, unknown> = {
997-
limit: DEFAULT_PAGE_SIZE,
998-
};
999-
if (options.name) {
1000-
queryParams.name = options.name;
1001-
}
1006+
const maxResults = parseLimit(options.limit);
1007+
const allBlueprints: unknown[] = [];
1008+
let startingAfter: string | undefined;
10021009

1003-
// Fetch blueprints
1004-
const page = (await client.blueprints.list(
1005-
queryParams,
1006-
)) as BlueprintsCursorIDPage<{ id: string }>;
1010+
do {
1011+
const remaining = maxResults - allBlueprints.length;
1012+
// Build query params
1013+
const queryParams: Record<string, unknown> = {
1014+
limit: Math.min(DEFAULT_PAGE_SIZE, remaining),
1015+
};
1016+
if (options.name) {
1017+
queryParams.name = options.name;
1018+
}
1019+
if (startingAfter) {
1020+
queryParams.starting_after = startingAfter;
1021+
}
10071022

1008-
// Extract blueprints array
1009-
const blueprints = page.blueprints || [];
1023+
// Fetch one page
1024+
const page = (await client.blueprints.list(
1025+
queryParams,
1026+
)) as BlueprintsCursorIDPage<{ id: string }>;
1027+
1028+
const pageBlueprints = page.blueprints || [];
1029+
allBlueprints.push(...pageBlueprints);
1030+
1031+
if (
1032+
page.has_more &&
1033+
pageBlueprints.length > 0 &&
1034+
allBlueprints.length < maxResults
1035+
) {
1036+
startingAfter = (
1037+
pageBlueprints[pageBlueprints.length - 1] as { id: string }
1038+
).id;
1039+
} else {
1040+
startingAfter = undefined;
1041+
}
1042+
} while (startingAfter !== undefined);
10101043

1011-
output(blueprints, { format: options.output, defaultFormat: "json" });
1044+
output(allBlueprints, { format: options.output, defaultFormat: "json" });
10121045
} catch (error) {
10131046
outputError("Failed to list blueprints", error);
10141047
}

src/commands/devbox/list.tsx

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { Column } from "../../components/Table.js";
1212
import { Table, createTextColumn } from "../../components/Table.js";
1313
import { formatTimeAgo } from "../../components/ResourceListView.js";
1414
import { SearchBar } from "../../components/SearchBar.js";
15-
import { output, outputError } from "../../utils/output.js";
15+
import { output, outputError, parseLimit } from "../../utils/output.js";
1616
import { DevboxDetailPage } from "../../components/DevboxDetailPage.js";
1717
import { DevboxCreatePage } from "../../components/DevboxCreatePage.js";
1818
import { ResourceActionsMenu } from "../../components/ResourceActionsMenu.js";
@@ -117,7 +117,7 @@ const ListDevboxesUI = ({
117117
const result = {
118118
items: pageDevboxes,
119119
hasMore: page.has_more || false,
120-
totalCount: page.total_count || pageDevboxes.length,
120+
totalCount: pageDevboxes.length,
121121
};
122122

123123
return result;
@@ -148,7 +148,7 @@ const ListDevboxesUI = ({
148148
!showActions &&
149149
!showPopup &&
150150
!search.searchMode,
151-
deps: [status, search.submittedSearchQuery, PAGE_SIZE],
151+
deps: [status, search.submittedSearchQuery],
152152
});
153153

154154
// Sync devboxes to store for detail screen
@@ -559,10 +559,18 @@ const ListDevboxesUI = ({
559559
bindings: {
560560
up: () => {
561561
if (selectedIndex > 0) setSelectedIndex(selectedIndex - 1);
562+
else if (!loading && !navigating && hasPrev) {
563+
prevPage();
564+
setSelectedIndex(devboxes.length - 1);
565+
}
562566
},
563567
down: () => {
564568
if (selectedIndex < devboxes.length - 1)
565569
setSelectedIndex(selectedIndex + 1);
570+
else if (!loading && !navigating && hasMore) {
571+
nextPage();
572+
setSelectedIndex(0);
573+
}
566574
},
567575
n: goToNextPage,
568576
right: goToNextPage,
@@ -712,7 +720,7 @@ const ListDevboxesUI = ({
712720
{!showPopup && (
713721
<Box marginTop={1} paddingX={1}>
714722
<Text color={colors.primary} bold>
715-
{figures.hamburger} {totalCount}
723+
{figures.hamburger} {hasMore ? `${totalCount}+` : totalCount}
716724
</Text>
717725
<Text color={colors.textDim} dimColor>
718726
{" "}
@@ -730,7 +738,8 @@ const ListDevboxesUI = ({
730738
</Text>
731739
) : (
732740
<Text color={colors.textDim} dimColor>
733-
Page {currentPage + 1} of {totalPages}
741+
Page {currentPage + 1} of{" "}
742+
{hasMore ? `${totalPages}+` : totalPages}
734743
</Text>
735744
)}
736745
</>
@@ -740,7 +749,8 @@ const ListDevboxesUI = ({
740749
{" "}
741750
</Text>
742751
<Text color={colors.textDim} dimColor>
743-
Showing {startIndex + 1}-{endIndex} of {totalCount}
752+
Showing {startIndex + 1}-{endIndex} of{" "}
753+
{hasMore ? `${totalCount}+` : totalCount}
744754
</Text>
745755
{search.submittedSearchQuery && (
746756
<>
@@ -796,23 +806,45 @@ export async function listDevboxes(options: ListOptions) {
796806
try {
797807
const client = getClient();
798808

799-
// Build query params
800-
const queryParams: Record<string, unknown> = {
801-
limit: options.limit ? parseInt(options.limit, 10) : DEFAULT_PAGE_SIZE,
802-
};
803-
if (options.status) {
804-
queryParams.status = options.status;
805-
}
809+
const maxResults = parseLimit(options.limit);
810+
const allDevboxes: unknown[] = [];
811+
let startingAfter: string | undefined;
806812

807-
// Fetch devboxes
808-
const page = (await client.devboxes.list(
809-
queryParams,
810-
)) as DevboxesCursorIDPage<{ id: string }>;
813+
do {
814+
const remaining = maxResults - allDevboxes.length;
815+
// Build query params
816+
const queryParams: Record<string, unknown> = {
817+
limit: Math.min(DEFAULT_PAGE_SIZE, remaining),
818+
};
819+
if (options.status) {
820+
queryParams.status = options.status;
821+
}
822+
if (startingAfter) {
823+
queryParams.starting_after = startingAfter;
824+
}
811825

812-
// Extract devboxes array
813-
const devboxes = page.devboxes || [];
826+
// Fetch one page
827+
const page = (await client.devboxes.list(
828+
queryParams,
829+
)) as DevboxesCursorIDPage<{ id: string }>;
830+
831+
const pageDevboxes = page.devboxes || [];
832+
allDevboxes.push(...pageDevboxes);
833+
834+
if (
835+
page.has_more &&
836+
pageDevboxes.length > 0 &&
837+
allDevboxes.length < maxResults
838+
) {
839+
startingAfter = (
840+
pageDevboxes[pageDevboxes.length - 1] as { id: string }
841+
).id;
842+
} else {
843+
startingAfter = undefined;
844+
}
845+
} while (startingAfter !== undefined);
814846

815-
output(devboxes, { format: options.output, defaultFormat: "json" });
847+
output(allDevboxes, { format: options.output, defaultFormat: "json" });
816848
} catch (error) {
817849
outputError("Failed to list devboxes", error);
818850
}

0 commit comments

Comments
 (0)