Skip to content

Commit cd9b1d1

Browse files
committed
PR review
1 parent d5014b1 commit cd9b1d1

File tree

12 files changed

+264
-176
lines changed

12 files changed

+264
-176
lines changed

backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryServerAttributesProvider.java

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
import java.util.Objects;
2525
import java.util.Optional;
2626
import java.util.function.Predicate;
27-
import org.sonarsource.sonarlint.core.telemetry.gessie.event.payload.IDESupportedLanguageViewedPayload.ConnectionType;
27+
import org.sonarsource.sonarlint.core.commons.Binding;
28+
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieConnectionInfo;
2829
import javax.annotation.CheckForNull;
2930
import javax.annotation.Nullable;
3031
import org.sonarsource.sonarlint.core.SonarCloudRegion;
@@ -166,23 +167,27 @@ private Predicate<BoundScope> isSonarCloudConnectionConfiguration() {
166167
* of the given config scope ID. Returns empty if the scope is not bound.
167168
*/
168169
public Optional<GessieConnectionInfo> getGessieConnectionInfo(String configScopeId) {
169-
return configurationRepository.getEffectiveBinding(configScopeId).flatMap(binding -> {
170-
var connectionId = binding.connectionId();
171-
var connection = connectionConfigurationRepository.getConnectionById(connectionId);
172-
if (connection == null) {
173-
return Optional.empty();
174-
}
175-
var storage = storageService.connection(connectionId);
176-
var userUuid = nullIfEmpty(storage.user().read().orElse(null));
177-
if (connection instanceof SonarCloudConnectionConfiguration) {
178-
var organizationUuidV4 = storage.organization().read().map(Organization::uuidV4).map(Object::toString).orElse(null);
179-
return Optional.of(new GessieConnectionInfo(ConnectionType.SQC, userUuid, organizationUuidV4, null));
180-
} else if (connection instanceof SonarQubeConnectionConfiguration) {
181-
var sqsInstallationId = nullIfEmpty(storage.serverInfo().read().map(StoredServerInfo::serverId).orElse(null));
182-
return Optional.of(new GessieConnectionInfo(ConnectionType.SQS, userUuid, null, sqsInstallationId));
183-
}
184-
return Optional.empty();
185-
});
170+
return configurationRepository.getEffectiveBinding(configScopeId)
171+
.map(Binding::connectionId)
172+
.map(this::buildGessieConnectionInfo);
173+
}
174+
175+
@CheckForNull
176+
private GessieConnectionInfo buildGessieConnectionInfo(String connectionId) {
177+
var connection = connectionConfigurationRepository.getConnectionById(connectionId);
178+
if (connection == null) {
179+
return null;
180+
}
181+
var storage = storageService.connection(connectionId);
182+
var userUuid = nullIfEmpty(storage.user().read().orElse(null));
183+
if (connection instanceof SonarCloudConnectionConfiguration) {
184+
var organizationUuidV4 = storage.organization().read().map(Organization::uuidV4).map(Object::toString).orElse(null);
185+
return new GessieConnectionInfo(GessieConnectionInfo.ConnectionType.SQC, userUuid, organizationUuidV4, null);
186+
} else if (connection instanceof SonarQubeConnectionConfiguration) {
187+
var sqsInstallationId = nullIfEmpty(storage.serverInfo().read().map(StoredServerInfo::serverId).orElse(null));
188+
return new GessieConnectionInfo(GessieConnectionInfo.ConnectionType.SQS, userUuid, null, sqsInstallationId);
189+
}
190+
return null;
186191
}
187192

188193
@CheckForNull

backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetryService.java

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.util.function.Consumer;
3030
import java.util.stream.Collectors;
3131
import javax.annotation.Nullable;
32-
import org.apache.commons.lang3.SystemUtils;
3332
import org.sonarsource.sonarlint.core.analysis.AnalysisFinishedEvent;
3433
import org.sonarsource.sonarlint.core.analysis.AutomaticAnalysisSettingChangedEvent;
3534
import org.sonarsource.sonarlint.core.analysis.IssuesRaisedEvent;
@@ -47,7 +46,6 @@
4746
import org.sonarsource.sonarlint.core.rpc.protocol.backend.ai.AiAgent;
4847
import org.sonarsource.sonarlint.core.rpc.protocol.backend.config.binding.BindingSuggestionOrigin;
4948
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;
50-
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;
5149
import org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.GetStatusResponse;
5250
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedFindingDto;
5351
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.RaisedIssueDto;
@@ -57,9 +55,6 @@
5755
import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.McpTransportMode;
5856
import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.ToolCalledParams;
5957
import org.sonarsource.sonarlint.core.rpc.protocol.common.Language;
60-
import org.sonarsource.sonarlint.core.monitoring.MonitoringUserIdStore;
61-
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieService;
62-
import org.sonarsource.sonarlint.core.telemetry.gessie.event.payload.IDESupportedLanguageViewedPayload;
6358
import org.springframework.context.ApplicationEventPublisher;
6459
import org.springframework.context.event.EventListener;
6560

@@ -78,21 +73,15 @@ public class TelemetryService {
7873
private final SonarLintRpcClient client;
7974
private final boolean isTelemetryFeatureEnabled;
8075
private final ApplicationEventPublisher applicationEventPublisher;
81-
private final GessieService gessieService;
82-
private final TelemetryClientConstantAttributesDto telemetryConstantAttributes;
83-
private final MonitoringUserIdStore monitoringUserIdStore;
8476

8577
public TelemetryService(InitializeParams initializeParams, SonarLintRpcClient sonarlintClient,
8678
TelemetryServerAttributesProvider telemetryServerAttributesProvider, TelemetryManager telemetryManager,
87-
ApplicationEventPublisher applicationEventPublisher, GessieService gessieService, MonitoringUserIdStore monitoringUserIdStore) {
79+
ApplicationEventPublisher applicationEventPublisher) {
8880
this.isTelemetryFeatureEnabled = initializeParams.getBackendCapabilities().contains(TELEMETRY);
8981
this.client = sonarlintClient;
9082
this.telemetryServerAttributesProvider = telemetryServerAttributesProvider;
9183
this.telemetryManager = telemetryManager;
9284
this.applicationEventPublisher = applicationEventPublisher;
93-
this.gessieService = gessieService;
94-
this.telemetryConstantAttributes = initializeParams.getTelemetryConstantAttributes();
95-
this.monitoringUserIdStore = monitoringUserIdStore;
9685
this.scheduledExecutor = FailSafeExecutors.newSingleThreadScheduledExecutor("SonarLint Telemetry");
9786

9887
initTelemetryAndScheduleUpload(initializeParams);
@@ -426,29 +415,6 @@ public void close() {
426415
}
427416
}
428417

429-
public void supportedLanguageViewed(String configScopeId) {
430-
if (!gessieService.isEnabled()) {
431-
return;
432-
}
433-
var localUserId = monitoringUserIdStore.getOrCreate().map(Object::toString).orElse(null);
434-
if (localUserId == null) {
435-
if (InternalDebug.isEnabled()) {
436-
LOG.warn("Could not retrieve local user ID — skipping Gessie event");
437-
}
438-
return;
439-
}
440-
var connectionInfo = telemetryServerAttributesProvider.getGessieConnectionInfo(configScopeId).orElse(null);
441-
gessieService.supportedLanguageViewed(new IDESupportedLanguageViewedPayload(
442-
localUserId,
443-
telemetryConstantAttributes.getProductVersion(),
444-
SystemUtils.OS_NAME,
445-
connectionInfo != null ? connectionInfo.connectionType() : null,
446-
connectionInfo != null ? connectionInfo.userUuid() : null,
447-
connectionInfo != null ? connectionInfo.organizationUuidV4() : null,
448-
connectionInfo != null ? connectionInfo.sqsInstallationId() : null
449-
));
450-
}
451-
452418
public void updateListFilesPerformance(int size, long timeMs) {
453419
if (!isTelemetryFeatureEnabled) {
454420
LOG.info("Telemetry disabled on server startup");

backend/core/src/main/java/org/sonarsource/sonarlint/core/telemetry/TelemetrySpringConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.nio.file.Path;
2323
import org.sonarsource.sonarlint.core.UserPaths;
2424
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;
25+
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieEventPublisher;
2526
import org.springframework.context.annotation.Bean;
2627
import org.springframework.context.annotation.Configuration;
2728
import org.springframework.context.annotation.Import;
@@ -32,7 +33,8 @@
3233
TelemetryManager.class,
3334
TelemetryLocalStorageManager.class,
3435
TelemetryHttpClient.class,
35-
TelemetryServerAttributesProvider.class
36+
TelemetryServerAttributesProvider.class,
37+
GessieEventPublisher.class
3638
})
3739
public class TelemetrySpringConfig {
3840

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* SonarLint Core - Implementation
3+
* Copyright (C) 2016-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.core.telemetry.gessie;
21+
22+
import org.apache.commons.lang3.SystemUtils;
23+
import org.sonarsource.sonarlint.core.telemetry.InternalDebug;
24+
import org.sonarsource.sonarlint.core.telemetry.TelemetryServerAttributesProvider;
25+
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
26+
import org.sonarsource.sonarlint.core.monitoring.MonitoringUserIdStore;
27+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;
28+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;
29+
import org.sonarsource.sonarlint.core.telemetry.gessie.event.payload.IDESupportedLanguageViewedPayload;
30+
31+
public class GessieEventPublisher {
32+
33+
private static final SonarLintLogger LOG = SonarLintLogger.get();
34+
35+
private final GessieService gessieService;
36+
private final MonitoringUserIdStore monitoringUserIdStore;
37+
private final TelemetryServerAttributesProvider telemetryServerAttributesProvider;
38+
private final TelemetryClientConstantAttributesDto telemetryConstantAttributes;
39+
40+
public GessieEventPublisher(GessieService gessieService, MonitoringUserIdStore monitoringUserIdStore,
41+
TelemetryServerAttributesProvider telemetryServerAttributesProvider, InitializeParams initializeParams) {
42+
this.gessieService = gessieService;
43+
this.monitoringUserIdStore = monitoringUserIdStore;
44+
this.telemetryServerAttributesProvider = telemetryServerAttributesProvider;
45+
this.telemetryConstantAttributes = initializeParams.getTelemetryConstantAttributes();
46+
}
47+
48+
public void supportedLanguageViewed(String configScopeId) {
49+
if (!gessieService.isEnabled()) {
50+
return;
51+
}
52+
53+
monitoringUserIdStore.getOrCreate().map(Object::toString).ifPresentOrElse(localUserId -> {
54+
GessieConnectionInfo connectionInfo = telemetryServerAttributesProvider.getGessieConnectionInfo(configScopeId).orElse(null);
55+
gessieService.supportedLanguageViewed(new IDESupportedLanguageViewedPayload(
56+
localUserId,
57+
telemetryConstantAttributes.getProductVersion(),
58+
SystemUtils.OS_NAME,
59+
connectionInfo != null ? connectionInfo.connectionType() : null,
60+
connectionInfo != null ? connectionInfo.userUuid() : null,
61+
connectionInfo != null ? connectionInfo.organizationUuidV4() : null,
62+
connectionInfo != null ? connectionInfo.sqsInstallationId() : null
63+
));
64+
}, () -> {
65+
if (InternalDebug.isEnabled()) {
66+
LOG.warn("Could not retrieve local user ID — skipping Gessie event");
67+
}
68+
});
69+
}
70+
71+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* SonarLint Core - Implementation
3+
* Copyright (C) 2016-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.core.telemetry;
21+
22+
import java.util.Optional;
23+
import java.util.UUID;
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.mockito.ArgumentCaptor;
27+
import org.sonarsource.sonarlint.core.monitoring.MonitoringUserIdStore;
28+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.InitializeParams;
29+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.initialize.TelemetryClientConstantAttributesDto;
30+
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieConnectionInfo;
31+
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieConnectionInfo.ConnectionType;
32+
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieEventPublisher;
33+
import org.sonarsource.sonarlint.core.telemetry.gessie.GessieService;
34+
import org.sonarsource.sonarlint.core.telemetry.gessie.event.payload.IDESupportedLanguageViewedPayload;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.mockito.ArgumentMatchers.any;
38+
import static org.mockito.Mockito.mock;
39+
import static org.mockito.Mockito.never;
40+
import static org.mockito.Mockito.verify;
41+
import static org.mockito.Mockito.when;
42+
43+
class GessieEventPublisherTests {
44+
45+
private GessieService gessieService;
46+
private MonitoringUserIdStore monitoringUserIdStore;
47+
private TelemetryServerAttributesProvider telemetryServerAttributesProvider;
48+
49+
private GessieEventPublisher underTest;
50+
51+
@BeforeEach
52+
void setUp() {
53+
gessieService = mock(GessieService.class);
54+
monitoringUserIdStore = mock(MonitoringUserIdStore.class);
55+
telemetryServerAttributesProvider = mock(TelemetryServerAttributesProvider.class);
56+
57+
var telemetryConstantAttributes = mock(TelemetryClientConstantAttributesDto.class);
58+
when(telemetryConstantAttributes.getProductKey()).thenReturn("vscode");
59+
when(telemetryConstantAttributes.getProductVersion()).thenReturn("1.0.0");
60+
61+
var init = mock(InitializeParams.class);
62+
when(init.getTelemetryConstantAttributes()).thenReturn(telemetryConstantAttributes);
63+
64+
underTest = new GessieEventPublisher(gessieService, monitoringUserIdStore, telemetryServerAttributesProvider, init);
65+
}
66+
67+
@Test
68+
void supportedLanguageViewed_does_nothing_when_gessie_disabled() {
69+
when(gessieService.isEnabled()).thenReturn(false);
70+
71+
underTest.supportedLanguageViewed("scopeId");
72+
73+
verify(gessieService, never()).supportedLanguageViewed(any());
74+
}
75+
76+
@Test
77+
void supportedLanguageViewed_does_nothing_when_local_user_id_unavailable() {
78+
when(gessieService.isEnabled()).thenReturn(true);
79+
when(monitoringUserIdStore.getOrCreate()).thenReturn(Optional.empty());
80+
81+
underTest.supportedLanguageViewed("scopeId");
82+
83+
verify(gessieService, never()).supportedLanguageViewed(any());
84+
}
85+
86+
@Test
87+
void supportedLanguageViewed_sends_event_without_connection_info_for_unbound_scope() {
88+
when(gessieService.isEnabled()).thenReturn(true);
89+
when(monitoringUserIdStore.getOrCreate()).thenReturn(Optional.of(UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
90+
when(telemetryServerAttributesProvider.getGessieConnectionInfo("scopeId")).thenReturn(Optional.empty());
91+
92+
underTest.supportedLanguageViewed("scopeId");
93+
94+
var captor = ArgumentCaptor.forClass(IDESupportedLanguageViewedPayload.class);
95+
verify(gessieService).supportedLanguageViewed(captor.capture());
96+
var payload = captor.getValue();
97+
assertThat(payload.localUserId()).isEqualTo("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
98+
assertThat(payload.connectionType()).isNull();
99+
assertThat(payload.userUuid()).isNull();
100+
assertThat(payload.organizationUuidV4()).isNull();
101+
assertThat(payload.sqsInstallationId()).isNull();
102+
}
103+
104+
@Test
105+
void supportedLanguageViewed_sends_event_with_sqc_connection_info() {
106+
when(gessieService.isEnabled()).thenReturn(true);
107+
when(monitoringUserIdStore.getOrCreate()).thenReturn(Optional.of(UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
108+
var connectionInfo = new GessieConnectionInfo(ConnectionType.SQC, "user-uuid", "org-uuid-v4", null);
109+
when(telemetryServerAttributesProvider.getGessieConnectionInfo("scopeId")).thenReturn(Optional.of(connectionInfo));
110+
111+
underTest.supportedLanguageViewed("scopeId");
112+
113+
var captor = ArgumentCaptor.forClass(IDESupportedLanguageViewedPayload.class);
114+
verify(gessieService).supportedLanguageViewed(captor.capture());
115+
var payload = captor.getValue();
116+
assertThat(payload.localUserId()).isEqualTo("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
117+
assertThat(payload.connectionType()).isEqualTo(ConnectionType.SQC);
118+
assertThat(payload.userUuid()).isEqualTo("user-uuid");
119+
assertThat(payload.organizationUuidV4()).isEqualTo("org-uuid-v4");
120+
assertThat(payload.sqsInstallationId()).isNull();
121+
}
122+
123+
@Test
124+
void supportedLanguageViewed_sends_event_with_sqs_connection_info() {
125+
when(gessieService.isEnabled()).thenReturn(true);
126+
when(monitoringUserIdStore.getOrCreate()).thenReturn(Optional.of(UUID.fromString("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")));
127+
var connectionInfo = new GessieConnectionInfo(ConnectionType.SQS, "sqs-user-uuid", null, "installation-id");
128+
when(telemetryServerAttributesProvider.getGessieConnectionInfo("scopeId")).thenReturn(Optional.of(connectionInfo));
129+
130+
underTest.supportedLanguageViewed("scopeId");
131+
132+
var captor = ArgumentCaptor.forClass(IDESupportedLanguageViewedPayload.class);
133+
verify(gessieService).supportedLanguageViewed(captor.capture());
134+
var payload = captor.getValue();
135+
assertThat(payload.localUserId()).isEqualTo("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
136+
assertThat(payload.connectionType()).isEqualTo(ConnectionType.SQS);
137+
assertThat(payload.userUuid()).isEqualTo("sqs-user-uuid");
138+
assertThat(payload.organizationUuidV4()).isNull();
139+
assertThat(payload.sqsInstallationId()).isEqualTo("installation-id");
140+
}
141+
142+
}

0 commit comments

Comments
 (0)