diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index ac243328c..2298e9cba 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -85,7 +85,27 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({ type: "integer", description: "When set, only site resources associated with this site (via network) are returned" - }) + }), + labels: z + .preprocess((val) => { + if (val === undefined || val === null || val === "") { + return undefined; + } + if (Array.isArray(val)) { + return val; + } + // the array is returned as this + if (typeof val === "string") { + return val.split(","); + } + return undefined; + }, z.array(z.string())) + .optional() + .catch([]) + .openapi({ + type: "array", + description: "Filter by resource labels" + }) }); export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{ @@ -239,8 +259,16 @@ export async function listAllSiteResourcesByOrg( } const { orgId } = parsedParams.data; - const { page, pageSize, query, mode, sort_by, order, siteId } = - parsedQuery.data; + const { + page, + pageSize, + query, + mode, + sort_by, + order, + siteId, + labels: labelFilter + } = parsedQuery.data; const isLabelFeatureEnabled = await isLicensedOrSubscribed( orgId, @@ -276,6 +304,22 @@ export async function listAllSiteResourcesByOrg( conditions.push(eq(siteResources.mode, mode)); } + if (isLabelFeatureEnabled && labelFilter && labelFilter.length > 0) { + conditions.push( + inArray( + siteResources.siteResourceId, + db + .select({ id: siteResourceLabels.siteResourceId }) + .from(siteResourceLabels) + .innerJoin( + labels, + eq(labels.labelId, siteResourceLabels.labelId) + ) + .where(inArray(labels.name, labelFilter)) + ) + ); + } + if (query) { const q = "%" + query.toLowerCase() + "%"; const queryList = [ diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index 156cc7f41..a14ad8346 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -64,6 +64,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { LabelBadge } from "./label-badge"; import { LabelsSelector, type SelectedLabel } from "./labels-selector"; +import { LabelColumnFilterButton } from "./LabelColumnFilterButton"; export type InternalResourceSiteRow = ResourceSiteRow; @@ -220,59 +221,6 @@ export default function ClientResourcesTable({ } }; - function SiteCell({ resourceRow }: { resourceRow: InternalResourceRow }) { - const { siteNames, siteNiceIds, orgId } = resourceRow; - - if (!siteNames || siteNames.length === 0) { - return ( - - {t("noSites", { defaultValue: "No sites" })} - - ); - } - - if (siteNames.length === 1) { - return ( - - - - ); - } - - return ( - - - - - - {siteNames.map((siteName, idx) => ( - - - {siteName} - - - - ))} - - - ); - } - const internalColumns = useMemo< ExtendedColumnDef[] >(() => { @@ -585,9 +533,15 @@ export default function ClientResourcesTable({ id: "labels", accessorKey: "labels", header: () => ( - - {t("labels")} - + + handleFilterChange("labels", value) + } + label={t("labels")} + className="p-3" + /> ), cell: ({ row }: { row: { original: InternalResourceRow } }) => ( searchParams.append(column, val)); } filter({ searchParams