Skip to content

Search contexts #273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 25, 2025
Merged

Search contexts #273

merged 14 commits into from
Apr 25, 2025

Conversation

brendan-kellam
Copy link
Contributor

@brendan-kellam brendan-kellam commented Apr 23, 2025

This PR adds support for search contexts as a enterprise feature (more on that below).

Overview

A search context is a user-defined grouping of repositories that helps focus searches on specific areas of your codebase, like frontend, backend, or infrastructure code. Some example queries using search contexts:

  • context:data_engineering userId - search for userId across all repos related to Data Engineering.
  • context:k8s ingress - search for anything related to ingresses in your k8's configs.
  • ( context:project1 or context:project2 ) logger\.debug - search for debug log calls in project1 and project2

Search contexts are defined in the context object inside of a declarative config. Repositories can be included / excluded from a search context by specifying the repo's URL in either the include array or exclude array. Glob patterns are supported.

Repository URL details

Repo URLs are expected to be formatted without the leading http(s):// prefix. For example:

  • github.com/sourcebot-dev/sourcebot (link)
  • gitlab.com/gitlab-org/gitlab (link)
  • chromium.googlesource.com/chromium (link)

Here's an example config that defines a search context:

{
    "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json",
    "contexts": {
        "example_context": {
            "description": "This is an example context.",
            // To include repositories in a search context,
            // you can reference them...
            "include": [
                // ... individually by specifying the repo URL.
                "github.com/sourcebot-dev/sourcebot",
                "gitlab.com/gitlab-org/gitlab",

                // ... or as groups using glob patterns. This is
                // particularly useful for including entire "sub-folders"
                // of repositories in one go.
                "chromium.googlesource.com/chromium/**"
            ],
            
            // Same with excluding repositories.
            "exclude": [
                "chromium.googlesource.com/chromium/buildtools",
                "chromium.googlesource.com/chromium/deps/**"
            ]
        }
    },
    "connections": {
       // ...connection definitions ....
    }
}

Sourcebot enterprise TLDR

