Repository: nginx/nginx-gateway-fabric

Issue:
Product Telemetry (Yes/No) for NGINX One Console Connection
**As a** maintainer for NGF
**I want** to know how many clusters are connected to the NGINX One Console
**So that** I know how valuable that connection is
**And** how much I should prioritize Console features.

### Acceptance
- A Yes/No response is reported for the installation of NGF via product telemetry.

Discussion:

Solution patch:
diff --git a/internal/controller/manager.go b/internal/controller/manager.go
index 999f22d9..a4e9fd9c 100644
--- a/internal/controller/manager.go
+++ b/internal/controller/manager.go
@@ -289,8 +289,9 @@ func StartManager(cfg config.Config) error {
 				Namespace: cfg.GatewayPodConfig.Namespace,
 				Name:      cfg.GatewayPodConfig.Name,
 			},
-			ImageSource: cfg.ImageSource,
-			Flags:       cfg.Flags,
+			ImageSource:               cfg.ImageSource,
+			Flags:                     cfg.Flags,
+			NginxOneConsoleConnection: cfg.NginxOneConsoleTelemetryConfig.DataplaneKeySecretName != "",
 		})
 
 		job, err := createTelemetryJob(cfg, dataCollector, healthChecker.getReadyCh())
diff --git a/internal/controller/telemetry/collector.go b/internal/controller/telemetry/collector.go
index 2ea076f7..e06da3f0 100644
--- a/internal/controller/telemetry/collector.go
+++ b/internal/controller/telemetry/collector.go
@@ -64,6 +64,8 @@ type Data struct {
 	NginxPodCount int64
 	// ControlPlanePodCount is the total number of NGF control plane Pods.
 	ControlPlanePodCount int64
+	// NginxOneConnectionEnabled is a boolean that indicates whether the connection to the Nginx One Console is enabled.
+	NginxOneConnectionEnabled bool
 }
 
 // NGFResourceCounts stores the counts of all relevant resources that NGF processes and generates configuration from.
@@ -113,14 +115,16 @@ type DataCollectorConfig struct {
 	GraphGetter GraphGetter
 	// ConfigurationGetter allows us to get the Configuration.
 	ConfigurationGetter ConfigurationGetter
-	// Version is the NGF version.
-	Version string
 	// PodNSName is the NamespacedName of the NGF Pod.
 	PodNSName types.NamespacedName
+	// Version is the NGF version.
+	Version string
 	// ImageSource is the source of the NGF image.
 	ImageSource string
 	// Flags contains the command-line NGF flag keys and values.
 	Flags config.Flags
+	// NginxOneConsoleConnection is a boolean that indicates whether the connection to the Nginx One Console is enabled.
+	NginxOneConsoleConnection bool
 }
 
 // DataCollectorImpl is am implementation of DataCollector.
@@ -189,6 +193,7 @@ func (c DataCollectorImpl) Collect(ctx context.Context) (Data, error) {
 		SnippetsFiltersDirectivesCount: snippetsFiltersDirectivesCount,
 		NginxPodCount:                  nginxPodCount,
 		ControlPlanePodCount:           int64(replicaCount),
+		NginxOneConnectionEnabled:      c.cfg.NginxOneConsoleConnection,
 	}
 
 	return data, nil
diff --git a/internal/controller/telemetry/data.avdl b/internal/controller/telemetry/data.avdl
index 95d99f31..c1988131 100644
--- a/internal/controller/telemetry/data.avdl
+++ b/internal/controller/telemetry/data.avdl
@@ -111,5 +111,8 @@ attached at the Gateway level. */
 		/** ControlPlanePodCount is the total number of NGF control plane Pods. */
 		long? ControlPlanePodCount = null;
 		
+		/** NginxOneConnectionEnabled is a boolean that indicates whether the connection to the Nginx One Console is enabled. */
+		boolean? NginxOneConnectionEnabled = null;
+		
 	}
 }
diff --git a/internal/controller/telemetry/data_attributes_generated.go b/internal/controller/telemetry/data_attributes_generated.go
index afbd8dfb..3b8b3dcf 100644
--- a/internal/controller/telemetry/data_attributes_generated.go
+++ b/internal/controller/telemetry/data_attributes_generated.go
@@ -22,6 +22,7 @@ func (d *Data) Attributes() []attribute.KeyValue {
 	attrs = append(attrs, d.NGFResourceCounts.Attributes()...)
 	attrs = append(attrs, attribute.Int64("NginxPodCount", d.NginxPodCount))
 	attrs = append(attrs, attribute.Int64("ControlPlanePodCount", d.ControlPlanePodCount))
+	attrs = append(attrs, attribute.Bool("NginxOneConnectionEnabled", d.NginxOneConnectionEnabled))
 
 	return attrs
 }



