Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions front-api/routes/w/[wId]/groups/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Authenticator } from "@app/lib/auth";
import { GroupFactory } from "@app/tests/utils/GroupFactory";
import { createPrivateApiMockRequest } from "@app/tests/utils/generic_private_api_tests";
import { MembershipFactory } from "@app/tests/utils/MembershipFactory";
import { UserFactory } from "@app/tests/utils/UserFactory";
import { honoApp } from "@front-api/app";
import { describe, expect, it } from "vitest";

function getGroupsRequest(wId: string, query: Record<string, string> = {}) {
const qs = new URLSearchParams(query).toString();
return honoApp.request(`/api/w/${wId}/groups${qs ? `?${qs}` : ""}`);
}

describe("GET /api/w/:wId/groups", () => {
it("returns memberCount but no memberIds by default", async () => {
const { workspace } = await createPrivateApiMockRequest({
role: "admin",
});
const adminAuth = await Authenticator.internalAdminForWorkspace(
workspace.sId
);
const alice = await UserFactory.basic();
await MembershipFactory.associate(workspace, alice, { role: "user" });
const sales = await GroupFactory.regularManual(workspace, "Sales");
await GroupFactory.withMembers(adminAuth, sales, [alice]);

const response = await getGroupsRequest(workspace.sId, {
kind: "regular_manual",
});

expect(response.status).toBe(200);
const body = await response.json();
expect(body.groups).toEqual([
expect.objectContaining({
sId: sales.sId,
name: "Sales",
memberCount: 1,
}),
]);
expect(body.groups[0].memberIds).toBeUndefined();
});

it("returns memberIds when withMembers=true is requested", async () => {
const { workspace } = await createPrivateApiMockRequest({
role: "admin",
});
const adminAuth = await Authenticator.internalAdminForWorkspace(
workspace.sId
);
const alice = await UserFactory.basic();
await MembershipFactory.associate(workspace, alice, { role: "user" });
const sales = await GroupFactory.regularManual(workspace, "Sales");
await GroupFactory.withMembers(adminAuth, sales, [alice]);

const response = await getGroupsRequest(workspace.sId, {
kind: "regular_manual",
withMembers: "true",
});

expect(response.status).toBe(200);
const body = await response.json();
expect(body.groups).toEqual([
expect.objectContaining({
sId: sales.sId,
name: "Sales",
memberCount: 1,
memberIds: [alice.sId],
}),
]);
});
});
10 changes: 8 additions & 2 deletions front-api/routes/w/[wId]/groups/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export type GetGroupsResponseBody = {
const GetGroupsQuerySchema = z.object({
kind: z.union([GroupKindCodec, z.array(GroupKindCodec)]).optional(),
spaceId: z.string().optional(),
// When "true", each group also carries its member sIds (one extra batched
// query) instead of just memberCount.
withMembers: z.enum(["true", "false"]).optional(),
});

// Mounted at /api/w/:wId/groups.
Expand All @@ -34,7 +37,7 @@ app.get(
validate("query", GetGroupsQuerySchema),
async (ctx): HandlerResult<GetGroupsResponseBody> => {
const auth = ctx.get("auth");
const { kind, spaceId } = ctx.req.valid("query");
const { kind, spaceId, withMembers } = ctx.req.valid("query");

const groupKinds: GroupKind[] = kind
? Array.isArray(kind)
Expand All @@ -47,7 +50,10 @@ app.get(
: await GroupResource.listAllWorkspaceGroups(auth, { groupKinds });

return ctx.json({
groups: await GroupResource.toJSONWithMemberCounts(auth, groups),
groups:
withMembers === "true"
? await GroupResource.fetchJSONWithMembers(auth, groups)
: await GroupResource.toJSONWithMemberCounts(auth, groups),
});
}
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import { DEFAULT_CONSUMPTION_DIMENSION } from "@app/components/workspace/analyti
import { UsageFilterPanel } from "@app/components/workspace/analytics/UsageFilterPanel";
import type { UsageFilter } from "@app/components/workspace/analytics/usageFilter";
import { toConsumptionScopeFilter } from "@app/components/workspace/analytics/usageFilter";
import {
USAGE_FILTER_MOCK_GROUPS,
USAGE_FILTER_MOCK_OPTIONS,
} from "@app/components/workspace/analytics/usageFilterMockData";
import { USAGE_FILTER_MOCK_OPTIONS } from "@app/components/workspace/analytics/usageFilterMockData";
import type { ConsumptionPeriodSelection } from "@app/lib/analytics/consumption_period";
import { DEFAULT_CONSUMPTION_PERIOD } from "@app/lib/analytics/consumption_period";
import { useFeatureFlags, useWorkspace } from "@app/lib/auth/AuthContext";
Expand Down Expand Up @@ -93,7 +90,6 @@ export function AnalyticsConsumptionPage() {
<UsageFilterPanel
owner={owner}
categoryOptions={USAGE_FILTER_MOCK_OPTIONS}
groups={USAGE_FILTER_MOCK_GROUPS}
filter={filter}
onFilterChange={setFilter}
/>
Expand Down
Loading
Loading