mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-13 04:45:19 +00:00
Compare commits
No commits in common. "main" and "v4.10.2" have entirely different histories.
32 changed files with 234 additions and 229 deletions
18
.github/workflows/ghcr-publish.yml
vendored
18
.github/workflows/ghcr-publish.yml
vendored
|
|
@ -27,9 +27,9 @@ jobs:
|
|||
platform: [linux/amd64, linux/arm64]
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
- platform: linux/arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2204-arm
|
||||
|
||||
steps:
|
||||
- name: Prepare
|
||||
|
|
@ -57,8 +57,8 @@ jobs:
|
|||
with:
|
||||
cosign-release: "v2.2.4"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup Blacksmith Builder
|
||||
uses: useblacksmith/setup-docker-builder@v1
|
||||
|
||||
- name: Login to GitHub Packages Docker Registry
|
||||
uses: docker/login-action@v3
|
||||
|
|
@ -69,12 +69,10 @@ jobs:
|
|||
|
||||
- name: Build Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ env.PLATFORM_PAIR }}
|
||||
cache-to: type=gha,mode=max,scope=${{ env.PLATFORM_PAIR }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true,annotation.org.opencontainers.image.description=Blazingly fast code search
|
||||
build-args: |
|
||||
|
|
@ -109,7 +107,7 @@ jobs:
|
|||
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
packages: write
|
||||
needs:
|
||||
|
|
@ -122,8 +120,8 @@ jobs:
|
|||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup Blacksmith Builder
|
||||
uses: useblacksmith/setup-docker-builder@v1
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
|
|
|
|||
4
.github/workflows/pr-gate.yml
vendored
4
.github/workflows/pr-gate.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
|
|
@ -19,6 +19,6 @@ jobs:
|
|||
|
||||
- name: Build Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
|
|
|
|||
2
.github/workflows/test-backend.yml
vendored
2
.github/workflows/test-backend.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
|
|
|
|||
2
.github/workflows/test-web.yml
vendored
2
.github/workflows/test-web.yml
vendored
|
|
@ -7,7 +7,7 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
|
|
|
|||
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -7,21 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where parenthesis in query params were not being encoded, resulting in a poor experience when embedding links in Markdown. [#674](https://github.com/sourcebot-dev/sourcebot/pull/674)
|
||||
|
||||
## [4.10.3] - 2025-12-12
|
||||
|
||||
### Fixed
|
||||
- Fixed review agent so that it works with GHES instances [#611](https://github.com/sourcebot-dev/sourcebot/pull/611)
|
||||
- Updated next package version to fix CVE-2025-55184 and CVE-2025-55183. [#673](https://github.com/sourcebot-dev/sourcebot/pull/673)
|
||||
|
||||
### Added
|
||||
- Added support for arbitrary user IDs required for OpenShift. [#658](https://github.com/sourcebot-dev/sourcebot/pull/658)
|
||||
|
||||
### Updated
|
||||
- Improved error messages in file source api. [#665](https://github.com/sourcebot-dev/sourcebot/pull/665)
|
||||
|
||||
## [4.10.2] - 2025-12-04
|
||||
|
||||
### Fixed
|
||||
|
|
@ -56,7 +41,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Changed the default search behaviour to match patterns as substrings and **not** regular expressions. Regular expressions can be used by toggling the regex button in search bar. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623)
|
||||
- Renamed `public` query prefix to `visibility`. Allowed values for `visibility` are `public`, `private`, and `any`. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623)
|
||||
- Changed `archived` query prefix to accept values `yes`, `no`, and `only`. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623)
|
||||
- Changed `lang` query prefix to be case sensitive. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623)
|
||||
|
||||
### Removed
|
||||
- Removed `case` query prefix. [#623](https://github.com/sourcebot-dev/sourcebot/pull/623)
|
||||
|
|
|
|||
|
|
@ -195,7 +195,6 @@ RUN addgroup -g $GID sourcebot && \
|
|||
adduser -D -u $UID -h /app -S sourcebot && \
|
||||
adduser sourcebot postgres && \
|
||||
adduser sourcebot redis && \
|
||||
chown -R sourcebot /app && \
|
||||
adduser sourcebot node && \
|
||||
mkdir /var/log/sourcebot && \
|
||||
chown sourcebot /var/log/sourcebot
|
||||
|
|
@ -245,12 +244,7 @@ RUN mkdir -p /run/postgresql && \
|
|||
chown -R postgres:postgres /run/postgresql && \
|
||||
chmod 775 /run/postgresql
|
||||
|
||||
# Make app directory accessible to both root and sourcebot user
|
||||
RUN chown -R sourcebot /app \
|
||||
&& chgrp -R 0 /app \
|
||||
&& chmod -R g=u /app
|
||||
# Make data directory accessible to both root and sourcebot user
|
||||
RUN chown -R sourcebot /data
|
||||
RUN chown -R sourcebot:sourcebot /data
|
||||
|
||||
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
COPY prefix-output.sh ./prefix-output.sh
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ services:
|
|||
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/postgres} # CHANGEME
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379} # CHANGEME
|
||||
- SOURCEBOT_EE_LICENSE_KEY=${SOURCEBOT_EE_LICENSE_KEY:-}
|
||||
- SOURCEBOT_TELEMETRY_DISABLED=${SOURCEBOT_TELEMETRY_DISABLED:-false}
|
||||
|
||||
# For the full list of environment variables see:
|
||||
# https://docs.sourcebot.dev/docs/configuration/environment-variables
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
---
|
||||
title: "Permission syncing"
|
||||
sidebarTitle: "Permission syncing"
|
||||
tag: "experimental"
|
||||
---
|
||||
|
||||
import LicenseKeyRequired from '/snippets/license-key-required.mdx'
|
||||
import ExperimentalFeatureWarning from '/snippets/experimental-feature-warning.mdx'
|
||||
|
||||
<LicenseKeyRequired />
|
||||
<ExperimentalFeatureWarning />
|
||||
|
||||
# Overview
|
||||
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@
|
|||
"langfuse-vercel": "^3.38.4",
|
||||
"lucide-react": "^0.517.0",
|
||||
"micromatch": "^4.0.8",
|
||||
"next": "15.5.9",
|
||||
"next": "^15.5.7",
|
||||
"next-auth": "^5.0.0-beta.30",
|
||||
"next-navigation-guard": "^0.2.0",
|
||||
"next-themes": "^0.3.0",
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ const auditService = getAuditService();
|
|||
/**
|
||||
* "Service Error Wrapper".
|
||||
*
|
||||
* Captures any thrown exceptions, logs them to the console and Sentry,
|
||||
* and returns a generic unexpected service error.
|
||||
* Captures any thrown exceptions and converts them to a unexpected
|
||||
* service error. Also logs them with Sentry.
|
||||
*/
|
||||
export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> => {
|
||||
try {
|
||||
|
|
@ -52,6 +52,10 @@ export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> =>
|
|||
return e.serviceError;
|
||||
}
|
||||
|
||||
if (e instanceof Error) {
|
||||
return unexpectedError(e.message);
|
||||
}
|
||||
|
||||
return unexpectedError(`An unexpected error occurred. Please try again later.`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,8 @@ export const CodePreviewPanel = async ({ path, repoName, revisionName }: CodePre
|
|||
getRepoInfoByName(repoName),
|
||||
]);
|
||||
|
||||
if (isServiceError(fileSourceResponse)) {
|
||||
return <div>Error loading file source: {fileSourceResponse.message}</div>
|
||||
}
|
||||
|
||||
if (isServiceError(repoInfoResponse)) {
|
||||
return <div>Error loading repo info: {repoInfoResponse.message}</div>
|
||||
if (isServiceError(fileSourceResponse) || isServiceError(repoInfoResponse)) {
|
||||
return <div>Error loading file source</div>
|
||||
}
|
||||
|
||||
const codeHostInfo = getCodeHostInfoForRepo({
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const TrialIndicator = ({ subscription }: Props) => {
|
|||
|
||||
if (isServiceError(subscription)) {
|
||||
captureEvent('wa_trial_nav_subscription_fetch_fail', {
|
||||
errorCode: subscription.errorCode,
|
||||
error: subscription.errorCode,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export const SearchLandingPage = async ({
|
|||
<Query query="repo:torvalds/linux test" domain={domain}><Highlight>repo:</Highlight>torvalds/linux test</Query> <QueryExplanation>(by repo)</QueryExplanation>
|
||||
</QueryExample>
|
||||
<QueryExample>
|
||||
<Query query="lang:TypeScript" domain={domain}><Highlight>lang:</Highlight>TypeScript</Query> <QueryExplanation>(by language)</QueryExplanation>
|
||||
<Query query="lang:typescript" domain={domain}><Highlight>lang:</Highlight>typescript</Query> <QueryExplanation>(by language)</QueryExplanation>
|
||||
</QueryExample>
|
||||
<QueryExample>
|
||||
<Query query="rev:HEAD" domain={domain}><Highlight>rev:</Highlight>HEAD</Query> <QueryExplanation>(by branch or tag)</QueryExplanation>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export function ChangeOrgDomainCard({ orgDomain, currentUserRole, rootDomain }:
|
|||
description: `❌ Failed to update organization url. Reason: ${result.message}`,
|
||||
})
|
||||
captureEvent('wa_org_domain_updated_fail', {
|
||||
errorCode: result.errorCode,
|
||||
error: result.errorCode,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export function ChangeOrgNameCard({ orgName, currentUserRole }: ChangeOrgNameCar
|
|||
description: `❌ Failed to update organization name. Reason: ${result.message}`,
|
||||
})
|
||||
captureEvent('wa_org_name_updated_fail', {
|
||||
errorCode: result.errorCode,
|
||||
error: result.errorCode,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export const InviteMemberCard = ({ currentUserRole, isBillingEnabled, seatsAvail
|
|||
description: `❌ Failed to invite members. Reason: ${res.message}`
|
||||
});
|
||||
captureEvent('wa_invite_member_card_invite_fail', {
|
||||
errorCode: res.errorCode,
|
||||
error: res.errorCode,
|
||||
num_emails: data.emails.length,
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const InvitesList = ({ invites, currentUserRole }: InviteListProps) => {
|
|||
description: `❌ Failed to cancel invite. Reason: ${response.message}`
|
||||
})
|
||||
captureEvent('wa_invites_list_cancel_invite_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName }
|
|||
description: `❌ Failed to remove member. Reason: ${response.message}`
|
||||
})
|
||||
captureEvent('wa_members_list_remove_member_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
|
|
@ -91,7 +91,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName }
|
|||
description: `❌ Failed to transfer ownership. Reason: ${response.message}`
|
||||
})
|
||||
captureEvent('wa_members_list_transfer_ownership_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
|
|
@ -111,7 +111,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName }
|
|||
description: `❌ Failed to leave organization. Reason: ${response.message}`
|
||||
})
|
||||
captureEvent('wa_members_list_leave_org_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const RequestsList = ({ requests, currentUserRole }: RequestsListProps) =
|
|||
description: `❌ Failed to approve request. Reason: ${response.message}`
|
||||
})
|
||||
captureEvent('wa_requests_list_approve_request_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
|
|
@ -83,7 +83,7 @@ export const RequestsList = ({ requests, currentUserRole }: RequestsListProps) =
|
|||
description: `❌ Failed to reject request.`
|
||||
})
|
||||
captureEvent('wa_requests_list_reject_request_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -6,32 +6,28 @@ import { WebhookEventDefinition} from "@octokit/webhooks/types";
|
|||
import { EndpointDefaults } from "@octokit/types";
|
||||
import { env } from "@sourcebot/shared";
|
||||
import { processGitHubPullRequest } from "@/features/agents/review-agent/app";
|
||||
import { throttling, type ThrottlingOptions } from "@octokit/plugin-throttling";
|
||||
import { throttling } from "@octokit/plugin-throttling";
|
||||
import fs from "fs";
|
||||
import { GitHubPullRequest } from "@/features/agents/review-agent/types";
|
||||
import { createLogger } from "@sourcebot/shared";
|
||||
|
||||
const logger = createLogger('github-webhook');
|
||||
|
||||
const DEFAULT_GITHUB_API_BASE_URL = "https://api.github.com";
|
||||
type GitHubAppBaseOptions = Omit<ConstructorParameters<typeof App>[0], "Octokit"> & { throttle: ThrottlingOptions };
|
||||
|
||||
let githubAppBaseOptions: GitHubAppBaseOptions | undefined;
|
||||
const githubAppCache = new Map<string, App>();
|
||||
|
||||
let githubApp: App | undefined;
|
||||
if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET && env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH) {
|
||||
try {
|
||||
const privateKey = fs.readFileSync(env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH, "utf8");
|
||||
|
||||
githubAppBaseOptions = {
|
||||
const throttledOctokit = Octokit.plugin(throttling);
|
||||
githubApp = new App({
|
||||
appId: env.GITHUB_REVIEW_AGENT_APP_ID,
|
||||
privateKey,
|
||||
privateKey: privateKey,
|
||||
webhooks: {
|
||||
secret: env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET,
|
||||
},
|
||||
Octokit: throttledOctokit,
|
||||
throttle: {
|
||||
enabled: true,
|
||||
onRateLimit: (retryAfter, _options, _octokit, retryCount) => {
|
||||
onRateLimit: (retryAfter: number, options: Required<EndpointDefaults>, octokit: Octokit, retryCount: number) => {
|
||||
if (retryCount > 3) {
|
||||
logger.warn(`Rate limit exceeded: ${retryAfter} seconds`);
|
||||
return false;
|
||||
|
|
@ -39,55 +35,13 @@ if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET
|
|||
|
||||
return true;
|
||||
},
|
||||
onSecondaryRateLimit: (_retryAfter, options) => {
|
||||
// no retries on secondary rate limits
|
||||
logger.warn(`SecondaryRateLimit detected for ${options.method} ${options.url}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error initializing GitHub app: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeGithubApiBaseUrl = (baseUrl?: string) => {
|
||||
if (!baseUrl) {
|
||||
return DEFAULT_GITHUB_API_BASE_URL;
|
||||
}
|
||||
|
||||
return baseUrl.replace(/\/+$/, "");
|
||||
};
|
||||
|
||||
const resolveGithubApiBaseUrl = (headers: Record<string, string>) => {
|
||||
const enterpriseHost = headers["x-github-enterprise-host"];
|
||||
if (enterpriseHost) {
|
||||
return normalizeGithubApiBaseUrl(`https://${enterpriseHost}/api/v3`);
|
||||
}
|
||||
|
||||
return DEFAULT_GITHUB_API_BASE_URL;
|
||||
};
|
||||
|
||||
const getGithubAppForBaseUrl = (baseUrl: string) => {
|
||||
if (!githubAppBaseOptions) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalizedBaseUrl = normalizeGithubApiBaseUrl(baseUrl);
|
||||
const cachedApp = githubAppCache.get(normalizedBaseUrl);
|
||||
if (cachedApp) {
|
||||
return cachedApp;
|
||||
}
|
||||
|
||||
const OctokitWithBaseUrl = Octokit.plugin(throttling).defaults({ baseUrl: normalizedBaseUrl });
|
||||
const app = new App({
|
||||
...githubAppBaseOptions,
|
||||
Octokit: OctokitWithBaseUrl,
|
||||
});
|
||||
|
||||
githubAppCache.set(normalizedBaseUrl, app);
|
||||
return app;
|
||||
};
|
||||
|
||||
function isPullRequestEvent(eventHeader: string, payload: unknown): payload is WebhookEventDefinition<"pull-request-opened"> | WebhookEventDefinition<"pull-request-synchronize"> {
|
||||
return eventHeader === "pull_request" && typeof payload === "object" && payload !== null && "action" in payload && typeof payload.action === "string" && (payload.action === "opened" || payload.action === "synchronize");
|
||||
}
|
||||
|
|
@ -98,16 +52,12 @@ function isIssueCommentEvent(eventHeader: string, payload: unknown): payload is
|
|||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
const body = await request.json();
|
||||
const headers = Object.fromEntries(Array.from(request.headers.entries(), ([key, value]) => [key.toLowerCase(), value]));
|
||||
const headers = Object.fromEntries(request.headers.entries());
|
||||
|
||||
const githubEvent = headers['x-github-event'];
|
||||
const githubEvent = headers['x-github-event'] || headers['X-GitHub-Event'];
|
||||
if (githubEvent) {
|
||||
logger.info('GitHub event received:', githubEvent);
|
||||
|
||||
const githubApiBaseUrl = resolveGithubApiBaseUrl(headers);
|
||||
logger.debug('Using GitHub API base URL for event', { githubApiBaseUrl });
|
||||
const githubApp = getGithubAppForBaseUrl(githubApiBaseUrl);
|
||||
|
||||
if (!githubApp) {
|
||||
logger.warn('Received GitHub webhook event but GitHub app env vars are not set');
|
||||
return Response.json({ status: 'ok' });
|
||||
|
|
@ -163,4 +113,4 @@ export const POST = async (request: NextRequest) => {
|
|||
}
|
||||
|
||||
return Response.json({ status: 'ok' });
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import { Toaster } from "@/components/ui/toaster";
|
|||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { env } from "@sourcebot/shared";
|
||||
import { env as clientEnv } from "@sourcebot/shared/client";
|
||||
import { PlanProvider } from "@/features/entitlements/planProvider";
|
||||
import { getEntitlements } from "@sourcebot/shared";
|
||||
|
||||
|
|
@ -43,8 +42,6 @@ export default function RootLayout({
|
|||
// @note: the posthog api key doesn't need to be kept secret,
|
||||
// so we are safe to send it to the client.
|
||||
posthogApiKey={env.POSTHOG_PAPIK}
|
||||
sourcebotVersion={clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION}
|
||||
sourcebotInstallId={env.SOURCEBOT_INSTALL_ID}
|
||||
>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
|
|
|
|||
|
|
@ -34,17 +34,9 @@ interface PostHogProviderProps {
|
|||
children: React.ReactNode
|
||||
isDisabled: boolean
|
||||
posthogApiKey: string
|
||||
sourcebotVersion: string
|
||||
sourcebotInstallId: string
|
||||
}
|
||||
|
||||
export function PostHogProvider({
|
||||
children,
|
||||
isDisabled,
|
||||
posthogApiKey,
|
||||
sourcebotVersion,
|
||||
sourcebotInstallId,
|
||||
}: PostHogProviderProps) {
|
||||
export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHogProviderProps) {
|
||||
const { data: session } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -69,33 +61,27 @@ export function PostHogProvider({
|
|||
'$referrer',
|
||||
'$referring_domain',
|
||||
'$ip',
|
||||
] : [],
|
||||
loaded: (posthog) => {
|
||||
// Include install id & version in all events.
|
||||
posthog.register({
|
||||
sourcebot_version: sourcebotVersion,
|
||||
install_id: sourcebotInstallId,
|
||||
});
|
||||
}
|
||||
] : []
|
||||
});
|
||||
} else {
|
||||
console.debug("PostHog telemetry disabled");
|
||||
}
|
||||
}, [isDisabled, posthogApiKey, sourcebotInstallId, sourcebotVersion]);
|
||||
}, [isDisabled, posthogApiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog.identify(
|
||||
session.user.id,
|
||||
// Only include email & name when running in a cloud environment.
|
||||
env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined ? {
|
||||
// Only identify the user if we are running in a cloud environment.
|
||||
if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) {
|
||||
posthog.identify(session.user.id, {
|
||||
email: session.user.email,
|
||||
name: session.user.name,
|
||||
} : undefined
|
||||
);
|
||||
});
|
||||
} else {
|
||||
console.debug("PostHog identify skipped");
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export function ChangeBillingEmailCard({ currentUserRole, billingEmail }: Change
|
|||
description: "❌ Failed to update billing email. Please try again.",
|
||||
})
|
||||
captureEvent('wa_billing_email_updated_fail', {
|
||||
errorCode: result.errorCode,
|
||||
error: result.message,
|
||||
})
|
||||
}
|
||||
setIsLoading(false)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export const Checkout = () => {
|
|||
variant: "destructive",
|
||||
});
|
||||
captureEvent('wa_onboard_checkout_fail', {
|
||||
errorCode: errorMessage,
|
||||
error: errorMessage,
|
||||
});
|
||||
}
|
||||
}, [errorCode, errorMessage, toast, captureEvent]);
|
||||
|
|
@ -45,7 +45,7 @@ export const Checkout = () => {
|
|||
variant: "destructive",
|
||||
})
|
||||
captureEvent('wa_onboard_checkout_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
});
|
||||
} else {
|
||||
captureEvent('wa_onboard_checkout_success', {});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function ManageSubscriptionButton({ currentUserRole }: { currentUserRole:
|
|||
const session = await getCustomerPortalSessionLink(domain);
|
||||
if (isServiceError(session)) {
|
||||
captureEvent('wa_manage_subscription_button_create_portal_session_fail', {
|
||||
errorCode: session.errorCode,
|
||||
error: session.errorCode,
|
||||
});
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const TeamUpgradeCard = ({ buttonText }: TeamUpgradeCardProps) => {
|
|||
variant: "destructive",
|
||||
});
|
||||
captureEvent('wa_team_upgrade_checkout_fail', {
|
||||
errorCode: response.errorCode,
|
||||
error: response.errorCode,
|
||||
});
|
||||
} else {
|
||||
router.push(response.url);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@
|
|||
import { CaptureOptions } from "posthog-js";
|
||||
import posthog from "posthog-js";
|
||||
import { PosthogEvent, PosthogEventMap } from "../lib/posthogEvents";
|
||||
import { env } from "@sourcebot/shared/client";
|
||||
|
||||
export function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E], options?: CaptureOptions) {
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
options.send_instantly = true;
|
||||
posthog.capture(event, properties, options);
|
||||
posthog.capture(event, {
|
||||
...properties,
|
||||
sourcebot_version: env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||
}, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { PostHog } from 'posthog-node'
|
||||
import { env } from '@sourcebot/shared'
|
||||
import { env as clientEnv } from '@sourcebot/shared/client';
|
||||
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { PosthogEvent, PosthogEventMap } from './posthogEvents';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
import { auth } from '@/auth';
|
||||
import { getVerifiedApiObject } from '@/withAuthV2';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
/**
|
||||
* @note: This is a subset of the properties stored in the
|
||||
|
|
@ -50,43 +47,13 @@ const getPostHogCookie = (cookieStore: Pick<RequestCookies, 'get'>): PostHogCook
|
|||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to retrieve the distinct id of the current user.
|
||||
*/
|
||||
const tryGetDistinctId = async () => {
|
||||
// First, attempt to retrieve the distinct id from the cookie.
|
||||
const cookieStore = await cookies();
|
||||
const cookie = getPostHogCookie(cookieStore);
|
||||
if (cookie) {
|
||||
return cookie.distinct_id;
|
||||
}
|
||||
|
||||
// Next, from the session.
|
||||
const session = await auth();
|
||||
if (session) {
|
||||
return session.user.id;
|
||||
}
|
||||
|
||||
// Finally, from the api key.
|
||||
const headersList = await headers();
|
||||
const apiKeyString = headersList.get("X-Sourcebot-Api-Key") ?? undefined;
|
||||
if (!apiKeyString) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const apiKey = await getVerifiedApiObject(apiKeyString);
|
||||
return apiKey?.createdById;
|
||||
}
|
||||
|
||||
export async function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
|
||||
if (env.SOURCEBOT_TELEMETRY_DISABLED === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
const distinctId = await tryGetDistinctId();
|
||||
|
||||
const headersList = await headers();
|
||||
const host = headersList.get("host") ?? undefined;
|
||||
const cookieStore = await cookies();
|
||||
const cookie = getPostHogCookie(cookieStore);
|
||||
|
||||
const posthog = new PostHog(env.POSTHOG_PAPIK, {
|
||||
host: 'https://us.i.posthog.com',
|
||||
|
|
@ -96,12 +63,7 @@ export async function captureEvent<E extends PosthogEvent>(event: E, properties:
|
|||
|
||||
posthog.capture({
|
||||
event,
|
||||
properties: {
|
||||
...properties,
|
||||
sourcebot_version: clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||
install_id: env.SOURCEBOT_INSTALL_ID,
|
||||
$host: host,
|
||||
},
|
||||
distinctId,
|
||||
properties,
|
||||
distinctId: cookie?.distinct_id ?? '',
|
||||
});
|
||||
}
|
||||
|
|
@ -29,10 +29,71 @@ export type PosthogEventMap = {
|
|||
fileLanguages: string[],
|
||||
isSearchExhaustive: boolean
|
||||
},
|
||||
share_link_created: {},
|
||||
////////////////////////////////////////////////////////////////
|
||||
wa_secret_created_success: {
|
||||
key: string,
|
||||
},
|
||||
wa_secret_deleted_success: {
|
||||
key: string,
|
||||
},
|
||||
wa_secret_deleted_fail: {
|
||||
key: string,
|
||||
error: string,
|
||||
},
|
||||
wa_secret_created_fail: {
|
||||
key: string,
|
||||
error: string,
|
||||
},
|
||||
wa_secret_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_warning_nav_connection_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
wa_warning_nav_hover: {},
|
||||
wa_warning_nav_pressed: {},
|
||||
wa_warning_nav_connection_pressed: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_error_nav_connection_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
wa_error_nav_hover: {},
|
||||
wa_error_nav_pressed: {},
|
||||
wa_error_nav_job_pressed: {},
|
||||
wa_error_nav_job_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_progress_nav_connection_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
wa_progress_nav_repo_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
wa_progress_nav_hover: {},
|
||||
wa_progress_nav_pressed: {},
|
||||
wa_progress_nav_job_pressed: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_trial_nav_pressed: {},
|
||||
wa_trial_nav_subscription_fetch_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connection_list_item_error_hover: {},
|
||||
wa_connection_list_item_error_pressed: {},
|
||||
wa_connection_list_item_warning_hover: {},
|
||||
wa_connection_list_item_warning_pressed: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connection_list_item_manage_pressed: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_create_connection_success: {
|
||||
type: string,
|
||||
},
|
||||
wa_create_connection_fail: {
|
||||
type: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_config_editor_quick_action_pressed: {
|
||||
|
|
@ -40,64 +101,124 @@ export type PosthogEventMap = {
|
|||
type: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_secret_combobox_import_secret_pressed: {
|
||||
type: string,
|
||||
},
|
||||
wa_secret_combobox_import_secret_success: {
|
||||
type: string,
|
||||
},
|
||||
wa_secret_combobox_import_secret_fail: {
|
||||
type: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_billing_email_updated_success: {},
|
||||
wa_billing_email_updated_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
wa_billing_email_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
wa_manage_subscription_button_create_portal_session_success: {},
|
||||
wa_manage_subscription_button_create_portal_session_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_invite_member_card_invite_success: {
|
||||
num_emails: number,
|
||||
},
|
||||
wa_invite_member_card_invite_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
num_emails: number,
|
||||
},
|
||||
wa_invite_member_card_invite_cancel: {
|
||||
num_emails: number,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_onboard_skip_onboarding: {
|
||||
step: string,
|
||||
},
|
||||
wa_onboard_invite_team_invite_success: {
|
||||
num_emails: number,
|
||||
},
|
||||
wa_onboard_invite_team_invite_fail: {
|
||||
error: string,
|
||||
num_emails: number,
|
||||
},
|
||||
wa_onboard_invite_team_skip: {
|
||||
num_emails: number,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_members_list_remove_member_success: {},
|
||||
wa_members_list_remove_member_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
wa_members_list_transfer_ownership_success: {},
|
||||
wa_members_list_transfer_ownership_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
wa_members_list_leave_org_success: {},
|
||||
wa_members_list_leave_org_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_invites_list_cancel_invite_success: {},
|
||||
wa_invites_list_cancel_invite_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
wa_invites_list_copy_invite_link_success: {},
|
||||
wa_invites_list_copy_invite_link_fail: {},
|
||||
wa_invites_list_copy_email_success: {},
|
||||
wa_invites_list_copy_email_fail: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_onboard_org_create_success: {},
|
||||
wa_onboard_org_create_fail: {
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connect_code_host_button_pressed: {
|
||||
name: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_onboard_checkout_success: {},
|
||||
wa_onboard_checkout_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_team_upgrade_card_pressed: {},
|
||||
wa_team_upgrade_checkout_success: {},
|
||||
wa_team_upgrade_checkout_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
wa_enterprise_upgrade_card_pressed: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connection_delete_success: {},
|
||||
wa_connection_delete_fail: {
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connection_failed_status_hover: {},
|
||||
wa_connection_retry_sync_success: {},
|
||||
wa_connection_retry_sync_fail: {
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connection_not_found_warning_displayed: {},
|
||||
wa_connection_secrets_navigation_pressed: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_connection_retry_all_failed_repos_pressed: {},
|
||||
wa_connection_retry_all_failed_repos_fetch_fail: {
|
||||
error: string,
|
||||
},
|
||||
wa_connection_retry_all_failed_repos_fail: {},
|
||||
wa_connection_retry_all_failed_repos_success: {},
|
||||
wa_connection_retry_all_failed_no_repos: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_repo_retry_index_success: {},
|
||||
wa_repo_retry_index_fail: {
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_login_with_github: {},
|
||||
wa_login_with_google: {},
|
||||
wa_login_with_gitlab: {},
|
||||
|
|
@ -114,16 +235,24 @@ export type PosthogEventMap = {
|
|||
//////////////////////////////////////////////////////////////////
|
||||
wa_org_name_updated_success: {},
|
||||
wa_org_name_updated_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_org_domain_updated_success: {},
|
||||
wa_org_domain_updated_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_onboard_github_selected: {},
|
||||
wa_onboard_gitlab_selected: {},
|
||||
wa_onboard_gitea_selected: {},
|
||||
wa_onboard_gerrit_selected: {},
|
||||
wa_onboard_bitbucket_cloud_selected: {},
|
||||
wa_onboard_bitbucket_server_selected: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_security_page_click: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_demo_card_click: {},
|
||||
wa_demo_try_card_pressed: {},
|
||||
wa_share_link_created: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
|
@ -133,12 +262,12 @@ export type PosthogEventMap = {
|
|||
//////////////////////////////////////////////////////////////////
|
||||
wa_requests_list_approve_request_success: {},
|
||||
wa_requests_list_approve_request_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_requests_list_reject_request_success: {},
|
||||
wa_requests_list_reject_request_fail: {
|
||||
errorCode: string,
|
||||
error: string,
|
||||
},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_api_key_created: {},
|
||||
|
|
@ -152,6 +281,11 @@ export type PosthogEventMap = {
|
|||
wa_chat_thread_created: {},
|
||||
//////////////////////////////////////////////////////////////////
|
||||
wa_demo_docs_link_pressed: {},
|
||||
wa_demo_search_context_card_pressed: {
|
||||
contextType: string,
|
||||
contextName: string,
|
||||
contextDisplayName: string,
|
||||
},
|
||||
wa_demo_search_example_card_pressed: {
|
||||
exampleTitle: string,
|
||||
exampleUrl: string,
|
||||
|
|
|
|||
|
|
@ -62,18 +62,10 @@ export const createPathWithQueryParams = (path: string, ...queryParams: [string,
|
|||
return path;
|
||||
}
|
||||
|
||||
const queryString = queryParams.map(([key, value]) => `${encodeURIComponent(key)}=${encodeRFC3986URIComponent(value ?? '')}`).join('&');
|
||||
const queryString = queryParams.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value ?? '')}`).join('&');
|
||||
return `${path}?${queryString}`;
|
||||
}
|
||||
|
||||
// @see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
|
||||
const encodeRFC3986URIComponent = (str: string) => {
|
||||
return encodeURIComponent(str).replace(
|
||||
/[!'()*]/g,
|
||||
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
||||
);
|
||||
}
|
||||
|
||||
type AuthProviderInfo = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ export const getAuthenticatedUser = async () => {
|
|||
/**
|
||||
* Returns a API key object if the API key string is valid, otherwise returns undefined.
|
||||
*/
|
||||
export const getVerifiedApiObject = async (apiKeyString: string): Promise<ApiKey | undefined> => {
|
||||
const getVerifiedApiObject = async (apiKeyString: string): Promise<ApiKey | undefined> => {
|
||||
const parts = apiKeyString.split("-");
|
||||
if (parts.length !== 2 || parts[0] !== "sourcebot") {
|
||||
return undefined;
|
||||
|
|
|
|||
20
yarn.lock
20
yarn.lock
|
|
@ -3294,10 +3294,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:15.5.9":
|
||||
version: 15.5.9
|
||||
resolution: "@next/env@npm:15.5.9"
|
||||
checksum: 10c0/92c4e29d81a8e78c33c2da179648a4f478a9a6852966192e079007b19ec9955e72530d5ca7df55ea0efeccbf5b1c9d0efcaf80433e26af89c6478193e1d088f1
|
||||
"@next/env@npm:15.5.7":
|
||||
version: 15.5.7
|
||||
resolution: "@next/env@npm:15.5.7"
|
||||
checksum: 10c0/f92d99e5fa3516c6b7699abafd9bd813f5c1889dd257ab098f1b71f93137f5e4f49792e22f6dddf8a59efcb134e8e84277c983ff88607b2a42aac651bfde78ea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -8243,7 +8243,7 @@ __metadata:
|
|||
langfuse-vercel: "npm:^3.38.4"
|
||||
lucide-react: "npm:^0.517.0"
|
||||
micromatch: "npm:^4.0.8"
|
||||
next: "npm:15.5.9"
|
||||
next: "npm:^15.5.7"
|
||||
next-auth: "npm:^5.0.0-beta.30"
|
||||
next-navigation-guard: "npm:^0.2.0"
|
||||
next-themes: "npm:^0.3.0"
|
||||
|
|
@ -16178,11 +16178,11 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:15.5.9":
|
||||
version: 15.5.9
|
||||
resolution: "next@npm:15.5.9"
|
||||
"next@npm:^15.5.7":
|
||||
version: 15.5.7
|
||||
resolution: "next@npm:15.5.7"
|
||||
dependencies:
|
||||
"@next/env": "npm:15.5.9"
|
||||
"@next/env": "npm:15.5.7"
|
||||
"@next/swc-darwin-arm64": "npm:15.5.7"
|
||||
"@next/swc-darwin-x64": "npm:15.5.7"
|
||||
"@next/swc-linux-arm64-gnu": "npm:15.5.7"
|
||||
|
|
@ -16233,7 +16233,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: 10c0/6a120afbc45b96aa14debba6375602d6319093af4e3e8c648cf22b12ffb9db016c889df5e764cf5e0aa414ad60505db4e2095624a19f4b71316561076158651a
|
||||
checksum: 10c0/baf5b9f42416c478702b3894479b3d7862bc4abf18afe0e43b7fc7ed35567b8dc6cb76cd94906505bab9013cb8d0f3370cdc0451c01ec15ae5a638d37b5ba7c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue