Skip to content

Comments

feat(KFLUXUI-1048/49): Segment config and analytics initialisation for Active users telemetry#694

Open
milantaky wants to merge 16 commits intokonflux-ci:mainfrom
milantaky:analytics-config
Open

feat(KFLUXUI-1048/49): Segment config and analytics initialisation for Active users telemetry#694
milantaky wants to merge 16 commits intokonflux-ci:mainfrom
milantaky:analytics-config

Conversation

@milantaky
Copy link
Member

@milantaky milantaky commented Feb 6, 2026

Fixes

https://issues.redhat.com/browse/KFLUXUI-1048
https://issues.redhat.com/browse/KFLUXUI-1049

Description

  • Adds @segment/analytics-next package
  • Defines types for Analytics config
  • Creates config loading function
  • Tests config loading
  • Adds Analytics initialisation function -> non-blocking, dynamically loaded
  • Makes analytic instance accessible
  • Gracefully handles errors on init failure
  • Tests initialisation, error handling

Type of change

  • Feature
  • Bugfix
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

Screen shots / Gifs for design review

How to test or reproduce?

In src/analytics/index.ts comment out line 21 and add
const config = { enabled: true, writeKey: 'test-write-key-123', apiUrl: 'https://api.segment.io/v1', };
to see failed api call in the console (returns 404 now, because no proper api, and write-key is provided).

Browser conformance:

  • Chrome
  • Firefox
  • Safari
  • Edge

Summary by CodeRabbit

  • New Features
    • Optional analytics support (disabled by default) with runtime-configurable enable flag, write key, and API URL; typed config and lazy initialization added; initializes at startup in parallel without blocking rendering.
  • Tests
    • Comprehensive tests for config loading, trimming/fallbacks, init behavior, error handling, and analytics retrieval.
  • Chores
    • Added Segment analytics dependency.

@snyk-io
Copy link
Contributor

snyk-io bot commented Feb 6, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'version', 'include', 'exclude', 'rules', 'limits'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • 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
📝 Walkthrough

Walkthrough

Adds Segment analytics: new runtime config entries and defaults; TypeScript types and loader; a lazy analytics initializer that dynamically imports @segment/analytics-next with get/init APIs; tests covering config and init flows; and parallelized app startup that runs analytics init alongside monitoring.

Changes

