aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java11
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java18
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java4
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml17
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl3
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java21
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsers.java39
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88.java33
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java2
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest.java43
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88Test.java42
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest/schema.sql28
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java12
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java1
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java10
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java6
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java8
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java6
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java3
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java6
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java2
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java14
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java6
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java2
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java5
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java3
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java18
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java2
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java94
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java1
-rw-r--r--server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java122
32 files changed, 572 insertions, 12 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
index b498d86c0dd..8fe12dcae2a 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
@@ -43,7 +43,7 @@ import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput;
import static org.sonar.db.user.UserDto.SCM_ACCOUNTS_SEPARATOR;
public class UserDao implements Dao {
-
+ private final static long WEEK_IN_MS = 7L * 24L * 3_600L * 1_000L;
private final System2 system2;
private final UuidFactory uuidFactory;
@@ -112,6 +112,10 @@ public class UserDao implements Dao {
return dto;
}
+ public void updateSonarlintLastConnectionDate(DbSession session, String login) {
+ mapper(session).updateSonarlintLastConnectionDate(login, system2.now());
+ }
+
public void setRoot(DbSession session, String login, boolean root) {
mapper(session).setRoot(login, root, system2.now());
}
@@ -171,6 +175,11 @@ public class UserDao implements Dao {
return mapper(dbSession).selectByExternalLoginAndIdentityProvider(externalLogin, externalIdentityProvider);
}
+ public long countSonarlintWeeklyUsers(DbSession dbSession) {
+ long threshold = system2.now() - WEEK_IN_MS;
+ return mapper(dbSession).countActiveSonarlintUsers(threshold);
+ }
+
public void scrollByUuids(DbSession dbSession, Collection<String> uuids, Consumer<UserDto> consumer) {
UserMapper mapper = mapper(dbSession);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
index 251aaa1373c..5ed11212836 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
@@ -63,6 +63,13 @@ public class UserDto {
@Nullable
private Long lastConnectionDate;
+ /**
+ * Date of the last time sonarlint connected to sonarqube WSs with this user's authentication.
+ * Can be null when user has never been authenticated, or has not been authenticated since the creation of the column in SonarQube 8.8.
+ */
+ @Nullable
+ private Long lastSonarlintConnectionDate;
+
private Long createdAt;
private Long updatedAt;
@@ -281,6 +288,17 @@ public class UserDto {
return this;
}
+ @CheckForNull
+ public Long getLastSonarlintConnectionDate() {
+ return lastSonarlintConnectionDate;
+ }
+
+ public UserDto setLastSonarlintConnectionDate(@Nullable Long lastSonarlintConnectionDate) {
+ this.lastSonarlintConnectionDate = lastSonarlintConnectionDate;
+ return this;
+ }
+
+
public Long getCreatedAt() {
return createdAt;
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java
index 930ce1d9464..0f76f721667 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java
@@ -66,6 +66,8 @@ public interface UserMapper {
void scrollAll(ResultHandler<UserDto> handler);
+ void updateSonarlintLastConnectionDate(@Param("login") String login, @Param("now") long now);
+
/**
* Count actives users which are root and which login is not the specified one.
*/
@@ -82,4 +84,6 @@ public interface UserMapper {
void clearHomepages(@Param("homepageType") String type, @Param("homepageParameter") String value, @Param("now") long now);
void clearHomepage(@Param("login") String login, @Param("now") long now);
+
+ long countActiveSonarlintUsers(@Param("sinceDate") long sinceDate);
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
index 323ef1d27bb..6f6a3daa2a0 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
@@ -23,6 +23,7 @@
u.homepage_type as "homepageType",
u.homepage_parameter as "homepageParameter",
u.last_connection_date as "lastConnectionDate",
+ u.last_sonarlint_connection as "lastSonarlintConnectionDate",
u.created_at as "createdAt",
u.updated_at as "updatedAt"
</sql>
@@ -166,9 +167,17 @@
salt = null,
crypted_password = null,
last_connection_date = null,
+ last_sonarlint_connection = null,
updated_at = #{now, jdbcType=BIGINT}
</sql>
+ <update id="updateSonarlintLastConnectionDate" parameterType="map">
+ update users set
+ last_sonarlint_connection = #{now, jdbcType=BIGINT}
+ where
+ login = #{login, jdbcType=VARCHAR}
+ </update>
+
<update id="deactivateUser" parameterType="map">
update users set
<include refid="deactivateUserUpdatedFields"/>
@@ -221,6 +230,7 @@
hash_method,
is_root,
onboarded,
+ last_sonarlint_connection,
reset_password,
homepage_type,
homepage_parameter,
@@ -242,6 +252,7 @@
#{user.hashMethod,jdbcType=VARCHAR},
#{user.root,jdbcType=BOOLEAN},
#{user.onboarded,jdbcType=BOOLEAN},
+ #{user.lastSonarlintConnectionDate,jdbcType=BIGINT},
#{user.resetPassword,jdbcType=BOOLEAN},
#{user.homepageType,jdbcType=VARCHAR},
#{user.homepageParameter,jdbcType=VARCHAR},
@@ -269,9 +280,15 @@
homepage_type = #{user.homepageType, jdbcType=VARCHAR},
homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR},
last_connection_date = #{user.lastConnectionDate,jdbcType=BIGINT},
+ last_sonarlint_connection = #{user.lastSonarlintConnectionDate,jdbcType=BIGINT},
updated_at = #{user.updatedAt,jdbcType=BIGINT}
where
uuid = #{user.uuid, jdbcType=VARCHAR}
</update>
+ <select id="countActiveSonarlintUsers" parameterType="map" resultType="long">
+ select count(login) from users
+ where last_sonarlint_connection > #{sinceDate,jdbcType=BIGINT}
+ </select>
+
</mapper>
diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl
index 35c44d970c0..55bc6dbb64e 100644
--- a/server/sonar-db-dao/src/schema/schema-sq.ddl
+++ b/server/sonar-db-dao/src/schema/schema-sq.ddl
@@ -914,7 +914,8 @@ CREATE TABLE "USERS"(
"LAST_CONNECTION_DATE" BIGINT,
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT,
- "RESET_PASSWORD" BOOLEAN NOT NULL
+ "RESET_PASSWORD" BOOLEAN NOT NULL,
+ "LAST_SONARLINT_CONNECTION" BIGINT
);
ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN");
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
index d69d1ce6529..8922f20e22d 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
@@ -511,6 +511,25 @@ public class UserDaoTest {
}
@Test
+ public void update_last_sonarlint_connection_date() {
+ UserDto user = db.users().insertUser();
+ assertThat(user.getLastSonarlintConnectionDate()).isNull();
+ underTest.updateSonarlintLastConnectionDate(db.getSession(), user.getLogin());
+ assertThat(underTest.selectByLogin(db.getSession(), user.getLogin()).getLastSonarlintConnectionDate()).isEqualTo(NOW);
+ }
+
+ @Test
+ public void count_sonarlint_weekly_users() {
+ UserDto user1 = db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 100_000));
+ UserDto user2 = db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW));
+ // these don't count
+ UserDto user3 = db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 1_000_000_000));
+ UserDto user4 = db.users().insertUser();
+
+ assertThat(underTest.countSonarlintWeeklyUsers(db.getSession())).isEqualTo(2);
+ }
+
+ @Test
public void clean_user_homepage() {
UserDto user = newUserDto().setHomepageType("RANDOM").setHomepageParameter("any-string");
@@ -628,7 +647,7 @@ public class UserDaoTest {
.extracting(UserDto::getUuid).containsExactlyInAnyOrder(user1.getUuid());
assertThat(underTest.selectByExternalIdsAndIdentityProvider(session,
asList(user1.getExternalId(), user2.getExternalId(), user3.getExternalId(), disableUser.getExternalId()), "github"))
- .extracting(UserDto::getUuid).containsExactlyInAnyOrder(user1.getUuid(), user2.getUuid(), disableUser.getUuid());
+ .extracting(UserDto::getUuid).containsExactlyInAnyOrder(user1.getUuid(), user2.getUuid(), disableUser.getUuid());
assertThat(underTest.selectByExternalIdsAndIdentityProvider(session, singletonList("unknown"), "github")).isEmpty();
assertThat(underTest.selectByExternalIdsAndIdentityProvider(session, singletonList(user1.getExternalId()), "unknown")).isEmpty();
}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
index bb1008a4950..1c4356cc5d0 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
@@ -35,6 +35,7 @@ import org.sonar.server.platform.db.migration.version.v84.DbVersion84;
import org.sonar.server.platform.db.migration.version.v85.DbVersion85;
import org.sonar.server.platform.db.migration.version.v86.DbVersion86;
import org.sonar.server.platform.db.migration.version.v87.DbVersion87;
+import org.sonar.server.platform.db.migration.version.v88.DbVersion88;
public class MigrationConfigurationModule extends Module {
@Override
@@ -50,6 +51,7 @@ public class MigrationConfigurationModule extends Module {
DbVersion85.class,
DbVersion86.class,
DbVersion87.class,
+ DbVersion88.class,
// migration steps
MigrationStepRegistryImpl.class,
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsers.java
new file mode 100644
index 00000000000..773117dfa7d
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsers.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v88;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddLastSonarlintConnectionToUsers extends DdlChange {
+ public AddLastSonarlintConnectionToUsers(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AddColumnsBuilder(getDialect(), "users")
+ .addColumn(BigIntegerColumnDef.newBigIntegerColumnDefBuilder().setColumnName("last_sonarlint_connection").setIsNullable(true).build())
+ .build());
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88.java
new file mode 100644
index 00000000000..2c19ad08043
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88.java
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v88;
+
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+public class DbVersion88 implements DbVersion {
+
+ @Override
+ public void addSteps(MigrationStepRegistry registry) {
+ registry
+ .add(4300, "Add 'last_sonarlint_connection' to 'users", AddLastSonarlintConnectionToUsers.class)
+ ;
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java
index 291f331225a..d495c37873c 100644
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v87/DbVersion87Test.java
@@ -30,7 +30,7 @@ public class DbVersion87Test {
private final DbVersion underTest = new DbVersion87();
@Test
- public void migrationNumber_starts_at_4100() {
+ public void migrationNumber_starts_at_4200() {
verifyMinimumMigrationNumber(underTest, 4200);
}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest.java
new file mode 100644
index 00000000000..2583cd7f4bb
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v88;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddLastSonarlintConnectionToUsersTest {
+ private static final String TABLE_NAME = "users";
+ private static final String COLUMN_NAME = "last_sonarlint_connection";
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(AddLastSonarlintConnectionToUsersTest.class, "schema.sql");
+ private final DdlChange underTest = new AddLastSonarlintConnectionToUsers(db.database());
+
+ @Test
+ public void add_column() throws SQLException {
+ db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+ underTest.execute();
+ db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.BIGINT, null, true);
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88Test.java
new file mode 100644
index 00000000000..3dbf9b66479
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v88/DbVersion88Test.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v88;
+
+import org.junit.Test;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty;
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
+
+public class DbVersion88Test {
+
+ private final DbVersion underTest = new DbVersion88();
+
+ @Test
+ public void migrationNumber_starts_at_4300() {
+ verifyMinimumMigrationNumber(underTest, 4300);
+ }
+
+ @Test
+ public void verify_migration_count() {
+ verifyMigrationNotEmpty(underTest);
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest/schema.sql
new file mode 100644
index 00000000000..92b679b0905
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v88/AddLastSonarlintConnectionToUsersTest/schema.sql
@@ -0,0 +1,28 @@
+CREATE TABLE "USERS"(
+ "UUID" VARCHAR(255) NOT NULL,
+ "LOGIN" VARCHAR(255) NOT NULL,
+ "NAME" VARCHAR(200),
+ "EMAIL" VARCHAR(100),
+ "CRYPTED_PASSWORD" VARCHAR(100),
+ "SALT" VARCHAR(40),
+ "HASH_METHOD" VARCHAR(10),
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" VARCHAR(4000),
+ "EXTERNAL_LOGIN" VARCHAR(255) NOT NULL,
+ "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100) NOT NULL,
+ "EXTERNAL_ID" VARCHAR(255) NOT NULL,
+ "IS_ROOT" BOOLEAN NOT NULL,
+ "USER_LOCAL" BOOLEAN,
+ "ONBOARDED" BOOLEAN NOT NULL,
+ "HOMEPAGE_TYPE" VARCHAR(40),
+ "HOMEPAGE_PARAMETER" VARCHAR(40),
+ "LAST_CONNECTION_DATE" BIGINT,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "RESET_PASSWORD" BOOLEAN NOT NULL
+);
+ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS"("UPDATED_AT");
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_ID" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER", "EXTERNAL_ID");
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_LOGIN" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER", "EXTERNAL_LOGIN");
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
index b625d1aead5..a6d51d11204 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
@@ -52,6 +52,7 @@ public class TelemetryData {
private final Boolean hasUnanalyzedC;
private final Boolean hasUnanalyzedCpp;
private final List<String> customSecurityConfigs;
+ private final long sonarlintWeeklyUsers;
private TelemetryData(Builder builder) {
serverId = builder.serverId;
@@ -62,6 +63,7 @@ public class TelemetryData {
projectCount = builder.projectMeasuresStatistics.getProjectCount();
usingBranches = builder.usingBranches;
database = builder.database;
+ sonarlintWeeklyUsers = builder.sonarlintWeeklyUsers;
projectCountByLanguage = builder.projectMeasuresStatistics.getProjectCountByLanguage();
almIntegrationCountByAlm = builder.almIntegrationCountByAlm;
nclocByLanguage = builder.projectMeasuresStatistics.getNclocByLanguage();
@@ -93,6 +95,10 @@ public class TelemetryData {
return ncloc;
}
+ public long sonarlintWeeklyUsers() {
+ return sonarlintWeeklyUsers;
+ }
+
public long getUserCount() {
return userCount;
}
@@ -169,6 +175,7 @@ public class TelemetryData {
private String serverId;
private String version;
private long userCount;
+ private long sonarlintWeeklyUsers;
private Map<String, String> plugins;
private Database database;
private ProjectMeasuresStatistics projectMeasuresStatistics;
@@ -201,6 +208,11 @@ public class TelemetryData {
return this;
}
+ Builder setSonarlintWeeklyUsers(long sonarlintWeeklyUsers) {
+ this.sonarlintWeeklyUsers = sonarlintWeeklyUsers;
+ return this;
+ }
+
Builder setServerId(String serverId) {
this.serverId = serverId;
return this;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
index e675e1dc101..f427f0c5e28 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
@@ -105,6 +105,7 @@ public class TelemetryDataJsonWriter {
});
json.endArray();
+ json.prop("sonarlintWeeklyUsers", statistics.sonarlintWeeklyUsers());
if (statistics.getInstallationDate() != null) {
json.prop("installationDate", statistics.getInstallationDate());
}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
index ca3e14f774c..2b0c918d9c5 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
@@ -59,6 +59,7 @@ public class TelemetryDataJsonWriterTest {
.setNcloc(42L)
.setExternalAuthenticationProviders(asList("github", "gitlab"))
.setProjectCountByScm(Collections.emptyMap())
+ .setSonarlintWeeklyUsers(10)
.setDatabase(new TelemetryData.Database("H2", "11"))
.setUsingBranches(true);
@@ -97,6 +98,15 @@ public class TelemetryDataJsonWriterTest {
}
@Test
+ public void write_sonarlint_weekly_users() {
+ TelemetryData data = SOME_TELEMETRY_DATA.build();
+
+ String json = writeTelemetryData(data);
+
+ assertJson(json).isSimilarTo("{ \"sonarlintWeeklyUsers\": 10 }");
+ }
+
+ @Test
@UseDataProvider("allEditions")
public void writes_edition_if_non_null(EditionProvider.Edition edition) {
TelemetryData data = SOME_TELEMETRY_DATA
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
index c0481b08875..9bf08d2ec8e 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
@@ -76,6 +76,12 @@ public abstract class AbstractUserSession implements UserSession {
}
@Override
+ @CheckForNull
+ public Long getLastSonarlintConnectionDate() {
+ return null;
+ }
+
+ @Override
public final boolean hasPermission(GlobalPermission permission) {
return isRoot() || hasPermissionImpl(permission);
}
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java
index cf409135538..c0909a7c686 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java
@@ -55,7 +55,7 @@ public class ServerUserSession extends AbstractUserSession {
private Set<GlobalPermission> permissions;
private Map<String, Set<String>> permissionsByProjectUuid;
- ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) {
+ public ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) {
this.dbClient = dbClient;
this.userDto = userDto;
}
@@ -71,6 +71,12 @@ public class ServerUserSession extends AbstractUserSession {
@Override
@CheckForNull
+ public Long getLastSonarlintConnectionDate() {
+ return userDto == null ? null : userDto.getLastSonarlintConnectionDate();
+ }
+
+ @Override
+ @CheckForNull
public String getLogin() {
return userDto == null ? null : userDto.getLogin();
}
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
index 8ca25ad35a3..d9b06b04c3f 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
@@ -58,6 +58,12 @@ public class ThreadLocalUserSession implements UserSession {
@Override
@CheckForNull
+ public Long getLastSonarlintConnectionDate() {
+ return get().getLastSonarlintConnectionDate();
+ }
+
+ @Override
+ @CheckForNull
public String getLogin() {
return get().getLogin();
}
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
index 285c1f21f97..4bd33d3c0a5 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
@@ -56,6 +56,9 @@ public interface UserSession {
@CheckForNull
String getName();
+ @CheckForNull
+ Long getLastSonarlintConnectionDate();
+
/**
* The groups that the logged-in user is member of. An empty
* collection is returned if {@link #isLoggedIn()} is {@code false}.
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
index 827b94189aa..0d21463a0d5 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
@@ -100,6 +100,12 @@ public class ServerUserSessionTest {
}
@Test
+ public void getLastSonarlintConnectionDate() {
+ UserDto user = db.users().insertUser(p -> p.setLastSonarlintConnectionDate(1000L));
+ assertThat(newUserSession(user).getLastSonarlintConnectionDate()).isEqualTo(1000L);
+ }
+
+ @Test
public void getGroups_keeps_groups_in_cache() {
UserDto user = db.users().insertUser();
GroupDto group1 = db.users().insertGroup();
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
index 85fb7fbb27c..1183f5b0054 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
@@ -53,11 +53,13 @@ public class ThreadLocalUserSessionTest {
MockUserSession expected = new MockUserSession("karadoc")
.setUuid("karadoc-uuid")
.setResetPassword(true)
+ .setLastSonarlintConnectionDate(1000L)
.setGroups(group);
threadLocalUserSession.set(expected);
UserSession session = threadLocalUserSession.get();
assertThat(session).isSameAs(expected);
+ assertThat(threadLocalUserSession.getLastSonarlintConnectionDate()).isEqualTo(1000L);
assertThat(threadLocalUserSession.getLogin()).isEqualTo("karadoc");
assertThat(threadLocalUserSession.getUuid()).isEqualTo("karadoc-uuid");
assertThat(threadLocalUserSession.isLoggedIn()).isTrue();
diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
index 58d77309478..413ba96e679 100644
--- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
+++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
@@ -23,6 +23,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
+import javax.annotation.CheckForNull;
+import org.jetbrains.annotations.Nullable;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.user.AbstractUserSession;
@@ -41,6 +43,7 @@ public class MockUserSession extends AbstractMockUserSession<MockUserSession> {
private List<GroupDto> groups = new ArrayList<>();
private UserSession.IdentityProvider identityProvider;
private UserSession.ExternalIdentity externalIdentity;
+ private Long lastSonarlintConnectionDate;
public MockUserSession(String login) {
super(MockUserSession.class);
@@ -62,6 +65,17 @@ public class MockUserSession extends AbstractMockUserSession<MockUserSession> {
this.externalIdentity = identity.getExternalIdentity();
}
+ @CheckForNull
+ @Override
+ public Long getLastSonarlintConnectionDate() {
+ return lastSonarlintConnectionDate;
+ }
+
+ public MockUserSession setLastSonarlintConnectionDate(@Nullable Long lastSonarlintConnectionDate) {
+ this.lastSonarlintConnectionDate = lastSonarlintConnectionDate;
+ return this;
+ }
+
@Override
public boolean isLoggedIn() {
return true;
diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
index 6301f90f2fd..d85fa44e2c2 100644
--- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
+++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
@@ -279,6 +279,12 @@ public class UserSessionRule implements TestRule, UserSession {
}
@Override
+ @CheckForNull
+ public Long getLastSonarlintConnectionDate() {
+ return currentUserSession.getLastSonarlintConnectionDate();
+ }
+
+ @Override
public Collection<GroupDto> getGroups() {
return currentUserSession.getGroups();
}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
index c944b32e857..16354f7b359 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
@@ -144,6 +144,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
.stream()
.collect(Collectors.toMap(ProjectCountPerAnalysisPropertyValue::getPropertyValue, ProjectCountPerAnalysisPropertyValue::getCount));
data.setProjectCountByScm(projectCountPerScmDetected);
+ data.setSonarlintWeeklyUsers(dbClient.userDao().countSonarlintWeeklyUsers(dbSession));
}
setSecurityCustomConfigIfPresent(data);
@@ -153,7 +154,6 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
Optional<String> installationVersionProperty = internalProperties.read(InternalProperties.INSTALLATION_VERSION);
data.setInstallationVersion(installationVersionProperty.orElse(null));
data.setInDocker(dockerSupport.isRunningInDocker());
-
return data.build();
}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java
index 560e4461f19..e22bbe0cd20 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/ClusterSystemInfoWriterTest.java
@@ -73,8 +73,9 @@ public class ClusterSystemInfoWriterTest {
+ "\"Application Nodes\":[{\"Name\":\"appNodes\",\"\":{\"name\":\"appNodes\"}}],"
+ "\"Search Nodes\":[{\"Name\":\"searchNodes\",\"\":{\"name\":\"searchNodes\"}}],"
+ "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],"
- + "\"userCount\":0,\"projectCount\":0,\"usingBranches\":false,\"ncloc\":0,\"projectCountByLanguage\":[]," +
- "\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
+ + "\"userCount\":0,\"projectCount\":0,\"usingBranches\":false,\"ncloc\":0,\"projectCountByLanguage\":[],"
+ + "\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"sonarlintWeeklyUsers\":0,\"installationDate\":0,"
+ + "\"installationVersion\":\"\",\"docker\":false}}");
}
private static NodeInfo createNodeInfo(String name) {
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java
index c22757e9e81..d9e68091ae1 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StandaloneSystemInfoWriterTest.java
@@ -80,7 +80,8 @@ public class StandaloneSystemInfoWriterTest {
// response does not contain empty "Section Three"
assertThat(writer).hasToString("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
"\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],\"userCount\":0,\"projectCount\":0,\"usingBranches\":false," +
- "\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
+ "\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[],\"almIntegrationCount\":[],\"externalAuthProviders\":[],\"projectCountByScm\":[],"
+ + "\"sonarlintWeeklyUsers\":0,\"installationDate\":0,\"installationVersion\":\"\",\"docker\":false}}");
}
private void logInAsSystemAdministrator() {
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java
index dd78c60e233..4ec49748d9b 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java
@@ -66,15 +66,17 @@ import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_K
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
public class TelemetryDataLoaderImplTest {
+ private final static Long NOW = 100_000_000L;
+ private final TestSystem2 system2 = new TestSystem2().setNow(NOW);
+
@Rule
- public DbTester db = DbTester.create();
+ public DbTester db = DbTester.create(system2);
@Rule
public EsTester es = EsTester.create();
private final FakeServer server = new FakeServer();
private final PluginRepository pluginRepository = mock(PluginRepository.class);
private final Configuration configuration = mock(Configuration.class);
- private final TestSystem2 system2 = new TestSystem2().setNow(System.currentTimeMillis());
private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
private final DockerSupport dockerSupport = mock(DockerSupport.class);
private final InternalProperties internalProperties = spy(new MapInternalProperties());
@@ -202,6 +204,18 @@ public class TelemetryDataLoaderImplTest {
}
@Test
+ public void data_contains_weekly_count_sonarlint_users() {
+ db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 100_000L));
+ db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW));
+ // these don't count
+ db.users().insertUser(c -> c.setLastSonarlintConnectionDate(NOW - 1_000_000_000L));
+ db.users().insertUser();
+
+ TelemetryData data = communityUnderTest.load();
+ assertThat(data.sonarlintWeeklyUsers()).isEqualTo(2L);
+ }
+
+ @Test
public void data_has_license_type_on_commercial_edition_if_no_license() {
String licenseType = randomAlphabetic(12);
LicenseReader.License license = mock(LicenseReader.License.class);
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 528fd1c473c..c3d821a8353 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -133,6 +133,7 @@ import org.sonar.server.platform.ClusterVerification;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.SystemInfoWriterModule;
import org.sonar.server.platform.WebCoreExtensionsInstaller;
+import org.sonar.server.platform.web.SonarLintConnectionFilter;
import org.sonar.server.platform.web.WebServiceFilter;
import org.sonar.server.platform.web.WebServiceReroutingFilter;
import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
@@ -338,6 +339,7 @@ public class PlatformLevel4 extends PlatformLevel {
// web services
WebServiceEngine.class,
WebServicesWsModule.class,
+ SonarLintConnectionFilter.class,
WebServiceFilter.class,
WebServiceReroutingFilter.class,
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java
new file mode 100644
index 00000000000..2b11c6a9328
--- /dev/null
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SonarLintConnectionFilter.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.web;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Optional;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.ServletFilter;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.ServletRequest;
+
+public class SonarLintConnectionFilter extends ServletFilter {
+ private static final UrlPattern URL_PATTERN = UrlPattern.builder()
+ .includes("/api/*")
+ .build();
+ private final DbClient dbClient;
+ private final UserSession userSession;
+ private final System2 system2;
+
+ public SonarLintConnectionFilter(DbClient dbClient, UserSession userSession, System2 system2) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ this.system2 = system2;
+ }
+
+ @Override
+ public UrlPattern doGetPattern() {
+ return URL_PATTERN;
+ }
+
+ @Override
+ public void doFilter(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ ServletRequest wsRequest = new ServletRequest(request);
+
+ Optional<String> agent = wsRequest.header("User-Agent");
+ if (agent.isPresent() && agent.get().toLowerCase(Locale.ENGLISH).contains("sonarlint")) {
+ update();
+ }
+ chain.doFilter(servletRequest, servletResponse);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ // Nothing to do
+ }
+
+ @Override
+ public void destroy() {
+ // Nothing to do
+ }
+
+ public void update() {
+ if (shouldUpdate()) {
+ try (DbSession session = dbClient.openSession(false)) {
+ dbClient.userDao().updateSonarlintLastConnectionDate(session, userSession.getLogin());
+ session.commit();
+ }
+ }
+ }
+
+ private boolean shouldUpdate() {
+ if (!userSession.isLoggedIn()) {
+ return false;
+ }
+ long now = system2.now();
+ Long lastUpdate = userSession.getLastSonarlintConnectionDate();
+ return (lastUpdate == null || lastUpdate < now - 3_600_000L);
+ }
+}
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java
index 91225b72daa..794c545a807 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/WebServiceFilter.java
@@ -26,7 +26,6 @@ import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.sonar.api.SonarRuntime;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.web.ServletFilter;
import org.sonar.core.util.stream.MoreCollectors;
diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java
new file mode 100644
index 00000000000..53285dbba9c
--- /dev/null
+++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java
@@ -0,0 +1,122 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.web;
+
+import java.io.IOException;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.user.ServerUserSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SonarLintConnectionFilterTest {
+ private static final String LOGIN = "user1";
+ private final TestSystem2 system2 = new TestSystem2();
+
+ @Rule
+ public DbTester dbTester = DbTester.create(system2);
+
+ @Test
+ public void update() throws IOException, ServletException {
+ system2.setNow(10_000_000L);
+ addUser(LOGIN, 1_000_000L);
+
+ runFilter(LOGIN, "SonarLint for IntelliJ");
+ assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
+ }
+
+ @Test
+ public void update_first_time() throws IOException, ServletException {
+ system2.setNow(10_000_000L);
+ addUser(LOGIN, null);
+
+ runFilter(LOGIN, "SonarLint for IntelliJ");
+ assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
+ }
+
+ @Test
+ public void only_applies_to_api() {
+ SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ServerUserSession.class), system2);
+ assertThat(underTest.doGetPattern().matches("/api/test")).isTrue();
+ assertThat(underTest.doGetPattern().matches("/test")).isFalse();
+
+ }
+
+ @Test
+ public void do_nothing_if_no_sonarlint_agent() throws IOException, ServletException {
+ system2.setNow(10_000L);
+ addUser(LOGIN, 1_000L);
+
+ runFilter(LOGIN, "unknown");
+ runFilter(LOGIN, null);
+ assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000L);
+ }
+
+ @Test
+ public void do_nothing_if_not_logged_in() throws IOException, ServletException {
+ system2.setNow(10_000_000L);
+ addUser("invalid", 1_000_000L);
+
+ runFilter(LOGIN, "SonarLint for IntelliJ");
+ assertThat(getLastUpdate("invalid")).isEqualTo(1_000_000L);
+ }
+
+ @Test
+ public void only_update_if_not_updated_within_1h() throws IOException, ServletException {
+ system2.setNow(2_000_000L);
+ addUser(LOGIN, 1_000_000L);
+
+ runFilter(LOGIN, "SonarLint for IntelliJ");
+ assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000_000L);
+ }
+
+ private void addUser(String login, @Nullable Long lastUpdate) {
+ dbTester.users().insertUser(u -> u.setLogin(login).setLastSonarlintConnectionDate(lastUpdate));
+ }
+
+ @CheckForNull
+ private Long getLastUpdate(String login) {
+ return dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), login).getLastSonarlintConnectionDate();
+ }
+
+ private void runFilter(String loggedInUser, @Nullable String agent) throws IOException, ServletException {
+ UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser);
+ ServerUserSession session = new ServerUserSession(dbTester.getDbClient(), user);
+ SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2);
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getHeader("User-Agent")).thenReturn(agent);
+ FilterChain chain = mock(FilterChain.class);
+ underTest.doFilter(request, mock(ServletResponse.class), chain);
+ verify(chain).doFilter(any(), any());
+ }
+}