Sfoglia il codice sorgente

SONAR-10345 Migrate webhooks from PROPERTIES table to WEBHOOKS table.

tags/7.5
Guillaume Jambet 6 anni fa
parent
commit
8edd835fae
16 ha cambiato i file con 620 aggiunte e 66 eliminazioni
  1. 6
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java
  2. 1
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java
  3. 241
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTable.java
  4. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java
  5. 205
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTableTest.java
  6. 76
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTableTest/migrate_webhooks.sql
  7. 1
    7
      server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java
  8. 8
    18
      server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
  9. 0
    1
      server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java
  10. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
  11. 4
    4
      server/sonar-server/src/main/java/org/sonar/server/webhook/ws/ListAction.java
  12. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java
  13. 21
    2
      server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java
  14. 26
    1
      server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java
  15. 23
    23
      server/sonar-server/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java
  16. 4
    4
      sonar-ws/src/main/protobuf/ws-webhooks.proto

+ 6
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java Vedi File

@@ -41,11 +41,15 @@ public class WebhookDao implements Dao {
return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid));
}

public List<WebhookDto> selectByOrganizationUuid(DbSession dbSession, OrganizationDto organizationDto) {
public List<WebhookDto> selectByOrganization(DbSession dbSession, OrganizationDto organizationDto) {
return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationDto.getUuid());
}

public List<WebhookDto> selectByProjectUuid(DbSession dbSession, ComponentDto componentDto) {
public List<WebhookDto> selectByOrganizationUuid(DbSession dbSession, String organizationUuid) {
return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationUuid);
}

public List<WebhookDto> selectByProject(DbSession dbSession, ComponentDto componentDto) {
return mapper(dbSession).selectForProjectUuidOrderedByName(componentDto.uuid());
}


+ 1
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71.java Vedi File

@@ -41,6 +41,7 @@ public class DbVersion71 implements DbVersion {
.add(2011, "Drop table PROJECT_LINKS", DropTableProjectLinks.class)
.add(2012, "Rename table PROJECT_LINKS2 to PROJECT_LINKS", RenameTableProjectLinks2ToProjectLinks.class)
.add(2013, "Create WEBHOOKS Table", CreateWebhooksTable.class)
.add(2014, "Migrate webhooks from SETTINGS table to WEBHOOKS table", MigrateWebhooksToWebhooksTable.class)
;
}
}

+ 241
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTable.java Vedi File

@@ -0,0 +1,241 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v71;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
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.version.v63.DefaultOrganizationUuidProvider;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