note: a more thorough [blog post](https://www.sourcebot.dev/blog) is on the way discussing this

This PR introduces our first paid feature as part of our "Sourcebot enterprise" offering. Functionally speaking, we are implementing this as a open-core model, where the core will continue to be licensed under MIT, and any paid features (like search contexts) will be licensed under a commercial license (see ee/LICENSE). This PR additionally moves the billing related code under the commercial license since it's only relevant for Sourcebot cloud.

We want to be as transparent as possible with this, so as always, feel free to [open a discussion](https://github.com/sourcebot-dev/sourcebot/discussions) or [email us](mailto:team@sourcebot.dev) directly.

Fixes #81
Fixes #141

Copy link

coderabbitai bot commented Apr 23, 2025

Walkthrough

This set of changes introduces the "search contexts" feature to the codebase, enabling users to define and use named groupings of repositories for more focused code searches. The update includes database schema migrations, configuration schema and type extensions, backend and UI logic to support context-aware searching, and comprehensive documentation for both configuration and usage. Additionally, the billing and entitlement systems are refactored and extended, centralizing support email addresses and improving error handling. The documentation navigation is updated to reflect new and expanded topics, and the licensing structure is clarified for both open-source and enterprise components.

Changes

Files / Groups Change Summary
CHANGELOG.md, docs/docs.json, docs/docs/more/syntax-reference.mdx, docs/self-hosting/license-key.mdx, docs/self-hosting/more/search-contexts.mdx, docs/self-hosting/more/declarative-config.mdx, packages/web/src/features/entitlements/README.md Added/updated documentation for search contexts, syntax, licensing, and entitlements; updated navigation and warnings.
LICENSE, ee/LICENSE Updated main license with detailed terms; added new EE license file.
Makefile, package.json, packages/web/package.json Updated scripts and dependencies for workspace and migration management.
packages/db/prisma/migrations/.../migration.sql, packages/db/prisma/schema.prisma Added database tables and relations for search contexts and repo associations.
packages/schemas/src/v3/index.schema.ts, packages/schemas/src/v3/index.type.ts, schemas/v3/index.json Extended config schema/types with contexts and SearchContext definition.
packages/web/src/ee/features/searchContexts/syncSearchContexts.ts, packages/web/src/initialize.ts Added logic to synchronize search contexts from config to DB; modularized config sync.
packages/web/src/lib/server/searchService.ts Added query transformation for context-aware search; mapped context: to repo sets.
packages/web/src/app/api/(client)/client.ts, packages/web/src/app/[domain]/search/page.tsx Improved error handling for search API and UI.
packages/web/src/app/[domain]/components/searchBar/constants.ts, searchSuggestionsBox.tsx, useRefineModeSuggestions.ts, useSuggestionModeAndQuery.ts, useSuggestionModeMappings.ts, useSuggestionsData.ts, zoektLanguageExtension.ts Added UI support for search context suggestions and prefix handling.
packages/web/src/features/entitlements/constants.ts, planProvider.tsx, server.ts, useHasEntitlement.ts, usePlan.ts Introduced entitlements system, plan context, and related hooks/utilities.
packages/web/src/lib/constants.ts, error.tsx, components/securityCard.tsx, login/verify/page.tsx, login/verify/verificationFailed.tsx, ee/features/billing/components/enterpriseUpgradeCard.tsx Centralized support email usage.
packages/web/src/lib/errorCodes.ts Added new error code for missing search context.
packages/web/src/env.mjs Added EE license key environment variable.
packages/web/src/ee/features/billing/actions.ts, serverUtils.ts, stripe.ts, components/* Refactored and extended billing actions, utilities, and entitlement gating.
packages/web/src/app/*/layout.tsx, settings/billing/page.tsx, settings/layout.tsx, settings/members/page.tsx, upgrade/page.tsx, onboard/page.tsx, onboard/components/onboardHeader.tsx, app/layout.tsx, api/(server)/stripe/route.ts Updated imports and logic for new billing and entitlement modules; integrated plan provider.
packages/web/src/lib/schemas.ts Minor formatting change (blank line added).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Server
    participant DB

    User->>UI: Enter search query with context: prefix
    UI->>Server: search({ query: "context:frontend ..." })
    Server->>DB: Lookup search context "frontend"
    DB-->>Server: Return list of repos in context
    Server->>Server: Transform query to reposet:<repo1,repo2,...>
    Server->>DB: Perform search within repo set
    DB-->>Server: Return search results
    Server-->>UI: Return results
    UI-->>User: Display results
Loading

Assessment against linked issues

Objective Addressed Explanation
Support for search contexts: user-defined groupings of repositories, configurable via config, usable in search queries (81)
Ability to filter search results by group/organization, e.g., via context/group filters (141)

Suggested reviewers

  • msukkari

Poem

In fields of code, a rabbit hops,
Through contexts grouped in clever crops.
Now search is sharp, with filters neat,
Find backend, frontend, all complete!
Entitlements guide what you can see,
And billing’s clear as it can be.
With docs and schemas all in sync,
This bunny’s proud—what do you think? 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@brendan-kellam brendan-kellam marked this pull request as ready for review April 24, 2025 23:55
@brendan-kellam brendan-kellam requested a review from msukkari April 24, 2025 23:56
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (1)
packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx (1)

265-277: ⚠️ Potential issue

Missing searchContextSuggestions in dependency list

For the reasons above, please include searchContextSuggestions in the dependency array so React can refresh the memo when context suggestions arrive.

🧹 Nitpick comments (25)
packages/web/src/lib/constants.ts (1)

31-32: Add default single-tenant organization name
The new SINGLE_TENANT_ORG_NAME constant aligns with other single-tenant defaults, improving configurability. Consider whether this value should be configurable via environment variables or a config file in the future for added flexibility.

package.json (1)

7-8: Update workspace command syntax
Switching to yarn workspaces foreach -A run ensures all workspaces (including private ones) execute the build and test scripts. Confirm this change doesn’t introduce dependency ordering issues during build/test. You may also consider adding flags like --topological-run to enforce execution order or --parallel for speed.

ee/LICENSE (1)

1-3: Use consistent ASCII quotation marks
The license text currently uses curly quotes (/), which can cause encoding or parsing inconsistencies. Replace them with straight ASCII double quotes (") to improve portability.

packages/web/src/ee/features/billing/components/checkout.tsx (1)

15-15: Consistent import style for billing actions.

Currently importing createOnboardingSubscription via a relative path ("../actions"), while other EE billing components use absolute imports ("@/ee/features/billing/actions"). For consistency and maintainability, consider switching to the absolute path:

-import { createOnboardingSubscription } from "../actions";
+import { createOnboardingSubscription } from "@/ee/features/billing/actions";
packages/web/src/env.mjs (1)

52-54: New EE license key environment variable.

The addition of SOURCEBOT_EE_LICENSE_KEY is appropriate for the enterprise features being implemented. Making it optional ensures the application can run without it for the open-source version.

It might be helpful to include a brief comment explaining the purpose of this environment variable and how it relates to feature entitlements.

 // EE License
-SOURCEBOT_EE_LICENSE_KEY: z.string().optional(),
+// Used to determine the current plan and entitlements for enterprise features
+SOURCEBOT_EE_LICENSE_KEY: z.string().optional(),
docs/self-hosting/license-key.mdx (1)

1-17: Clear documentation for license key setup

The documentation provides concise instructions for activating a license key and explains the licensing model clearly.

Consider enhancing this documentation with:

  1. A list of specific features that require a license key (e.g., search contexts)
  2. A link to more detailed documentation about enterprise features
  3. Information about how to obtain a license key

This would provide users with a more complete understanding of the enterprise offerings without needing to visit the pricing page.

packages/web/src/features/entitlements/README.md (1)

1-9: Grammar corrections needed in entitlements documentation

The README provides a clear explanation of the entitlements system, but has minor grammatical issues:

  1. Line 8: "a instance" should be "an instance" (vowel sound rule)
  2. Consider adding spacing after paragraphs for better readability
 # Entitlements

 Entitlements control the availability of certain features dependent on the current plan. Entitlements are managed at the **instance** level.

 Some definitions:

 - `Plan`: A plan is a tier of features. Examples: `oss`, `cloud:team`, `self-hosted:enterprise`.
-- `Entitlement`: An entitlement is a feature that is available to a instance. Examples: `search-contexts`, `billing`.
+- `Entitlement`: An entitlement is a feature that is available to an instance. Examples: `search-contexts`, `billing`.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~7-~7: Loose punctuation mark.
Context: ...ce** level. Some definitions: - Plan: A plan is a tier of features. Examples:...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~8-~8: Loose punctuation mark.
Context: ...self-hosted:enterprise. - Entitlement`: An entitlement is a feature that is ava...

(UNLIKELY_OPENING_PUNCTUATION)


[misspelling] ~8-~8: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...ement is a feature that is available to a instance. Examples: search-contexts, ...

(EN_A_VS_AN)

packages/web/src/features/entitlements/planProvider.tsx (1)

22-22: Minor: Remove extra blank line

There's an extra blank line at the end of the file. Consider removing it to maintain consistent file formatting.

};
-
docs/docs/more/syntax-reference.mdx (3)

5-5: Fix verb tense in introduction

There's a verb tense issue in the introduction sentence.

-Sourcebot uses a powerful regex-based query language that enabled precise code search within large codebases.
+Sourcebot uses a powerful regex-based query language that enables precise code search within large codebases.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: This verb may not be in the correct tense. Consider changing the tense to fit the context better.
Context: ...owerful regex-based query language that enabled precise code search within large codeba...

(AI_EN_LECTOR_REPLACEMENT_VERB_TENSE)


30-35: Add missing commas after "By default" phrases

Several instances of "By default" are missing commas, which affects readability.

-Filter results from filepaths that match the regex. By default all files are searched.
+Filter results from filepaths that match the regex. By default, all files are searched.

-Filter results from repos that match the regex. By default all repos are searched.
+Filter results from repos that match the regex. By default, all repos are searched.

-Filter results from a specific branch or tag. By default **only** the default branch is searched.
+Filter results from a specific branch or tag. By default, **only** the default branch is searched.

-Filter results by language (as defined by [linguist](https://github.com/github-linguist/linguist/blob/main/lib/linguist/languages.yml)). By default all languages are searched.
+Filter results by language (as defined by [linguist](https://github.com/github-linguist/linguist/blob/main/lib/linguist/languages.yml)). By default, all languages are searched.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~30-~30: Did you mean: “By default,”?
Context: ...ts from filepaths that match the regex. By default all files are searched. | file:README...

(BY_DEFAULT_COMMA)


[uncategorized] ~30-~30: A determiner appears to be missing. Consider inserting it.
Context: ...r results to filepaths that match regex /README/
file:"my file" - Filter results to filepaths that ma...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~31-~31: Did you mean: “By default,”?
Context: ...esults from repos that match the regex. By default all repos are searched. | repo:linux ...

(BY_DEFAULT_COMMA)


[uncategorized] ~32-~32: Did you mean: “By default,”?
Context: ... results from a specific branch or tag. By default only the default branch is searched...

(BY_DEFAULT_COMMA)


[uncategorized] ~33-~33: Did you mean: “By default,”?
Context: ...blob/main/lib/linguist/languages.yml)). By default all languages are searched. | `lang:Typ...

(BY_DEFAULT_COMMA)


30-30: Add missing article in example description

A determiner/article is missing in one of the examples.

-`file:README` - Filter results to filepaths that match regex `/README/`
+`file:README` - Filter results to filepaths that match the regex `/README/`
🧰 Tools
🪛 LanguageTool

[uncategorized] ~30-~30: Did you mean: “By default,”?
Context: ...ts from filepaths that match the regex. By default all files are searched. | file:README...

(BY_DEFAULT_COMMA)


[uncategorized] ~30-~30: A determiner appears to be missing. Consider inserting it.
Context: ...r results to filepaths that match regex /README/
file:"my file" - Filter results to filepaths that ma...

(AI_EN_LECTOR_MISSING_DETERMINER)

packages/web/src/features/entitlements/server.ts (3)

15-19: Consider adding more specific error handling in the decoding function.

The function correctly decodes and validates the license payload, but it would be beneficial to have more granular error handling to distinguish between different types of failures (JSON parsing errors vs schema validation errors).

 const decodeLicenseKeyPayload = (payload: string) => {
-    const decodedPayload = base64Decode(payload);
-    const payloadJson = JSON.parse(decodedPayload);
-    return eeLicenseKeyPayloadSchema.parse(payloadJson);
+    try {
+        const decodedPayload = base64Decode(payload);
+        try {
+            const payloadJson = JSON.parse(decodedPayload);
+            return eeLicenseKeyPayloadSchema.parse(payloadJson);
+        } catch (error) {
+            throw new Error(`Invalid JSON in license key payload: ${error.message}`);
+        }
+    } catch (error) {
+        throw new Error(`Failed to decode base64 license key payload: ${error.message}`);
+    }
 }

26-27: Use optional chaining as suggested by static analysis.

The static analysis suggests using optional chaining for safer property access.

 const licenseKey = env.SOURCEBOT_EE_LICENSE_KEY;
-if (licenseKey && licenseKey.startsWith(eeLicenseKeyPrefix)) {
+if (licenseKey?.startsWith(eeLicenseKeyPrefix)) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 27-27: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


21-47: Consider implementing caching for plan determination.

The getPlan function performs license validation on every call, which could be inefficient if called frequently. Consider implementing a caching mechanism to avoid redundant computation.

+// Cache the plan to avoid redundant computation
+let cachedPlan: Plan | null = null;
+let planCacheTime = 0;
+const PLAN_CACHE_TTL = 60 * 1000; // 1 minute in ms

 export const getPlan = (): Plan => {
+    // Return cached plan if available and not expired
+    const now = Date.now();
+    if (cachedPlan && now - planCacheTime < PLAN_CACHE_TTL) {
+        return cachedPlan;
+    }
+    
     if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT) {
-        return "cloud:team";
+        cachedPlan = "cloud:team";
+        planCacheTime = now;
+        return cachedPlan;
     }

     const licenseKey = env.SOURCEBOT_EE_LICENSE_KEY;
     if (licenseKey?.startsWith(eeLicenseKeyPrefix)) {
         const payload = licenseKey.substring(eeLicenseKeyPrefix.length);

         try {
             const { expiryDate } = decodeLicenseKeyPayload(payload);

             if (expiryDate && new Date(expiryDate).getTime() < new Date().getTime()) {
                 console.error(`The provided license key has expired. Falling back to oss plan. Please contact ${SOURCEBOT_SUPPORT_EMAIL} for support.`);
-                return "oss";
+                cachedPlan = "oss";
+                planCacheTime = now;
+                return cachedPlan;
             }

-            return "self-hosted:enterprise";
+            cachedPlan = "self-hosted:enterprise";
+            planCacheTime = now;
+            return cachedPlan;
         } catch (error) {
             console.error(`Failed to decode license key payload with error: ${error}`);
             console.info('Falling back to oss plan.');
-            return "oss";
+            cachedPlan = "oss";
+            planCacheTime = now;
+            return cachedPlan;
         }
     }

-    return "oss";
+    cachedPlan = "oss";
+    planCacheTime = now;
+    return cachedPlan;
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 27-27: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

docs/self-hosting/more/search-contexts.mdx (2)

17-17: Minor grammar improvement suggested.

The phrase "inside of" is redundant and could be simplified.

-Search contexts are defined in the `context` object inside of a [declarative config](/self-hosting/more/declarative-config). Repositories can be included / excluded from a search context by specifying the repo's URL in either the `include` array or `exclude` array. Glob patterns are supported.
+Search contexts are defined in the `context` object inside a [declarative config](/self-hosting/more/declarative-config). Repositories can be included / excluded from a search context by specifying the repo's URL in either the `include` array or `exclude` array. Glob patterns are supported.
🧰 Tools
🪛 LanguageTool

[style] ~17-~17: This phrase is redundant. Consider using “inside”.
Context: ...xts are defined in the context object inside of a [declarative config](/self-hosting/mo...

(OUTSIDE_OF)


48-85: Note about JSON comments in the configuration example.

The example configuration contains JavaScript-style comments, but standard JSON doesn't support comments. Since this is documentation for users, it should be clarified that these comments are for documentation purposes only and wouldn't be valid in an actual JSON file.

Add a note before the JSON example:

+> Note: The comments in the example below are for documentation purposes only. Standard JSON does not support comments, so they should be removed in your actual configuration file.
+
 ```json
 {
     "$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json",
schemas/v3/index.json (1)

72-77: Strengthen validation for the include array

A search context without any repository patterns is probably a configuration mistake, yet the schema permits an empty array.
Consider enforcing at least one entry and uniqueness:

 "include": {
   "type": "array",
   "description": "...",
   "items": { "type": "string" },
+  "minItems": 1,
+  "uniqueItems": true,
   "examples": [

You may want the same uniqueItems guard on exclude.

packages/web/src/ee/features/searchContexts/syncSearchContexts.ts (1)

21-42: Consider adding pagination for repository fetching.

The current implementation fetches all repositories at once, which could lead to performance issues with large repository sets. Consider implementing pagination if the organization might have a large number of repositories.

-            const allRepos = await prisma.repo.findMany({
+            // Fetch repositories in batches to handle large repository sets
+            const batchSize = 100;
+            let skip = 0;
+            let allRepos: { id: number; name: string }[] = [];
+            
+            while (true) {
+                const batch = await prisma.repo.findMany({
                     where: {
                         orgId: SINGLE_TENANT_ORG_ID,
                     },
                     select: {
                         id: true,
                         name: true,
                     },
+                    take: batchSize,
+                    skip: skip
                 }
-            });
+                );
+                
+                if (batch.length === 0) break;
+                
+                allRepos = [...allRepos, ...batch];
+                skip += batchSize;
+                
+                if (batch.length < batchSize) break;
+            }
packages/web/src/app/[domain]/settings/billing/page.tsx (1)

1-108: Consider implementing error boundary for improved error handling.

While the current error handling approach works, consider implementing a React Error Boundary component to catch and handle errors more gracefully at the UI level.

// Example implementation in a separate ErrorBoundary component
import { ErrorBoundary } from "@/components/errorBoundary";

// In the parent component that renders BillingPage
<ErrorBoundary 
  fallback={<BillingErrorState />}
>
  <BillingPage params={{ domain }} />
</ErrorBoundary>
packages/web/src/actions.ts (1)

1134-1148: Minor: parenthesis readability makes withAuth’s true flag easy to mis-place

The current multiline layout obscures that true is the second argument to withAuth. A tiny refactor makes the intent obvious:

-export const getSearchContexts = async (domain: string) => sew(() =>
-    withAuth((session) =>
-        withOrgMembership(session, domain, async ({ orgId }) => {
+export const getSearchContexts = async (domain: string) => sew(() =>
+    withAuth(
+        (session) => withOrgMembership(session, domain, async ({ orgId }) => {
             const searchContexts = await prisma.searchContext.findMany({ where: { orgId } });
             return searchContexts.map(({ name, description }) => ({ name, description: description ?? undefined }));
-        }
-    ), /* allowSingleTenantUnauthedAccess = */ true));
+        }),
+        /* allowSingleTenantUnauthedAccess = */ true,
+    ));

No functional change – just avoids future mis-parenthesising.

packages/web/src/ee/features/billing/actions.ts (5)

3-6: Consolidate duplicated imports from @/actions.

The file imports @/actions twice—once for getMe, sew, withAuth and again for withOrgMembership. This incurs an unnecessary module resolution, slightly affects bundle size, and can drift out-of-sync if one list is updated but the other isn’t.

-import { getMe, sew, withAuth } from "@/actions";
-import { withOrgMembership } from "@/actions";
+import { getMe, sew, withAuth, withOrgMembership } from "@/actions";

77-80: Cache or configure the Stripe Price ID instead of listing prices on every request.

stripeClient.prices.list is performed on each subscription/checkout call.
Listing can add 200–300 ms latency per request and is rate-limited by Stripe. Since your product/price are static, prefer:

  1. Store the Price ID in an env var (STRIPE_PRICE_ID) and reference it directly, or
  2. Cache the first call result in a module-level variable (the module is hot-reused in serverless).
-const prices = await stripeClient.prices.list({ product: env.STRIPE_PRODUCT_ID, expand: ['data.product'] });
-const priceId = prices.data[0].id;
+const priceId =
+  env.STRIPE_PRICE_ID
+  ?? (await getCachedPriceId(stripeClient, env.STRIPE_PRODUCT_ID));

(You would implement getCachedPriceId to memoise the lookup.)

Also applies to: 149-152


235-260: Validate the new billing email before sending it to Stripe.

changeSubscriptionBillingEmail forwards newEmail directly to Stripe without syntax or reasonableness checks. A malformed address will be accepted by Stripe’s API but later cause invoice-delivery failures.

Consider:

import isEmail from "validator/lib/isEmail";

if (!isEmail(newEmail)) {
  return {
    statusCode: StatusCodes.BAD_REQUEST,
    errorCode: ErrorCode.INVALID_EMAIL,
    message: "Provided email is not valid",
  } satisfies ServiceError;
}

16-118: Race-condition warning on trial subscription creation.

Between the existence check (getSubscriptionForOrg) and the subsequent stripeClient.subscriptions.create, another request could slip in and create a subscription, leading to duplicate subs and reconciliation headaches.

Consider wrapping the creation in a DB transaction with a pessimistic lock on the org row, or using a unique constraint on orgId inside a subscription table and catching the unique-violation error.


1-280: Add explicit return types for exported server actions.

TypeScript infers the unions of success objects and ServiceError, but explicit signatures improve IDE DX and guard against accidental structural changes.

Example:

export const getSubscriptionInfo = async (
  domain: string
): Promise<{
  status: string;
  plan: string;
  seats: number;
  perSeatPrice: number;
  nextBillingDate: number;
} | ServiceError> => sew(() => 
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c201a5e and ab5ce39.

⛔ Files ignored due to path filters (2)
  • docs/images/search_contexts_example.png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (61)
  • CHANGELOG.md (1 hunks)
  • LICENSE (1 hunks)
  • Makefile (1 hunks)
  • docs/docs.json (3 hunks)
  • docs/docs/more/syntax-reference.mdx (1 hunks)
  • docs/self-hosting/license-key.mdx (1 hunks)
  • docs/self-hosting/more/declarative-config.mdx (1 hunks)
  • docs/self-hosting/more/search-contexts.mdx (1 hunks)
  • ee/LICENSE (1 hunks)
  • package.json (1 hunks)
  • packages/db/prisma/migrations/20250403044104_add_search_contexts/migration.sql (1 hunks)
  • packages/db/prisma/schema.prisma (2 hunks)
  • packages/schemas/src/v3/index.schema.ts (2 hunks)
  • packages/schemas/src/v3/index.type.ts (2 hunks)
  • packages/web/package.json (2 hunks)
  • packages/web/src/actions.ts (6 hunks)
  • packages/web/src/app/[domain]/components/navigationMenu.tsx (2 hunks)
  • packages/web/src/app/[domain]/components/searchBar/constants.ts (2 hunks)
  • packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx (6 hunks)
  • packages/web/src/app/[domain]/components/searchBar/useRefineModeSuggestions.ts (1 hunks)
  • packages/web/src/app/[domain]/components/searchBar/useSuggestionModeAndQuery.ts (2 hunks)
  • packages/web/src/app/[domain]/components/searchBar/useSuggestionModeMappings.ts (1 hunks)
  • packages/web/src/app/[domain]/components/searchBar/useSuggestionsData.ts (6 hunks)
  • packages/web/src/app/[domain]/components/searchBar/zoektLanguageExtension.ts (1 hunks)
  • packages/web/src/app/[domain]/layout.tsx (2 hunks)
  • packages/web/src/app/[domain]/onboard/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/search/page.tsx (3 hunks)
  • packages/web/src/app/[domain]/settings/billing/page.tsx (2 hunks)
  • packages/web/src/app/[domain]/settings/layout.tsx (1 hunks)
  • packages/web/src/app/[domain]/settings/members/page.tsx (1 hunks)
  • packages/web/src/app/[domain]/upgrade/page.tsx (1 hunks)
  • packages/web/src/app/api/(client)/client.ts (2 hunks)
  • packages/web/src/app/api/(server)/stripe/route.ts (1 hunks)
  • packages/web/src/app/components/securityCard.tsx (2 hunks)
  • packages/web/src/app/error.tsx (2 hunks)
  • packages/web/src/app/layout.tsx (2 hunks)
  • packages/web/src/app/login/verify/page.tsx (2 hunks)
  • packages/web/src/app/login/verify/verificationFailed.tsx (2 hunks)
  • packages/web/src/app/onboard/components/onboardHeader.tsx (1 hunks)
  • packages/web/src/ee/features/billing/actions.ts (1 hunks)
  • packages/web/src/ee/features/billing/components/changeBillingEmailCard.tsx (1 hunks)
  • packages/web/src/ee/features/billing/components/checkout.tsx (1 hunks)
  • packages/web/src/ee/features/billing/components/enterpriseUpgradeCard.tsx (2 hunks)
  • packages/web/src/ee/features/billing/components/manageSubscriptionButton.tsx (1 hunks)
  • packages/web/src/ee/features/billing/components/teamUpgradeCard.tsx (1 hunks)
  • packages/web/src/ee/features/billing/serverUtils.ts (1 hunks)
  • packages/web/src/ee/features/billing/stripe.ts (1 hunks)
  • packages/web/src/ee/features/searchContexts/syncSearchContexts.ts (1 hunks)
  • packages/web/src/env.mjs (1 hunks)
  • packages/web/src/features/entitlements/README.md (1 hunks)
  • packages/web/src/features/entitlements/constants.ts (1 hunks)
  • packages/web/src/features/entitlements/planProvider.tsx (1 hunks)
  • packages/web/src/features/entitlements/server.ts (1 hunks)
  • packages/web/src/features/entitlements/useHasEntitlement.ts (1 hunks)
  • packages/web/src/features/entitlements/usePlan.ts (1 hunks)
  • packages/web/src/initialize.ts (4 hunks)
  • packages/web/src/lib/constants.ts (1 hunks)
  • packages/web/src/lib/errorCodes.ts (1 hunks)
  • packages/web/src/lib/schemas.ts (1 hunks)
  • packages/web/src/lib/server/searchService.ts (2 hunks)
  • schemas/v3/index.json (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (18)
packages/web/src/app/login/verify/page.tsx (1)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/app/[domain]/layout.tsx (1)
packages/web/src/ee/features/billing/actions.ts (1)
  • getSubscriptionInfo (262-279)
packages/web/src/features/entitlements/usePlan.ts (1)
packages/web/src/features/entitlements/planProvider.tsx (1)
  • PlanContext (6-6)
packages/web/src/app/login/verify/verificationFailed.tsx (1)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/app/components/securityCard.tsx (1)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/app/error.tsx (1)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/app/[domain]/components/searchBar/useSuggestionModeAndQuery.ts (1)
packages/web/src/app/[domain]/components/searchBar/useSuggestionModeMappings.ts (1)
  • useSuggestionModeMappings (22-104)
packages/web/src/app/api/(client)/client.ts (4)
packages/web/src/lib/server/searchService.ts (1)
  • search (87-146)
packages/web/src/lib/types.ts (2)
  • SearchRequest (7-7)
  • SearchResponse (8-8)
packages/web/src/lib/serviceError.ts (1)
  • ServiceError (11-11)
packages/web/src/lib/utils.ts (1)
  • isServiceError (151-157)
packages/web/src/features/entitlements/planProvider.tsx (1)
packages/web/src/features/entitlements/constants.ts (1)
  • Plan (7-7)
packages/web/src/features/entitlements/useHasEntitlement.ts (2)
packages/web/src/features/entitlements/constants.ts (2)
  • Entitlement (14-14)
  • entitlementsByPlan (16-20)
packages/web/src/features/entitlements/usePlan.ts (1)
  • usePlan (4-7)
packages/web/src/ee/features/billing/components/enterpriseUpgradeCard.tsx (1)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/app/[domain]/components/searchBar/useRefineModeSuggestions.ts (2)
packages/web/src/features/entitlements/useHasEntitlement.ts (1)
  • useHasEntitlement (6-10)
packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx (1)
  • Suggestion (22-27)
packages/web/src/app/[domain]/search/page.tsx (3)
packages/web/src/components/hooks/use-toast.ts (2)
  • useToast (194-194)
  • toast (194-194)
packages/web/src/lib/utils.ts (1)
  • unwrapServiceError (243-250)
packages/web/src/app/api/(client)/client.ts (1)
  • search (8-23)
packages/web/src/app/[domain]/settings/billing/page.tsx (1)
packages/web/src/ee/features/billing/actions.ts (1)
  • getSubscriptionInfo (262-279)
packages/web/src/initialize.ts (3)
packages/schemas/src/v3/index.type.ts (2)
  • ConnectionConfig (7-11)
  • SourcebotConfig (13-28)
packages/web/src/lib/constants.ts (1)
  • SINGLE_TENANT_ORG_ID (29-29)
packages/web/src/ee/features/searchContexts/syncSearchContexts.ts (1)
  • syncSearchContexts (8-111)
packages/web/src/features/entitlements/server.ts (4)
packages/web/src/lib/utils.ts (1)
  • base64Decode (160-163)
packages/web/src/features/entitlements/constants.ts (3)
  • Plan (7-7)
  • Entitlement (14-14)
  • entitlementsByPlan (16-20)
packages/backend/src/env.ts (1)
  • env (22-52)
packages/web/src/lib/constants.ts (1)
  • SOURCEBOT_SUPPORT_EMAIL (33-33)
packages/web/src/actions.ts (2)
packages/web/src/ee/features/billing/serverUtils.ts (3)
  • getSubscriptionForOrg (53-80)
  • incrementOrgSeatCount (9-29)
  • decrementOrgSeatCount (31-51)
packages/web/src/lib/utils.ts (1)
  • isServiceError (151-157)
packages/web/src/ee/features/billing/serverUtils.ts (3)
packages/web/src/ee/features/billing/stripe.ts (1)
  • stripeClient (8-11)
packages/web/src/lib/serviceError.ts (4)
  • stripeClientNotInitialized (123-129)
  • ServiceError (11-11)
  • notFound (91-97)
  • orgInvalidSubscription (107-113)
packages/web/src/lib/utils.ts (1)
  • isServiceError (151-157)
🪛 LanguageTool
packages/web/src/features/entitlements/README.md

[uncategorized] ~7-~7: Loose punctuation mark.
Context: ...ce** level. Some definitions: - Plan: A plan is a tier of features. Examples:...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~8-~8: Loose punctuation mark.
Context: ...self-hosted:enterprise. - Entitlement`: An entitlement is a feature that is ava...

(UNLIKELY_OPENING_PUNCTUATION)


[misspelling] ~8-~8: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...ement is a feature that is available to a instance. Examples: search-contexts, ...

(EN_A_VS_AN)

docs/self-hosting/more/search-contexts.mdx

[style] ~17-~17: This phrase is redundant. Consider using “inside”.
Context: ...xts are defined in the context object inside of a [declarative config](/self-hosting/mo...

(OUTSIDE_OF)

docs/docs/more/syntax-reference.mdx

[uncategorized] ~5-~5: This verb may not be in the correct tense. Consider changing the tense to fit the context better.
Context: ...owerful regex-based query language that enabled precise code search within large codeba...

(AI_EN_LECTOR_REPLACEMENT_VERB_TENSE)


[uncategorized] ~30-~30: Did you mean: “By default,”?
Context: ...ts from filepaths that match the regex. By default all files are searched. | file:README...

(BY_DEFAULT_COMMA)


[uncategorized] ~30-~30: A determiner appears to be missing. Consider inserting it.
Context: ...r results to filepaths that match regex /README/
file:"my file" - Filter results to filepaths that ma...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~31-~31: Did you mean: “By default,”?
Context: ...esults from repos that match the regex. By default all repos are searched. | repo:linux ...

(BY_DEFAULT_COMMA)


[uncategorized] ~32-~32: Did you mean: “By default,”?
Context: ... results from a specific branch or tag. By default only the default branch is searched...

(BY_DEFAULT_COMMA)


[uncategorized] ~33-~33: Did you mean: “By default,”?
Context: ...blob/main/lib/linguist/languages.yml)). By default all languages are searched. | `lang:Typ...

(BY_DEFAULT_COMMA)

🪛 Biome (1.9.4)
packages/web/src/features/entitlements/server.ts

[error] 27-27: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (93)
packages/web/src/lib/constants.ts (1)

33-33: Centralize support email
Introducing SOURCEBOT_SUPPORT_EMAIL replaces hardcoded addresses and ensures consistency across the UI. Please verify that all components reference this constant and remove any remaining hardcoded email strings.

Makefile (1)

36-36: Reset Prisma migrations in soft-reset target
Adding yarn dev:prisma:migrate:reset ensures the database schema is correctly reset after clearing data. Verify that the dev:prisma:migrate:reset script exists in package.json and that it performs as intended.

packages/web/src/app/api/(server)/stripe/route.ts (1)

6-6: Refactor billing import path
The stripeClient import now points to the EE billing module, aligning with the billing feature refactor. Ensure the exported stripeClient in @/ee/features/billing/stripe initializes correctly with the proper API keys and that no residual imports of the old path remain.

packages/web/src/app/onboard/components/onboardHeader.tsx (1)

3-3: Update import to new billing module path.

The IS_BILLING_ENABLED flag import has been correctly updated to reference the enterprise edition billing module (@/ee/features/billing/stripe). This aligns with the recent billing feature refactor. No further changes needed.

packages/web/src/ee/features/billing/components/changeBillingEmailCard.tsx (1)

8-8: Import billing action from EE feature directory.

The changeSubscriptionBillingEmail function import has been updated to the enterprise edition billing actions module (@/ee/features/billing/actions). This centralizes billing logic as intended. Looks good.

packages/web/src/app/[domain]/settings/members/page.tsx (1)

10-10: Align billing flag import with EE billing module.

The IS_BILLING_ENABLED import was correctly updated to pull from @/ee/features/billing/stripe, matching other components. No additional adjustments are required.

packages/web/src/lib/schemas.ts (1)

5-5: No-op formatting change.

A single blank line was added after the import statement. There are no functional or logical changes in this segment.

packages/web/src/ee/features/billing/components/manageSubscriptionButton.tsx (1)

11-11: Import path refactoring looks good

The updated import path for getCustomerPortalSessionLink properly aligns with the restructuring of billing functionality into the dedicated Enterprise Edition module. This change supports the PR objective of organizing billing-related code under the commercial license.

packages/web/src/ee/features/billing/components/teamUpgradeCard.tsx (1)

11-11: Relative import path is appropriate

The change from an absolute import to a relative import for createStripeCheckoutSession is consistent with the billing code restructuring. Both the component and the imported actions now reside within the same billing feature module, making this relative path a logical choice.

packages/web/src/app/[domain]/settings/layout.tsx (1)

5-5: Import path properly updated for billing feature

The updated import path for IS_BILLING_ENABLED correctly reflects the movement of billing-related functionality to the Enterprise Edition module. This maintains consistency with the overall restructuring of billing features under the commercial license.

packages/web/src/lib/errorCodes.ts (1)

25-25: New error code supports search contexts feature

The addition of the SEARCH_CONTEXT_NOT_FOUND error code is appropriate for the new search contexts feature. This error code follows the established naming pattern and will be useful for handling cases where a user tries to search using a non-existent context.

docs/self-hosting/more/declarative-config.mdx (1)

6-8: Well-placed warning about multi-tenancy limitations.

The warning clearly informs users about a limitation of declarative configuration with multi-tenancy mode. This is an important clarification that helps users understand the constraints of the feature.

packages/web/src/app/login/verify/verificationFailed.tsx (1)

7-7: Good refactoring to use a centralized support email constant.

Centralizing the support email address in constants.ts improves maintainability. If the support email changes in the future, it can be updated in a single location.

Also applies to: 38-38

packages/web/src/app/error.tsx (1)

12-12: Good refactoring to use a centralized support email constant.

This change ensures consistency across the application by using the centralized SOURCEBOT_SUPPORT_EMAIL constant instead of hardcoding the email address. This makes future maintenance easier.

Also applies to: 80-80

CHANGELOG.md (1)

8-12: Good documentation of the new search contexts feature.

The changelog entry appropriately documents the addition of search contexts as a new enterprise feature, with a clear description of what the feature does and a link to the related PR.

packages/web/src/app/[domain]/components/searchBar/zoektLanguageExtension.ts (1)

50-50: Correctly updated tokenizer to recognize the new context prefix.

The change properly adds the context: prefix to the regular expression that identifies keywords in the search query language. This ensures syntax highlighting works correctly with the new search contexts feature.

packages/web/src/app/components/securityCard.tsx (2)

6-6: Good practice: Centralizing constants.

Adding the import for SOURCEBOT_SUPPORT_EMAIL from a central constants file is a good practice for maintainability.


66-66: Consistent usage of support email constant.

Replacing the hardcoded email address with the centralized constant improves maintainability. If the support email needs to change in the future, it can be updated in a single location.

packages/web/package.json (2)

116-116: Appropriate dependency for glob pattern matching.

Adding the micromatch library is suitable for implementing repository filtering with glob patterns in search contexts.


141-141: Type definitions for micromatch.

Including the TypeScript type definitions for micromatch ensures proper type checking and IDE support.

packages/web/src/ee/features/billing/components/enterpriseUpgradeCard.tsx (2)

3-3: Good practice: Centralizing constants.

Adding the import for SOURCEBOT_SUPPORT_EMAIL is consistent with the pattern applied in other components.


17-17: Consistent usage of support email constant while preserving subject.

Replacing the hardcoded email address with the centralized constant while maintaining the email subject is a good refactoring. This ensures consistent contact information across the application.

packages/web/src/app/login/verify/page.tsx (1)

16-16: Good: Using centralized constants for support email

The change improves maintainability by replacing the hardcoded email address with a centralized constant from @/lib/constants. This ensures consistency across the application and makes future updates easier.

Also applies to: 93-93

packages/web/src/features/entitlements/usePlan.ts (1)

1-7: Well-implemented React hook following best practices

This custom hook follows React best practices for context consumption and provides a clean API for components to access the current plan. The hook is concise and has a single responsibility, making it easy to understand and test.

packages/web/src/app/[domain]/onboard/page.tsx (1)

8-8: Good refactoring of billing import paths

The updated import paths correctly reflect the architectural change of moving billing-related code under the enterprise edition (ee) directory structure. This aligns with the goal of separating open-source and commercial features.

Also applies to: 10-10

packages/web/src/app/[domain]/layout.tsx (1)

13-13: Refactored billing code to EE module

The changes have correctly relocated billing-related code to the Enterprise Edition module. The getSubscriptionInfo function replaces fetchSubscription with equivalent functionality but proper organization within the EE feature structure.

Also applies to: 16-16, 62-62

packages/web/src/app/layout.tsx (1)

10-11: Proper integration of the plan provider system

The addition of PlanProvider at the root layout level effectively enables plan-based feature gating throughout the application. The component is correctly positioned in the provider tree to make plan information available to all child components.

Also applies to: 32-47

packages/web/src/ee/features/billing/stripe.ts (1)

4-4: Successfully gated billing behind entitlements

The implementation now correctly gates billing features behind both the presence of a Stripe secret key AND the 'billing' entitlement. This aligns with the enterprise feature strategy described in the PR objectives.

Also applies to: 6-6

packages/web/src/features/entitlements/useHasEntitlement.ts (1)

1-10: Well-structured hook for feature entitlement checks

This hook is cleanly implemented and follows React best practices. It effectively checks if a user's current plan includes a specific entitlement by:

  1. Getting the current plan via the usePlan hook
  2. Looking up available entitlements for that plan
  3. Checking if the requested entitlement is included

This implementation aligns with the enterprise feature gating described in the PR.

LICENSE (1)

1-7: License update properly reflects the dual-licensing model

This license update clearly establishes the dual-licensing model for the project:

  • Enterprise code in "ee/" and "packages/web/src/ee/" directories is under the enterprise license
  • Third-party components retain their original licenses
  • All other content remains under the MIT license

This aligns with the PR objectives of introducing enterprise features while maintaining an open-core model.

packages/web/src/app/[domain]/components/navigationMenu.tsx (2)

14-16: Updated imports align with relocated billing functionality

These import changes correctly reference the relocated billing-related code that has been moved to the enterprise edition directory structure.


28-28: Updated subscription retrieval logic using the new function

The subscription retrieval logic has been properly updated to use the new getSubscriptionInfo function from the enterprise edition directory, while maintaining the same conditional behavior based on billing enablement.

packages/web/src/features/entitlements/planProvider.tsx (1)

1-21: Well-implemented context provider for plan-based feature gating

The PlanContext and PlanProvider provide a clean implementation for sharing the current plan throughout the application. This is essential for the entitlements system to gate enterprise features like search contexts.

The implementation follows React best practices for context providers and consumers.

packages/web/src/app/[domain]/components/searchBar/useSuggestionModeAndQuery.ts (2)

5-5: Updated import to use dynamic suggestion mappings

The code now imports the useSuggestionModeMappings hook instead of using static mappings, which will enable entitlement-based suggestion modes.


21-22: Dynamic suggestion mappings based on user entitlements

The suggestion mode mappings are now obtained from the useSuggestionModeMappings hook which dynamically includes the "context" suggestion mode based on user entitlements. This properly gates the search contexts feature behind entitlements.

-    const suggestionModeMappings = ...
+    const suggestionModeMappings = useSuggestionModeMappings();
packages/web/src/app/[domain]/search/page.tsx (4)

13-13: Good addition of utility imports for error handling

Added imports for unwrapServiceError and useToast to support the new error handling functionality.

Also applies to: 25-25


48-48: Destructuring toast from useToast hook

Properly added toast functionality to display error messages.


50-63: Enhanced error handling in search query

The query function now properly handles service errors by:

  1. Using unwrapServiceError to throw errors instead of returning them
  2. Disabling automatic retries with retry: false to prevent repeating failed searches
  3. Capturing the error object for use in notifications

This improves error handling for the new search contexts feature.


65-71: Added user-friendly error notifications

Added an effect to display toast notifications when search queries fail, providing clear feedback to users. This is especially important for enterprise features like search contexts that might return specific errors.

packages/web/src/app/api/(client)/client.ts (3)

4-6: Added necessary imports for error handling

Added imports for the ServiceError type and the isServiceError utility function to support the error handling changes.


8-8: Updated return type to handle service errors

The function signature now correctly indicates that it can return either a SearchResponse or a ServiceError, making the error handling more explicit and type-safe.


18-20: Added explicit handling of service errors

The function now checks if the result is a ServiceError before attempting to parse it, preventing potential runtime errors when service errors occur. This is particularly important for features like search contexts that might return specific error types.

docs/docs.json (3)

42-42: Added syntax reference documentation

Added link to new syntax reference documentation, helping users understand the query language capabilities including the new search contexts feature.


56-57: Added license key documentation

Added documentation for license key activation, which is essential for users who want to use Enterprise Edition features like search contexts.


66-67: Added search contexts documentation

Added dedicated documentation for the new search contexts feature, ensuring users can understand how to configure and use this Enterprise Edition capability.

packages/web/src/app/[domain]/components/searchBar/useRefineModeSuggestions.ts (5)

1-7: Clean imports and client directive setup.

The imports are well-organized and the client directive is correctly placed at the top of the file. The inclusion of the useHasEntitlement hook will be used to conditionally render the search contexts feature based on user entitlements.


8-10: Simple helper function for negating search prefixes.

This utility function provides a clean way to generate negated search prefixes, promoting code reuse throughout the component.


12-14: Feature flag check for search contexts.

Good use of the entitlement system to conditionally enable the search contexts feature based on user plan.


15-28: Conditional rendering of search context suggestions.

The implementation correctly uses the feature flag to conditionally include search context suggestions in the returned array. The spread operator with a conditional array is a clean pattern for this use case.


97-101: Properly memoized suggestions with correct dependency.

The useMemo dependency array correctly includes isSearchContextsEnabled, ensuring the suggestions are recalculated if the entitlement status changes.

packages/web/src/app/[domain]/upgrade/page.tsx (3)

4-5: Updated import paths for billing components.

The imports have been updated from relative to absolute paths, which is consistent with the PR objective of moving billing-related code to the enterprise edition.


12-13: Updated import paths for billing utilities.

These imports have been updated to reflect the new location of billing-related code in the enterprise edition, consistent with the PR objectives.


20-20: Updated function call to enterprise billing action.

The function call has been updated to use the new enterprise-specific billing action.

packages/web/src/features/entitlements/constants.ts (3)

2-7: Well-defined plan types and labels.

The plan labels are clearly defined with descriptive names for each plan type. The use of as const ensures type safety by preserving the literal types.


10-14: Clear definition of available entitlements.

The entitlements are defined as a constant array with descriptive names. The TypeScript type definition using indexed access types ensures type safety for entitlement references throughout the codebase.


16-20: Logical entitlement mapping by plan.

The entitlements are logically mapped to each plan type:

  • OSS plan has no premium features
  • Cloud team plan includes billing features
  • Self-hosted enterprise plan includes search contexts

This aligns with the PR objective of introducing search contexts as an enterprise feature.

packages/web/src/app/[domain]/components/searchBar/useSuggestionsData.ts (7)

6-6: Added import for search contexts data fetching.

The import for getSearchContexts is correctly added to support the new search contexts feature.


22-22: Updated utility imports.

The import now explicitly includes isServiceError which is used for error handling in the queries.


60-63: Improved error handling for file suggestions.

Added proper error handling for file suggestions query to gracefully handle service errors.


79-82: Improved error handling for symbol suggestions.

Added proper error handling for symbol suggestions query to gracefully handle service errors.


101-117: Well-implemented search contexts suggestion fetching.

The implementation of search context suggestions follows the established pattern in the file:

  1. Uses useQuery with appropriate query key
  2. Provides error handling for service errors
  3. Transforms fetched data into the expected suggestion format
  4. Only enabled when the suggestion mode is "context"
  5. Properly tracks loading state

This ensures consistent behavior with other suggestion types.


146-147: Updated loading state to include search contexts.

The loading state is correctly updated to include the search contexts loading state, ensuring the UI properly shows loading indicators when fetching context suggestions.


153-153: Added search contexts to returned suggestions.

The return object now includes the search context suggestions, making them available to the component that uses this hook.

packages/db/prisma/schema.prisma (2)

69-80: Well-designed search context schema model

The new SearchContext model is well-structured with appropriate fields and relationships. The unique constraint on [name, orgId] ensures that context names are unique within an organization.


64-64: Appropriate bidirectional relationships

The bidirectional relationships between SearchContext and both Repo and Org models are correctly established, enabling efficient querying in both directions.

Also applies to: 157-157

packages/schemas/src/v3/index.type.ts (2)

16-21: Good implementation of contexts configuration

The contexts property is properly documented as an Enterprise Edition feature with a link to the documentation. The dictionary pattern with string keys mapping to SearchContext objects is appropriate for configuration flexibility.


81-103: Well-defined SearchContext interface

The SearchContext interface has clear, required include and optional exclude arrays for repository patterns, with good documentation that explains the expected format without protocol prefixes and support for glob patterns.

packages/web/src/app/[domain]/components/searchBar/useSuggestionModeMappings.ts (2)

22-104: Great implementation of feature-gated suggestion mappings

The useSuggestionModeMappings hook properly utilizes useMemo for performance optimization and implements feature-gating through the useHasEntitlement hook. The conditional inclusion of search context mappings ensures the UI only shows available features.


91-99: Correct TypeScript pattern for conditional inclusion

The use of the spread operator with a conditional array and the satisfies operator ensures type safety while conditionally including the search contexts feature when entitled.

docs/docs/more/syntax-reference.mdx (1)

34-35: Excellent documentation of the new context prefix

The documentation for the context: prefix is clear and concise, with helpful examples showing both positive and negative filtering. The link to the detailed documentation is appropriate.

packages/web/src/features/entitlements/server.ts (3)

1-5: LGTM! Imports are appropriate for the license/entitlement system.

The imports bring in all necessary dependencies including environment variables, type definitions, utilities for decoding, validation schema support, and constants for error messaging.


9-13: Well-structured schema definition for license key validation.

The schema properly defines the required fields (id) and optional fields (expiryDate) with appropriate validation (datetime format for expiry). This provides a solid foundation for license key validation.


49-53: LGTM! Entitlement check logic is clear and concise.

The function correctly obtains the current plan and checks if the requested entitlement is included in the plan's entitlements.

docs/self-hosting/more/search-contexts.mdx (4)

1-9: Well-structured enterprise feature documentation with clear license note.

The documentation properly identifies this as an Enterprise Edition feature and links to license key information, making it clear to users that this functionality requires enterprise licensing.


10-15: Great examples demonstrating search context usage.

The examples provide clear, practical use cases showing how to utilize search contexts in queries, including combined contexts with logical operators. This helps users understand the feature's capabilities.


87-92: Helpful accordion explanation of repository URL format.

The accordion section clearly explains how repository URLs should be formatted, with good examples for different Git hosts. This prevents potential user confusion about URL formatting.


102-106: Good explanation of advanced query options.

The documentation nicely explains how to combine or negate contexts in search queries and provides a link to more detailed syntax information. This helps users leverage the feature's full capabilities.

packages/web/src/app/[domain]/components/searchBar/constants.ts (1)

7-23: Clean addition of the new context search prefix.

The context prefix has been correctly added to the SearchPrefix enum, enabling search context functionality in the search bar. This aligns with the documentation and other parts of the implementation.

packages/schemas/src/v3/index.schema.ts (2)

69-108: Well-defined schema for the SearchContext type.

The schema properly defines:

  1. Required fields (include) for repositories to include in the context
  2. Optional fields (exclude, description) with clear descriptions
  3. Appropriate property types and good examples

The schema aligns perfectly with the documentation and provides a strong foundation for validating search context configurations.


117-126: Well-structured top-level contexts property in the schema.

The contexts property is:

  1. Properly marked as a Sourcebot EE feature
  2. Limited to valid context names via regex pattern
  3. References the SearchContext definition
  4. Includes appropriate documentation link

This integration enables the search contexts feature while maintaining schema validation.

packages/web/src/ee/features/searchContexts/syncSearchContexts.ts (2)

8-19: Well-implemented entitlement check and error handling.

The function correctly validates the environment and entitlement requirements before proceeding with synchronization. The error message is detailed and includes helpful information about the required plan and support contact.


56-90: 🛠️ Refactor suggestion

Consider wrapping operations in a transaction.

The database operations aren't wrapped in a transaction, which could lead to an inconsistent state if an error occurs mid-operation. Wrapping related operations in a transaction would ensure atomicity.

+            // Wrap in a transaction for atomicity
+            await prisma.$transaction(async (tx) => {
-            await prisma.searchContext.upsert({
+                await tx.searchContext.upsert({
                    where: {
                        name_orgId: {
                            name: key,
                            orgId: SINGLE_TENANT_ORG_ID,
                        }
                    },
                    // ... rest of the upsert operation
                });
+            });

Likely an incorrect or invalid review comment.

packages/web/src/app/[domain]/settings/billing/page.tsx (2)

3-6: Clean import refactoring to use enterprise edition features.

The imports have been updated to correctly reference the billing components and actions from the enterprise edition directory structure.


31-31: Function update aligns with the new billing architecture.

The change from getSubscriptionData to getSubscriptionInfo maintains the same interface and functionality while using the refactored billing implementation.

packages/db/prisma/migrations/20250403044104_add_search_contexts/migration.sql (4)

1-9: Well-structured SearchContext table creation.

The table structure is clean with appropriate column types and constraints. The primary key is properly defined.


11-17: Good implementation of the many-to-many relationship join table.

The join table _RepoToSearchContext correctly implements a many-to-many relationship with a composite primary key.


19-23: Appropriate indexing for performance.

The unique index on (name, orgId) ensures that context names are unique within an organization, and the index on the join table will improve query performance.


25-32: Correct foreign key constraints with appropriate cascade behavior.

The foreign key constraints are well-defined with CASCADE for both DELETE and UPDATE operations, ensuring referential integrity as organizations or repositories are modified or removed.

packages/web/src/lib/server/searchService.ts (4)

32-32: Good addition of reposet to zoektPrefixes enum.

Adding the reposet prefix to the enum ensures consistency with other prefixes and maintains the documentation of supported prefixes.


35-85: Well-implemented query transformation with good error handling.

The transformZoektQuery function logically handles different query prefixes and provides good error handling when a search context isn't found. The transformation of context: into reposet: is a clean implementation.

However, consider adding caching for context lookups to improve performance with repeated queries:

+ // Add a simple in-memory cache with TTL
+ const contextCache = new Map<string, { repos: string[], timestamp: number }>();
+ const CACHE_TTL_MS = 60 * 1000; // 1 minute cache

  const transformZoektQuery = async (query: string, orgId: number): Promise<string | ServiceError> => {
      // ...

      // Inside the context: handling section
      else if (part.match(/^-?context:.+$/)) {
          const isNegated = part.startsWith("-");
          const contextName = part.slice(part.indexOf(":") + 1);
          
+         // Check cache first
+         const cacheKey = `${orgId}:${contextName}`;
+         const cachedContext = contextCache.get(cacheKey);
+         
+         if (cachedContext && (Date.now() - cachedContext.timestamp) < CACHE_TTL_MS) {
+             newQueryParts.push(`${isNegated ? "-" : ""}${zoektPrefixes.reposet}${cachedContext.repos.join(",")}`);
+             continue;
+         }

          const context = await prisma.searchContext.findUnique({
              // ...
          });

          // If the context doesn't exist, return an error.
          if (!context) {
              return {
                  errorCode: ErrorCode.SEARCH_CONTEXT_NOT_FOUND,
                  message: `Search context "${contextName}" not found`,
                  statusCode: StatusCodes.NOT_FOUND,
              } satisfies ServiceError;
          }

          const names = context.repos.map((repo) => repo.name);
+         // Update cache
+         contextCache.set(cacheKey, { repos: names, timestamp: Date.now() });
          
          newQueryParts.push(`${isNegated ? "-" : ""}${zoektPrefixes.reposet}${names.join(",")}`);
      }

87-92: Good integration of query transformation in the search function.

The search function now correctly integrates the transformation and handles any errors before proceeding with the search. The previous functionality is preserved while adding the new capabilities.


35-85:

❓ Verification inconclusive

Add handling for nested query structures.

The current implementation splits queries on spaces, which won't correctly handle parenthesized expressions or quoted strings. Consider using a more robust parsing approach for complex queries.


🏁 Script executed:

#!/bin/bash
# Verify if there are any nested or parenthesized queries using context: in codebase examples

# Look for examples of complex context: queries with parentheses or quotes
rg -i "context:.+(\(.+\)|\".*\")" --glob "*.{ts,tsx,js,jsx,md,mdx}"

# Check if any usage examples show nested query structures
rg -i "example.*context:.+(and|or|not)" --glob "*.{md,mdx}"

Length of output: 388


Handle nested query structures in transformZoektQuery

The current implementation simply does query.split(" "), which will break on any quoted terms (e.g. "foo bar") or parenthesized groups (e.g. (A OR B)). We didn’t find any instances of nested or quoted context: atoms in the docs or code, but if your search grammar supports grouping or quotes, this splitter will mis‐tokenize those queries.

• Verify whether your UI or Zoekt query syntax allows quoted strings or Boolean/grouped expressions.
• If so, replace the ad-hoc .split(" ") plus regex logic with a proper query‐AST parser (or reuse Sourcegraph’s existing query parser) so that you only rewrite the rev:, revision:, and context: atoms without losing nested structure.
• Update tests to cover cases like:

  • context:"my context"
  • (repo:foo OR repo:bar)
  • -"complex term"
packages/web/src/ee/features/billing/actions.ts (1)

121-181: Confirm whether any member should be allowed to initiate billing flows.

createStripeCheckoutSession is guarded only by “is a member of org”, whereas sensitive actions (createOnboardingSubscription, changeSubscriptionBillingEmail) require OrgRole.OWNER.
If the intent is to restrict checkout to owners as well, add the role requirement:

-withOrgMembership(session, domain, async ({ orgId }) => {
+withOrgMembership(session, domain, async ({ orgId }) => {-}, /* minRequiredRole = */ undefined)
+}, /* minRequiredRole = */ OrgRole.OWNER)

Please verify that the current behaviour matches product requirements.

msukkari
msukkari previously approved these changes Apr 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
docs/self-hosting/more/search-contexts.mdx (1)

21-29: Clarify group terminology
Nit: clarify that web, backend, and shared refer to GitLab group namespaces rather than filesystem folders.

🧹 Nitpick comments (6)
packages/web/src/actions.ts (1)

1138-1152: Well-implemented search contexts feature

The new getSearchContexts function follows the established patterns in the codebase:

  1. Uses the sew wrapper for consistent error handling
  2. Properly implements authentication and authorization
  3. Allows single-tenant unauthenticated access, consistent with other query functions

Consider adding pagination if there could be a large number of search contexts per organization.

export const getSearchContexts = async (domain: string, options?: { limit?: number; offset?: number }) => sew(() =>
    withAuth((session) =>
        withOrgMembership(session, domain, async ({ orgId }) => {
            const searchContexts = await prisma.searchContext.findMany({
                where: {
                    orgId,
                },
+               skip: options?.offset || 0,
+               take: options?.limit || 100,
            });

            return {
+               searchContexts: searchContexts.map((context) => ({
                    name: context.name,
                    description: context.description ?? undefined,
                })),
+               totalCount: await prisma.searchContext.count({
+                   where: { orgId }
+               })
            };
        }
    ), /* allowSingleTenantUnauthedAccess = */ true));
docs/self-hosting/more/search-contexts.mdx (5)

1-4: Enhance frontmatter metadata
Consider adding a description field to the frontmatter for better SEO, accessibility, and documentation navigation.


6-8: Use a consistent admonition component
To align with other MDX patterns, consider replacing <Note> with the standardized <Callout> or <Alert> component (if available) for improved styling and clarity.


17-18: Improve phrasing
Replace “inside of a [declarative config]” with “in a [declarative config]” to remove redundancy.

🧰 Tools
🪛 LanguageTool

[style] ~17-~17: This phrase is redundant. Consider using “inside”.
Context: ...xts are defined in the context object inside of a [declarative config](/self-hosting/mo...

(OUTSIDE_OF)


48-85: Annotate JSON example
The inline comments (//) improve readability but aren’t valid JSON syntax. Consider indicating that this is illustrative JSONC or remove comments from the actual config snippet.


100-100: Add descriptive alt text
Replace ![Example] with a more descriptive alt text, e.g., ![Search contexts example illustrating context usage], to improve accessibility.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab5ce39 and 5d68451.

📒 Files selected for processing (5)
  • docs/self-hosting/license-key.mdx (1 hunks)
  • docs/self-hosting/more/search-contexts.mdx (1 hunks)
  • packages/web/src/actions.ts (6 hunks)
  • packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx (7 hunks)
  • vendor/zoekt (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • vendor/zoekt
  • docs/self-hosting/license-key.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/app/[domain]/components/searchBar/searchSuggestionsBox.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/web/src/actions.ts (3)
packages/web/src/ee/features/billing/serverUtils.ts (3)
  • getSubscriptionForOrg (53-80)
  • incrementOrgSeatCount (9-29)
  • decrementOrgSeatCount (31-51)
packages/web/src/ee/features/billing/stripe.ts (1)
  • IS_BILLING_ENABLED (6-6)
packages/web/src/lib/utils.ts (1)
  • isServiceError (151-157)
🪛 LanguageTool
docs/self-hosting/more/search-contexts.mdx

[style] ~17-~17: This phrase is redundant. Consider using “inside”.
Context: ...xts are defined in the context object inside of a [declarative config](/self-hosting/mo...

(OUTSIDE_OF)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (12)
packages/web/src/actions.ts (5)

29-29: Good refactoring of billing-related imports

The code now properly centralizes and imports billing utility functions from the enterprise features directory, improving modularity and maintainability.


232-233: Clean abstraction of subscription fetching

Replacing direct Stripe client calls with the centralized getSubscriptionForOrg utility function improves code organization and maintainability.


847-852: Fixed transaction handling for seat increment

The code now properly uses transactions with throw instead of return for error handling, addressing the previous race condition issue where Stripe seat count could be incremented without adding the member.


1004-1020: Fixed seat-decrement and membership removal transaction

This implementation properly addresses the past review comment by keeping seat-decrement and membership removal within the same transaction. Using throw for errors ensures proper transaction rollback.


1049-1065: Consistent transaction handling in leaveOrg

The same pattern of transaction handling is correctly applied here as in removeMemberFromOrg, ensuring atomicity between database operations and billing updates.

docs/self-hosting/more/search-contexts.mdx (7)

10-15: Clear definition and example queries
The introduction and sample queries effectively demonstrate how search contexts work.


23-40: Illustrate repository structure clearly
The shell tree output provides a helpful visual, and using the tree format is intuitive for readers.


42-47: Context list is clear
Listing each context (web, backend, pipelines) with brief descriptions helps readers understand how they map to repo groupings.


87-92: Good use of Accordion
The <Accordion> around repository URL details helps keep the guide concise while providing deeper context on demand.


95-98: Strong usage examples
The bullet points effectively demonstrate how to apply contexts in search bar queries.


102-105: Clear explanation of negation and combinations
The examples for excluding contexts and combining them with or accurately show advanced query usage.


108-154: Comprehensive schema reference
The JSON schema reference is thorough and aligns well with the code changes.

@brendan-kellam brendan-kellam merged commit cfe8b8c into main Apr 25, 2025
5 checks passed
@brendan-kellam brendan-kellam deleted the feat/search-contexts branch April 25, 2025 05:28
@coderabbitai coderabbitai bot mentioned this pull request May 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature request] Be able to filter by group/organization Support for search contexts
2 participants