diff options
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()); + } +} |