Skip to content

Comments

Experiments#451

Merged
Blaumaus merged 15 commits intoswetrix-revampfrom
experiments
Dec 14, 2025
Merged

Experiments#451
Blaumaus merged 15 commits intoswetrix-revampfrom
experiments

Conversation

@Blaumaus
Copy link
Member

@Blaumaus Blaumaus commented Dec 11, 2025

Changes

If applicable, please describe what changes were made in this pull request.

Community Edition support

  • Your feature is implemented for the Swetrix Community Edition
  • This PR only updates the Cloud (Enterprise) Edition code (e.g. Paddle webhooks, blog, payouts, etc.)

Database migrations

  • Clickhouse / MySQL migrations added for this PR
  • No table schemas changed in this PR

Documentation

  • You have updated the documentation according to your PR
  • This PR did not change any publicly documented endpoints

Summary by CodeRabbit

Release Notes

  • New Features

    • Added A/B testing experiments with variant management, rollout control, and Bayesian statistical analysis
    • Integrated feature flag support for experiments with automatic flag creation/linking
    • Added experiment lifecycle management (draft, running, paused, completed states)
    • Added real-time results visualization with win probability charts and conversion metrics
    • Added goal linking and exposure criteria configuration for experiments
  • Improvements

    • Enhanced project navigation with dedicated experiments tab
    • Improved header UI across analytics dashboards with new DashboardHeader component
  • Chores

    • Removed SDK dependency; updated translations for experiment functionality

✏️ Tip: You can customize this high-level summary in your review settings.

@Blaumaus Blaumaus self-assigned this Dec 11, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR introduces comprehensive A/B testing (Experiments) functionality across the full stack. The backend adds a new NestJS Experiment module with entities, DTOs, service, and controller supporting experiment lifecycle management and Bayesian statistics calculation. Feature flags integrate with experiments to manage variant assignment. The frontend provides views for creating, managing, and analyzing experiments with results visualization. Supporting database migrations and localization are included.

Changes