Test patch:
diff --git a/internal/controller/telemetry/collector_test.go b/internal/controller/telemetry/collector_test.go
index ea900450..8c749fdf 100644
--- a/internal/controller/telemetry/collector_test.go
+++ b/internal/controller/telemetry/collector_test.go
@@ -176,6 +176,7 @@ var _ = Describe("Collector", Ordered, func() {
 			FlagValues:                     flags.Values,
 			SnippetsFiltersDirectives:      []string{},
 			SnippetsFiltersDirectivesCount: []int64{},
+			NginxOneConnectionEnabled:      true,
 		}
 
 		k8sClientReader = &kubernetesfakes.FakeReader{}
@@ -186,13 +187,14 @@ var _ = Describe("Collector", Ordered, func() {
 		fakeConfigurationGetter.GetLatestConfigurationReturns(nil)
 
 		dataCollector = telemetry.NewDataCollectorImpl(telemetry.DataCollectorConfig{
-			K8sClientReader:     k8sClientReader,
-			GraphGetter:         fakeGraphGetter,
-			ConfigurationGetter: fakeConfigurationGetter,
-			Version:             version,
-			PodNSName:           podNSName,
-			ImageSource:         "local",
-			Flags:               flags,
+			K8sClientReader:           k8sClientReader,
+			GraphGetter:               fakeGraphGetter,
+			ConfigurationGetter:       fakeConfigurationGetter,
+			Version:                   version,
+			PodNSName:                 podNSName,
+			ImageSource:               "local",
+			Flags:                     flags,
+			NginxOneConsoleConnection: true,
 		})
 
 		baseGetCalls = createGetCallsFunc(ngfPod, ngfReplicaSet, kubeNamespace)
@@ -516,6 +518,7 @@ var _ = Describe("Collector", Ordered, func() {
 				// empty + one gateway using daemonset
 				expData.NginxPodCount = int64(8)
 				expData.ControlPlanePodCount = int64(2)
+				expData.NginxOneConnectionEnabled = true
 
 				data, err := dataCollector.Collect(ctx)
 				Expect(err).ToNot(HaveOccurred())
diff --git a/internal/controller/telemetry/data_test.go b/internal/controller/telemetry/data_test.go
index 867424e1..49c8e354 100644
--- a/internal/controller/telemetry/data_test.go
+++ b/internal/controller/telemetry/data_test.go
@@ -46,6 +46,7 @@ func TestDataAttributes(t *testing.T) {
 		SnippetsFiltersDirectivesCount: []int64{3, 2, 1},
 		NginxPodCount:                  3,
 		ControlPlanePodCount:           3,
+		NginxOneConnectionEnabled:      true,
 	}
 
 	expected := []attribute.KeyValue{
@@ -84,6 +85,7 @@ func TestDataAttributes(t *testing.T) {
 		attribute.Int64("GatewayAttachedNpCount", 15),
 		attribute.Int64("NginxPodCount", 3),
 		attribute.Int64("ControlPlanePodCount", 3),
+		attribute.Bool("NginxOneConnectionEnabled", true),
 	}
 
 	result := data.Attributes()
@@ -129,6 +131,7 @@ func TestDataAttributesWithEmptyData(t *testing.T) {
 		attribute.Int64("GatewayAttachedNpCount", 0),
 		attribute.Int64("NginxPodCount", 0),
 		attribute.Int64("ControlPlanePodCount", 0),
+		attribute.Bool("NginxOneConnectionEnabled", false),
 	}
 
 	result := data.Attributes()
diff --git a/tests/suite/telemetry_test.go b/tests/suite/telemetry_test.go
index a4d5f502..2ad0c0b3 100644
--- a/tests/suite/telemetry_test.go
+++ b/tests/suite/telemetry_test.go
@@ -95,6 +95,7 @@ var _ = Describe("Telemetry test with OTel collector", Label("telemetry"), func(
 				"GatewayAttachedNpCount: Int(0)",
 				"NginxPodCount: Int(0)",
 				"ControlPlanePodCount: Int(1)",
+				"NginxOneConnectionEnabled: Bool(false)",
 			},
 		)
 	})