public class MigrateWebhooksToWebhooksTable extends DataChange {

private static final Logger LOGGER = Loggers.get(MigrateWebhooksToWebhooksTable.class);

private DefaultOrganizationUuidProvider defaultOrganizationUuidProvider;
private UuidFactory uuidFactory;

public MigrateWebhooksToWebhooksTable(Database db, DefaultOrganizationUuidProvider defaultOrganizationUuidProvider, UuidFactory uuidFactory) {
super(db);
this.defaultOrganizationUuidProvider = defaultOrganizationUuidProvider;
this.uuidFactory = uuidFactory;
}

@Override
public void execute(Context context) throws SQLException {
Map<String, PropertyRow> rows = context
.prepareSelect("select id, prop_key, resource_id, text_value, created_at from properties where prop_key like 'sonar.webhooks%'")
.list(row -> new PropertyRow(
row.getLong(1),
row.getString(2),
row.getLong(3),
row.getString(4),
row.getLong(5)))
.stream()
.collect(toMap(PropertyRow::key, Function.identity()));

if (!rows.isEmpty()) {
migrateGlobalWebhooks(context, rows);
migrateProjectsWebhooks(context, rows);
context
.prepareUpsert("delete from properties where prop_key like 'sonar.webhooks.global%' or prop_key like 'sonar.webhooks.project%'")
.execute()
.commit();
}
}

private void migrateProjectsWebhooks(Context context, Map<String, PropertyRow> properties) throws SQLException {
PropertyRow index = properties.get("sonar.webhooks.project");
if (index != null) {
// can't lambda due to checked exception.
for (Webhook webhook : extractProjectWebhooksFrom(context, properties, index.value().split(","))) {
insert(context, webhook);
}
}
}

private void migrateGlobalWebhooks(Context context, Map<String, PropertyRow> properties) throws SQLException {
PropertyRow index = properties.get("sonar.webhooks.global");
if (index != null) {
// can't lambda due to checked exception.
for (Webhook webhook : extractGlobalWebhooksFrom(context, properties, index.value().split(","))) {
insert(context, webhook);
}
}
}

private void insert(Context context, Webhook webhook) throws SQLException {
if (webhook.isValid()) {
context.prepareUpsert("insert into webhooks (uuid, name, url, organization_uuid, project_uuid, created_at, updated_at) values (?, ?, ?, ?, ?, ?, ?)")
.setString(1, uuidFactory.create())
.setString(2, webhook.name())
.setString(3, webhook.url())
.setString(4, webhook.organisationUuid())
.setString(5, webhook.projectUuid())
.setLong(6, webhook.createdAt())
.setLong(7, webhook.createdAt())
.execute()
.commit();
} else {
LOGGER.info("Unable to migrate inconsistent webhook (entry deleted from PROPERTIES) : " + webhook);
}
}

private List<Webhook> extractGlobalWebhooksFrom(Context context, Map<String, PropertyRow> properties, String[] values) throws SQLException {
String defaultOrganizationUuid = defaultOrganizationUuidProvider.get(context);
return Arrays.stream(values)
.map(value -> new Webhook(
properties.get("sonar.webhooks.global." + value + ".name"),
properties.get("sonar.webhooks.global." + value + ".url"),
defaultOrganizationUuid, null))
.collect(toList());
}

private static List<Webhook> extractProjectWebhooksFrom(Context context, Map<String, PropertyRow> properties, String[] values) throws SQLException {
List<Webhook> webhooks = new ArrayList<>();
for (String value : values) {
PropertyRow name = properties.get("sonar.webhooks.project." + value + ".name");
PropertyRow url = properties.get("sonar.webhooks.project." + value + ".url");
webhooks.add(new Webhook(name, url, null, projectUuidOf(context, name)));
}
return webhooks;
}

private static String projectUuidOf(Context context, PropertyRow row) throws SQLException {
return context
.prepareSelect("select uuid from projects where id = ?")
.setLong(1, row.resourceId())
.list(row1 -> row1.getString(1)).stream().findFirst().orElse(null);
}

private static class PropertyRow {

private final Long id;
private final String key;
private final Long resourceId;
private final String value;
private final Long createdAt;

public PropertyRow(long id, String key, Long resourceId, String value, Long createdAt) {
this.id = id;
this.key = key;
this.resourceId = resourceId;
this.value = value;
this.createdAt = createdAt;
}

public Long id() {
return id;
}

public String key() {
return key;
}

public Long resourceId() {
return resourceId;
}

public String value() {
return value;
}

public Long createdAt() {
return createdAt;
}

@Override
public String toString() {
return "{" +
"id=" + id +
", key='" + key + '\'' +
", resourceId=" + resourceId +
", value='" + value + '\'' +
", createdAt=" + createdAt +
'}';
}
}

private static class Webhook {

private final PropertyRow name;
private final PropertyRow url;
private String organisationUuid;
private String projectUuid;

public Webhook(@Nullable PropertyRow name, @Nullable PropertyRow url, @Nullable String organisationUuid, @Nullable String projectUuid) {
this.name = name;
this.url = url;
this.organisationUuid = organisationUuid;
this.projectUuid = projectUuid;
}

public String name() {
return name.value();
}

public String url() {
return url.value();
}

public String organisationUuid() {
return organisationUuid;
}

public String projectUuid() {
return projectUuid;
}

public Long createdAt() {
return name.createdAt();
}

public boolean isValid() {
return name != null && url != null && name() != null && url() != null && (organisationUuid() != null || projectUuid() != null) && createdAt() != null;
}

@Override
public String toString() {
final StringBuilder s = new StringBuilder().append("Webhook{").append("name=").append(name);
if (name != null) {
s.append(name.toString());
}
s.append(", url=").append(url);
if (url != null) {
s.append(url.toString());
}
s.append(", organisationUuid='").append(organisationUuid).append('\'')
.append(", projectUuid='").append(projectUuid).append('\'').append('}');
return s.toString();
}
}

}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/DbVersion71Test.java Vedi File

