fix(web): sort publisher previews by downloads (#2176)

This commit is contained in:
Vyctor H. Brzezowski
2026-05-12 22:17:52 -07:00
committed by GitHub
parent e6a3bdf4e3
commit d794f4633d
4 changed files with 154 additions and 6 deletions
+1
View File
@@ -10,6 +10,7 @@
### Fixes
- Web: rank publisher card preview items by downloads instead of recent publish order (thanks @vyctorbrzezowski).
- Web: keep skill/plugin detail tabs at mobile-friendly touch target height.
## 0.15.0 - 2026-05-12
+139 -4
View File
@@ -78,7 +78,12 @@ const listPublicPageHandler = (
paginationOpts: { cursor: string | null; numItems: number };
},
{
page: Array<{ handle: string; kind: "user" | "org"; stats: { downloads: number } }>;
page: Array<{
handle: string;
kind: "user" | "org";
stats: { downloads: number };
publishedItems: Array<{ displayName: string; downloads: number }>;
}>;
counts: { all: number; individuals: number; organizations: number };
globalCounts: { all: number; individuals: number; organizations: number };
continueCursor: string;
@@ -301,7 +306,7 @@ describe("publishers membership controls", () => {
}
if (
(table === "skills" || table === "packages") &&
indexName === "by_owner_publisher_active_updated"
indexName === "by_owner_publisher_active_downloads"
) {
return indexedRows([]);
}
@@ -407,7 +412,7 @@ describe("publishers membership controls", () => {
}
if (
(table === "skills" || table === "packages") &&
indexName === "by_owner_publisher_active_updated"
indexName === "by_owner_publisher_active_downloads"
) {
return indexedRows([]);
}
@@ -428,6 +433,136 @@ describe("publishers membership controls", () => {
expect(result.page.map((item) => item.handle)).toEqual(["alice"]);
});
it("orders public publisher card previews by downloads", async () => {
const publisherRows = [
{
_id: "publishers:openclaw",
_creationTime: 1,
kind: "org",
handle: "openclaw",
displayName: "OpenClaw",
publishedSkills: 1,
publishedPackages: 4,
totalInstalls: 20,
totalDownloads: 364,
totalStars: 2,
createdAt: 1,
updatedAt: 1,
},
];
const skillRows = [
{
_id: "skills:popular-skill",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
displayName: "Popular Skill",
statsDownloads: 98,
statsStars: 1,
statsInstallsAllTime: 1,
stats: { downloads: 98, stars: 1, installsCurrent: 1, installsAllTime: 1 },
updatedAt: 1,
},
];
const packageRows = [
{
_id: "packages:popular-plugin",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
family: "code-plugin",
displayName: "Popular Plugin",
stats: { downloads: 128, stars: 1, installs: 1, versions: 1 },
updatedAt: 1,
},
{
_id: "packages:recent-plugin",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
family: "code-plugin",
displayName: "Recent Plugin",
stats: { downloads: 12, stars: 1, installs: 1, versions: 1 },
updatedAt: 5,
},
{
_id: "packages:recent-helper",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
family: "code-plugin",
displayName: "Recent Helper",
stats: { downloads: 11, stars: 1, installs: 1, versions: 1 },
updatedAt: 4,
},
{
_id: "packages:recent-tool",
ownerPublisherId: "publishers:openclaw",
softDeletedAt: undefined,
family: "code-plugin",
displayName: "Recent Tool",
stats: { downloads: 10, stars: 1, installs: 1, versions: 1 },
updatedAt: 3,
},
];
const rowsByDownloads = <
T extends { updatedAt: number; stats?: { downloads: number }; statsDownloads?: number },
>(
rows: T[],
) =>
[...rows].sort(
(a, b) =>
(b.statsDownloads ?? b.stats?.downloads ?? 0) -
(a.statsDownloads ?? a.stats?.downloads ?? 0) || b.updatedAt - a.updatedAt,
);
const ctx = {
db: {
get: vi.fn(),
query: vi.fn((table: string) => ({
withIndex: vi.fn((indexName: string, buildQuery: (q: unknown) => unknown) => {
const fields: Record<string, unknown> = {};
const q = {
eq: (field: string, value: unknown) => {
fields[field] = value;
return q;
},
};
buildQuery(q);
if (table === "publishers" && indexName === "by_active_total_downloads") {
return {
order: vi.fn(() => ({
take: vi.fn(async () => publisherRows),
})),
};
}
if (table === "skills" && indexName === "by_owner_publisher_active_downloads") {
return indexedRows(
rowsByDownloads(
skillRows.filter((skill) => skill.ownerPublisherId === fields.ownerPublisherId),
),
);
}
if (table === "packages" && indexName === "by_owner_publisher_active_downloads") {
return indexedRows(
rowsByDownloads(
packageRows.filter((pkg) => pkg.ownerPublisherId === fields.ownerPublisherId),
),
);
}
throw new Error(`unexpected ${table} index ${indexName}`);
}),
})),
},
};
const result = await listPublicPageHandler(ctx as never, {
paginationOpts: { cursor: null, numItems: 25 },
});
expect(result.page[0]?.publishedItems.map((item) => item.displayName)).toEqual([
"Popular Plugin",
"Popular Skill",
"Recent Plugin",
]);
expect(result.page[0]?.publishedItems.map((item) => item.downloads)).toEqual([128, 98, 12]);
});
it("does not hydrate every publisher before filtering public publisher pages", async () => {
const publisherRows = Array.from({ length: 120 }, (_, index) => ({
_id: `publishers:user-${index}`,
@@ -468,7 +603,7 @@ describe("publishers membership controls", () => {
}
if (
(table === "skills" || table === "packages") &&
indexName === "by_owner_publisher_active_updated"
indexName === "by_owner_publisher_active_downloads"
) {
ownerPublisherQueries.push(String(fields.ownerPublisherId));
return indexedRows([]);
+2 -2
View File
@@ -161,14 +161,14 @@ async function getPublisherPublishedPreviewRows(
const [skills, packages] = await Promise.all([
ctx.db
.query("skills")
.withIndex("by_owner_publisher_active_updated", (q) =>
.withIndex("by_owner_publisher_active_downloads", (q) =>
q.eq("ownerPublisherId", publisherId).eq("softDeletedAt", undefined),
)
.order("desc")
.take(PUBLISHER_LIST_PREVIEW_LIMIT),
ctx.db
.query("packages")
.withIndex("by_owner_publisher_active_updated", (q) =>
.withIndex("by_owner_publisher_active_downloads", (q) =>
q.eq("ownerPublisherId", publisherId).eq("softDeletedAt", undefined),
)
.order("desc")
+12
View File
@@ -450,6 +450,12 @@ const skills = defineTable({
.index("by_owner_publisher_slug", ["ownerPublisherId", "slug"])
.index("by_owner_active_updated", ["ownerUserId", "softDeletedAt", "updatedAt"])
.index("by_owner_publisher_active_updated", ["ownerPublisherId", "softDeletedAt", "updatedAt"])
.index("by_owner_publisher_active_downloads", [
"ownerPublisherId",
"softDeletedAt",
"statsDownloads",
"updatedAt",
])
.index("by_updated", ["updatedAt"])
.index("by_stats_downloads", ["statsDownloads", "updatedAt"])
.index("by_stats_stars", ["statsStars", "updatedAt"])
@@ -872,6 +878,12 @@ const packages = defineTable({
.index("by_owner", ["ownerUserId"])
.index("by_owner_publisher", ["ownerPublisherId"])
.index("by_owner_publisher_active_updated", ["ownerPublisherId", "softDeletedAt", "updatedAt"])
.index("by_owner_publisher_active_downloads", [
"ownerPublisherId",
"softDeletedAt",
"stats.downloads",
"updatedAt",
])
.index("by_family_updated", ["family", "updatedAt"])
.index("by_family_channel_updated", ["family", "channel", "updatedAt"])
.index("by_family_official_updated", ["family", "isOfficial", "updatedAt"])