mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
fix: hide official marks on org catalog rows (#3415)
* fix: hide official marks on org catalog rows * style: format app shell selector * revert: restore locked formatter output * fix: preserve item badges on unverified orgs
This commit is contained in:
@@ -262,6 +262,107 @@ describe("user profile route", () => {
|
||||
expect(screen.queryByRole("searchbox", { name: /catalog search/i })).toBeNull();
|
||||
});
|
||||
|
||||
it("hides official marks from skill rows on organization pages", async () => {
|
||||
paginatedQueryMock.mockReturnValue({
|
||||
loadMore: vi.fn(),
|
||||
results: [
|
||||
{
|
||||
_id: "skills:gpu",
|
||||
kind: "skill",
|
||||
displayName: "GPU Helper",
|
||||
summary: "GPU tasks",
|
||||
topics: [],
|
||||
icon: null,
|
||||
href: "/nvidia/gpu-helper",
|
||||
installs: 1,
|
||||
stars: 0,
|
||||
isOfficial: true,
|
||||
updatedAt: 1,
|
||||
},
|
||||
],
|
||||
status: "Exhausted",
|
||||
});
|
||||
const route = await loadRoute();
|
||||
const Component = route.__config.component as ComponentType;
|
||||
|
||||
render(<Component />);
|
||||
|
||||
expect(screen.getByText("GPU Helper")).toBeTruthy();
|
||||
expect(
|
||||
within(screen.getByLabelText("Publisher catalog")).queryByLabelText("Official"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps official marks on skill and plugin rows for unverified organizations", async () => {
|
||||
const unverifiedPublisher = {
|
||||
...publisher,
|
||||
official: false,
|
||||
stats: {
|
||||
...publisher.stats,
|
||||
skills: 1,
|
||||
packages: 1,
|
||||
},
|
||||
};
|
||||
loaderDataMock.mockReturnValue({ publisher: unverifiedPublisher });
|
||||
queryMock.mockImplementation((_query, args: Record<string, unknown> | "skip") => {
|
||||
if (args === "skip") return undefined;
|
||||
if ("publisherHandle" in args) return { publisher: unverifiedPublisher, members: [] };
|
||||
if ("kind" in args) return null;
|
||||
return unverifiedPublisher;
|
||||
});
|
||||
paginatedQueryMock.mockImplementation((_query, args: Record<string, unknown>) => ({
|
||||
loadMore: vi.fn(),
|
||||
results:
|
||||
args.kind === "plugin"
|
||||
? [
|
||||
{
|
||||
_id: "packages:diagnostics",
|
||||
kind: "plugin",
|
||||
displayName: "Diagnostics",
|
||||
summary: "Plugin diagnostics",
|
||||
topics: [],
|
||||
icon: null,
|
||||
href: "/nvidia/plugins/diagnostics",
|
||||
installs: 1,
|
||||
stars: 0,
|
||||
isOfficial: true,
|
||||
updatedAt: 1,
|
||||
},
|
||||
]
|
||||
: args.kind === "skill"
|
||||
? [
|
||||
{
|
||||
_id: "skills:gpu",
|
||||
kind: "skill",
|
||||
displayName: "GPU Helper",
|
||||
summary: "GPU tasks",
|
||||
topics: [],
|
||||
icon: null,
|
||||
href: "/nvidia/gpu-helper",
|
||||
installs: 1,
|
||||
stars: 0,
|
||||
isOfficial: true,
|
||||
updatedAt: 1,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
status: "Exhausted",
|
||||
}));
|
||||
const route = await loadRoute();
|
||||
const Component = route.__config.component as ComponentType;
|
||||
|
||||
render(<Component />);
|
||||
|
||||
const catalog = screen.getByLabelText("Publisher catalog");
|
||||
expect(within(catalog).getByText("GPU Helper")).toBeTruthy();
|
||||
expect(within(catalog).getByLabelText("Official")).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /plugins 1/i }));
|
||||
|
||||
expect(within(catalog).getByText("Diagnostics")).toBeTruthy();
|
||||
expect(within(catalog).getByLabelText("Official")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("uses downloads sort for published catalog pages by default", async () => {
|
||||
const route = await loadRoute();
|
||||
const Component = route.__config.component as ComponentType;
|
||||
|
||||
@@ -15,6 +15,7 @@ type PluginListItemProps = {
|
||||
item: PackageListItem;
|
||||
variant?: "list" | "card";
|
||||
href?: string;
|
||||
showOfficialBadge?: boolean;
|
||||
};
|
||||
|
||||
const PLUGIN_CATEGORIES_BY_SLUG = new Map(
|
||||
@@ -41,7 +42,12 @@ function getPluginCategories(item: PackageListItem) {
|
||||
});
|
||||
}
|
||||
|
||||
export function PluginListItem({ item, variant = "list", href }: PluginListItemProps) {
|
||||
export function PluginListItem({
|
||||
item,
|
||||
variant = "list",
|
||||
href,
|
||||
showOfficialBadge = true,
|
||||
}: PluginListItemProps) {
|
||||
const downloads = formatCompactStat(item.stats?.downloads ?? 0);
|
||||
const taxonomy = getPluginTaxonomyDisplay(item);
|
||||
const categories = getPluginCategories(item);
|
||||
@@ -76,7 +82,7 @@ export function PluginListItem({ item, variant = "list", href }: PluginListItemP
|
||||
<span className="skill-card-owner">
|
||||
{item.ownerHandle ? `@${item.ownerHandle}` : "community"}
|
||||
</span>
|
||||
{item.isOfficial ? <OfficialBadge /> : null}
|
||||
{showOfficialBadge && item.isOfficial ? <OfficialBadge /> : null}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,7 +137,7 @@ export function PluginListItem({ item, variant = "list", href }: PluginListItemP
|
||||
<span className="skill-list-item-owner">@{item.ownerHandle}</span>
|
||||
) : null}
|
||||
</span>
|
||||
{item.isOfficial ? <OfficialBadge /> : null}
|
||||
{showOfficialBadge && item.isOfficial ? <OfficialBadge /> : null}
|
||||
<CatalogTopicList topics={taxonomy.labels} limit={2} ariaLabel={taxonomy.ariaLabel} />
|
||||
</div>
|
||||
<p className="skill-list-item-summary">
|
||||
|
||||
@@ -102,6 +102,20 @@ describe("PublishedItemCard", () => {
|
||||
expect(screen.queryByText("Official")).toBeNull();
|
||||
});
|
||||
|
||||
it.each([baseSkill, basePlugin])(
|
||||
"can hide the official mark from publisher catalog rows",
|
||||
(item) => {
|
||||
render(
|
||||
<PublishedItemCard
|
||||
item={{ ...item, icon: null, isOfficial: true }}
|
||||
showOfficialBadge={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText("Official")).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it("does not add source-backed chrome to GitHub-backed skill rows", () => {
|
||||
render(
|
||||
<PublishedItemCard
|
||||
|
||||
@@ -17,6 +17,7 @@ type SkillListItemProps = {
|
||||
ownerHandle?: string | null;
|
||||
owner?: PublicPublisher | null;
|
||||
href?: string;
|
||||
showOfficialBadge?: boolean;
|
||||
};
|
||||
|
||||
export function SkillListItem({
|
||||
@@ -24,6 +25,7 @@ export function SkillListItem({
|
||||
ownerHandle,
|
||||
owner,
|
||||
href: hrefOverride,
|
||||
showOfficialBadge = true,
|
||||
}: SkillListItemProps) {
|
||||
const handle = ownerHandle ?? owner?.handle ?? null;
|
||||
const ownerSegment = handle?.trim() || String(skill.ownerPublisherId ?? skill.ownerUserId);
|
||||
@@ -49,7 +51,7 @@ export function SkillListItem({
|
||||
</span>
|
||||
{handle ? <span className="skill-list-item-owner">@{handle}</span> : null}
|
||||
</span>
|
||||
{isOfficial ? <OfficialBadge /> : null}
|
||||
{showOfficialBadge && isOfficial ? <OfficialBadge /> : null}
|
||||
{badges
|
||||
.filter((badge) => badge !== "Official")
|
||||
.map((badge) => (
|
||||
|
||||
+50
-10
@@ -741,6 +741,7 @@ export function PublisherProfilePage({
|
||||
const hiddenOrgCount = Math.max(0, affiliations.length - VISIBLE_ORG_CHIPS);
|
||||
const showOrganizations = publisher.kind === "user" && affiliations.length > 0;
|
||||
const showMembers = publisher.kind === "org" && memberEntries.length > 0;
|
||||
const showCatalogOfficialBadge = publisher.kind !== "org" || publisher.official !== true;
|
||||
const publisherStatCards = buildPublisherStatCards(publisher);
|
||||
const profileBio = publisher.bio?.trim() || DEFAULT_PUBLISHER_BIO;
|
||||
|
||||
@@ -918,6 +919,7 @@ export function PublisherProfilePage({
|
||||
groups={catalogGroups}
|
||||
selectedGroup={selectedCatalogGroup}
|
||||
onSelectedGroupChange={setSelectedCatalogGroup}
|
||||
showOfficialBadge={showCatalogOfficialBadge}
|
||||
totalCount={catalogSearch.trim() ? undefined : catalogCount}
|
||||
footer={
|
||||
showCatalogLoadMore ? (
|
||||
@@ -933,7 +935,10 @@ export function PublisherProfilePage({
|
||||
/>
|
||||
) : filteredItems.length > 0 ? (
|
||||
<>
|
||||
<PublisherCatalogItems items={filteredItems} />
|
||||
<PublisherCatalogItems
|
||||
items={filteredItems}
|
||||
showOfficialBadge={showCatalogOfficialBadge}
|
||||
/>
|
||||
{showCatalogLoadMore ? (
|
||||
<div className="publisher-profile-load-more">
|
||||
<Button type="button" onClick={() => activeLoadMore(12)}>
|
||||
@@ -1119,19 +1124,35 @@ export function shouldShowPublisherCatalogLoadMore({
|
||||
);
|
||||
}
|
||||
|
||||
function PublisherCatalogItems({ items }: { items: PublicPublisherCatalogItem[] }) {
|
||||
function PublisherCatalogItems({
|
||||
items,
|
||||
showOfficialBadge = true,
|
||||
}: {
|
||||
items: PublicPublisherCatalogItem[];
|
||||
showOfficialBadge?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="browse-list-stack">
|
||||
<div className="results-list">
|
||||
{items.map((item) => (
|
||||
<PublishedItemCard key={`${item.kind}:${item._id}`} item={item} />
|
||||
<PublishedItemCard
|
||||
key={`${item.kind}:${item._id}`}
|
||||
item={item}
|
||||
showOfficialBadge={showOfficialBadge}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PublisherCatalogGroupSection({ group }: { group: PublisherCatalogGroup }) {
|
||||
function PublisherCatalogGroupSection({
|
||||
group,
|
||||
showOfficialBadge,
|
||||
}: {
|
||||
group: PublisherCatalogGroup;
|
||||
showOfficialBadge?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className="publisher-profile-manifest-section"
|
||||
@@ -1141,7 +1162,7 @@ function PublisherCatalogGroupSection({ group }: { group: PublisherCatalogGroup
|
||||
<h3 id={`catalog-group-${group.key}`}>{group.title}</h3>
|
||||
{group.description ? <p>{group.description}</p> : null}
|
||||
</header>
|
||||
<PublisherCatalogItems items={group.items} />
|
||||
<PublisherCatalogItems items={group.items} showOfficialBadge={showOfficialBadge} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1152,12 +1173,14 @@ export function PublisherGroupedCatalog({
|
||||
onSelectedGroupChange,
|
||||
footer,
|
||||
totalCount,
|
||||
showOfficialBadge,
|
||||
}: {
|
||||
groups: PublisherCatalogGroup[];
|
||||
selectedGroup: string;
|
||||
onSelectedGroupChange: (value: string) => void;
|
||||
footer?: ReactNode;
|
||||
totalCount?: number;
|
||||
showOfficialBadge?: boolean;
|
||||
}) {
|
||||
const activeGroup =
|
||||
selectedGroup === "all" ? null : (groups.find((group) => group.key === selectedGroup) ?? null);
|
||||
@@ -1179,11 +1202,15 @@ export function PublisherGroupedCatalog({
|
||||
{selectedGroup === "all" ? (
|
||||
<div className="publisher-profile-catalog-sections">
|
||||
{groups.map((group) => (
|
||||
<PublisherCatalogGroupSection key={group.key} group={group} />
|
||||
<PublisherCatalogGroupSection
|
||||
key={group.key}
|
||||
group={group}
|
||||
showOfficialBadge={showOfficialBadge}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : activeGroup ? (
|
||||
<PublisherCatalogGroupSection group={activeGroup} />
|
||||
<PublisherCatalogGroupSection group={activeGroup} showOfficialBadge={showOfficialBadge} />
|
||||
) : null}
|
||||
{footer}
|
||||
</div>
|
||||
@@ -1365,12 +1392,25 @@ function catalogItemToPackageListItem(item: PublicPublisherCatalogItem): Package
|
||||
};
|
||||
}
|
||||
|
||||
export function PublishedItemCard({ item }: { item: PublicPublisherCatalogItem }) {
|
||||
export function PublishedItemCard({
|
||||
item,
|
||||
showOfficialBadge = true,
|
||||
}: {
|
||||
item: PublicPublisherCatalogItem;
|
||||
showOfficialBadge?: boolean;
|
||||
}) {
|
||||
if (item.kind === "plugin") {
|
||||
const plugin = catalogItemToPackageListItem(item);
|
||||
return <PluginListItem item={plugin} variant="list" href={item.href} />;
|
||||
return (
|
||||
<PluginListItem
|
||||
item={plugin}
|
||||
variant="list"
|
||||
href={item.href}
|
||||
showOfficialBadge={showOfficialBadge}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const skill = catalogItemToPublicSkill(item);
|
||||
return <SkillListItem skill={skill} href={item.href} />;
|
||||
return <SkillListItem skill={skill} href={item.href} showOfficialBadge={showOfficialBadge} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user