@@ -36,7 +36,7 @@ public class DbVersion71Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 14);
verifyMigrationCount(underTest, 15);
}

}

+ 205
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTableTest.java Vedi File

@@ -0,0 +1,205 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.v71;

import java.sql.SQLException;
import java.util.Map;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.core.util.UuidFactory;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.CoreDbTester;
import org.sonar.server.platform.db.migration.version.v63.DefaultOrganizationUuidProviderImpl;

import static java.lang.Long.parseLong;
import static java.lang.String.valueOf;
import static org.apache.commons.lang.RandomStringUtils.randomNumeric;
import static org.assertj.core.api.Assertions.assertThat;

public class MigrateWebhooksToWebhooksTableTest {

@Rule
public final CoreDbTester dbTester = CoreDbTester.createForSchema(MigrateWebhooksToWebhooksTableTest.class, "migrate_webhooks.sql");
private static final long NOW = 1_500_000_000_000L;
private System2 system2 = new TestSystem2().setNow(NOW);
private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();

private MigrateWebhooksToWebhooksTable underTest = new MigrateWebhooksToWebhooksTable(dbTester.database(), new DefaultOrganizationUuidProviderImpl(), uuidFactory);

@Test
public void should_do_nothing_if_no_webhooks() throws SQLException {

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
}

@Test
public void should_migrate_one_global_webhook() throws SQLException {
String uuid = insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1", null, system2.now());
insertProperty("sonar.webhooks.global.1.name", "a webhook", null, system2.now());
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, system2.now());

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);

Map<String, Object> migrated = dbTester.selectFirst("select * from webhooks");
assertThat(migrated.get("UUID")).isNotNull();
assertThat(migrated.get("NAME")).isEqualTo("a webhook");
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("PROJECT_UUID")).isNull();
assertThat(migrated.get("ORGANIZATION_UUID")).isEqualTo(uuid);
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("CREATED_AT")).isEqualTo(system2.now());
assertThat(migrated.get("UPDATED_AT")).isEqualTo(system2.now());
}

@Test
public void should_migrate_one_project_webhook() throws SQLException {
String organization = insertDefaultOrganization();
String projectId = "156";
String projectUuid = UuidFactoryFast.getInstance().create();
;
insertProject(organization, projectId, projectUuid);

insertProperty("sonar.webhooks.project", "1", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.name", "a webhook", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.url", "http://webhook.com", projectId, system2.now());

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);

Map<String, Object> migrated = dbTester.selectFirst("select * from webhooks");
assertThat(migrated.get("UUID")).isNotNull();
assertThat(migrated.get("NAME")).isEqualTo("a webhook");
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("PROJECT_UUID")).isEqualTo(projectUuid);
assertThat(migrated.get("ORGANIZATION_UUID")).isNull();
assertThat(migrated.get("URL")).isEqualTo("http://webhook.com");
assertThat(migrated.get("CREATED_AT")).isEqualTo(system2.now());
assertThat(migrated.get("UPDATED_AT")).isEqualTo(system2.now());
}

@Test
public void should_migrate_global_webhooks() throws SQLException {
insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1,2", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.url", "http://webhook.com", null, parseLong(randomNumeric(7)));

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
}

