Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 11346fe

Browse files
authored
fix(loops): scope creator member lookups
Generated-By: PostHog Code Task-Id: 9edb68e1-a82b-48f8-9d42-f3c635939c09
1 parent 9c0efc5 commit 11346fe

5 files changed

Lines changed: 44 additions & 8 deletions

File tree

‎packages/api-client/src/posthog-client.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,6 +2589,14 @@ export class PostHogAPIClient {
25892589
// Everyone in the current organization — the pool of taggable teammates for
25902590
// thread @-mentions. Membership churn is slow, so callers cache aggressively.
25912591
async listOrganizationMembers(): Promise<OrganizationMemberBasic[]> {
2592+
const result = await this.listOrganizationMembersWithStatus();
2593+
return result.members;
2594+
}
2595+
2596+
async listOrganizationMembersWithStatus(): Promise<{
2597+
members: OrganizationMemberBasic[];
2598+
isComplete: boolean;
2599+
}> {
25922600
const ORG_MEMBERS_MAX_PAGES = 20;
25932601
const ORG_MEMBERS_PAGE_SIZE = 200;
25942602
const all: OrganizationMemberBasic[] = [];
@@ -2609,15 +2617,15 @@ export class PostHogAPIClient {
26092617
next: string | null;
26102618
};
26112619
all.push(...page.results);
2612-
if (!page.next) return all;
2620+
if (!page.next) return { members: all, isComplete: true };
26132621
const nextUrl = new URL(page.next);
26142622
urlPath = `${nextUrl.pathname}${nextUrl.search}`;
26152623
}
26162624
log.warn(
26172625
`listOrganizationMembers hit MAX_PAGES (${ORG_MEMBERS_MAX_PAGES}); returning partial results`,
26182626
{ returned: all.length },
26192627
);
2620-
return all;
2628+
return { members: all, isComplete: false };
26212629
}
26222630