Cohort / File(s) Summary
Backend: Experiment Module Core
backend/apps/cloud/src/experiment/experiment.module.ts, backend/apps/cloud/src/experiment/experiment.service.ts, backend/apps/cloud/src/experiment/experiment.controller.ts
New NestJS module with service providing CRUD and pagination for experiments, and controller exposing lifecycle endpoints (create, start, pause, complete, delete) with access control, validation, and feature flag integration
Backend: Experiment Domain Models
backend/apps/cloud/src/experiment/entity/experiment.entity.ts, backend/apps/cloud/src/experiment/entity/experiment-variant.entity.ts, backend/apps/cloud/src/experiment/dto/experiment.dto.ts
Entity definitions with enums (ExperimentStatus, ExposureTrigger, MultipleVariantHandling, FeatureFlagMode) and comprehensive DTOs for request/response handling with validation
Backend: Bayesian Statistics
backend/apps/cloud/src/experiment/bayesian.ts
Self-contained Bayesian A/B testing module using seeded PRNG and Beta-Gamma sampling to compute Monte Carlo-based variant win probabilities
Backend: Feature Flag Integration
backend/apps/cloud/src/feature-flag/evaluation.ts, backend/apps/cloud/src/feature-flag/feature-flag.controller.ts, backend/apps/cloud/src/feature-flag/feature-flag.module.ts, backend/apps/cloud/src/feature-flag/entity/feature-flag.entity.ts
Added experiment variant evaluation function, controller support for experiment exposure tracking, module circular dependency handling, and experimentId field
Backend: App Wiring & Relationships
backend/apps/cloud/src/app.module.ts, backend/apps/cloud/src/project/entity/project.entity.ts
ExperimentModule imported into AppModule; Project entity extended with one-to-many Experiment relation
Backend: Utilities & Deprecations
backend/apps/cloud/src/feature-flag/dto/feature-flag.dto.ts, backend/apps/cloud/src/feature-flag/feature-flag.service.ts, backend/apps/community/src/feature-flag/*
Unexported VALID_TARGETING_COLUMNS and EvaluatableFeatureFlag; removed ILike import; community app mirrored changes
Database Migrations
backend/migrations/mysql/2025_12_11_experiments.sql, backend/migrations/clickhouse/2025_12_11_experiments.js
MySQL tables for experiments and variants with relationships; ClickHouse table for tracking experiment exposures with TTL
Frontend: Experiment Views
web/app/pages/Project/Experiments/View/ExperimentsView.tsx, web/app/pages/Project/Experiments/View/ExperimentResults.tsx, web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx, web/app/pages/Project/Experiments/View/index.tsx
New experiment listing, results visualization with Bayesian statistics charts, and modal for creating/editing experiments with variant management
Frontend: API Client & Types
web/app/api/index.ts
New experiment types (ExperimentStatus, ExposureTrigger, MultipleVariantHandling, FeatureFlagMode), interfaces, and API functions for all experiment operations; removed exports for error-related types
Frontend: UI Infrastructure
web/app/pages/Project/View/components/DashboardHeader.tsx, web/app/pages/Project/View/components/BackButton.tsx, web/app/pages/Project/View/components/ProjectSidebar.tsx
DashboardHeader and BackButton components with navigation, time-bucket selection, and mobile sidebar support with MobileSidebarTrigger
Frontend: View Refactors
web/app/pages/Project/**/View/*.tsx (Alerts, Errors, Goals, Performance, Sessions, Funnels, Traffic, Captcha, Profiles)
Integrated DashboardHeader and LoadingBar for consistent header/loading UI; removed legacy components and adjusted state management for new common patterns
Frontend: Component Cleanup
web/app/pages/Project/View/Panels.tsx, web/app/pages/Project/View/components/Errors.tsx, web/app/pages/Project/View/components/SessionDetails.tsx, web/app/components/DangerouslySetInnerHTML.tsx
Removed ExtensionErrorBoundary, CustomTab, and entire Errors and SessionDetails components; deleted DangerouslySetInnerHTML component
Frontend: Route & Const Cleanup
web/app/routes/projects.$pid.alerts.create.tsx, web/app/routes/projects.$pid.alerts.settings.$id.tsx, web/app/utils/routes.ts, web/app/lib/constants/index.ts
Removed alert route files and route entries; added experiments tab to PROJECT_TABS; removed CDN_URL, MARKETPLACE_URL, and period-pair constants
Frontend: Hook & Utility Changes
web/app/hooks/useAnnotations.ts, web/app/utils/generic.ts
Removed options parameter from useAnnotations; removed removeDuplicates utility function
Frontend: UI Components
web/app/ui/Button.tsx, web/app/ui/Switch.tsx, web/app/ui/FilterValueInput.tsx
Added ghost prop to Button; replaced random id generation in Switch with useId; unexported deviceIconMapping, VERSION_SEPARATOR, and FilterValueInputProps
Frontend: Context & Helpers
web/app/pages/Project/View/ViewProject.tsx, web/app/pages/Project/View/ViewProject.helpers.tsx
Extended ViewProjectContextType with comparison fields and methods; added radius property to bar chart configurations; added Experiments view integration and mobile sidebar handling
Frontend: Localization & Config
web/public/locales/en.json, web/.env.example, web/vite.config.ts, web/package.json
Added extensive experiments translation keys; removed @swetrix/sdk from dependencies and Vite config; removed CDN_URL from env example

Sequence Diagram

sequenceDiagram
    participant User as User/Browser
    participant Controller as ExperimentController
    participant Service as ExperimentService
    participant FeatureFlagSvc as FeatureFlagService
    participant DB as Database
    participant Analytics as AnalyticsService
    participant Bayesian as Bayesian Module

    User->>Controller: Start Experiment
    Controller->>Service: Validate & fetch experiment
    Service->>DB: Get experiment with relations
    DB-->>Service: Experiment data
    
    Controller->>FeatureFlagSvc: Create or link feature flag
    FeatureFlagSvc->>DB: Save feature flag
    DB-->>FeatureFlagSvc: Feature flag created
    
    Controller->>Service: Update experiment status
    Service->>DB: Set status=RUNNING, startedAt
    DB-->>Service: Updated
    Controller-->>User: Started (200)

    User->>Controller: Get Experiment Results
    Controller->>Analytics: Query exposures & conversions
    Analytics-->>Controller: Raw exposure/conversion data
    
    Controller->>Bayesian: calculateBayesianProbabilities(variants, data)
    Bayesian-->>Controller: Win probabilities map
    
    Controller->>Controller: generateExperimentChart()
    Controller-->>User: Results with probabilities & chart (200)

    User->>Controller: Complete Experiment
    Controller->>Service: Fetch experiment
    Service->>DB: Get experiment
    DB-->>Service: Experiment
    
    Controller->>FeatureFlagSvc: Disable feature flag
    FeatureFlagSvc->>DB: Update flag enabled=false
    DB-->>FeatureFlagSvc: Updated
    
    Controller->>Service: Update status=COMPLETED, endedAt
    Service->>DB: Save
    DB-->>Service: Updated
    Controller-->>User: Completed (200)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring careful attention:

  • Bayesian Statistics Implementation (backend/apps/cloud/src/experiment/bayesian.ts): Verify correctness of seeded RNG, Beta-Gamma sampling, and Monte Carlo probability calculations; ensure deterministic output matches specifications
  • Feature Flag Integration (backend/apps/cloud/src/feature-flag/evaluation.ts, feature-flag.controller.ts): Complex integration between experiments and feature flags; verify variant assignment logic, exposure tracking, and circular dependency handling via forwardRef
  • Experiment Controller Validation (experiment.controller.ts): Extensive business logic validation (variant counts, rollout percentages, control flags, exposure criteria); verify all edge cases and error messages
  • Frontend State Management & Context Changes (ViewProject.tsx): Large context refactor adding comparison fields and methods; verify all dependent components receive and use new context properties correctly
  • View Component Refactoring (Alerts, Errors, Goals, etc.): Systematic introduction of DashboardHeader across views; verify consistent behavior, navigation, and loading states across all refactored views
  • Alert Route Migration: Alert functionality moved from dedicated routes to modal-based editing within settings; verify modal wiring, back navigation, and parameter passing work correctly
  • Test Coverage: Large addition of experiment domain logic; ensure new entities, DTOs, service methods, and controller endpoints have appropriate test coverage

Possibly related PRs

  • Country blacklist project setting #427: Modifies the Project entity to add relationships and fields—directly related as this PR also extends Project with the experiments relation
  • feat: Feature flags #450: Modifies feature-flag domain (controller, service, evaluation module)—related as this PR integrates experiments with feature flag evaluation and variant assignment
  • UI updates #441: Modifies ProjectSidebar component and its props—related as this PR adds mobile sidebar support and MobileSidebarTrigger to the same component

Poem

🐰 A/B tests now bloom in Swetrix's garden,
With Bayesian magic, we don't need to pardon,
Variants dance through feature flags so bright,
Win probabilities shining in the night,
Experiments hop toward insights profound!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete, providing only checklist selections without substantive detail about the experimental feature implementation, architecture, or changes made. Add a comprehensive description explaining the experiments feature, key components (Bayesian analysis, feature flag integration, experiment lifecycle), database schema changes, and API endpoints added.
Title check ❓ Inconclusive The title 'Experiments' is vague and generic; it does not clearly convey the specific nature of the changes implemented in this comprehensive pull request. Use a more descriptive title such as 'Add A/B testing and experiment management system' or 'Implement experiments feature with Bayesian analytics' to better summarize the main changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@Blaumaus Blaumaus changed the base branch from main to swetrix-revamp December 11, 2025 02:33
@Blaumaus Blaumaus marked this pull request as ready for review December 14, 2025 01:13
@Blaumaus
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Blaumaus
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 14, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
web/app/pages/Project/Alerts/View/ProjectAlertsView.tsx (1)

308-315: Deleting an alert doesn’t update the list/total/page (stale UI)
onDelete calls deleteAlertApi + toast, but the UI stays unchanged until a manual reload / paging. This is user-visible and can break pagination totals.

Minimal fix (optimistic local update + optional reload to keep totals correct):

   const onDelete = async (id: string) => {
     try {
       await deleteAlertApi(id)
       toast.success(t('alertsSettings.alertDeleted'))
+      setAlerts((prev) => prev.filter((a) => a.id !== id))
+      setTotal((prev) => Math.max(0, prev - 1))
+      // Optional: if you prefer server-truth (and to handle page underflow), reload:
+      // loadAlerts(DEFAULT_ALERTS_TAKE, (page - 1) * DEFAULT_ALERTS_TAKE)
     } catch (reason: any) {
       toast.error(reason?.response?.data?.message || reason?.message || 'Something went wrong')
     }
   }

Also applies to: 396-446

web/app/pages/Project/Alerts/Settings/ProjectAlertsSettings.tsx (1)

40-62: onClose is optional but “Cancel” assumes it exists
If a caller forgets onClose, Cancel becomes a silent no-op. Either make onClose required, or provide a safe default (e.g., navigate to backLink or a known alerts route).

 interface ProjectAlertsSettingsProps {
   alertId?: string | null
   projectId: string
   isSettings?: boolean
-  onClose?: () => void
+  onClose: () => void
   onSave?: () => void
   backLink?: string
 }

Also applies to: 543-579

web/app/pages/Project/Profiles/View/ProfilesView.tsx (2)

63-136: Increment profilesRequestIdRef (stale responses can overwrite newer state).
const requestId = profilesRequestIdRef.current is never advanced, so the “only apply latest response” guard is ineffective.

 const loadProfiles = async (forcedSkip?: number, override?: boolean) => {
   if (profilesLoading) {
     return
   }
 
-  const requestId = profilesRequestIdRef.current
+  const requestId = ++profilesRequestIdRef.current
   setProfilesLoading(true)

138-184: Profile/sessions refresh can be skipped + stale profile loads can win.

  • loadProfileSessionsData(..., override=true) is called after getProfile, but if a sessions request is already running, the early if (profileSessionsLoading) return drops the refresh.
  • Rapidly switching profileId can set activeProfile from an older in-flight request (no request-id check).

Suggestion: add per-request ids for loadProfile and loadProfileSessionsData (and allow override to bypass the “loading” short-circuit), e.g.

+ const profileRequestIdRef = useRef(0)
+ const profileSessionsRequestIdRef = useRef(0)

 const loadProfile = async (profileId: string) => {
+  const requestId = ++profileRequestIdRef.current
   const hasExistingProfileData = !!activeProfile && activeProfile.profileId === profileId
   setProfileLoading(true)
   ...
   const data = await getProfile(...)
-  if (isMountedRef.current) {
+  if (isMountedRef.current && requestId === profileRequestIdRef.current) {
     setActiveProfile(data)
     loadProfileSessionsData(profileId, 0, true)
   }
 }

 const loadProfileSessionsData = async (profileId: string, forcedSkip?: number, override?: boolean) => {
-  if (profileSessionsLoading) {
+  if (profileSessionsLoading && !override) {
     return
   }
+  const requestId = ++profileSessionsRequestIdRef.current
   setProfileSessionsLoading(true)
   ...
-  if (isMountedRef.current) {
+  if (isMountedRef.current && requestId === profileSessionsRequestIdRef.current) {
     ...
   }

Also applies to: 186-258

🟡 Minor comments (14)
web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx-672-674 (1)

672-674: Multiple hardcoded English strings in Advanced settings section.

Several UI strings are hardcoded: "Advanced settings", "Feature flag source", "Create new", "Link existing", "Default", "Custom event", "Exclude", "First only", and tooltip contents.

These should use translation keys for i18n support.

Also applies to: 686-690, 704-705, 716-717, 770-771, 782-783, 808-812, 829-830, 841-842

web/app/pages/Project/Experiments/View/ExperimentResults.tsx-449-449 (1)

449-449: Hardcoded "Value" column header should use translation.

-               <TableHeader>Value</TableHeader>
+               <TableHeader>{t('experiments.value')}</TableHeader>
web/app/pages/Project/Experiments/View/ExperimentResults.tsx-574-578 (1)

574-578: Hardcoded "Significant" text should use translation.

The "Significant" label is hardcoded in English while other labels use t() for i18n.

                        {variant.probabilityOfBeingBest >= 95 ? (
                          <span className='rounded-full bg-green-100 px-1.5 py-0.5 text-[10px] font-medium text-green-700 dark:bg-green-900/50 dark:text-green-300'>
-                           Significant
+                           {t('experiments.significant')}
                          </span>
                        ) : null}
web/app/pages/Project/Experiments/View/ExperimentResults.tsx-391-407 (1)

391-407: Hardcoded "Total" text should use translation.

The "Total" row label is hardcoded in English.

               <tr className='bg-gray-50 dark:bg-slate-800'>
                 <TableCell>
                   <Text weight='semibold' size='sm'>
-                    Total
+                    {t('common.total')}
                   </Text>
                 </TableCell>
web/app/pages/Project/Experiments/View/ExperimentResults.tsx-181-202 (1)

181-202: Tooltip HTML template could be vulnerable to XSS if variant names contain malicious content.

The el.name is interpolated directly into HTML without sanitization. If a variant name contained HTML/script content, it could be rendered. While this data likely comes from your own backend, consider sanitizing or escaping the name.

           ${_map(items, (el: { id: string; name: string; value: number }) => {
+            const safeName = el.name.replace(/</g, '&lt;').replace(/>/g, '&gt;')
             return `
             <li class='flex justify-between'>
               <div class='flex justify-items-start'>
                 <div class='w-3 h-3 rounded-xs mt-1.5 mr-2' style=background-color:${color(el.id)}></div>
-                <span>${el.name}</span>
+                <span>${safeName}</span>
               </div>
               <span class='pl-4 font-medium'>${el.value}%</span>
             </li>
             `
           }).join('')}</ul>`
web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx-599-606 (1)

599-606: Tooltip text should use translation key.

Hardcoded English tooltip text for goal selection.

                       <Tooltip
-                        text='Select a goal to measure which variant performs better. You can start the experiment without a goal and add one later.'
+                        text={t('experiments.goalHint')}
                         className='flex items-center'
                       />
web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx-459-461 (1)

459-461: Tooltip text should use translation key.

Hardcoded English tooltip text for variants explanation.

                         <Tooltip
-                          text='Define the variants users will see. The control is your baseline (usually the current version). Other variants are what you want to test.'
+                          text={t('experiments.variantsHint')}
                           className='flex items-center'
                         />
web/app/pages/Project/View/components/TrafficHeaderActions.tsx-166-172 (1)

166-172: Hardcoded export type in analytics tracking.

The analytics tracking always reports type: 'csv' regardless of the actual export type selected. If multiple export formats are supported (or planned), consider passing the actual type from the ExportType item.

Apply this diff if export types should be tracked accurately:

 interface ExportType {
   label: string
+  type: string
   onClick: () => void
 }
 onSelect={(item) => {
   trackCustom('DASHBOARD_EXPORT', {
-    type: 'csv',
+    type: item.type,
   })

   item.onClick()
 }}

Committable suggestion skipped: line range outside the PR's diff.

web/app/pages/Project/FeatureFlags/View/FeatureFlagsView.tsx-790-865 (1)

790-865: Empty state logic doesn't account for search with no results.

When filterQuery has a value but the search returns no results (flags is empty), the onboarding CTA ("Add your first flag") is shown instead of the "no flags match filter" message. This could confuse users who have flags but are searching for something that doesn't exist.

Consider adjusting the condition to differentiate between "no flags exist" and "no flags match the filter":

-        {_isEmpty(flags) ? (
+        {_isEmpty(flags) && !filterQuery ? (
           <div className='mt-5 rounded-xl bg-gray-700 p-5'>
             ...
           </div>
+        ) : _isEmpty(flags) && filterQuery ? (
+          <p className='py-8 text-center text-sm text-gray-500 dark:text-gray-400'>
+            {t('featureFlags.noFlagsMatchFilter')}
+          </p>
         ) : (
web/app/pages/Project/View/components/DashboardHeader.tsx-21-39 (1)

21-39: Prevent rendering two back buttons when both backLink and onBack are provided.
Prefer a clear precedence (e.g., onBack wins) or warn in prop typing/docs.

-        {backLink ? <BackButton to={backLink} /> : null}
-        {onBack ? <BackButton onClick={onBack} /> : null}
+        {onBack ? <BackButton onClick={onBack} /> : backLink ? <BackButton to={backLink} /> : null}

Also applies to: 123-130

web/app/pages/Project/Performance/View/PerformanceView.tsx-634-690 (1)

634-690: Avoid remounting Panels via key={activeTabs...}; also localize “Loading map…”.

Using key={activeTabs.location|device|page} forces remounts on tab switches (state loss + extra work). Prefer a stable key (e.g., type). Also the Suspense fallback string is hardcoded (Line 661-669).

-  <Panel
-    key={activeTabs.location}
+  <Panel
+    key={type}
     icon={panelIconMapping.cc}
     id={activeTabs.location}
     ...

-  <Panel
-    key={activeTabs.device}
+  <Panel
+    key={type}
     icon={panelIconMapping.os}
     id={activeTabs.device}
     ...

-  <Panel
-    key={activeTabs.page}
+  <Panel
+    key={type}
     icon={panelIconMapping.pg}
     id={activeTabs.page}
     ...

-  <span className='text-sm text-neutral-600 dark:text-neutral-300'>
-    Loading map...
-  </span>
+  <span className='text-sm text-neutral-600 dark:text-neutral-300'>
+    {t('common.loading')}
+  </span>

Also applies to: 699-723, 735-775, 661-669

web/app/pages/Project/Experiments/View/ExperimentsView.tsx-219-225 (1)

219-225: Localize hardcoded tooltips (“Pause this experiment…”, “Completed experiments…”).

backend/apps/cloud/src/experiment/experiment.controller.ts-281-335 (1)

281-335: Non-atomic experiment creation with linked feature flag.

If the experiment is created successfully (line 317) but the feature flag update fails (line 321-323), the experiment will exist without proper flag linkage. Consider wrapping in a transaction.

// Consider using a transaction for atomicity:
await this.experimentService.transaction(async (manager) => {
  const newExperiment = await manager.save(experiment)
  if (existingFeatureFlag) {
    await this.featureFlagService.update(existingFeatureFlag.id, {
      experimentId: newExperiment.id,
    })
  }
  return newExperiment
})
backend/apps/cloud/src/experiment/experiment.controller.ts-984-986 (1)

984-986: Edge case: empty variants array could cause error.

If variantResults is empty (e.g., no variants), the reduce call without an initial value will throw. While this shouldn't happen given prior validation, defensive coding would be safer.

-    const highestProbVariant = variantResults.reduce((a, b) =>
-      a.probabilityOfBeingBest > b.probabilityOfBeingBest ? a : b,
-    )
+    const highestProbVariant = variantResults.length > 0
+      ? variantResults.reduce((a, b) =>
+          a.probabilityOfBeingBest > b.probabilityOfBeingBest ? a : b,
+        )
+      : null
-    const hasWinner = highestProbVariant.probabilityOfBeingBest >= 95
+    const hasWinner = highestProbVariant 
+      ? highestProbVariant.probabilityOfBeingBest >= 95 
+      : false
🧹 Nitpick comments (32)
web/app/ui/Button.tsx (1)

8-26: Add a variant-precedence rule (or dev-time guard) for ghost vs other variants.

Right now callers can pass ghost alongside primary/secondary/danger/white/semiDanger/noBorder, which can create conflicting Tailwind utilities (e.g., border + border-none, bg-* collisions). Consider enforcing exclusivity (union type) or at least documenting/guarding precedence (e.g., ghost wins, or ghost is ignored when primary etc. is set).

Also applies to: 28-49

web/app/pages/Project/Alerts/View/ProjectAlertsView.tsx (2)

220-227: backLink becomes "?" when there are no remaining params
When pureSearchParams is empty, backLink={?${pureSearchParams}} yields "?", which is a weird navigation target (and may break consumers that expect ''/undefined). Consider only prefixing ? when non-empty.

-        backLink={`?${pureSearchParams}`}
+        backLink={pureSearchParams ? `?${pureSearchParams}` : ''}

Also applies to: 391-393


278-307: Clear conflicting URL params when switching modes (edit vs create)
handleNewAlert() sets newAlert=true but doesn’t clear alertId; openAlert() sets alertId but doesn’t clear newAlert. It likely “works” due to activeAlertId || isCreatingAlert, but leaves stale params in the URL.

   const handleNewAlert = () => {
     if (isLimitReached) {
       setIsPaidFeatureOpened(true)
       return
     }

     const newSearchParams = new URLSearchParams(searchParams.toString())
+    newSearchParams.delete('alertId')
     newSearchParams.set('newAlert', 'true')
     setSearchParams(newSearchParams)
   }

   const openAlert = (alertId: string) => {
     const newSearchParams = new URLSearchParams(searchParams.toString())
+    newSearchParams.delete('newAlert')
     newSearchParams.set('alertId', alertId)
     setSearchParams(newSearchParams)
   }
web/app/pages/Project/Alerts/Settings/ProjectAlertsSettings.tsx (2)

82-118: Load sequencing based on alertId looks OK; consider clearing stale alert on mode switch
When switching from edit → create (alertId becomes null), you set isLoading(false) but keep alert as-is until overwritten. Consider setAlert(null) (and maybe resetting form) to avoid any transient stale state.

     if (alertId) {
       loadAlert(alertId)
     } else {
+      setAlert(null)
       setIsLoading(false)
     }

269-283: Delete flow correctly gates on alertId; consider also closing after delete
After deleteAlert, you onSave?.() but don’t necessarily close the settings view. If the parent doesn’t close on save (today it does), you’ll be left on a now-invalid edit screen. Consider onClose?.() (or document that parent must close).

       .then(() => {
         toast.success(t('alertsSettings.alertDeleted'))
         onSave?.()
+        onClose?.()
       })

Also applies to: 581-591

web/app/pages/Project/View/components/ProjectSidebar.tsx (3)

509-518: Consider adding focus management for accessibility.

The mobile sidebar overlay is well-implemented with backdrop click-to-close and ESC key handling. However, for improved accessibility:

  1. When the sidebar opens, focus should move to the sidebar (or the close button).
  2. Consider trapping focus within the sidebar while it's open.

This ensures keyboard-only users can navigate the mobile sidebar effectively.

Additionally, verify that animate-slide-in-left is defined in your Tailwind config or CSS:

#!/bin/bash
# Verify the animate-slide-in-left class is defined
rg -n "slide-in-left" --type=css --type=js --type=ts

453-453: Simplify boolean expression.

The ternary is redundant for a boolean expression.

-            isCollapsed={isCollapsed && !isMobileOpen ? true : false}
+            isCollapsed={isCollapsed && !isMobileOpen}

255-271: Minor: Consider preserving original overflow value.

The current implementation resets overflow to an empty string, which could unintentionally override a previously set value if another component modifies body overflow.

  useEffect(() => {
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape' && isMobileOpen) {
        onMobileClose?.()
      }
    }

+   let originalOverflow: string | undefined
    if (isMobileOpen) {
      document.addEventListener('keydown', handleEscape)
+     originalOverflow = document.body.style.overflow
      document.body.style.overflow = 'hidden'
    }

    return () => {
      document.removeEventListener('keydown', handleEscape)
-     document.body.style.overflow = ''
+     if (originalOverflow !== undefined) {
+       document.body.style.overflow = originalOverflow
+     }
    }
  }, [isMobileOpen, onMobileClose])
backend/migrations/clickhouse/2025_12_11_experiments.js (1)

15-15: Consider including profileId in the ORDER BY clause.

The current ordering (pid, experimentId, created) supports project and experiment-level queries but may not optimize queries filtering by profileId (e.g., "which variant did user X see?").

If queries frequently filter by profileId, consider updating the ORDER BY:

- ORDER BY (pid, experimentId, created)
+ ORDER BY (pid, experimentId, profileId, created)

This change would improve performance for user-centric experiment queries while maintaining efficiency for project-level aggregations.

web/app/pages/Project/Experiments/View/ExperimentResults.tsx (2)

161-166: Type cast in tick format function is safe but could be cleaner.

The @ts-expect-error and x as unknown as Date casts work but are awkward. The d3 timeFormat expects a Date, and billboard.js passes the Date object here. Consider typing the parameter directly as Date since billboard.js provides Date objects for timeseries axes.

         format:
-          // @ts-expect-error
-          timeFormat === TimeFormat['24-hour']
-            ? (x: string) => d3.timeFormat(tbsFormatMapper24h[timeBucket])(x as unknown as Date)
-            : (x: string) => d3.timeFormat(tbsFormatMapper[timeBucket])(x as unknown as Date),
+          timeFormat === TimeFormat['24-hour']
+            ? (x: Date) => d3.timeFormat(tbsFormatMapper24h[timeBucket])(x)
+            : (x: Date) => d3.timeFormat(tbsFormatMapper[timeBucket])(x),

658-702: Data loading effect handles errors gracefully but doesn't preserve previous data on refresh failure.

The current implementation clears error on each attempt but doesn't roll back state if a refresh fails after initial load. The comment on line 665 suggests intent to keep existing data, which is good. However, if getExperiment or getExperimentResults fails, the component will show the error state even if previous data existed.

Consider only showing the error UI when there's no existing data:

The current behavior at lines 718-735 already handles this correctly by checking !experiment || !results before showing error. The implementation is correct as-is.

web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx (5)

141-150: Missing cleanup for loading state if modal closes during load.

If the modal is closed while loadExperiment is in progress, the finally block will still set isLoading(false) on a potentially unmounted component. Consider adding a mounted ref or using AbortController.

+  const isMountedRef = useRef(true)
+
+  useEffect(() => {
+    isMountedRef.current = true
+    return () => {
+      isMountedRef.current = false
+    }
+  }, [])
+
   const loadExperiment = async () => {
     if (!experimentId) return
     setIsLoading(true)
     try {
       const experiment = await getExperiment(experimentId)
+      if (!isMountedRef.current) return
       setName(experiment.name)
       // ... rest of setters
     } catch (error) {
+      if (!isMountedRef.current) return
       console.error('Failed to load experiment:', error)
       toast.error(t('experiments.loadError'))
       onClose()
     } finally {
-      setIsLoading(false)
+      if (isMountedRef.current) setIsLoading(false)
     }
   }

188-232: Validation logic is comprehensive but could report multiple errors.

Currently, if multiple variant-related errors exist, only the last one is stored in newErrors.variants. Consider accumulating errors or prioritizing the most critical one.

The current approach is acceptable for UX as it guides users through fixing one issue at a time.


285-302: Variant naming uses character codes that can produce unexpected results.

When variantIndex is 1 (second variant being added), String.fromCharCode(64 + 1) = 'A' and String.fromCharCode(96 + 1) = 'a'. But when the array already has 2 variants, variantIndex is 2, producing 'B' and 'b'. This is correct but could be confusing if variants are removed and re-added.

Consider using a counter that only increments, or basing the letter on the actual count of non-control variants to avoid duplicate letters after removals. Current behavior is acceptable.


480-482: Using index as key for variant list items.

While this works when items are only added/removed from the end, it can cause issues if items are reordered or removed from the middle. Consider using a stable identifier.

                       {_map(variants, (variant, index) => (
                         <div
-                          key={index}
+                          key={variant.key || index}
                           className={cx(

Since variant keys should be unique after validation, this provides a more stable key.


634-635: Hidden text on small screens may affect usability.

The "Increase"/"Decrease" labels are hidden on small screens (hidden sm:inline), leaving only icons. Consider keeping at least abbreviated text or ensuring icons are self-explanatory with aria-labels.

Also applies to: 647-648

backend/apps/cloud/src/experiment/experiment.module.ts (1)

15-23: Multiple forwardRef usages - verify necessity.

Five of six module imports use forwardRef, which may indicate tight coupling. While the circular dependency with FeatureFlagModule is expected (experiments integrate with feature flags), verify whether UserModule, ProjectModule, AnalyticsModule, and GoalModule truly need forwardRef or if they can be direct imports.

If these modules don't have circular dependencies with ExperimentModule, remove the unnecessary forwardRef wrappers to simplify the module configuration and improve clarity.

web/app/pages/Project/Errors/View/ErrorsView.tsx (1)

770-801: Simplify redundant optional chaining.

After checking activeError &&, using activeError?.details?.status is redundant. The ?. on activeError isn't needed since it's already verified as truthy.

-    const resolveButton =
-      allowedToManage && activeError && activeError?.details?.status !== 'resolved' ? (
+    const resolveButton =
+      allowedToManage && activeError && activeError.details?.status !== 'resolved' ? (
         <button
           ...
         </button>
-      ) : allowedToManage && activeError && activeError?.details?.status === 'resolved' ? (
+      ) : allowedToManage && activeError && activeError.details?.status === 'resolved' ? (
backend/apps/cloud/src/feature-flag/entity/feature-flag.entity.ts (1)

60-63: Consider indexing experimentId (and/or modeling a relation) if it’s queried frequently.
If you’ll look up feature flags by experiment, add an index to avoid table scans.

 import {
   Column,
   CreateDateColumn,
   Entity,
   JoinColumn,
   ManyToOne,
+  Index,
   PrimaryGeneratedColumn,
   Unique,
 } from 'typeorm'

 ...

+ @Index(['experimentId'])
 @Entity()
 @Unique(['project', 'key'])
 export class FeatureFlag {
web/app/pages/Project/View/components/CaptchaView.tsx (1)

270-334: Avoid @ts-expect-error for logo maps; type the keys instead.
These suppressions tend to spread and hide real issues.

Example approach:

  • Type the maps as Record<string, string> (or Record<BrowserName, string> if you have a union), then index without suppression.
  • Or cast the lookup key once: const key = entryName as keyof typeof BROWSER_LOGO_MAP.
backend/apps/cloud/src/feature-flag/feature-flag.controller.ts (2)

321-334: Optional: dedupe experiment IDs before querying.


398-430: Consider skipping experiment exposure insert when flag-evaluations insert fails.

Right now exposures can be recorded even if feature_flag_evaluations insert errored (Line 398-430), which may complicate downstream analytics.

backend/apps/cloud/src/experiment/dto/experiment.dto.ts (2)

23-49: Coerce rolloutPercentage to number for validation reliability.

 export class ExperimentVariantDto {
   @ApiProperty()
   @IsNumber()
   @Min(0)
   @Max(100)
+  @Type(() => Number)
   rolloutPercentage: number

117-122: Enforce basic variant constraints (min 2 variants) and validate cross-variant invariants in service.

At DTO level, consider @ArrayMinSize(2) (and possibly @ArrayMaxSize(...)). In service-level validation, ensure: unique key, exactly one isControl, and rollout percentages sum to 100.

 import {
   ...
+  ArrayMinSize,
 } from 'class-validator'

 export class CreateExperimentDto {
   ...
   @ApiProperty({ type: [ExperimentVariantDto] })
   @IsArray()
+  @ArrayMinSize(2)
   @ValidateNested({ each: true })
   @Type(() => ExperimentVariantDto)
   variants: ExperimentVariantDto[]
 }

Also applies to: 187-193

backend/apps/cloud/src/experiment/experiment.service.ts (1)

75-85: Consider returning typed results from update/delete operations.

The update and delete methods return Promise<any>. Consider using Promise<UpdateResult> and Promise<DeleteResult> from TypeORM for better type safety.

-  async update(id: string, experimentData: Partial<Experiment>): Promise<any> {
+  async update(id: string, experimentData: Partial<Experiment>): Promise<UpdateResult> {
     return this.experimentRepository.update(id, experimentData)
   }

-  async delete(id: string): Promise<any> {
+  async delete(id: string): Promise<DeleteResult> {
     return this.experimentRepository.delete(id)
   }

Note: You'll need to import UpdateResult and DeleteResult from typeorm.

web/app/pages/Project/View/ViewProject.tsx (1)

739-758: Minor: Unused variables in onMainChartZoom.

fromFormatted and toFormatted are declared with let but never reassigned. Consider using const.

-      let fromFormatted = from.toISOString().split('T')[0] + 'T00:00:00.000Z'
-      let toFormatted = to.toISOString().split('T')[0] + 'T23:59:59.999Z'
+      const fromFormatted = from.toISOString().split('T')[0] + 'T00:00:00.000Z'
+      const toFormatted = to.toISOString().split('T')[0] + 'T23:59:59.999Z'
backend/apps/cloud/src/feature-flag/evaluation.ts (1)

208-215: Consider logging when fallback is triggered.

The fallback to the last variant (line 218) handles cases where percentages don't sum to 100. This could indicate a configuration error. Consider adding debug logging or metrics when this fallback is hit.

   // Fallback to last variant (shouldn't happen if percentages sum to 100)
+  // Note: If this code path is hit, it may indicate variant percentages don't sum to 100
   return variants[variants.length - 1].key
web/app/api/index.ts (2)

1570-1586: CreateExperiment interface is not exported.

Unlike other similar types in this file (e.g., CreateAlert, CreateGoal, CreateFeatureFlag), this interface is not exported. If consumers need to construct experiment payloads outside this module, they won't have access to this type.

Consider exporting for consistency:

-interface CreateExperiment {
+export interface CreateExperiment {

1590-1609: getProjectExperiments lacks search parameter support.

Unlike getProjectFeatureFlags (lines 1382-1410), which accepts and handles a search parameter, getProjectExperiments doesn't support searching/filtering experiments by name. This may be intentional if the backend doesn't support it yet.

backend/apps/cloud/src/experiment/experiment.controller.ts (3)

568-651: startExperiment creates feature flag with truncated ID.

Line 604-605: The generated flag key truncates the experiment ID to 20 characters. While unlikely to cause collisions, this could theoretically result in duplicate keys if two experiment IDs share the same first 20 characters (after replacing dashes).

Consider using a hash or the full ID:

-          `experiment_${experiment.id.replace(/-/g, '_').substring(0, 20)}`
+          `exp_${experiment.id.replace(/-/g, '_')}`

636-640: Redundant featureFlag assignment in update.

The featureFlag is already set on the entity via create (line 625) or was fetched from the database. Passing it again in the update payload may trigger unnecessary ORM operations.

     await this.experimentService.update(id, {
       status: ExperimentStatus.RUNNING,
       startedAt: new Date(),
-      featureFlag,
     })

981-987: Winner determination uses hardcoded 95% threshold.

The hasWinner check at line 987 uses a hardcoded 95% threshold. This is a reasonable default for statistical significance, but consider making it configurable per experiment if different confidence levels are needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c788e69 and 2062289.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (62)
  • backend/apps/cloud/src/app.module.ts (2 hunks)
  • backend/apps/cloud/src/experiment/bayesian.ts (1 hunks)
  • backend/apps/cloud/src/experiment/dto/experiment.dto.ts (1 hunks)
  • backend/apps/cloud/src/experiment/entity/experiment-variant.entity.ts (1 hunks)
  • backend/apps/cloud/src/experiment/entity/experiment.entity.ts (1 hunks)
  • backend/apps/cloud/src/experiment/experiment.controller.ts (1 hunks)
  • backend/apps/cloud/src/experiment/experiment.module.ts (1 hunks)
  • backend/apps/cloud/src/experiment/experiment.service.ts (1 hunks)
  • backend/apps/cloud/src/feature-flag/dto/feature-flag.dto.ts (1 hunks)
  • backend/apps/cloud/src/feature-flag/entity/feature-flag.entity.ts (2 hunks)
  • backend/apps/cloud/src/feature-flag/evaluation.ts (5 hunks)
  • backend/apps/cloud/src/feature-flag/feature-flag.controller.ts (4 hunks)
  • backend/apps/cloud/src/feature-flag/feature-flag.module.ts (2 hunks)
  • backend/apps/cloud/src/feature-flag/feature-flag.service.ts (1 hunks)
  • backend/apps/cloud/src/project/entity/project.entity.ts (2 hunks)
  • backend/apps/community/src/feature-flag/dto/feature-flag.dto.ts (1 hunks)
  • backend/apps/community/src/feature-flag/entity/feature-flag.entity.ts (1 hunks)
  • backend/apps/community/src/feature-flag/evaluation.ts (3 hunks)
  • backend/migrations/clickhouse/2025_12_11_experiments.js (1 hunks)
  • backend/migrations/mysql/2025_12_11_experiments.sql (1 hunks)
  • web/.env.example (0 hunks)
  • web/app/api/index.ts (5 hunks)
  • web/app/components/DangerouslySetInnerHTML.tsx (0 hunks)
  • web/app/hooks/useAnnotations.ts (1 hunks)
  • web/app/lib/constants/index.ts (3 hunks)
  • web/app/pages/Project/Alerts/Settings/ProjectAlertsSettings.tsx (11 hunks)
  • web/app/pages/Project/Alerts/Settings/index.tsx (0 hunks)
  • web/app/pages/Project/Alerts/View/ProjectAlertsView.tsx (7 hunks)
  • web/app/pages/Project/Errors/View/ErrorsView.tsx (3 hunks)
  • web/app/pages/Project/Experiments/View/ExperimentResults.tsx (1 hunks)
  • web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx (1 hunks)
  • web/app/pages/Project/Experiments/View/ExperimentsView.tsx (1 hunks)
  • web/app/pages/Project/Experiments/View/index.tsx (1 hunks)
  • web/app/pages/Project/FeatureFlags/View/FeatureFlagsView.tsx (2 hunks)
  • web/app/pages/Project/Funnels/View/FunnelsView.tsx (4 hunks)
  • web/app/pages/Project/Goals/View/GoalsView.tsx (2 hunks)
  • web/app/pages/Project/Performance/View/PerformanceView.tsx (4 hunks)
  • web/app/pages/Project/Profiles/View/ProfilesView.tsx (7 hunks)
  • web/app/pages/Project/Sessions/View/SessionsView.tsx (3 hunks)
  • web/app/pages/Project/Traffic/View/TrafficView.tsx (7 hunks)
  • web/app/pages/Project/View/Panels.tsx (2 hunks)
  • web/app/pages/Project/View/ViewProject.helpers.tsx (5 hunks)
  • web/app/pages/Project/View/ViewProject.tsx (25 hunks)
  • web/app/pages/Project/View/components/BackButton.tsx (1 hunks)
  • web/app/pages/Project/View/components/CaptchaView.tsx (6 hunks)
  • web/app/pages/Project/View/components/DashboardHeader.tsx (2 hunks)
  • web/app/pages/Project/View/components/Errors.tsx (0 hunks)
  • web/app/pages/Project/View/components/ProjectSidebar.tsx (19 hunks)
  • web/app/pages/Project/View/components/SessionDetailView.tsx (1 hunks)
  • web/app/pages/Project/View/components/SessionDetails.tsx (0 hunks)
  • web/app/pages/Project/View/components/TrafficHeaderActions.tsx (3 hunks)
  • web/app/routes/projects.$pid.alerts.create.tsx (0 hunks)
  • web/app/routes/projects.$pid.alerts.settings.$id.tsx (0 hunks)
  • web/app/styles/tailwind.css (2 hunks)
  • web/app/ui/Button.tsx (3 hunks)
  • web/app/ui/FilterValueInput.tsx (3 hunks)
  • web/app/ui/Switch.tsx (2 hunks)
  • web/app/utils/generic.ts (0 hunks)
  • web/app/utils/routes.ts (0 hunks)
  • web/package.json (0 hunks)
  • web/public/locales/en.json (3 hunks)
  • web/vite.config.ts (2 hunks)
💤 Files with no reviewable changes (10)
  • web/app/utils/routes.ts
  • web/package.json
  • web/app/utils/generic.ts
  • web/app/pages/Project/Alerts/Settings/index.tsx
  • web/app/pages/Project/View/components/SessionDetails.tsx
  • web/app/components/DangerouslySetInnerHTML.tsx
  • web/app/routes/projects.$pid.alerts.settings.$id.tsx
  • web/app/routes/projects.$pid.alerts.create.tsx
  • web/app/pages/Project/View/components/Errors.tsx
  • web/.env.example
🧰 Additional context used
🧬 Code graph analysis (21)
backend/apps/cloud/src/experiment/experiment.module.ts (3)
backend/apps/cloud/src/app.module.ts (1)
  • Module (95-109)
backend/apps/cloud/src/feature-flag/feature-flag.module.ts (1)
  • Module (13-26)
web/app/api/index.ts (2)
  • Experiment (1518-1539)
  • ExperimentVariant (1509-1516)
backend/apps/cloud/src/experiment/entity/experiment-variant.entity.ts (3)
backend/apps/cloud/src/experiment/entity/experiment.entity.ts (1)
  • Entity (42-138)
backend/apps/cloud/src/project/entity/project.entity.ts (1)
  • Entity (31-178)
web/app/api/index.ts (2)
  • ExperimentVariant (1509-1516)
  • Experiment (1518-1539)
backend/apps/cloud/src/project/entity/project.entity.ts (1)
web/app/api/index.ts (1)
  • Experiment (1518-1539)
web/app/pages/Project/FeatureFlags/View/FeatureFlagsView.tsx (1)
web/app/api/index.ts (1)
  • DEFAULT_FEATURE_FLAGS_TAKE (1380-1380)
backend/migrations/clickhouse/2025_12_11_experiments.js (1)
backend/migrations/clickhouse/setup.js (2)
  • dbName (107-107)
  • queriesRunner (76-105)
web/app/ui/FilterValueInput.tsx (1)
web/app/pages/Project/View/ViewProject.helpers.tsx (1)
  • deviceIconMapping (1677-1685)
web/app/pages/Project/View/components/BackButton.tsx (1)
web/app/utils/generic.ts (1)
  • cn (150-152)
web/app/pages/Project/Goals/View/GoalsView.tsx (1)
web/app/api/index.ts (1)
  • DEFAULT_GOALS_TAKE (1258-1258)
web/app/pages/Project/Experiments/View/ExperimentResults.tsx (3)
web/app/ui/Text.tsx (1)
  • Text (51-75)
web/app/api/index.ts (6)
  • ExperimentChartData (1552-1555)
  • ExperimentResults (1557-1568)
  • Experiment (1518-1539)
  • Goal (1232-1242)
  • getExperiment (1611-1617)
  • getExperimentResults (1667-1682)
web/app/lib/constants/index.ts (5)
  • TimeFormat (245-248)
  • tbsFormatMapper24h (237-243)
  • tbsFormatMapper (213-219)
  • tbsFormatMapperTooltip24h (229-235)
  • tbsFormatMapperTooltip (221-227)
web/app/pages/Project/Experiments/View/ExperimentSettingsModal.tsx (1)
web/app/api/index.ts (8)
  • ExperimentVariant (1509-1516)
  • Goal (1232-1242)
  • ExposureTrigger (1503-1503)
  • MultipleVariantHandling (1505-1505)
  • FeatureFlagMode (1507-1507)
  • getExperiment (1611-1617)
  • updateExperiment (1627-1633)
  • createExperiment (1619-1625)
web/app/pages/Project/Alerts/View/ProjectAlertsView.tsx (4)
web/app/providers/CurrentProjectProvider.tsx (1)
  • useCurrentProject (244-252)
web/app/providers/AuthProvider.tsx (1)
  • useAuth (130-138)
web/app/utils/server.ts (1)
  • isAuthenticated (46-48)
web/app/lib/constants/index.ts (1)
  • DEFAULT_ALERTS_TAKE (449-449)
backend/apps/cloud/src/feature-flag/feature-flag.controller.ts (2)
web/app/api/index.ts (1)
  • ExperimentStatus (1501-1501)
backend/apps/cloud/src/feature-flag/evaluation.ts (1)
  • getExperimentVariant (187-219)
backend/apps/cloud/src/experiment/dto/experiment.dto.ts (1)
web/app/api/index.ts (4)
  • ExposureTrigger (1503-1503)
  • MultipleVariantHandling (1505-1505)
  • FeatureFlagMode (1507-1507)
  • ExperimentStatus (1501-1501)
web/app/pages/Project/Sessions/View/SessionsView.tsx (1)
web/app/pages/Project/View/components/Sessions.tsx (1)
  • Sessions (220-238)
web/app/pages/Project/View/components/ProjectSidebar.tsx (1)
web/app/utils/generic.ts (1)
  • cn (150-152)
backend/apps/cloud/src/feature-flag/evaluation.ts (2)
backend/apps/community/src/feature-flag/evaluation.ts (1)
  • EvaluatableFeatureFlag (24-30)
web/app/api/index.ts (1)
  • ExperimentVariant (1509-1516)
web/app/pages/Project/Traffic/View/TrafficView.tsx (7)
web/app/pages/Project/View/interfaces/traffic.ts (2)
  • ProjectView (26-31)
  • ProjectViewCustomEvent (17-24)
web/app/pages/Project/View/ViewProject.helpers.tsx (4)
  • onCSVExportClick (1983-1983)
  • CHART_METRICS_MAPPING (1984-1984)
  • noRegionPeriods (1981-1981)
  • getDeviceRowMapper (1717-1833)
web/app/pages/Project/View/utils/filters.tsx (1)
  • FILTER_CHART_METRICS_MAPPING_FOR_COMPARE (16-16)
web/app/pages/Project/View/components/MetricCards.tsx (2)
  • MetricCards (186-261)
  • MetricCard (66-94)
web/app/lib/constants/index.ts (1)
  • TRAFFIC_PANELS_ORDER (172-172)
web/app/lib/models/Entry.ts (2)
  • CountryEntry (8-11)
  • Entry (1-6)
web/app/pages/Project/View/components/ChartContextMenu.tsx (1)
  • ChartContextMenu (20-138)
web/app/pages/Project/Performance/View/PerformanceView.tsx (3)
web/app/pages/Project/View/components/MetricCards.tsx (1)
  • PerformanceMetricCards (269-318)
web/app/lib/constants/index.ts (1)
  • PERFORMANCE_PANELS_ORDER (173-173)
web/app/pages/Project/View/components/ChartContextMenu.tsx (1)
  • ChartContextMenu (20-138)
web/app/pages/Project/View/components/CaptchaView.tsx (3)
web/app/providers/CurrentProjectProvider.tsx (1)
  • useCurrentProject (244-252)
web/app/lib/constants/index.ts (3)
  • BROWSER_LOGO_MAP (858-905)
  • OS_LOGO_MAP (907-933)
  • OS_LOGO_MAP_DARK (935-940)
web/app/pages/Project/View/ViewProject.helpers.tsx (2)
  • panelIconMapping (1979-1979)
  • deviceIconMapping (1677-1685)
web/app/pages/Project/View/components/TrafficHeaderActions.tsx (1)
web/app/utils/analytics.ts (1)
  • trackCustom (173-182)
web/app/pages/Project/Funnels/View/FunnelsView.tsx (2)
web/app/pages/Project/View/ViewProject.tsx (1)
  • useViewProjectContext (219-222)
web/app/ui/Text.tsx (1)
  • Text (51-75)

@Blaumaus Blaumaus merged commit 2b489e5 into swetrix-revamp Dec 14, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 20, 2025
9 tasks
This was referenced Feb 7, 2026
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.

1 participant