checkArgument(values.size() <= PARTITION_SIZE_FOR_ORACLE, message);
}
}
+
}
mapper(session).updateSonarlintLastConnectionDate(login, system2.now());
}
- public void dismissSonarlintAd(DbSession session, String login) {
- mapper(session).dismissSonarlintAd(login);
- }
-
public void deactivateUser(DbSession dbSession, UserDto user) {
mapper(dbSession).deactivateUser(user.getLogin(), system2.now());
auditPersister.deactivateUser(dbSession, new UserNewValue(user.getUuid(), user.getLogin()));
private String homepageParameter;
private boolean local = true;
private boolean resetPassword = false;
- private boolean sonarlintAdSeen = false;
/**
* Date of the last time the user has accessed to the server.
return this;
}
- public boolean isSonarlintAdSeen() {
- return sonarlintAdSeen;
- }
-
- public UserDto setSonarlintAdSeen(boolean sonarlintAdSeen) {
- this.sonarlintAdSeen = sonarlintAdSeen;
- return this;
- }
-
@CheckForNull
public Long getLastConnectionDate() {
return lastConnectionDate;
void clearHomepage(@Param("login") String login, @Param("now") long now);
long countActiveSonarlintUsers(@Param("sinceDate") long sinceDate);
-
- void dismissSonarlintAd(@Param("login") String login);
}
u.homepage_parameter as "homepageParameter",
u.last_connection_date as "lastConnectionDate",
u.last_sonarlint_connection as "lastSonarlintConnectionDate",
- u.sonarlint_ad_seen as "sonarlintAdSeen",
u.created_at as "createdAt",
u.updated_at as "updatedAt"
</sql>
login = #{login, jdbcType=VARCHAR}
</update>
- <update id="dismissSonarlintAd" parameterType="map">
- update users set
- sonarlint_ad_seen = ${_true}
- where
- login = #{login, jdbcType=VARCHAR}
- </update>
-
<update id="deactivateUser" parameterType="map">
update users set
<include refid="deactivateUserUpdatedFields"/>
crypted_password,
hash_method,
last_sonarlint_connection,
- sonarlint_ad_seen,
reset_password,
homepage_type,
homepage_parameter,
#{user.cryptedPassword,jdbcType=VARCHAR},
#{user.hashMethod,jdbcType=VARCHAR},
#{user.lastSonarlintConnectionDate,jdbcType=BIGINT},
- #{user.sonarlintAdSeen,jdbcType=BOOLEAN},
#{user.resetPassword,jdbcType=BOOLEAN},
#{user.homepageType,jdbcType=VARCHAR},
#{user.homepageParameter,jdbcType=VARCHAR},
homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR},
last_connection_date = #{user.lastConnectionDate,jdbcType=BIGINT},
last_sonarlint_connection = #{user.lastSonarlintConnectionDate,jdbcType=BIGINT},
- sonarlint_ad_seen = #{user.sonarlintAdSeen,jdbcType=BOOLEAN},
updated_at = #{user.updatedAt,jdbcType=BIGINT}
where
uuid = #{user.uuid, jdbcType=VARCHAR}
"CREATED_AT" BIGINT,
"UPDATED_AT" BIGINT,
"RESET_PASSWORD" BOOLEAN NOT NULL,
- "LAST_SONARLINT_CONNECTION" BIGINT,
- "SONARLINT_AD_SEEN" BOOLEAN DEFAULT FALSE
+ "LAST_SONARLINT_CONNECTION" BIGINT
);
ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN" NULLS FIRST);
assertThat(user).isNull();
}
- @Test
- public void dismiss_sonarlint_ad() {
- UserDto user = db.users().insertUser(u -> u.setLogin("user"));
- assertThat(user.isSonarlintAdSeen()).isFalse();
-
- underTest.dismissSonarlintAd(session, "user");
-
- user = db.users().selectUserByLogin("user").get();
-
- assertThat(user.isSonarlintAdSeen()).isTrue();
- }
-
@Test
public void selectUsersByLogins() {
db.users().insertUser(user -> user.setLogin("user1"));
context.execute(new DropColumnsBuilder(getDatabase().getDialect(), tableName, columnName).build());
}
- private boolean checkIfUseManagedColumnExists() throws SQLException {
+ public boolean checkIfUseManagedColumnExists() throws SQLException {
try (var connection = getDatabase().getDataSource().getConnection()) {
if (DatabaseUtils.tableColumnExists(connection, tableName, columnName)) {
return true;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.step;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.dialect.MsSql;
+import org.sonar.server.platform.db.migration.sql.DropColumnsBuilder;
+import org.sonar.server.platform.db.migration.sql.DropMsSQLDefaultConstraintsBuilder;
+
+public class DropColumnWithConstraint extends DropColumnChange {
+
+ private final String tableName;
+ private final String column;
+
+ public DropColumnWithConstraint(Database db, String tableName, String column) {
+ super(db, tableName, column);
+ this.tableName = tableName;
+ this.column = column;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ if (!checkIfUseManagedColumnExists()) {
+ return;
+ }
+
+ if (MsSql.ID.equals(getDatabase().getDialect().getId())) {
+ context.execute(new DropMsSQLDefaultConstraintsBuilder(getDatabase()).setTable(tableName).setColumns(column).build());
+ }
+ context.execute(new DropColumnsBuilder(getDatabase().getDialect(), tableName, column).build());
+ }
+
+}
*/
package org.sonar.server.platform.db.migration.version.v91;
-import java.sql.SQLException;
import org.sonar.db.Database;
-import org.sonar.db.DatabaseUtils;
-import org.sonar.db.dialect.MsSql;
-import org.sonar.server.platform.db.migration.sql.DropColumnsBuilder;
-import org.sonar.server.platform.db.migration.sql.DropMsSQLDefaultConstraintsBuilder;
-import org.sonar.server.platform.db.migration.step.DdlChange;
+import org.sonar.server.platform.db.migration.step.DropColumnWithConstraint;
-public class DropUserManagedColumnFromMetricsTable extends DdlChange {
+public class DropUserManagedColumnFromMetricsTable extends DropColumnWithConstraint {
private static final String TABLE_NAME = "metrics";
private static final String COLUMN = "user_managed";
public DropUserManagedColumnFromMetricsTable(Database db) {
- super(db);
+ super(db, TABLE_NAME, COLUMN);
}
- @Override
- public void execute(Context context) throws SQLException {
- if (!checkIfUseManagedColumnExists()) {
- return;
- }
-
- if (MsSql.ID.equals(getDatabase().getDialect().getId())) {
- context.execute(new DropMsSQLDefaultConstraintsBuilder(getDatabase()).setTable(TABLE_NAME).setColumns(COLUMN).build());
- }
- context.execute(new DropColumnsBuilder(getDatabase().getDialect(), TABLE_NAME, COLUMN).build());
- }
-
- private boolean checkIfUseManagedColumnExists() throws SQLException {
- try (var connection = getDatabase().getDataSource().getConnection()) {
- if (DatabaseUtils.tableColumnExists(connection, TABLE_NAME, COLUMN)) {
- return true;
- }
- }
- return false;
- }
}
.add(6505, "Add column 'rule_description_context_key' to 'issues'", AddRuleDescriptionContextKeyInIssuesTable.class)
.add(6506, "Add column 'education_principles' to 'rules'", AddEducationPrinciplesColumnToRuleTable.class)
.add(6507, "Overwrite plugin file hash to force reloading rules", ForceReloadingOfAllPlugins.class)
+ .add(6508, "Migrate 'sonarlint_ad_seen' from users to properties", MigrateSonarlintAdSeenFromUsersToProperties.class)
+ .add(6509, "Drop column sonarlint_ad_seen in 'users'", DropSonarlintAdSeenColumnInUsersTable.class)
;
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.v96;
+
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DropColumnWithConstraint;
+
+public class DropSonarlintAdSeenColumnInUsersTable extends DropColumnWithConstraint {
+
+ private static final String TABLE_NAME = "users";
+ private static final String COLUMN = "sonarlint_ad_seen";
+
+ public DropSonarlintAdSeenColumnInUsersTable(Database db) {
+ super(db, TABLE_NAME, COLUMN);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.v96;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class MigrateSonarlintAdSeenFromUsersToProperties extends DataChange {
+
+ public static final String USER_DISMISSED_NOTICES_SONARLINT_AD = "user.dismissedNotices.sonarlintAd";
+
+ private final UuidFactory uuidFactory;
+ private final System2 system2;
+
+ public MigrateSonarlintAdSeenFromUsersToProperties(Database db, UuidFactory uuidFactory, System2 system2) {
+ super(db);
+ this.uuidFactory = uuidFactory;
+ this.system2 = system2;
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+
+ massUpdate.select("select u.uuid, u.sonarlint_ad_seen, p.uuid from users u" +
+ " left join properties p on u.uuid = p.user_uuid and p.prop_key = ?" +
+ " where u.sonarlint_ad_seen = ?" +
+ " and p.uuid is null")
+ .setString(1, USER_DISMISSED_NOTICES_SONARLINT_AD)
+ .setBoolean(2, true);
+
+ massUpdate.update("insert into properties (uuid,prop_key,user_uuid,is_empty,created_at) values (?, ?, ?, ?, ?)");
+
+ massUpdate.execute((row, update) -> {
+ update.setString(1, uuidFactory.create());
+ update.setString(2, USER_DISMISSED_NOTICES_SONARLINT_AD);
+ update.setString(3, row.getString(1));
+ update.setBoolean(4, true);
+ update.setLong(5, system2.now());
+
+ return true;
+ });
+
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.v96;
+
+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 DropSonarlintAdSeenColumnInUsersTableTest {
+
+ private static final String COLUMN_NAME = "sonarlint_ad_seen";
+ private static final String TABLE_NAME = "users";
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(DropSonarlintAdSeenColumnInUsersTableTest.class, "schema.sql");
+
+ private final DdlChange underTest = new DropSonarlintAdSeenColumnInUsersTable(db.database());
+
+ @Test
+ public void migration_should_drop_column() throws SQLException {
+ db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.BOOLEAN, null, true);
+ underTest.execute();
+ db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+ }
+
+ @Test
+ public void migration_should_be_reentrant() throws SQLException {
+ db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.BOOLEAN, null, true);
+ underTest.execute();
+ // re-entrant
+ underTest.execute();
+ db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.v96;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.platform.db.migration.version.v96.MigrateSonarlintAdSeenFromUsersToProperties.USER_DISMISSED_NOTICES_SONARLINT_AD;
+
+public class MigrateSonarlintAdSeenFromUsersToPropertiesTest {
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(MigrateSonarlintAdSeenFromUsersToPropertiesTest.class, "schema.sql");
+
+ private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
+ private final System2 system2 = new System2();
+
+ private final DataChange underTest = new MigrateSonarlintAdSeenFromUsersToProperties(db.database(), uuidFactory, system2);
+
+ @Test
+ public void migrate_sonarlintAd_to_properties() throws SQLException {
+ insertUser(db, "uuid-user-1", "user1", "externalId1", "externalLogin1", true);
+ insertUser(db, "uuid-user-2", "user2", "externalId2", "externalLogin2", false);
+
+ underTest.execute();
+
+ assertThat(db.countSql("select count(*) from properties where prop_key='" + USER_DISMISSED_NOTICES_SONARLINT_AD + "' and user_uuid='uuid-user-1'")).isEqualTo(1);
+ assertThat(db.countSql("select count(*) from properties where prop_key='" + USER_DISMISSED_NOTICES_SONARLINT_AD + "' and user_uuid='uuid-user-2'")).isZero();
+ }
+
+ @Test
+ public void migration_is_reentrant() throws SQLException {
+ insertUser(db, "uuid-user-1", "user1", "externalId1", "externalLogin1", true);
+
+ underTest.execute();
+ underTest.execute();
+
+ assertThat(db.countSql("select count(*) from properties where prop_key='" + USER_DISMISSED_NOTICES_SONARLINT_AD + "' and user_uuid='uuid-user-1'")).isEqualTo(1);
+ }
+
+ private void insertUser(CoreDbTester db, String userUuid, String login, String externalId, String externalLogin, boolean seen) {
+ db.executeInsert("users", "UUID", userUuid,
+ "login", login,
+ "external_identity_provider", "none",
+ "external_id", externalId,
+ "external_login", externalLogin,
+ "reset_password", false,
+ "sonarlint_ad_seen", seen
+ );
+ }
+
+
+}
\ No newline at end of file
--- /dev/null
+CREATE TABLE "USERS"(
+ "UUID" CHARACTER VARYING(255) NOT NULL,
+ "LOGIN" CHARACTER VARYING(255) NOT NULL,
+ "NAME" CHARACTER VARYING(200),
+ "EMAIL" CHARACTER VARYING(100),
+ "CRYPTED_PASSWORD" CHARACTER VARYING(100),
+ "SALT" CHARACTER VARYING(40),
+ "HASH_METHOD" CHARACTER VARYING(10),
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" CHARACTER VARYING(4000),
+ "EXTERNAL_LOGIN" CHARACTER VARYING(255) NOT NULL,
+ "EXTERNAL_IDENTITY_PROVIDER" CHARACTER VARYING(100) NOT NULL,
+ "EXTERNAL_ID" CHARACTER VARYING(255) NOT NULL,
+ "USER_LOCAL" BOOLEAN,
+ "HOMEPAGE_TYPE" CHARACTER VARYING(40),
+ "HOMEPAGE_PARAMETER" CHARACTER VARYING(40),
+ "LAST_CONNECTION_DATE" BIGINT,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "RESET_PASSWORD" BOOLEAN NOT NULL,
+ "LAST_SONARLINT_CONNECTION" BIGINT,
+ "SONARLINT_AD_SEEN" BOOLEAN DEFAULT FALSE
+);
+ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN" NULLS FIRST);
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS"("UPDATED_AT" NULLS FIRST);
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_ID" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER" NULLS FIRST, "EXTERNAL_ID" NULLS FIRST);
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_LOGIN" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER" NULLS FIRST, "EXTERNAL_LOGIN" NULLS FIRST);
--- /dev/null
+CREATE TABLE "USERS"(
+ "UUID" CHARACTER VARYING(255) NOT NULL,
+ "LOGIN" CHARACTER VARYING(255) NOT NULL,
+ "NAME" CHARACTER VARYING(200),
+ "EMAIL" CHARACTER VARYING(100),
+ "CRYPTED_PASSWORD" CHARACTER VARYING(100),
+ "SALT" CHARACTER VARYING(40),
+ "HASH_METHOD" CHARACTER VARYING(10),
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" CHARACTER VARYING(4000),
+ "EXTERNAL_LOGIN" CHARACTER VARYING(255) NOT NULL,
+ "EXTERNAL_IDENTITY_PROVIDER" CHARACTER VARYING(100) NOT NULL,
+ "EXTERNAL_ID" CHARACTER VARYING(255) NOT NULL,
+ "USER_LOCAL" BOOLEAN,
+ "HOMEPAGE_TYPE" CHARACTER VARYING(40),
+ "HOMEPAGE_PARAMETER" CHARACTER VARYING(40),
+ "LAST_CONNECTION_DATE" BIGINT,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "RESET_PASSWORD" BOOLEAN NOT NULL,
+ "LAST_SONARLINT_CONNECTION" BIGINT,
+ "SONARLINT_AD_SEEN" BOOLEAN DEFAULT FALSE
+);
+ALTER TABLE "USERS" ADD CONSTRAINT "PK_USERS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS"("LOGIN" NULLS FIRST);
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS"("UPDATED_AT" NULLS FIRST);
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_ID" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER" NULLS FIRST, "EXTERNAL_ID" NULLS FIRST);
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_LOGIN" ON "USERS"("EXTERNAL_IDENTITY_PROVIDER" NULLS FIRST, "EXTERNAL_LOGIN" NULLS FIRST);
+
+
+CREATE TABLE "PROPERTIES"(
+ "UUID" CHARACTER VARYING(40) NOT NULL,
+ "PROP_KEY" CHARACTER VARYING(512) NOT NULL,
+ "IS_EMPTY" BOOLEAN NOT NULL,
+ "TEXT_VALUE" CHARACTER VARYING(4000),
+ "CLOB_VALUE" CHARACTER LARGE OBJECT,
+ "CREATED_AT" BIGINT NOT NULL,
+ "COMPONENT_UUID" CHARACTER VARYING(40),
+ "USER_UUID" CHARACTER VARYING(255)
+);
+ALTER TABLE "PROPERTIES" ADD CONSTRAINT "PK_PROPERTIES" PRIMARY KEY("UUID");
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES"("PROP_KEY" NULLS FIRST);
import static org.apache.commons.lang.StringUtils.EMPTY;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.server.user.ws.DismissNoticeAction.EDUCATION_PRINCIPLES;
+import static org.sonar.server.user.ws.DismissNoticeAction.SONARLINT_AD;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.APPLICATION;
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PORTFOLIO;
new Change("6.5", "showOnboardingTutorial is now returned in the response"),
new Change("7.1", "'parameter' is replaced by 'component' and 'organization' in the response"),
new Change("9.2", "boolean 'usingSonarLintConnectedMode' and 'sonarLintAdSeen' fields are now returned in the response"),
- new Change("9.5", "showOnboardingTutorial is not returned anymore in the response"));
+ new Change("9.5", "showOnboardingTutorial is not returned anymore in the response"),
+ new Change("9.6", "'sonarLintAdSeen' is removed and replaced by a 'dismissedNotices' map that support multiple values")
+ );
}
@Override
.setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build())
.setHomepage(buildHomepage(dbSession, user))
.setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null)
- .setSonarLintAdSeen(user.isSonarlintAdSeen())
- .putDismissedNotices(EDUCATION_PRINCIPLES, isNoticeDismissed(user, EDUCATION_PRINCIPLES));
+ .putDismissedNotices(EDUCATION_PRINCIPLES, isNoticeDismissed(user, EDUCATION_PRINCIPLES))
+ .putDismissedNotices(SONARLINT_AD, isNoticeDismissed(user, SONARLINT_AD));
ofNullable(emptyToNull(user.getEmail())).ifPresent(builder::setEmail);
ofNullable(emptyToNull(user.getEmail())).ifPresent(u -> builder.setAvatar(avatarResolver.create(user)));
ofNullable(user.getExternalLogin()).ifPresent(builder::setExternalIdentity);
public class DismissNoticeAction implements UsersWsAction {
public static final String EDUCATION_PRINCIPLES = "educationPrinciples";
+ public static final String SONARLINT_AD = "sonarlintAd";
public static final String USER_DISMISS_CONSTANT = "user.dismissedNotices.";
private final UserSession userSession;
@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction("dismiss_notice")
- .setDescription("Dismiss a notice for the current user.")
+ .setDescription("Dismiss a notice for the current user. Silently ignore if the notice is already dismissed.")
.setSince("9.6")
.setInternal(true)
.setHandler(this)
action.createParam("notice")
.setDescription("notice key to dismiss")
.setExampleValue(EDUCATION_PRINCIPLES)
- .setPossibleValues(EDUCATION_PRINCIPLES);
+ .setPossibleValues(EDUCATION_PRINCIPLES, SONARLINT_AD);
}
@Override
String noticeKeyParam = request.mandatoryParam("notice");
+ dismissNotice(response, currentUserUuid, noticeKeyParam);
+ }
+
+ public void dismissNotice(Response response, String currentUserUuid, String noticeKeyParam) {
try (DbSession dbSession = dbClient.openSession(false)) {
String paramKey = USER_DISMISS_CONSTANT + noticeKeyParam;
PropertyQuery query = new PropertyQuery.Builder()
.build();
if (!dbClient.propertiesDao().selectByQuery(query, dbSession).isEmpty()) {
- throw new IllegalArgumentException(String.format("Notice %s is already dismissed", noticeKeyParam));
+ // already dismissed
+ response.noContent();
}
PropertyDto property = new PropertyDto().setUserUuid(currentUserUuid).setKey(paramKey);
dbSession.commit();
response.noContent();
}
-
}
}
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
import org.sonar.server.user.UserSession;
+import static org.sonar.server.user.ws.DismissNoticeAction.SONARLINT_AD;
import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_DISMISS_SONARLINT_AD;
+/**
+ * @deprecated use DismissNoticeAction
+ */
+@Deprecated(since = "9.6", forRemoval = true)
public class DismissSonarlintAdAction implements UsersWsAction {
private final UserSession userSession;
- private final DbClient dbClient;
+ private final DismissNoticeAction dismissNoticeAction;
- public DismissSonarlintAdAction(UserSession userSession, DbClient dbClient) {
+ public DismissSonarlintAdAction(UserSession userSession, DismissNoticeAction dismissNoticeAction) {
this.userSession = userSession;
- this.dbClient = dbClient;
+ this.dismissNoticeAction = dismissNoticeAction;
}
@Override
public void define(WebService.NewController controller) {
controller.createAction(ACTION_DISMISS_SONARLINT_AD)
- .setDescription("Dismiss SonarLint advertisement.")
+ .setDescription("Dismiss SonarLint advertisement. Deprecated since 9.6, replaced api/users/dismiss_notice")
.setSince("9.2")
.setPost(true)
+ .setDeprecatedSince("9.6")
.setHandler(this);
}
@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn();
- try (DbSession dbSession = dbClient.openSession(false)) {
- dbClient.userDao().dismissSonarlintAd(dbSession, userSession.getLogin());
- dbSession.commit();
- }
- response.noContent();
+ dismissNoticeAction.dismissNotice(response, userSession.getUuid(), SONARLINT_AD);
}
}
"login": "obiwan.kenobi",
"name": "Obiwan Kenobi",
"email": "obiwan.kenobi@starwars.com",
- "avatar": "f5aa64437a1821ffe8b563099d506aef",
"local": true,
"externalIdentity": "obiwan.kenobi",
"externalProvider": "sonarqube",
"scmAccounts": ["obiwan:github", "obiwan:bitbucket"],
"groups": ["Jedi", "Rebel"],
- "usingSonarLintConnectedMode": false,
- "sonarLintAdSeen": false,
"permissions": {
"global": ["profileadmin", "scan"]
},
+ "avatar": "f5aa64437a1821ffe8b563099d506aef",
"homepage": {
"type": "PROJECT",
"component": "death-star-key"
+ },
+ "usingSonarLintConnectedMode": false,
+ "dismissedNotices": {
+ "educationPrinciples": false,
+ "sonarlintAd": false
}
}
assertThat(response)
.extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal,
- CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getUsingSonarLintConnectedMode, CurrentWsResponse::getSonarLintAdSeen)
- .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false, false);
+ CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getUsingSonarLintConnectedMode)
+ .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false);
assertThat(response.hasEmail()).isFalse();
assertThat(response.getScmAccountsList()).isEmpty();
assertThat(response.getGroupsList()).isEmpty();
}
@Test
- public void handle_givenSonarLintAdSeenUserInDatabase_returnSonarLintAdSeenUserFromTheEndpoint() {
- UserDto user = db.users().insertUser(u -> u.setSonarlintAdSeen(true));
+ public void return_sonarlintAd_dismiss_notice() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
+ PropertyDto property = new PropertyDto().setUserUuid(user.getUuid()).setKey("user.dismissedNotices.sonarlintAd");
+ db.properties().insertProperties(userSession.getLogin(), null, null, null, property);
+
+ CurrentWsResponse response = call();
+
+ assertThat(response.getDismissedNoticesMap().entrySet())
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .contains(Tuple.tuple("sonarlintAd", true));
+ }
+
+ @Test
+ public void return_sonarlintAd_not_dismissed() {
+ UserDto user = db.users().insertUser();
userSession.logIn(user);
CurrentWsResponse response = call();
- assertThat(response.getSonarLintAdSeen()).isTrue();
+ assertThat(response.getDismissedNoticesMap().entrySet())
+ .extracting(Map.Entry::getKey, Map.Entry::getValue)
+ .contains(Tuple.tuple("sonarlintAd", false));
}
+
@Test
public void test_definition() {
WebService.Action definition = ws.getDef();
assertThat(definition.isInternal()).isTrue();
assertThat(definition.responseExampleAsString()).isNotEmpty();
assertThat(definition.params()).isEmpty();
- assertThat(definition.changelog()).hasSize(4);
+ assertThat(definition.changelog()).isNotEmpty();
}
private CurrentWsResponse call() {
assertThat(propertyDto).isPresent();
}
+ @Test
+ public void dismiss_sonarlintAd() {
+ userSessionRule.logIn();
+
+ TestResponse testResponse = tester.newRequest()
+ .setParam("notice", "sonarlintAd")
+ .execute();
+
+ assertThat(testResponse.getStatus()).isEqualTo(204);
+
+ Optional<PropertyDto> propertyDto = db.properties().findFirstUserProperty(userSessionRule.getUuid(), "user.dismissedNotices.sonarlintAd");
+ assertThat(propertyDto).isPresent();
+ }
+
@Test
public void authentication_is_required() {
assertThatThrownBy(testRequest::execute)
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Value of parameter 'notice' (not_supported_value) must be one of: [educationPrinciples]");
+ .hasMessage("Value of parameter 'notice' (not_supported_value) must be one of: [educationPrinciples, sonarlintAd]");
}
@Test
- public void notice_already_exist() {
+ public void notice_already_exist_dont_fail() {
userSessionRule.logIn();
PropertyDto property = new PropertyDto().setKey("user.dismissedNotices.educationPrinciples").setUserUuid(userSessionRule.getUuid());
db.properties().insertProperties(userSessionRule.getLogin(), null, null, null, property);
+ assertThat(db.properties().findFirstUserProperty(userSessionRule.getUuid(), "user.dismissedNotices.educationPrinciples")).isPresent();
- TestRequest testRequest = tester.newRequest()
- .setParam("notice", "educationPrinciples");
+ TestResponse testResponse = tester.newRequest()
+ .setParam("notice", "sonarlintAd")
+ .execute();
- assertThatThrownBy(testRequest::execute)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Notice educationPrinciples is already dismissed");
+ assertThat(testResponse.getStatus()).isEqualTo(204);
+ assertThat(db.properties().findFirstUserProperty(userSessionRule.getUuid(), "user.dismissedNotices.educationPrinciples")).isPresent();
}
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
- private final WsActionTester tester = new WsActionTester(new DismissSonarlintAdAction(userSession, db.getDbClient()));
+ private final DismissNoticeAction dismissNoticeAction = new DismissNoticeAction(userSession, db.getDbClient());
+ private final WsActionTester underTest = new WsActionTester(new DismissSonarlintAdAction(userSession, dismissNoticeAction));
@Test
public void test_definition() {
- WebService.Action definition = tester.getDef();
+ WebService.Action definition = underTest.getDef();
assertThat(definition.key()).isEqualTo(ACTION_DISMISS_SONARLINT_AD);
- assertThat(definition.description()).isEqualTo("Dismiss SonarLint advertisement.");
+ assertThat(definition.description()).isEqualTo("Dismiss SonarLint advertisement. Deprecated since 9.6, replaced api/users/dismiss_notice");
assertThat(definition.since()).isEqualTo("9.2");
assertThat(definition.isPost()).isTrue();
assertThat(definition.params()).isEmpty();
@Test
public void endpoint_throw_exception_if_no_user_login() {
- final TestRequest request = tester.newRequest();
+ final TestRequest request = underTest.newRequest();
assertThatThrownBy(request::execute)
.isInstanceOf(UnauthorizedException.class);
}
.setName("Obiwan Kenobi")
.setEmail(null));
userSession.logIn(user);
- assertThat(user.isSonarlintAdSeen()).isFalse();
+ assertThat(db.properties().findFirstUserProperty(userSession.getUuid(), "user.dismissedNotices.sonarlintAd")).isEmpty();
- tester.newRequest().execute();
+ underTest.newRequest().execute();
UserDto updatedUser = db.users().selectUserByLogin(user.getLogin()).get();
- assertThat(updatedUser.isSonarlintAdSeen()).isTrue();
+ assertThat(db.properties().findFirstUserProperty(userSession.getUuid(), "user.dismissedNotices.sonarlintAd")).isPresent();
}
}
).content();
}
- /**
- *
- * This is part of the internal API.
- * This is a POST request.
- * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/dismiss_sonarlint_ad">Further information about this action online (including a response example)</a>
- * @since 9.2
- */
- public void dismissSonarlintAd() {
- call(
- new PostRequest(path("dismiss_sonarlint_ad"))
- .setMediaType(MediaTypes.JSON)
- ).content();
- }
-
-
/**
*
* This is part of the internal API.
optional Homepage homepage = 13;
reserved 15; // settings removed
optional bool usingSonarLintConnectedMode = 16;
- optional bool sonarLintAdSeen = 17;
+ reserved 17; // sonarLintAdSeen removed
map<string,bool> dismissedNotices = 18;
message Permissions {