26232631
async sendRunCommand(
Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
import type { UserBasic } from "@posthog/shared/domain-types";
2+
import { useAuthStateValue } from "@posthog/ui/features/auth/store";
23
import { userDisplayName } from "@posthog/ui/features/canvas/utils/userDisplay";
34
import { useAuthenticatedQuery } from "@posthog/ui/hooks/useAuthenticatedQuery";
45
import { useMemo } from "react";
56

67
// Membership churn is slow; one fetch per session window is plenty.
78
const ORG_MEMBERS_STALE_MS = 5 * 60_000;
89

9-
export const ORG_MEMBERS_QUERY_KEY = ["org-members"] as const;
10+
export const orgMembersQueryKey = (orgId: string | null) =>
11+
["org-members", orgId] as const;
1012

1113
/** Members of the current organization, sorted by display name. */
1214
export function useOrgMembers(options?: { enabled?: boolean }): {
1315
members: UserBasic[];
1416
isLoading: boolean;
1517
isError: boolean;
18+
isComplete: boolean;
1619
} {
20+
const currentOrgId = useAuthStateValue((state) => state.currentOrgId);
1721
const query = useAuthenticatedQuery(
18-
ORG_MEMBERS_QUERY_KEY,
19-
(client) => client.listOrganizationMembers(),
22+
orgMembersQueryKey(currentOrgId),
23+
(client) => client.listOrganizationMembersWithStatus(),
2024
{
2125
enabled: options?.enabled ?? true,
2226
staleTime: ORG_MEMBERS_STALE_MS,
2327
},
2428
);
2529
const members = useMemo(
2630
() =>
27-
(query.data ?? [])
31+
(query.data?.members ?? [])
2832
.map((member) => member.user)
2933
.filter((user) => !!user?.email)
3034
.sort((a, b) => userDisplayName(a).localeCompare(userDisplayName(b))),
3135
[query.data],
3236
);
33-
return { members, isLoading: query.isLoading, isError: query.isError };
37+
return {
38+
members,
39+
isLoading: query.isLoading,
40+
isError: query.isError,
41+
isComplete: query.data?.isComplete ?? false,
42+
};
3443
}

‎packages/ui/src/features/loops/components/LoopDetailView.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ function ConfigSummarySection({ loop }: { loop: LoopSchemas.Loop }) {
247247
members,
248248
isLoading: membersLoading,
249249
isError: membersError,
250+
isComplete: membersComplete,
250251
} = useOrgMembers({ enabled: loop.visibility === "team" });
251252
const creator = members.find((member) => member.id === loop.created_by_id);
252253
let creatorContent: React.ReactNode = null;
@@ -261,8 +262,10 @@ function ConfigSummarySection({ loop }: { loop: LoopSchemas.Loop }) {
261262
{userDisplayName(creator)}
262263
</Flex>
263264
);
264-
} else if (loop.visibility === "team") {
265+
} else if (loop.visibility === "team" && membersComplete) {
265266
creatorContent = "Former organization member";
267+
} else if (loop.visibility === "team") {
268+
creatorContent = "Creator unavailable";
266269
}
267270
const notificationDestinations = summarizeNotificationDestinations(
268271
loop.notifications,

‎packages/ui/src/features/loops/components/LoopRow.tsx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ export function LoopRow({
1616
creator,
1717
creatorLoading = false,
1818
creatorError = false,
19+
creatorLookupComplete = true,
1920
}: {
2021
loop: LoopSchemas.Loop;
2122
creator?: UserBasic;
2223
creatorLoading?: boolean;
2324
creatorError?: boolean;
25+
creatorLookupComplete?: boolean;
2426
}) {
2527
const description = loop.description.trim();
2628
const triggerLabel = loop.triggers.length === 1 ? "trigger" : "triggers";
@@ -32,6 +34,12 @@ export function LoopRow({
3234
let creatorLabel: string | null = null;
3335
if (loop.visibility === "team" && creatorError) {
3436
creatorLabel = "Creator unavailable";
37+
} else if (
38+
loop.visibility === "team" &&
39+
!creatorLoading &&
40+
!creatorLookupComplete
41+
) {
42+
creatorLabel = "Creator unavailable";
3543
} else if (loop.visibility === "team" && !creatorLoading) {
3644
creatorLabel = creator
3745
? `Created by ${userDisplayName(creator)}`

‎packages/ui/src/features/loops/components/LoopsListView.tsx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export function LoopsListView() {
6464
members,
6565
isLoading: membersLoading,
6666
isError: membersError,
67+
isComplete: membersComplete,
6768
} = useOrgMembers({ enabled: teamLoops.length > 0 });
6869

6970
return (
@@ -75,6 +76,7 @@ export function LoopsListView() {
7576
members={members}
7677
membersLoading={membersLoading}
7778
membersError={membersError}
79+
membersComplete={membersComplete}
7880
onStartBlank={startBlankLoop}
7981
onStartFromTemplate={startLoopFromTemplate}
8082
/>
@@ -89,6 +91,7 @@ interface LoopsListViewPresentationProps {
8991
members?: UserBasic[];
9092
membersLoading?: boolean;
9193
membersError?: boolean;
94+
membersComplete?: boolean;
9295
onStartBlank: () => void;
9396
onStartFromTemplate: (template: LoopTemplate) => void;
9497
}
@@ -101,6 +104,7 @@ export function LoopsListViewPresentation({
101104
members = EMPTY_MEMBERS,
102105
membersLoading = false,
103106
membersError = false,
107+
membersComplete = true,
104108
onStartBlank,
105109
onStartFromTemplate,
106110
}: LoopsListViewPresentationProps) {
@@ -175,6 +179,7 @@ export function LoopsListViewPresentation({
175179
members={members}
176180
membersLoading={membersLoading}
177181
membersError={membersError}
182+
membersComplete={membersComplete}
178183
/>
179184
) : null}
180185
</Flex>
@@ -205,12 +210,14 @@ function LoopListSection({
205210
members = EMPTY_MEMBERS,
206211
membersLoading = false,
207212
membersError = false,
213+
membersComplete = true,
208214
}: {
209215
title: string;
210216
loops: LoopSchemas.Loop[];
211217
members?: UserBasic[];
212218
membersLoading?: boolean;
213219
membersError?: boolean;
220+
membersComplete?: boolean;
214221
}) {
215222
return (
216223
<Flex direction="column" gap="3">
@@ -225,6 +232,7 @@ function LoopListSection({
225232
creator={members.find((member) => member.id === loop.created_by_id)}
226233
creatorLoading={membersLoading}
227234
creatorError={membersError}
235+
creatorLookupComplete={membersComplete}
228236
/>
229237
))}
230238
</Flex>

0 commit comments

Comments
 (0)