@Test
public void should_migrate_only_valid_webhooks() throws SQLException {
insertDefaultOrganization();
insertProperty("sonar.webhooks.global", "1,2,3,4", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.1.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.2.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.3.name", "a webhook", null, parseLong(randomNumeric(7)));
insertProperty("sonar.webhooks.global.3.url", "http://webhook.com", null, parseLong(randomNumeric(7)));
// nothing for 4

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(1);
}

@Test
public void should_migrate_project_webhooks() throws SQLException {
String organization = insertDefaultOrganization();
String projectId = "156";
String projectUuid = UuidFactoryFast.getInstance().create();
;
insertProject(organization, projectId, projectUuid);

insertProperty("sonar.webhooks.project", "1,2", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.name", "a webhook", projectId, system2.now());
insertProperty("sonar.webhooks.project.1.url", "http://webhook.com", projectId, system2.now());
insertProperty("sonar.webhooks.project.2.name", "another webhook", projectId, system2.now());
insertProperty("sonar.webhooks.project.2.url", "http://webhookhookhook.com", projectId, system2.now());

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(2);
}

@Test
public void should_not_migrate_more_than_10_webhooks_per_project() throws SQLException {

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
}

@Test
public void should_not_migrate_more_than_10_global_webhooks() throws SQLException {

underTest.execute();

assertThat(dbTester.countRowsOfTable("properties")).isEqualTo(0);
assertThat(dbTester.countRowsOfTable("webhooks")).isEqualTo(0);
}

private void insertProperty(String key, @Nullable String value, @Nullable String resourceId, Long date) {
dbTester.executeInsert("PROPERTIES",
"id", randomNumeric(7),
"prop_key", valueOf(key),
"text_value", value,
"is_empty", value.isEmpty() ? true : false,
"resource_id", resourceId == null ? null : valueOf(resourceId),
"created_at", valueOf(date));
}

private String insertDefaultOrganization() {
String uuid = UuidFactoryFast.getInstance().create();
dbTester.executeInsert(
"INTERNAL_PROPERTIES",
"KEE", "organization.default",
"IS_EMPTY", "false",
"TEXT_VALUE", uuid);
return uuid;
}

private void insertProject(String organizationUuid, String projectId, String projectUuid) {
dbTester.executeInsert(
"PROJECTS",
"ID", projectId,
"ORGANIZATION_UUID", organizationUuid,
"UUID", projectUuid);
}
}

+ 76
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v71/MigrateWebhooksToWebhooksTableTest/migrate_webhooks.sql Vedi File

@@ -0,0 +1,76 @@
CREATE TABLE "INTERNAL_PROPERTIES" (
"KEE" VARCHAR(20) NOT NULL PRIMARY KEY,
"IS_EMPTY" BOOLEAN NOT NULL,
"TEXT_VALUE" VARCHAR(4000),
"CLOB_VALUE" CLOB,
"CREATED_AT" BIGINT
);


CREATE TABLE "PROJECTS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
"KEE" VARCHAR(400),
"UUID" VARCHAR(50) NOT NULL,
-- "UUID_PATH" VARCHAR(1500) NOT NULL,
-- "ROOT_UUID" VARCHAR(50) NOT NULL,
-- "PROJECT_UUID" VARCHAR(50) NOT NULL,
"MODULE_UUID" VARCHAR(50),
"MODULE_UUID_PATH" VARCHAR(1500),
"MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
"NAME" VARCHAR(2000),
"DESCRIPTION" VARCHAR(2000),
-- "PRIVATE" BOOLEAN NOT NULL,
"TAGS" VARCHAR(500),
-- "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
"SCOPE" VARCHAR(3),
"QUALIFIER" VARCHAR(10),
"DEPRECATED_KEE" VARCHAR(400),
"PATH" VARCHAR(2000),
"LANGUAGE" VARCHAR(20),
"COPY_COMPONENT_UUID" VARCHAR(50),
"LONG_NAME" VARCHAR(2000),
"DEVELOPER_UUID" VARCHAR(50),
"CREATED_AT" TIMESTAMP,
"AUTHORIZATION_UPDATED_AT" BIGINT,
"B_CHANGED" BOOLEAN,
"B_COPY_COMPONENT_UUID" VARCHAR(50),
"B_DESCRIPTION" VARCHAR(2000),
"B_ENABLED" BOOLEAN,
"B_UUID_PATH" VARCHAR(1500),
"B_LANGUAGE" VARCHAR(20),
"B_LONG_NAME" VARCHAR(500),
"B_MODULE_UUID" VARCHAR(50),
"B_MODULE_UUID_PATH" VARCHAR(1500),
"B_NAME" VARCHAR(500),
"B_PATH" VARCHAR(2000),
"B_QUALIFIER" VARCHAR(10)
);

CREATE TABLE "PROPERTIES" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"PROP_KEY" VARCHAR(512) NOT NULL,
"RESOURCE_ID" INTEGER,
"USER_ID" INTEGER,
"IS_EMPTY" BOOLEAN NOT NULL,
"TEXT_VALUE" VARCHAR(4000),
"CLOB_VALUE" CLOB,
"CREATED_AT" BIGINT
);
CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");


