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
22 changes: 9 additions & 13 deletions packages/core/src/inbox/reportFiltering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,21 +98,17 @@ describe("filterReportsBySearch", () => {
});

describe("buildSignalReportListOrdering", () => {
it("puts status then suggested reviewer then descending field", () => {
expect(buildSignalReportListOrdering("total_weight", "desc")).toBe(
"status,-is_suggested_reviewer,-total_weight",
);
});

it("puts status then suggested reviewer then ascending field", () => {
expect(buildSignalReportListOrdering("created_at", "asc")).toBe(
"status,-is_suggested_reviewer,created_at",
);
it.each([
["total_weight", "desc", "status,-total_weight"],
["created_at", "asc", "status,created_at"],
["signal_count", "desc", "status,-signal_count"],
] as const)("orders by status then %s (%s)", (field, direction, expected) => {
expect(buildSignalReportListOrdering(field, direction)).toBe(expected);
});

it("works for signal_count", () => {
expect(buildSignalReportListOrdering("signal_count", "desc")).toBe(
"status,-is_suggested_reviewer,-signal_count",
it("does not float the current user's reports via ordering", () => {
expect(buildSignalReportListOrdering("priority", "asc")).not.toContain(
"is_suggested_reviewer",
);
});
});
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/inbox/reportFiltering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,18 @@ export function buildStatusFilterParam(statuses: SignalReportStatus[]): string {
/**
* Comma-separated `ordering` for the signal report list API:
* 1. Status rank (ready first – semantic server-side rank, always applied)
* 2. Suggested reviewer (current user's reports first)
* 3. Toolbar-selected field (priority, total_weight, created_at, etc.)
* 2. Toolbar-selected field (priority, total_weight, created_at, etc.)
*
* Reviewer scope is applied via the `suggested_reviewers` param, not ordering:
* a `-is_suggested_reviewer` tiebreak would float the user's reports to the top
* of the first (and only loaded) page, starving the "Entire project" scope.
*/
export function buildSignalReportListOrdering(
field: SignalReportOrderingField,
direction: "asc" | "desc",
): string {
const fieldKey = direction === "desc" ? `-${field}` : field;
return `status,-is_suggested_reviewer,${fieldKey}`;
return `status,${fieldKey}`;
}

export function buildSuggestedReviewerFilterParam(
Expand Down
21 changes: 19 additions & 2 deletions packages/ui/src/features/inbox/hooks/useInboxAllReports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {
} from "@posthog/core/inbox/reportFiltering";
import {
computeInboxTabCounts,
INBOX_SCOPE_FOR_YOU,
matchesReviewerScope,
parseTeammateInboxScope,
} from "@posthog/core/inbox/reportMembership";
import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser";
import { useInboxReportsInfinite } from "@posthog/ui/features/inbox/hooks/useInboxReports";
import { useInboxReviewerScopeStore } from "@posthog/ui/features/inbox/stores/inboxReviewerScopeStore";
import { useInboxSignalsFilterStore } from "@posthog/ui/features/inbox/stores/inboxSignalsFilterStore";
Expand Down Expand Up @@ -52,7 +55,16 @@ export function useInboxAllReports(options?: {
const priorityFilter = useInboxSignalsFilterStore((s) =>
ignoreFilters ? EMPTY_FILTER_ARRAY : s.priorityFilter,
);
const client = useOptionalAuthenticatedClient();
const { data: currentUser } = useCurrentUser({ client });

// Reviewer scope is applied server-side via `suggested_reviewers`: "For you"
// filters on the current user, a teammate scope on theirs, "Entire project"
// and the Runs tab (`ignoreScope`) send nothing.
const isForYou = !ignoreScope && scope === INBOX_SCOPE_FOR_YOU;
const teammateUuid = ignoreScope ? null : parseTeammateInboxScope(scope);
const reviewerUuid =
teammateUuid ?? (isForYou ? (currentUser?.uuid ?? null) : null);

const query = useInboxReportsInfinite(
{
Expand All @@ -63,11 +75,16 @@ export function useInboxAllReports(options?: {
? sourceProductFilter.join(",")
: undefined,
priority: buildPriorityFilterParam(priorityFilter),
suggested_reviewers: teammateUuid
? buildSuggestedReviewerFilterParam([teammateUuid])
suggested_reviewers: reviewerUuid
? buildSuggestedReviewerFilterParam([reviewerUuid])
: undefined,
},
{
// "For you" must always carry the current user's `suggested_reviewers`
// filter, so hold the query until that uuid resolves rather than firing a
// throwaway project-wide fetch first. Other scopes don't depend on the
// user and run immediately.
enabled: !isForYou || reviewerUuid != null,
refetchInterval: INBOX_REFETCH_INTERVAL_MS,
refetchIntervalInBackground: false,
},
Expand Down
Loading