Cohort / File(s) Summary
Type Definitions & Runtime
@types/index.d.ts, public/runtime-config.js
Add ANALYTICS_ENABLED, ANALYTICS_WRITE_KEY, ANALYTICS_API_URL to Window.KONFLUX_RUNTIME and provide runtime defaults (ANALYTICS_ENABLED='false', empty key/url).
Analytics Types & Loader
src/analytics/types.ts, src/analytics/load-config.ts
Introduce AnalyticsConfig and loadAnalyticsConfig() which parses KONFLUX_RUNTIME, returns disabled defaults when unset/false, and provides writeKey/apiUrl when enabled.
Analytics Core & Tests
src/analytics/index.ts, src/analytics/__tests__/*
New lazy analytics module with initAnalytics() and getAnalytics() (validates config, dynamic import of @segment/analytics-next, loads AnalyticsBrowser, stores instance, logs/errors reported). Add unit tests covering config parsing, init paths, trimming, errors, and getAnalytics behavior.
Integration & Dependency
package.json, src/main.tsx
Add dependency @segment/analytics-next. Initialize analytics in parallel with monitoring via Promise.allSettled, logging and reporting per-initializer failures.

Sequence Diagram(s)

sequenceDiagram
    participant App as App (main.tsx)
    participant Analytics as Analytics Module
    participant Config as Config Loader
    participant Segment as Segment SDK
    participant Monitoring as Monitoring Service

    App->>Analytics: initAnalytics()
    activate Analytics
    Analytics->>Config: loadAnalyticsConfig()
    activate Config
    Config->>Config: read window.KONFLUX_RUNTIME, parse booleans
    Config-->>Analytics: AnalyticsConfig
    deactivate Config

    Analytics->>Analytics: validate enabled, writeKey, apiUrl
    alt enabled && valid
        Analytics->>Segment: dynamic import `@segment/analytics-next`
        Analytics->>Segment: AnalyticsBrowser.load(writeKey, { host: apiUrl })
        Segment-->>Analytics: analyticsInstance
        Analytics-->>App: resolved (instance stored)
    else disabled or invalid
        Analytics-->>App: skip (undefined)
    else error
        Analytics->>Monitoring: captureException / captureMessage
        Analytics-->>App: handled (undefined)
    end
    deactivate Analytics

    App->>App: render React app
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A tiny key, a little URL bright,
Lazy imports wake the analytics light.
Configs trimmed tidy, errors caught with care,
Monitoring listens — the data will share.
Cheers to init in parallel flair 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main objective: implementing Segment analytics configuration and initialization for active users telemetry, directly addressing KFLUXUI-1048/49.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

Copy link
Contributor

@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: 3

🤖 Fix all issues with AI agents
In `@src/analytics/index.ts`:
- Around line 43-48: The catch block in initAnalytics (or the module-level error
handler in src/analytics/index.ts) is sending duplicate Sentry events by calling
monitoringService.captureException(error, { context: 'initAnalytics' }) and then
monitoringService.captureMessage('Error loading Analytics', 'error', { error });
remove the redundant monitoringService.captureMessage call so only
captureException is sent for the same failure, keeping the console.error and
captureException call intact to preserve error logging and context.
- Around line 23-27: The code reads config.apiUrl into apiHost and passes it as
a top-level option, but Segment requires apiHost under
integrations['Segment.io'] and protocol set separately; update the
AnalyticsBrowser.load call to pass { writeKey } plus an options object with
integrations: { 'Segment.io': { apiHost: <host+path without scheme>, protocol:
<scheme> } }; add logic in the config handling around config.apiUrl
(ANALYTICS_API_URL) to accept full URLs by parsing and stripping the scheme into
protocol and host/path (or validate that callers supply host+path only), and
then use those parsed values when calling AnalyticsBrowser.load (referencing
writeKey, apiHost, protocol, AnalyticsBrowser.load, and
integrations['Segment.io']).

In `@src/analytics/load-config.ts`:
- Around line 4-5: The JSDoc comment above the default analytics configuration
mistakenly says "monitoring"; update the comment text to refer to "analytics"
instead (i.e., change "Default monitoring configuration" to "Default analytics
configuration") on the JSDoc that documents the module's exported default
analytics config in load-config.ts so the comment accurately describes the
exported analytics configuration object.
🧹 Nitpick comments (4)
src/analytics/load-config.ts (1)

34-38: Consider trimming writeKey and apiUrl here rather than relying on the caller.

initAnalytics() in src/analytics/index.ts (lines 23-24) trims these values before use. Moving the .trim() into the config loader would make the returned config consistently clean and reduce burden on every future consumer.

Optional diff
   return {
     enabled: true,
-    writeKey: runtime.ANALYTICS_WRITE_KEY || '',
-    apiUrl: runtime.ANALYTICS_API_URL || '',
+    writeKey: (runtime.ANALYTICS_WRITE_KEY || '').trim(),
+    apiUrl: (runtime.ANALYTICS_API_URL || '').trim(),
   };
src/analytics/index.ts (1)

41-42: eslint-disable comments suppress no-console — consider a proper logging approach.

Per project conventions, suppressing lint rules should be avoided in favor of fixing the underlying issue. If the project has a logging utility or plans for structured logging, these should use that instead. If console is the accepted approach for pre-app-init diagnostics, consider adding a project-level ESLint override for this analytics module rather than per-line disables.

Based on learnings: "In the konflux-ci/konflux-ui repository, if code contains comments that suppress lint warnings or errors (e.g., eslint-disable, ts-ignore, ts-expect-error), remind contributors to fix the underlying issue rather than silencing the rule."

Also applies to: 44-45

src/main.tsx (1)

51-61: Index-based labeling is fragile.

The i === 0 ? 'monitoring' : 'analytics' mapping will silently produce wrong labels if the array order changes. Consider a more explicit approach:

Suggested refactor
-  const results = await Promise.allSettled([initMonitoring(), initAnalytics()]);
-  results.forEach((result, i) => {
-    if (result.status === 'rejected') {
-      const name = i === 0 ? 'monitoring' : 'analytics';
-      // eslint-disable-next-line no-console
-      console.error(`Failed to initialize ${name}`, result.reason);
-      monitoringService?.captureException(result.reason, {
-        context: i === 0 ? 'initMonitoring' : 'initAnalytics',
-      });
-    }
-  });
+  const initializers = [
+    { name: 'monitoring', context: 'initMonitoring', fn: initMonitoring },
+    { name: 'analytics', context: 'initAnalytics', fn: initAnalytics },
+  ] as const;
+
+  const results = await Promise.allSettled(initializers.map(({ fn }) => fn()));
+  results.forEach((result, i) => {
+    if (result.status === 'rejected') {
+      const { name, context } = initializers[i];
+      // eslint-disable-next-line no-console
+      console.error(`Failed to initialize ${name}`, result.reason);
+      monitoringService?.captureException(result.reason, { context });
+    }
+  });
src/analytics/__tests__/index.spec.ts (1)

120-133: Test name slightly misleading — apiUrl is also empty here.

The test named "should not load SDK when write key is missing" also has apiUrl: '', so it tests a combined condition. Consider either providing a valid apiUrl to isolate the writeKey check, or renaming the test.

Suggested fix to isolate the writeKey condition
     it('should not load SDK when write key is missing', async () => {
       const mockConfig: AnalyticsConfig = {
         enabled: true,
         writeKey: '',
-        apiUrl: '',
+        apiUrl: 'https://api.segment.io/v1',
       };

Same applies to the whitespace writeKey test on lines 135-148.

@milantaky milantaky marked this pull request as draft February 6, 2026 22:19
@milantaky
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

✅ 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
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@src/analytics/index.ts`:
- Around line 50-53: In the catch block that currently logs errors and calls
monitoringService.captureMessage('Error loading Analytics', 'error', { error }),
replace the lower-fidelity captureMessage call with
monitoringService.captureException(error) so the full error object, stack trace
and type are sent to Sentry; keep the existing console.error line, and remove or
stop calling captureMessage for this error to avoid redundancy (refer to the
catch handler and the monitoringService.captureMessage / captureException
methods).
🧹 Nitpick comments (2)
src/analytics/index.ts (1)

48-49: Consider addressing the lint suppression rather than silencing no-console.

Two eslint-disable-next-line no-console comments are used here. If the project has a logging utility or if console.info/console.error are intentional for this module, consider configuring the ESLint rule to allow them in this file (or using a dedicated logger) instead of inline suppressions. Based on learnings, contributors should fix the underlying issue rather than silencing the rule.

Also applies to: 51-52

src/analytics/__tests__/index.spec.ts (1)

179-200: Update error-handling assertions if captureException replaces captureMessage in the implementation.

If the implementation is updated per the suggestion on src/analytics/index.ts (Line 53), this test should assert captureException instead of captureMessage.

Additionally, consider adding a test for double initialization — calling initAnalytics() twice — to verify idempotency. Currently, analyticsInstance would be silently overwritten on a second call; an explicit test documents whether that's the intended behavior.

@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 72.65625% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.14%. Comparing base (d8a8dde) to head (b09a2dd).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/main.tsx 0.00% 30 Missing ⚠️
src/analytics/types.ts 0.00% 4 Missing and 1 partial ⚠️

❌ Your patch check has failed because the patch coverage (72.65%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #694      +/-   ##
==========================================
- Coverage   87.23%   87.14%   -0.10%     
==========================================
  Files         764      767       +3     
  Lines       58225    58333     +108     
  Branches     5658     6893    +1235     
==========================================
+ Hits        50795    50835      +40     
+ Misses       7376     7370       -6     
- Partials       54      128      +74     
Flag Coverage Δ
e2e 45.73% <31.81%> (-4.36%) ⬇️
unittests 85.80% <72.65%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/analytics/index.ts 100.00% <100.00%> (ø)
src/analytics/load-config.ts 100.00% <100.00%> (ø)
src/analytics/types.ts 0.00% <0.00%> (ø)
src/main.tsx 0.00% <0.00%> (ø)

... and 65 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d8a8dde...b09a2dd. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@milantaky milantaky marked this pull request as ready for review February 9, 2026 14:58
src/main.tsx Outdated
const results = await Promise.allSettled([initMonitoring(), initAnalytics()]);
results.forEach((result, i) => {
if (result.status === 'rejected') {
const name = i === 0 ? 'monitoring' : 'analytics';
Copy link
Member

Choose a reason for hiding this comment

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

I think that it might get complicated if we start adding more initializers in the future, how about something like this ->

  const initializers = [
    { name: 'monitoring', context: 'initMonitoring', init: initMonitoring },
    { name: 'analytics', context: 'initAnalytics', init: initAnalytics },
  ] as const;

  const results = await Promise.allSettled(initializers.map(({ init }) => init()));

  results.forEach((result, i) => {
    if (result.status === 'rejected') {
      const { name, context } = initializers[i];
      // eslint-disable-next-line no-console
      console.error(`Failed to initialize ${name}`, result.reason);
      monitoringService?.captureException(result.reason, { context });
    }
  });

so in the feature, if a new initializer needs to be added, we'll just have to update the initializers array. Let me know what you guys think :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice catch, thank you! Updated

Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@src/main.tsx`:
- Around line 51-65: The concurrent startup allows initAnalytics to hit its
internal catch before monitoringService is set; change startup to run
initMonitoring first then start the rest: call await initMonitoring() (the
initMonitoring function) and only after it resolves build/run Promise.allSettled
for the remaining initializers (e.g., initAnalytics) so monitoringService is
initialized before any analytics error handling runs; update the code that
currently maps initializers (and the results loop) to exclude the already-run
monitoring initializer.

Copy link
Member

@JoaoPedroPP JoaoPedroPP left a comment

Choose a reason for hiding this comment

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

Tested locally and the output match to what was described in the How to test or reproduce section. What are next step on this?

@milantaky milantaky requested a review from a team as a code owner February 18, 2026 18:14
src/main.tsx Outdated
{ name: 'analytics', context: 'initAnalytics', init: initAnalytics },
] as const;

void (await Promise.allSettled(initializers.map(({ init }) => init())).then((results) => {
Copy link
Member

Choose a reason for hiding this comment

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

I believe we don't need the await here, you can call it as void Promise.allSettled(...) :)

@milantaky
Copy link
Member Author

@JoaoPedroPP I believe that at this point, this should be sufficient testing for the initialisation. Seeing the (failed) API call should be correct now:)

Copy link
Member

@rrosatti rrosatti left a comment

Choose a reason for hiding this comment

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

LGTM, really nice job! :)

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.

4 participants