CREATE TABLE "WEBHOOKS" (
"UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
"NAME" VARCHAR(100) NOT NULL,
"URL" VARCHAR(2000) NOT NULL,
"ORGANIZATION_UUID" VARCHAR(40),
"PROJECT_UUID" VARCHAR(40),
"CREATED_AT" BIGINT NOT NULL,
"UPDATED_AT" BIGINT
);
CREATE UNIQUE INDEX "PK_WEBHOOKS" ON "WEBHOOKS" ("UUID");
CREATE INDEX "ORGANIZATION_WEBHOOK" ON "WEBHOOKS" ("ORGANIZATION_UUID");
CREATE INDEX "PROJECT_WEBHOOK" ON "WEBHOOKS" ("PROJECT_UUID");



+ 1
- 7
server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java Vedi File

@@ -35,16 +35,10 @@ public interface WebHooks {
*
* <p>
* This can be used to not do consuming operations before calling
* {@link #sendProjectAnalysisUpdate(ComponentDto, Analysis, Supplier)}
* {@link #sendProjectAnalysisUpdate(Analysis, Supplier)}
*/
boolean isEnabled(ComponentDto projectDto);

/**
* Calls all WebHooks configured in the specified {@link Configuration} for the specified analysis with the
* {@link WebhookPayload} provided by the specified Supplier.
*/
void sendProjectAnalysisUpdate(ComponentDto projectDto, Analysis analysis, Supplier<WebhookPayload> payloadSupplier);

/**
* Calls all WebHooks configured in the specified {@link Configuration} for the specified analysis with the
* {@link WebhookPayload} provided by the specified Supplier.

+ 8
- 18
server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java Vedi File

@@ -29,7 +29,6 @@ import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.webhook.WebhookDao;
import org.sonar.db.webhook.WebhookDto;
import org.sonar.server.async.AsyncExecution;
@@ -55,25 +54,25 @@ public class WebHooksImpl implements WebHooks {

@Override
public boolean isEnabled(ComponentDto projectDto) {
return readWebHooksFrom(projectDto)
return readWebHooksFrom(projectDto.uuid())
.findAny()
.isPresent();
}

private Stream<WebhookDto> readWebHooksFrom(ComponentDto projectDto) {
private Stream<WebhookDto> readWebHooksFrom(String projectUuid) {
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<OrganizationDto> optionalDto = dbClient.organizationDao().selectByUuid(dbSession, projectDto.getOrganizationUuid());
OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", projectDto.getOrganizationUuid());
Optional<ComponentDto> componentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull());
ComponentDto projectDto = checkStateWithOptional(componentDto, "the requested project '%s' was not found", projectUuid);
WebhookDao dao = dbClient.webhookDao();
return Stream.concat(
dao.selectByProjectUuid(dbSession, projectDto).stream(),
dao.selectByOrganizationUuid(dbSession, organizationDto).stream());
dao.selectByProject(dbSession, projectDto).stream(),
dao.selectByOrganizationUuid(dbSession, projectDto.getOrganizationUuid()).stream());
}
}

@Override
public void sendProjectAnalysisUpdate(ComponentDto componentDto, Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
List<Webhook> webhooks = readWebHooksFrom(componentDto)
public void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
List<Webhook> webhooks = readWebHooksFrom(analysis.getProjectUuid())
.map(dto -> new Webhook(analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), dto.getName(), dto.getUrl()))
.collect(MoreCollectors.toList());
if (webhooks.isEmpty()) {
@@ -89,15 +88,6 @@ public class WebHooksImpl implements WebHooks {
asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid()));
}

@Override
public void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<ComponentDto> optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, analysis.getProjectUuid()).orNull());
ComponentDto projectDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", analysis.getProjectUuid());
sendProjectAnalysisUpdate(projectDto, analysis, payloadSupplier);
}
}

private static void log(WebhookDelivery delivery) {
Optional<String> error = delivery.getErrorMessage();
if (error.isPresent()) {

+ 0
- 1
server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java Vedi File

@@ -79,7 +79,6 @@ public class WebhookQGChangeEventListener implements QGChangeEventListener {

private void callWebhook(DbSession dbSession, QGChangeEvent event, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
webhooks.sendProjectAnalysisUpdate(
event.getProject(),
new WebHooks.Analysis(event.getBranch().getUuid(), event.getAnalysis().getUuid(), null),
() -> buildWebHookPayload(dbSession, event, evaluatedQualityGate));
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java Vedi File

@@ -198,11 +198,11 @@ public class CreateAction implements WebhooksWsAction {
}

private int numberOfWebhookOf(DbSession dbSession, OrganizationDto organizationDto) {
return dbClient.webhookDao().selectByOrganizationUuid(dbSession, organizationDto).size();
return dbClient.webhookDao().selectByOrganization(dbSession, organizationDto).size();
}

private int numberOfWebhookOf(DbSession dbSession, ComponentDto componentDto) {
return dbClient.webhookDao().selectByProjectUuid(dbSession, componentDto).size();
return dbClient.webhookDao().selectByProject(dbSession, componentDto).size();
}

private OrganizationDto defaultOrganizationDto(DbSession dbSession) {

+ 4
- 4
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/ListAction.java Vedi File

@@ -33,7 +33,7 @@ import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.webhook.WebhookDto;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Webhooks.SearchWsResponse.Builder;
import org.sonarqube.ws.Webhooks.ListWsResponse.Builder;

import static java.util.Optional.ofNullable;
import static org.apache.commons.lang.StringUtils.isNotBlank;
@@ -45,7 +45,7 @@ import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.ws.WsUtils.checkStateWithOptional;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.Webhooks.SearchWsResponse.newBuilder;
import static org.sonarqube.ws.Webhooks.ListWsResponse.newBuilder;

public class ListAction implements WebhooksWsAction {

@@ -115,12 +115,12 @@ public class ListAction implements WebhooksWsAction {
webhookSupport.checkPermission(componentDto);
webhookSupport.checkThatProjectBelongsToOrganization(componentDto, organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey);
webhookSupport.checkPermission(componentDto);
return dbClient.webhookDao().selectByProjectUuid(dbSession, componentDto);
return dbClient.webhookDao().selectByProject(dbSession, componentDto);

} else {

webhookSupport.checkPermission(organizationDto);
return dbClient.webhookDao().selectByOrganizationUuid(dbSession, organizationDto);
return dbClient.webhookDao().selectByOrganization(dbSession, organizationDto);

}


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java Vedi File

@@ -32,7 +32,7 @@ public class WebhookSupport {

private final UserSession userSession;

WebhookSupport(UserSession userSession) {
public WebhookSupport(UserSession userSession) {
this.userSession = userSession;
}


+ 21
- 2
server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java Vedi File

@@ -19,6 +19,7 @@
*/
package org.sonar.server.organization.ws;

import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -39,6 +40,8 @@ import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.db.webhook.WebhookDbTester;
import org.sonar.db.webhook.WebhookDto;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.ProjectIndexers;
@@ -48,7 +51,6 @@ import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonar.server.qualityprofile.QProfileFactory;
import org.sonar.server.qualityprofile.QProfileFactoryImpl;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
@@ -90,7 +92,7 @@ public class DeleteActionTest {
private QProfileFactory qProfileFactory = new QProfileFactoryImpl(dbClient, mock(UuidFactory.class), System2.INSTANCE, mock(ActiveRuleIndexer.class));
private UserIndex userIndex = new UserIndex(es.client(), System2.INSTANCE);
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private QualityGateFinder qualityGateFinder = new QualityGateFinder(dbClient);
private final WebhookDbTester webhookDbTester = db.webhooks();
private WsActionTester wsTester = new WsActionTester(
new DeleteAction(userSession, dbClient, defaultOrganizationProvider, componentCleanerService, organizationFlags, userIndexer, qProfileFactory));

@@ -113,6 +115,23 @@ public class DeleteActionTest {
.matches(param -> "Organization key".equals(param.description()));
}

@Test
public void organization_deletion_also_ensure_that_webhooks_of_this_organization_if_they_exist_are_cleared() {
OrganizationDto organization = db.organizations().insert();
webhookDbTester.insertWebhook(organization);
webhookDbTester.insertWebhook(organization);
webhookDbTester.insertWebhook(organization);

userSession.logIn().addPermission(ADMINISTER, organization);

wsTester.newRequest()
.setParam(PARAM_ORGANIZATION, organization.getKey())
.execute();

List<WebhookDto> webhookDtos = dbClient.webhookDao().selectByOrganization(session, organization);
assertThat(webhookDtos).isEmpty();
}

@Test
public void organization_deletion_also_ensure_that_homepage_on_this_organization_if_it_exists_is_cleared() {
OrganizationDto organization = db.organizations().insert();

+ 26
- 1
server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java Vedi File

@@ -19,6 +19,7 @@
*/
package org.sonar.server.project.ws;

import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -33,6 +34,8 @@ import org.sonar.db.component.ComponentDbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ResourceTypesRule;
import org.sonar.db.user.UserDto;
import org.sonar.db.webhook.WebhookDbTester;
import org.sonar.db.webhook.WebhookDto;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.es.TestProjectIndexers;
import org.sonar.server.exceptions.ForbiddenException;
@@ -71,6 +74,7 @@ public class DeleteActionTest {
private WsTester ws;
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();
private WebhookDbTester webhookDbTester = db.webhooks();
private ComponentDbTester componentDbTester = new ComponentDbTester(db);
private ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class);

@@ -136,7 +140,6 @@ public class DeleteActionTest {

@Test
public void project_deletion_also_ensure_that_homepage_on_this_project_if_it_exists_is_cleared() throws Exception {

ComponentDto project = componentDbTester.insertPrivateProject();
UserDto insert = dbClient.userDao().insert(dbSession,
newUserDto().setHomepageType("PROJECT").setHomepageParameter(project.uuid()));
@@ -157,6 +160,28 @@ public class DeleteActionTest {
assertThat(userReloaded.getHomepageParameter()).isNull();
}

@Test
public void project_deletion_also_ensure_that_webhooks_on_this_project_if_they_exists_are_deleted() throws Exception {
ComponentDto project = componentDbTester.insertPrivateProject();
webhookDbTester.insertWebhook(project);
webhookDbTester.insertWebhook(project);
webhookDbTester.insertWebhook(project);
webhookDbTester.insertWebhook(project);

userSessionRule.logIn().addProjectPermission(ADMIN, project);

new WsTester(new ProjectsWs(
new DeleteAction(
new ComponentCleanerService(dbClient, new ResourceTypesRule().setAllQualifiers(PROJECT),
new TestProjectIndexers()), from(db), dbClient, userSessionRule)))
.newPostRequest(CONTROLLER, ACTION)
.setParam(PARAM_PROJECT, project.getDbKey())
.execute();

List<WebhookDto> webhookDtos = dbClient.webhookDao().selectByProject(dbSession, project);
assertThat(webhookDtos).isEmpty();
}

@Test
public void return_403_if_not_project_admin_nor_org_admin() throws Exception {
ComponentDto project = componentDbTester.insertPrivateProject();

+ 23
- 23
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java Vedi File

@@ -38,8 +38,8 @@ import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Webhooks.SearchWsResponse;
import org.sonarqube.ws.Webhooks.SearchWsResponse.Search;
import org.sonarqube.ws.Webhooks.ListWsResponse;
import org.sonarqube.ws.Webhooks.ListWsResponse.List;

import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
@@ -92,24 +92,24 @@ public class ListActionTest {
}

@Test
public void search_global_webhooks() {
public void List_global_webhooks() {

WebhookDto dto1 = webhookDbTester.insertWebhook(db.getDefaultOrganization());
WebhookDto dto2 = webhookDbTester.insertWebhook(db.getDefaultOrganization());
userSession.logIn().addPermission(ADMINISTER, db.getDefaultOrganization().getUuid());

SearchWsResponse response = wsActionTester.newRequest()
.executeProtobuf(SearchWsResponse.class);
ListWsResponse response = wsActionTester.newRequest()
.executeProtobuf(ListWsResponse.class);

assertThat(response.getWebhooksList())
.extracting(Search::getName, Search::getUrl)
.extracting(List::getName, List::getUrl)
.contains(tuple(dto1.getName(), dto1.getUrl()),
tuple(dto2.getName(), dto2.getUrl()));

}

@Test
public void search_project_webhooks_when_no_organization_is_provided() {
public void List_project_webhooks_when_no_organization_is_provided() {

ComponentDto project1 = componentDbTester.insertPrivateProject();
userSession.logIn().addProjectPermission(ADMIN, project1);
@@ -117,38 +117,38 @@ public class ListActionTest {
WebhookDto dto1 = webhookDbTester.insertWebhook(project1);
WebhookDto dto2 = webhookDbTester.insertWebhook(project1);

SearchWsResponse response = wsActionTester.newRequest()
ListWsResponse response = wsActionTester.newRequest()
.setParam(PROJECT_KEY_PARAM, project1.getKey())
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

assertThat(response.getWebhooksList())
.extracting(Search::getName, Search::getUrl)
.extracting(List::getName, List::getUrl)
.contains(tuple(dto1.getName(), dto1.getUrl()),
tuple(dto2.getName(), dto2.getUrl()));

}

@Test
public void search_organization_webhooks() {
public void List_organization_webhooks() {

OrganizationDto organizationDto = organizationDbTester.insert();
WebhookDto dto1 = webhookDbTester.insertWebhook(organizationDto);
WebhookDto dto2 = webhookDbTester.insertWebhook(organizationDto);
userSession.logIn().addPermission(ADMINISTER, organizationDto.getUuid());

SearchWsResponse response = wsActionTester.newRequest()
ListWsResponse response = wsActionTester.newRequest()
.setParam(ORGANIZATION_KEY_PARAM, organizationDto.getKey())
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

assertThat(response.getWebhooksList())
.extracting(Search::getName, Search::getUrl)
.extracting(List::getName, List::getUrl)
.contains(tuple(dto1.getName(), dto1.getUrl()),
tuple(dto2.getName(), dto2.getUrl()));

}

@Test
public void search_project_webhooks_when_organization_is_provided() {
public void List_project_webhooks_when_organization_is_provided() {

OrganizationDto organization = organizationDbTester.insert();
ComponentDto project = componentDbTester.insertPrivateProject(organization);
@@ -157,13 +157,13 @@ public class ListActionTest {
WebhookDto dto1 = webhookDbTester.insertWebhook(project);
WebhookDto dto2 = webhookDbTester.insertWebhook(project);

SearchWsResponse response = wsActionTester.newRequest()
ListWsResponse response = wsActionTester.newRequest()
.setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
.setParam(PROJECT_KEY_PARAM, project.getKey())
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

assertThat(response.getWebhooksList())
.extracting(Search::getName, Search::getUrl)
.extracting(List::getName, List::getUrl)
.contains(tuple(dto1.getName(), dto1.getUrl()),
tuple(dto2.getName(), dto2.getUrl()));

@@ -177,7 +177,7 @@ public class ListActionTest {

wsActionTester.newRequest()
.setParam(PROJECT_KEY_PARAM, "pipo")
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

}

@@ -189,7 +189,7 @@ public class ListActionTest {

wsActionTester.newRequest()
.setParam(ORGANIZATION_KEY_PARAM, "pipo")
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

}

@@ -218,7 +218,7 @@ public class ListActionTest {
expectedException.expect(UnauthorizedException.class);

wsActionTester.newRequest()
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

}

@@ -231,7 +231,7 @@ public class ListActionTest {
expectedException.expectMessage("Insufficient privileges");

wsActionTester.newRequest()
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);
}

@Test
@@ -246,7 +246,7 @@ public class ListActionTest {

wsActionTester.newRequest()
.setParam(PROJECT_KEY_PARAM, project.getKey())
.executeProtobuf(SearchWsResponse.class);
.executeProtobuf(ListWsResponse.class);

}


+ 4
- 4
sonar-ws/src/main/protobuf/ws-webhooks.proto Vedi File

@@ -24,11 +24,11 @@ option java_package = "org.sonarqube.ws";
option java_outer_classname = "Webhooks";
option optimize_for = SPEED;

// GET api/webhooks/search
message SearchWsResponse {
repeated Search webhooks = 1;
// GET api/webhooks/list
message ListWsResponse {
repeated List webhooks = 1;

message Search {
message List {
optional string key = 1;
optional string name = 2;
optional string url = 3;

Loading…
Annulla
Salva