Skip to content

Commit 9fa5d73

Browse files
Share repository integration semantics
Generated-By: PostHog Code Task-Id: 40c57a59-b4e1-4760-8e56-ecd03e9c2f0f
1 parent 70e84e9 commit 9fa5d73

13 files changed

Lines changed: 237 additions & 251 deletions

File tree

apps/mobile/src/app/task/index.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import { Text } from "@components/text";
2+
import {
3+
findRepositoryOption,
4+
isRepositorySelectionComplete,
5+
type RepositorySelection,
6+
toRepositorySelection,
7+
} from "@posthog/core/integrations/repositories";
28
import {
39
DEFAULT_CLAUDE_EXECUTION_MODE,
410
getAvailableModes,
@@ -73,15 +79,7 @@ import {
7379
pendingTaskPromptStoreApi,
7480
} from "@/features/tasks/stores/pendingTaskPromptStore";
7581
import { useTaskStore } from "@/features/tasks/stores/taskStore";
76-
import type {
77-
CreateTaskOptions,
78-
RepositorySelection,
79-
} from "@/features/tasks/types";
80-
import {
81-
findRepositoryOption,
82-
isRepositorySelectionComplete,
83-
toRepositorySelection,
84-
} from "@/features/tasks/utils/repositorySelection";
82+
import type { CreateTaskOptions } from "@/features/tasks/types";
8583
import { useScreenInsets } from "@/hooks/useScreenInsets";
8684
import { logger } from "@/lib/logger";
8785
import { getPostHogApiClient } from "@/lib/posthogApiClient";

apps/mobile/src/features/inbox/components/TinderView.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
formatSignalReportSummaryMarkdown,
44
inboxStatusLabel,
55
} from "@posthog/core/inbox/reportPresentation";
6+
import type { RepositoryOption } from "@posthog/core/integrations/repositories";
67
import type {
78
SignalReport,
89
SignalReportPriority,
@@ -28,10 +29,7 @@ import { MarkdownText } from "@/features/chat/components/MarkdownText";
2829
import { usePreferencesStore } from "@/features/preferences/stores/preferencesStore";
2930
import { getModelConfigOption } from "@/features/tasks/composer/options";
3031
import { useCloudTaskConfigOptions } from "@/features/tasks/hooks/useCloudTaskConfigOptions";
31-
import type {
32-
CreateTaskOptions,
33-
RepositoryOption,
34-
} from "@/features/tasks/types";
32+
import type { CreateTaskOptions } from "@/features/tasks/types";
3533
import {
3634
ANALYTICS_EVENTS,
3735
computeReportAgeHours,

apps/mobile/src/features/tasks/components/AutomationForm.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import {
66
deriveAutomationName,
77
parseCronExpression,
88
} from "@posthog/core/automations/automationSchedule";
9+
import {
10+
findRepositoryOption,
11+
isRepositorySelectionComplete,
12+
type RepositorySelection,
13+
toRepositorySelection,
14+
} from "@posthog/core/integrations/repositories";
915
import type { CreateTaskAutomationOptions } from "@posthog/shared";
1016
import { CaretDown, GithubLogo } from "phosphor-react-native";
1117
import { type MutableRefObject, useEffect, useMemo, useState } from "react";
@@ -20,12 +26,6 @@ import { MarkdownText } from "@/features/chat/components/MarkdownText";
2026
import { useThemeColors } from "@/lib/theme";
2127
import { RepositoryPickerInline } from "../composer/RepositoryPickerInline";
2228
import { useIntegrations } from "../hooks/useIntegrations";
23-
import type { RepositorySelection } from "../types";
24-
import {
25-
findRepositoryOption,
26-
isRepositorySelectionComplete,
27-
toRepositorySelection,
28-
} from "../utils/repositorySelection";
2929
import { GitHubConnectionPrompt } from "./GitHubConnectionPrompt";
3030
import { GitHubLoadNotice } from "./GitHubLoadNotice";
3131
import { ScheduleEditor } from "./ScheduleEditor";

apps/mobile/src/features/tasks/composer/RepositoryPickerInline.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Text } from "@components/text";
2+
import type { RepositoryOption } from "@posthog/core/integrations/repositories";
23
import { ArrowsClockwise, Check, MagnifyingGlass } from "phosphor-react-native";
34
import { useEffect, useMemo, useRef, useState } from "react";
45
import {
@@ -15,7 +16,6 @@ import Animated, {
1516
useSharedValue,
1617
withTiming,
1718
} from "react-native-reanimated";
18-
import type { RepositoryOption } from "@/features/tasks/types";
1919
import { useThemeColors } from "@/lib/theme";
2020

2121
// Tuning for the nested (ScrollView) path's progressive mount. The first

apps/mobile/src/features/tasks/hooks/useIntegrations.ts

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
1+
import {
2+
buildTeamRepositoryOptions,
3+
normalizeRepositoryNames,
4+
repositoryLoadWarning,
5+
repositoryOptionsEqual,
6+
} from "@posthog/core/integrations/repositories";
17
import { useQuery } from "@tanstack/react-query";
28
import { useEffect, useMemo } from "react";
39
import { useAuthStore } from "@/features/auth";
410
import { getPostHogApiClient } from "@/lib/posthogApiClient";
511
import { useRepositoryCacheStore } from "../stores/repositoryCacheStore";
6-
import type { RepositoryOption } from "../types";
7-
import { buildRepositoryOptions } from "../utils/repositorySelection";
8-
9-
/** Cheap content-equality check for repository option lists. Lets the cache
10-
* write effect skip no-op updates, which is what kept retriggering renders
11-
* before — `buildRepositoryOptions` always returns a fresh array, so the
12-
* effect's dep array churned every render. */
13-
function repositoryOptionsEqual(
14-
a: RepositoryOption[],
15-
b: RepositoryOption[],
16-
): boolean {
17-
if (a === b) return true;
18-
if (a.length !== b.length) return false;
19-
for (let i = 0; i < a.length; i++) {
20-
const left = a[i];
21-
const right = b[i];
22-
if (
23-
left.integrationId !== right.integrationId ||
24-
left.repository !== right.repository ||
25-
left.integrationLabel !== right.integrationLabel
26-
) {
27-
return false;
28-
}
29-
}
30-
return true;
31-
}
3212

3313
export const integrationKeys = {
3414
all: ["integrations"] as const,
@@ -78,11 +58,9 @@ export function useIntegrations(options: UseIntegrationsOptions = {}) {
7858
const results = await Promise.allSettled(
7959
githubIntegrations.map(async (integration) => ({
8060
integrationId: integration.id,
81-
repositories: (
82-
await getPostHogApiClient().getGithubRepositories(integration.id)
83-
)
84-
.map((repository) => repository.toLowerCase())
85-
.filter((repository) => repository.length > 0),
61+
repositories: normalizeRepositoryNames(
62+
await getPostHogApiClient().getGithubRepositories(integration.id),
63+
),
8664
})),
8765
);
8866

@@ -100,12 +78,10 @@ export function useIntegrations(options: UseIntegrationsOptions = {}) {
10078

10179
return {
10280
repositoriesByIntegration,
103-
partialError:
104-
failedCount === 0
105-
? null
106-
: failedCount === githubIntegrations.length
107-
? "Could not load GitHub repositories. Pull to retry."
108-
: "Some GitHub repositories could not be loaded. Pull to retry.",
81+
partialError: repositoryLoadWarning(
82+
failedCount,
83+
githubIntegrations.length,
84+
),
10985
};
11086
},
11187
enabled: enabled && githubIntegrations.length > 0,
@@ -121,7 +97,8 @@ export function useIntegrations(options: UseIntegrationsOptions = {}) {
12197
// churns the cache-write effect below AND defeats downstream React.memo /
12298
// useMemo callers that depend on `repositoryOptions`.
12399
const liveRepositoryOptions = useMemo(
124-
() => buildRepositoryOptions(githubIntegrations, repositoriesByIntegration),
100+
() =>
101+
buildTeamRepositoryOptions(githubIntegrations, repositoriesByIntegration),
125102
[githubIntegrations, repositoriesByIntegration],
126103
);
127104

apps/mobile/src/features/tasks/hooks/useUserIntegrations.ts

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
import {
2+
buildUserRepositoryOptions,
3+
normalizeRepositoryNames,
4+
type RepositoryOption,
5+
repositoryLoadWarning,
6+
} from "@posthog/core/integrations/repositories";
17
import { useQuery } from "@tanstack/react-query";
28
import { useCallback, useMemo } from "react";
39
import { useAuthStore } from "@/features/auth";
410
import { getPostHogApiClient } from "@/lib/posthogApiClient";
5-
import type { RepositoryOption, UserGithubIntegration } from "../types";
611

712
/**
813
* User-scoped sibling of {@link useIntegrations}. Reads the authenticated
@@ -29,10 +34,6 @@ interface UseUserIntegrationsOptions {
2934
enabled?: boolean;
3035
}
3136

32-
function integrationLabel(integration: UserGithubIntegration): string {
33-
return integration.account?.name ?? `GitHub ${integration.installation_id}`;
34-
}
35-
3637
export function useUserIntegrations(options: UseUserIntegrationsOptions = {}) {
3738
const { enabled = true } = options;
3839
const { oauthAccessToken } = useAuthStore();
@@ -66,13 +67,11 @@ export function useUserIntegrations(options: UseUserIntegrationsOptions = {}) {
6667
const results = await Promise.allSettled(
6768
integrations.map(async (integration) => ({
6869
installationId: integration.installation_id,
69-
repositories: (
70+
repositories: normalizeRepositoryNames(
7071
await getPostHogApiClient().getGithubUserRepositories(
7172
integration.installation_id,
72-
)
73-
)
74-
.map((repository) => repository.toLowerCase())
75-
.filter((repository) => repository.length > 0),
73+
),
74+
),
7675
})),
7776
);
7877

@@ -88,31 +87,17 @@ export function useUserIntegrations(options: UseUserIntegrationsOptions = {}) {
8887

8988
return {
9089
byInstallation,
91-
partialError:
92-
failedCount === 0
93-
? null
94-
: failedCount === integrations.length
95-
? "Could not load GitHub repositories. Pull to retry."
96-
: "Some GitHub repositories could not be loaded. Pull to retry.",
90+
partialError: repositoryLoadWarning(failedCount, integrations.length),
9791
};
9892
},
9993
enabled: enabled && integrations.length > 0,
10094
});
10195

10296
const repositoryOptions = useMemo<RepositoryOption[]>(() => {
103-
const byInstallation = repositoriesQuery.data?.byInstallation ?? {};
104-
return integrations
105-
.flatMap((integration) => {
106-
const repositories = byInstallation[integration.installation_id] ?? [];
107-
return repositories.map((repository) => ({
108-
// GitHub installation ids fit in a JS number; use it as the numeric
109-
// key the picker/RepositoryOption already expect.
110-
integrationId: Number(integration.installation_id),
111-
integrationLabel: integrationLabel(integration),
112-
repository,
113-
}));
114-
})
115-
.sort((left, right) => left.repository.localeCompare(right.repository));
97+
return buildUserRepositoryOptions(
98+
integrations,
99+
repositoriesQuery.data?.byInstallation ?? {},
100+
);
116101
}, [integrations, repositoriesQuery.data]);
117102

118103
/** Resolve the `UserIntegration` UUID for a selected installation id, to send

apps/mobile/src/features/tasks/stores/repositoryCacheStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import type { RepositoryOption } from "@posthog/core/integrations/repositories";
12
import AsyncStorage from "@react-native-async-storage/async-storage";
23
import { create } from "zustand";
34
import { createJSONStorage, persist } from "zustand/middleware";
4-
import type { RepositoryOption } from "../types";
55

66
interface RepositoryCacheState {
77
/** Last successfully fetched, sorted list of repository options across all

apps/mobile/src/features/tasks/stores/taskStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import type { RepositorySelection } from "@posthog/core/integrations/repositories";
12
import type { TaskActivitySortMode } from "@posthog/core/tasks/taskActivity";
23
import type { ExecutionMode, SupportedReasoningEffort } from "@posthog/shared";
34
import AsyncStorage from "@react-native-async-storage/async-storage";
45
import { create } from "zustand";
56
import { createJSONStorage, persist } from "zustand/middleware";
6-
import type { RepositorySelection } from "../types";
77

88
export type OrganizeMode = "by-project" | "chronological";
99
export type SortMode = TaskActivitySortMode;

apps/mobile/src/features/tasks/types.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,6 @@ export interface UserGithubIntegration {
5757
};
5858
}
5959

60-
export interface RepositoryOption {
61-
integrationId: number;
62-
integrationLabel: string;
63-
repository: string;
64-
}
65-
66-
export interface RepositorySelection {
67-
integrationId: number | null;
68-
repository: string | null;
69-
}
70-
7160
export interface CreateTaskOptions {
7261
description: string;
7362
title?: string;

apps/mobile/src/features/tasks/utils/repositorySelection.test.ts

Lines changed: 0 additions & 89 deletions
This file was deleted.

0 commit comments

Comments
 (0)