+ 25 // level 1 | + 25 // level 1 | ||||
+ 46 // content of DaoModule | + 46 // content of DaoModule | ||||
+ 2 // content of EsSearchModule | + 2 // content of EsSearchModule | ||||
+ 62 // content of CorePropertyDefinitions | |||||
+ 64 // content of CorePropertyDefinitions | |||||
+ 1 // content of CePropertyDefinitions | + 1 // content of CePropertyDefinitions | ||||
); | ); | ||||
assertThat(picoContainer.getParent().getParent().getParent().getParent()).isNull(); | assertThat(picoContainer.getParent().getParent().getParent().getParent()).isNull(); |
import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepositoryImpl; | import org.sonar.server.computation.task.projectanalysis.source.SourceHashRepositoryImpl; | ||||
import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl; | import org.sonar.server.computation.task.projectanalysis.source.SourceLinesRepositoryImpl; | ||||
import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps; | import org.sonar.server.computation.task.projectanalysis.step.ReportComputationSteps; | ||||
import org.sonar.server.computation.task.projectanalysis.webhook.WebhookModule; | |||||
import org.sonar.server.computation.task.step.ComputationStepExecutor; | import org.sonar.server.computation.task.step.ComputationStepExecutor; | ||||
import org.sonar.server.computation.task.step.ComputationSteps; | import org.sonar.server.computation.task.step.ComputationSteps; | ||||
import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl; | import org.sonar.server.computation.taskprocessor.MutableTaskResultHolderImpl; | ||||
// views | // views | ||||
ViewIndex.class, | ViewIndex.class, | ||||
MeasureToMeasureDto.class); | |||||
MeasureToMeasureDto.class, | |||||
// webhooks | |||||
WebhookModule.class); | |||||
} | } | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import javax.annotation.concurrent.Immutable; | |||||
import static java.util.Objects.requireNonNull; | |||||
@Immutable | |||||
public class Webhook { | |||||
private final String name; | |||||
private final String url; | |||||
public Webhook(String name, String url) { | |||||
this.name = requireNonNull(name); | |||||
this.url = requireNonNull(url); | |||||
} | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
public String getUrl() { | |||||
return url; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
public interface WebhookCaller { | |||||
/** | |||||
* Call webhook by sending a HTTP(S) POST request containing | |||||
* the JSON payload. | |||||
* <br/> | |||||
* Errors are silently ignored. They don't generate logs or | |||||
* throw exceptions. The error status is stored in the | |||||
* returned {@link WebhookDelivery}. | |||||
*/ | |||||
WebhookDelivery call(Webhook webhook, WebhookPayload payload); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import java.io.IOException; | |||||
import okhttp3.MediaType; | |||||
import okhttp3.OkHttpClient; | |||||
import okhttp3.Request; | |||||
import okhttp3.RequestBody; | |||||
import okhttp3.Response; | |||||
import org.sonar.api.ce.ComputeEngineSide; | |||||
import org.sonar.api.utils.System2; | |||||
@ComputeEngineSide | |||||
public class WebhookCallerImpl implements WebhookCaller { | |||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); | |||||
private static final String PROJECT_KEY_HEADER = "X-SonarQube-Project"; | |||||
private final System2 system; | |||||
private final OkHttpClient okHttpClient; | |||||
public WebhookCallerImpl(System2 system, OkHttpClient okHttpClient) { | |||||
this.system = system; | |||||
this.okHttpClient = okHttpClient; | |||||
} | |||||
@Override | |||||
public WebhookDelivery call(Webhook webhook, WebhookPayload payload) { | |||||
Request.Builder request = new Request.Builder(); | |||||
request.url(webhook.getUrl()); | |||||
request.header(PROJECT_KEY_HEADER, payload.getProjectKey()); | |||||
RequestBody body = RequestBody.create(JSON, payload.toJson()); | |||||
request.post(body); | |||||
WebhookDelivery.Builder builder = new WebhookDelivery.Builder(); | |||||
long startedAt = system.now(); | |||||
builder | |||||
.setAt(startedAt) | |||||
.setPayload(payload) | |||||
.setWebhook(webhook); | |||||
try { | |||||
Response response = okHttpClient.newCall(request.build()).execute(); | |||||
builder.setHttpStatus(response.code()); | |||||
builder.setDurationInMs(system.now() - startedAt); | |||||
} catch (IOException e) { | |||||
builder.setThrowable(e); | |||||
} | |||||
return builder.build(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import java.util.Optional; | |||||
import javax.annotation.Nullable; | |||||
import javax.annotation.concurrent.Immutable; | |||||
import static java.util.Objects.requireNonNull; | |||||
/** | |||||
* A {@link WebhookDelivery} represents the result of a webhook call. | |||||
*/ | |||||
@Immutable | |||||
public class WebhookDelivery { | |||||
private final Webhook webhook; | |||||
private final WebhookPayload payload; | |||||
private final Integer httpStatus; | |||||
private final Long durationInMs; | |||||
private final long at; | |||||
private final Throwable throwable; | |||||
private WebhookDelivery(Builder builder) { | |||||
this.webhook = requireNonNull(builder.webhook); | |||||
this.payload = requireNonNull(builder.payload); | |||||
this.httpStatus = builder.httpStatus; | |||||
this.durationInMs = builder.durationInMs; | |||||
this.at = builder.at; | |||||
this.throwable = builder.throwable; | |||||
} | |||||
public Webhook getWebhook() { | |||||
return webhook; | |||||
} | |||||
public WebhookPayload getPayload() { | |||||
return payload; | |||||
} | |||||
/** | |||||
* @return the HTTP status if {@link #getThrowable()} is empty, else returns | |||||
* {@link Optional#empty()} | |||||
*/ | |||||
public Optional<Integer> getHttpStatus() { | |||||
return Optional.ofNullable(httpStatus); | |||||
} | |||||
/** | |||||
* @return the duration in milliseconds if {@link #getThrowable()} is empty, | |||||
* else returns {@link Optional#empty()} | |||||
*/ | |||||
public Optional<Long> getDurationInMs() { | |||||
return Optional.ofNullable(durationInMs); | |||||
} | |||||
/** | |||||
* @return the date of sending | |||||
*/ | |||||
public long getAt() { | |||||
return at; | |||||
} | |||||
/** | |||||
* @return the error raised if the request could not be executed due to a connectivity | |||||
* problem or timeout | |||||
*/ | |||||
public Optional<Throwable> getThrowable() { | |||||
return Optional.ofNullable(throwable); | |||||
} | |||||
public static class Builder { | |||||
private Webhook webhook; | |||||
private WebhookPayload payload; | |||||
private Integer httpStatus; | |||||
private Long durationInMs; | |||||
private long at; | |||||
private Throwable throwable; | |||||
public Builder setWebhook(Webhook w) { | |||||
this.webhook = w; | |||||
return this; | |||||
} | |||||
public Builder setPayload(WebhookPayload payload) { | |||||
this.payload = payload; | |||||
return this; | |||||
} | |||||
public Builder setHttpStatus(@Nullable Integer httpStatus) { | |||||
this.httpStatus = httpStatus; | |||||
return this; | |||||
} | |||||
public Builder setDurationInMs(@Nullable Long durationInMs) { | |||||
this.durationInMs = durationInMs; | |||||
return this; | |||||
} | |||||
public Builder setAt(long at) { | |||||
this.at = at; | |||||
return this; | |||||
} | |||||
public Builder setThrowable(@Nullable Throwable t) { | |||||
this.throwable = t; | |||||
return this; | |||||
} | |||||
public WebhookDelivery build() { | |||||
return new WebhookDelivery(this); | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import org.sonar.core.platform.Module; | |||||
public class WebhookModule extends Module { | |||||
@Override | |||||
protected void configureModule() { | |||||
add( | |||||
WebhookCallerImpl.class, | |||||
WebhookPostTask.class); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import java.io.StringWriter; | |||||
import java.io.Writer; | |||||
import java.util.Date; | |||||
import java.util.Optional; | |||||
import javax.annotation.Nullable; | |||||
import javax.annotation.concurrent.Immutable; | |||||
import org.sonar.api.ce.posttask.CeTask; | |||||
import org.sonar.api.ce.posttask.PostProjectAnalysisTask; | |||||
import org.sonar.api.ce.posttask.Project; | |||||
import org.sonar.api.ce.posttask.QualityGate; | |||||
import org.sonar.api.utils.text.JsonWriter; | |||||
import static java.util.Objects.requireNonNull; | |||||
@Immutable | |||||
public class WebhookPayload { | |||||
private final String projectKey; | |||||
private final String json; | |||||
public WebhookPayload(String projectKey, String json) { | |||||
this.projectKey = requireNonNull(projectKey); | |||||
this.json = requireNonNull(json); | |||||
} | |||||
public String getProjectKey() { | |||||
return projectKey; | |||||
} | |||||
public String toJson() { | |||||
return json; | |||||
} | |||||
public static WebhookPayload from(PostProjectAnalysisTask.ProjectAnalysis analysis) { | |||||
Writer string = new StringWriter(); | |||||
JsonWriter writer = JsonWriter.of(string); | |||||
writer.beginObject(); | |||||
writeTask(writer, analysis.getCeTask()); | |||||
Optional<Date> analysisDate = analysis.getAnalysisDate(); | |||||
if (analysisDate.isPresent()) { | |||||
writer.propDateTime("analysedAt", analysisDate.get()); | |||||
} | |||||
writeProject(analysis, writer, analysis.getProject()); | |||||
writeQualityGate(writer, analysis.getQualityGate()); | |||||
writer.endObject().close(); | |||||
return new WebhookPayload(analysis.getProject().getKey(), string.toString()); | |||||
} | |||||
private static void writeTask(JsonWriter writer, CeTask ceTask) { | |||||
writer.prop("taskId", ceTask.getId()); | |||||
writer.prop("status", ceTask.getStatus().toString()); | |||||
} | |||||
private static void writeProject(PostProjectAnalysisTask.ProjectAnalysis analysis, JsonWriter writer, Project project) { | |||||
writer.name("project"); | |||||
writer.beginObject(); | |||||
writer.prop("key", project.getKey()); | |||||
writer.prop("name", analysis.getProject().getName()); | |||||
writer.endObject(); | |||||
} | |||||
private static void writeQualityGate(JsonWriter writer, @Nullable QualityGate gate) { | |||||
if (gate != null) { | |||||
writer.name("qualityGate"); | |||||
writer.beginObject(); | |||||
writer.prop("name", gate.getName()); | |||||
writer.prop("status", gate.getStatus().toString()); | |||||
writer.name("conditions").beginArray(); | |||||
for (QualityGate.Condition condition : gate.getConditions()) { | |||||
writer.beginObject(); | |||||
writer.prop("metric", condition.getMetricKey()); | |||||
writer.prop("operator", condition.getOperator().name()); | |||||
if (condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE) { | |||||
writer.prop("value", condition.getValue()); | |||||
} | |||||
writer.prop("status", condition.getStatus().name()); | |||||
writer.prop("onLeakPeriod", condition.isOnLeakPeriod()); | |||||
writer.prop("errorThreshold", condition.getErrorThreshold()); | |||||
writer.prop("warningThreshold", condition.getWarningThreshold()); | |||||
writer.endObject(); | |||||
} | |||||
writer.endArray(); | |||||
writer.endObject(); | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import com.google.common.collect.Iterables; | |||||
import java.util.Arrays; | |||||
import java.util.List; | |||||
import java.util.Optional; | |||||
import org.sonar.api.ce.posttask.PostProjectAnalysisTask; | |||||
import org.sonar.api.config.Settings; | |||||
import org.sonar.api.utils.log.Logger; | |||||
import org.sonar.api.utils.log.Loggers; | |||||
import org.sonar.core.config.WebhookProperties; | |||||
import org.sonar.core.util.stream.Collectors; | |||||
import org.sonar.server.computation.task.projectanalysis.component.SettingsRepository; | |||||
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; | |||||
import static com.google.common.base.Throwables.getRootCause; | |||||
import static java.lang.String.format; | |||||
public class WebhookPostTask implements PostProjectAnalysisTask { | |||||
private static final Logger LOGGER = Loggers.get(WebhookPostTask.class); | |||||
private final TreeRootHolder rootHolder; | |||||
private final SettingsRepository settingsRepository; | |||||
private final WebhookCaller caller; | |||||
public WebhookPostTask(TreeRootHolder rootHolder, SettingsRepository settingsRepository, WebhookCaller caller) { | |||||
this.rootHolder = rootHolder; | |||||
this.settingsRepository = settingsRepository; | |||||
this.caller = caller; | |||||
} | |||||
@Override | |||||
public void finished(ProjectAnalysis analysis) { | |||||
Settings settings = settingsRepository.getSettings(rootHolder.getRoot()); | |||||
Iterable<String> webhookProps = Iterables.concat( | |||||
getWebhookProperties(settings, WebhookProperties.GLOBAL_KEY), | |||||
getWebhookProperties(settings, WebhookProperties.PROJECT_KEY) | |||||
); | |||||
if (!Iterables.isEmpty(webhookProps)) { | |||||
process(settings, analysis, webhookProps); | |||||
} | |||||
} | |||||
private static List<String> getWebhookProperties(Settings settings, String propertyKey) { | |||||
String[] webhookIds = settings.getStringArray(propertyKey); | |||||
return Arrays.stream(webhookIds) | |||||
.map(webhookId -> format("%s.%s", propertyKey, webhookId)) | |||||
.collect(Collectors.toList(webhookIds.length)); | |||||
} | |||||
private void process(Settings settings, ProjectAnalysis analysis, Iterable<String> webhookProperties) { | |||||
WebhookPayload payload = WebhookPayload.from(analysis); | |||||
for (String webhookProp : webhookProperties) { | |||||
String name = settings.getString(format("%s.%s", webhookProp, WebhookProperties.NAME_FIELD)); | |||||
String url = settings.getString(format("%s.%s", webhookProp, WebhookProperties.URL_FIELD)); | |||||
// as webhooks are defined as property sets, we can't ensure validity of fields on creation. | |||||
if (name != null && url != null) { | |||||
Webhook webhook = new Webhook(name, url); | |||||
WebhookDelivery delivery = caller.call(webhook, payload); | |||||
log(delivery); | |||||
} | |||||
} | |||||
} | |||||
private static void log(WebhookDelivery delivery) { | |||||
Optional<Throwable> throwable = delivery.getThrowable(); | |||||
if (throwable.isPresent()) { | |||||
LOGGER.debug("Failed to send webhook '{}' | url={} | message={}", | |||||
delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), getRootCause(throwable.get()).getMessage()); | |||||
} else { | |||||
LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}", | |||||
delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1L), delivery.getHttpStatus().orElse(-1)); | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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. | |||||
*/ | |||||
@ParametersAreNonnullByDefault | |||||
package org.sonar.server.computation.task.projectanalysis.webhook; | |||||
import javax.annotation.ParametersAreNonnullByDefault; |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.component; | |||||
import org.sonar.api.config.Settings; | |||||
/** | |||||
* Implementation of {@link SettingsRepository} that always return the | |||||
* same mutable {@link Settings}, whatever the component. | |||||
*/ | |||||
public class TestSettingsRepository implements SettingsRepository { | |||||
private final Settings settings; | |||||
public TestSettingsRepository(Settings settings) { | |||||
this.settings = settings; | |||||
} | |||||
@Override | |||||
public Settings getSettings(Component component) { | |||||
return settings; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import java.util.LinkedList; | |||||
import java.util.Queue; | |||||
import java.util.concurrent.atomic.AtomicInteger; | |||||
import javax.annotation.Nullable; | |||||
import static java.util.Objects.requireNonNull; | |||||
public class TestWebhookCaller implements WebhookCaller { | |||||
private final Queue<Item> deliveries = new LinkedList<>(); | |||||
private final AtomicInteger countSent = new AtomicInteger(0); | |||||
public TestWebhookCaller enqueueSuccess(long at, int httpCode, long durationMs) { | |||||
deliveries.add(new Item(at, httpCode, durationMs, null)); | |||||
return this; | |||||
} | |||||
public TestWebhookCaller enqueueFailure(long at, Throwable t) { | |||||
deliveries.add(new Item(at, null, null, t)); | |||||
return this; | |||||
} | |||||
@Override | |||||
public WebhookDelivery call(Webhook webhook, WebhookPayload payload) { | |||||
Item item = requireNonNull(deliveries.poll(), "Queue is empty"); | |||||
countSent.incrementAndGet(); | |||||
return new WebhookDelivery.Builder() | |||||
.setAt(item.at) | |||||
.setHttpStatus(item.httpCode) | |||||
.setDurationInMs(item.durationMs) | |||||
.setThrowable(item.throwable) | |||||
.setPayload(payload) | |||||
.setWebhook(webhook) | |||||
.build(); | |||||
} | |||||
public int countSent() { | |||||
return countSent.get(); | |||||
} | |||||
private static class Item { | |||||
final long at; | |||||
final Integer httpCode; | |||||
final Long durationMs; | |||||
final Throwable throwable; | |||||
Item(long at, @Nullable Integer httpCode, @Nullable Long durationMs, @Nullable Throwable throwable) { | |||||
this.at = at; | |||||
this.httpCode = httpCode; | |||||
this.durationMs = durationMs; | |||||
this.throwable = throwable; | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import okhttp3.mockwebserver.MockResponse; | |||||
import okhttp3.mockwebserver.MockWebServer; | |||||
import okhttp3.mockwebserver.RecordedRequest; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.sonar.api.SonarQubeSide; | |||||
import org.sonar.api.SonarRuntime; | |||||
import org.sonar.api.config.MapSettings; | |||||
import org.sonar.api.internal.SonarRuntimeImpl; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.api.utils.Version; | |||||
import org.sonar.api.utils.internal.TestSystem2; | |||||
import org.sonar.server.util.OkHttpClientProvider; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
public class WebhookCallerImplTest { | |||||
private static final long NOW = 1_500_000_000_000L; | |||||
@Rule | |||||
public MockWebServer server = new MockWebServer(); | |||||
private System2 system = new TestSystem2().setNow(NOW); | |||||
@Test | |||||
public void send_posts_payload_to_http_server() throws Exception { | |||||
Webhook webhook = new Webhook("my-webhook", server.url("/ping").toString()); | |||||
WebhookPayload payload = new WebhookPayload("P1", "{the payload}"); | |||||
server.enqueue(new MockResponse().setBody("pong").setResponseCode(201)); | |||||
WebhookDelivery delivery = newSender().call(webhook, payload); | |||||
assertThat(delivery.getHttpStatus().get()).isEqualTo(201); | |||||
assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0L); | |||||
assertThat(delivery.getThrowable()).isEmpty(); | |||||
assertThat(delivery.getAt()).isEqualTo(NOW); | |||||
assertThat(delivery.getWebhook()).isSameAs(webhook); | |||||
assertThat(delivery.getPayload()).isSameAs(payload); | |||||
RecordedRequest recordedRequest = server.takeRequest(); | |||||
assertThat(recordedRequest.getMethod()).isEqualTo("POST"); | |||||
assertThat(recordedRequest.getPath()).isEqualTo("/ping"); | |||||
assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(payload.toJson()); | |||||
assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2"); | |||||
assertThat(recordedRequest.getHeader("Content-Type")).isEqualTo("application/json; charset=utf-8"); | |||||
assertThat(recordedRequest.getHeader("X-SonarQube-Project")).isEqualTo(payload.getProjectKey()); | |||||
} | |||||
@Test | |||||
public void send_does_not_throw_exception_on_errors() throws Exception { | |||||
Webhook webhook = new Webhook("my-webhook", server.url("/ping").toString()); | |||||
WebhookPayload payload = new WebhookPayload("P1", "{the payload}"); | |||||
server.shutdown(); | |||||
WebhookDelivery delivery = newSender().call(webhook, payload); | |||||
assertThat(delivery.getHttpStatus()).isEmpty(); | |||||
assertThat(delivery.getDurationInMs()).isEmpty(); | |||||
assertThat(delivery.getThrowable().get().getMessage()).startsWith("Failed to connect to"); | |||||
assertThat(delivery.getAt()).isEqualTo(NOW); | |||||
assertThat(delivery.getWebhook()).isSameAs(webhook); | |||||
assertThat(delivery.getPayload()).isSameAs(payload); | |||||
} | |||||
private WebhookCaller newSender() { | |||||
SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER); | |||||
return new WebhookCallerImpl(system, new OkHttpClientProvider().provide(new MapSettings(), runtime)); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import java.util.Date; | |||||
import java.util.Optional; | |||||
import javax.annotation.Nullable; | |||||
import org.junit.Test; | |||||
import org.sonar.api.ce.posttask.CeTask; | |||||
import org.sonar.api.ce.posttask.PostProjectAnalysisTask; | |||||
import org.sonar.api.ce.posttask.Project; | |||||
import org.sonar.api.ce.posttask.QualityGate; | |||||
import org.sonar.api.ce.posttask.ScannerContext; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newCeTaskBuilder; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newConditionBuilder; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newProjectBuilder; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newQualityGateBuilder; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newScannerContextBuilder; | |||||
import static org.sonar.test.JsonAssert.assertJson; | |||||
public class WebhookPayloadTest { | |||||
private static final String PROJECT_KEY = "P1"; | |||||
@Test | |||||
public void create_payload_for_successful_analysis() { | |||||
CeTask task = newCeTaskBuilder() | |||||
.setStatus(CeTask.Status.SUCCESS) | |||||
.setId("#1") | |||||
.build(); | |||||
QualityGate gate = newQualityGateBuilder() | |||||
.setId("G1") | |||||
.setName("Gate One") | |||||
.setStatus(QualityGate.Status.WARN) | |||||
.add(newConditionBuilder() | |||||
.setMetricKey("coverage") | |||||
.setOperator(QualityGate.Operator.GREATER_THAN) | |||||
.setOnLeakPeriod(true) | |||||
.setWarningThreshold("75.0") | |||||
.setErrorThreshold("70.0") | |||||
.build(QualityGate.EvaluationStatus.WARN, "74.0")) | |||||
.build(); | |||||
PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate); | |||||
WebhookPayload payload = WebhookPayload.from(analysis); | |||||
assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); | |||||
assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/success.json")); | |||||
} | |||||
@Test | |||||
public void create_payload_for_failed_analysis() { | |||||
CeTask ceTask = newCeTaskBuilder().setStatus(CeTask.Status.FAILED).setId("#1").build(); | |||||
PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(ceTask, null); | |||||
WebhookPayload payload = WebhookPayload.from(analysis); | |||||
assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); | |||||
assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/failed.json")); | |||||
} | |||||
@Test | |||||
public void create_payload_with_gate_conditions_without_value() { | |||||
CeTask task = newCeTaskBuilder() | |||||
.setStatus(CeTask.Status.SUCCESS) | |||||
.setId("#1") | |||||
.build(); | |||||
QualityGate gate = newQualityGateBuilder() | |||||
.setId("G1") | |||||
.setName("Gate One") | |||||
.setStatus(QualityGate.Status.WARN) | |||||
.add(newConditionBuilder() | |||||
.setMetricKey("coverage") | |||||
.setOperator(QualityGate.Operator.GREATER_THAN) | |||||
.setWarningThreshold("75.0") | |||||
.setErrorThreshold("70.0") | |||||
.buildNoValue()) | |||||
.build(); | |||||
PostProjectAnalysisTask.ProjectAnalysis analysis = newAnalysis(task, gate); | |||||
WebhookPayload payload = WebhookPayload.from(analysis); | |||||
assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); | |||||
assertJson(payload.toJson()).isSimilarTo(getClass().getResource("WebhookPayloadTest/gate_condition_without_value.json")); | |||||
} | |||||
private static PostProjectAnalysisTask.ProjectAnalysis newAnalysis(CeTask task, @Nullable QualityGate gate) { | |||||
return new PostProjectAnalysisTask.ProjectAnalysis() { | |||||
@Override | |||||
public CeTask getCeTask() { | |||||
return task; | |||||
} | |||||
@Override | |||||
public Project getProject() { | |||||
return newProjectBuilder() | |||||
.setUuid("P1_UUID") | |||||
.setKey(PROJECT_KEY) | |||||
.setName("Project One") | |||||
.build(); | |||||
} | |||||
@Override | |||||
public QualityGate getQualityGate() { | |||||
return gate; | |||||
} | |||||
@Override | |||||
public Date getDate() { | |||||
throw new UnsupportedOperationException(); | |||||
} | |||||
@Override | |||||
public Optional<Date> getAnalysisDate() { | |||||
return Optional.of(new Date(1_500_000_000_000L)); | |||||
} | |||||
@Override | |||||
public ScannerContext getScannerContext() { | |||||
return newScannerContextBuilder().build(); | |||||
} | |||||
}; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.computation.task.projectanalysis.webhook; | |||||
import java.io.IOException; | |||||
import java.util.Date; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.sonar.api.ce.posttask.CeTask; | |||||
import org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester; | |||||
import org.sonar.api.config.MapSettings; | |||||
import org.sonar.api.utils.log.LogTester; | |||||
import org.sonar.api.utils.log.LoggerLevel; | |||||
import org.sonar.server.computation.task.projectanalysis.component.SettingsRepository; | |||||
import org.sonar.server.computation.task.projectanalysis.component.TestSettingsRepository; | |||||
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newCeTaskBuilder; | |||||
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newProjectBuilder; | |||||
import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.DUMB_PROJECT; | |||||
public class WebhookPostTaskTest { | |||||
private static final long NOW = 1_500_000_000_000L; | |||||
@Rule | |||||
public LogTester logTester = new LogTester().setLevel(LoggerLevel.DEBUG); | |||||
@Rule | |||||
public TreeRootHolderRule rootHolder = new TreeRootHolderRule().setRoot(DUMB_PROJECT); | |||||
private final MapSettings settings = new MapSettings(); | |||||
private final TestWebhookCaller caller = new TestWebhookCaller(); | |||||
@Test | |||||
public void do_nothing_if_no_webhooks() { | |||||
execute(); | |||||
assertThat(caller.countSent()).isEqualTo(0); | |||||
assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty(); | |||||
} | |||||
@Test | |||||
public void send_global_webhooks() { | |||||
settings.setProperty("sonar.webhooks.global", "1,2"); | |||||
settings.setProperty("sonar.webhooks.global.1.name", "First"); | |||||
settings.setProperty("sonar.webhooks.global.1.url", "http://url1"); | |||||
settings.setProperty("sonar.webhooks.global.2.name", "Second"); | |||||
settings.setProperty("sonar.webhooks.global.2.url", "http://url2"); | |||||
caller.enqueueSuccess(NOW, 200, 1_234); | |||||
caller.enqueueFailure(NOW, new IOException("Fail to connect")); | |||||
execute(); | |||||
assertThat(caller.countSent()).isEqualTo(2); | |||||
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200"); | |||||
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect"); | |||||
} | |||||
@Test | |||||
public void send_project_webhooks() { | |||||
settings.setProperty("sonar.webhooks.project", "1"); | |||||
settings.setProperty("sonar.webhooks.project.1.name", "First"); | |||||
settings.setProperty("sonar.webhooks.project.1.url", "http://url1"); | |||||
caller.enqueueSuccess(NOW, 200, 1_234); | |||||
execute(); | |||||
assertThat(caller.countSent()).isEqualTo(1); | |||||
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200"); | |||||
} | |||||
private void execute() { | |||||
SettingsRepository settingsRepository = new TestSettingsRepository(settings); | |||||
WebhookPostTask task = new WebhookPostTask(rootHolder, settingsRepository, caller); | |||||
PostProjectAnalysisTaskTester.of(task) | |||||
.at(new Date()) | |||||
.withCeTask(newCeTaskBuilder() | |||||
.setStatus(CeTask.Status.SUCCESS) | |||||
.setId("#1") | |||||
.build()) | |||||
.withProject(newProjectBuilder() | |||||
.setUuid("P1_UUID") | |||||
.setKey("P1") | |||||
.setName("Project One") | |||||
.build()) | |||||
.execute(); | |||||
} | |||||
} |
{ | |||||
"taskId": "#1", | |||||
"status": "FAILED", | |||||
"project": { | |||||
"key": "P1", | |||||
"name": "Project One" | |||||
} | |||||
} |
{ | |||||
"taskId": "#1", | |||||
"status": "SUCCESS", | |||||
"analysedAt": "2017-07-14T04:40:00+0200", | |||||
"project": { | |||||
"key": "P1", | |||||
"name": "Project One" | |||||
}, | |||||
"qualityGate": { | |||||
"name": "Gate One", | |||||
"status": "WARN", | |||||
"conditions": [ | |||||
{ | |||||
"metric": "coverage", | |||||
"operator": "GREATER_THAN", | |||||
"status": "NO_VALUE", | |||||
"onLeakPeriod": false, | |||||
"errorThreshold": "70.0", | |||||
"warningThreshold": "75.0" | |||||
} | |||||
] | |||||
} | |||||
} |
{ | |||||
"taskId": "#1", | |||||
"status": "SUCCESS", | |||||
"analysedAt": "2017-07-14T04:40:00+0200", | |||||
"project": { | |||||
"key": "P1", | |||||
"name": "Project One" | |||||
}, | |||||
"qualityGate": { | |||||
"name": "Gate One", | |||||
"status": "WARN", | |||||
"conditions": [ | |||||
{ | |||||
"metric": "coverage", | |||||
"operator": "GREATER_THAN", | |||||
"value": "74.0", | |||||
"status": "WARN", | |||||
"onLeakPeriod": true, | |||||
"errorThreshold": "70.0", | |||||
"warningThreshold": "75.0" | |||||
} | |||||
] | |||||
} | |||||
} |
defs.addAll(DebtProperties.all()); | defs.addAll(DebtProperties.all()); | ||||
defs.addAll(PurgeProperties.all()); | defs.addAll(PurgeProperties.all()); | ||||
defs.addAll(EmailSettings.definitions()); | defs.addAll(EmailSettings.definitions()); | ||||
defs.addAll(WebhookProperties.all()); | |||||
defs.addAll(ImmutableList.of( | defs.addAll(ImmutableList.of( | ||||
PropertyDefinition.builder(PROP_PASSWORD) | PropertyDefinition.builder(PROP_PASSWORD) |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2016 SonarSource SA | |||||
* mailto:contact 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.core.config; | |||||
import com.google.common.collect.ImmutableList; | |||||
import java.util.List; | |||||
import org.sonar.api.PropertyType; | |||||
import org.sonar.api.config.PropertyDefinition; | |||||
import org.sonar.api.config.PropertyFieldDefinition; | |||||
import org.sonar.api.resources.Qualifiers; | |||||
public class WebhookProperties { | |||||
public static final String GLOBAL_KEY = "sonar.webhooks.global"; | |||||
public static final String PROJECT_KEY = "sonar.webhooks.project"; | |||||
public static final String NAME_FIELD = "name"; | |||||
public static final String URL_FIELD = "url"; | |||||
private static final String CATEGORY = "webhooks"; | |||||
private static final String DESCRIPTION = "Webhooks are used to notify external services when a project analysis is done. " + | |||||
"A HTTP POST request including a JSON payload is sent to each of the provided URLs. " + | |||||
"Learn more in the <a href=\"#\">Webhooks documentation</a>."; | |||||
private WebhookProperties() { | |||||
// only static stuff | |||||
} | |||||
static List<PropertyDefinition> all() { | |||||
return ImmutableList.of( | |||||
PropertyDefinition.builder(GLOBAL_KEY) | |||||
.category(CATEGORY) | |||||
.name("Webhooks") | |||||
.description(DESCRIPTION) | |||||
.fields( | |||||
PropertyFieldDefinition.build(NAME_FIELD) | |||||
.name("Name") | |||||
.type(PropertyType.STRING) | |||||
.build(), | |||||
PropertyFieldDefinition.build(URL_FIELD) | |||||
.name("URL") | |||||
.type(PropertyType.STRING) | |||||
.build()) | |||||
.build(), | |||||
PropertyDefinition.builder(PROJECT_KEY) | |||||
.category(CATEGORY) | |||||
.name("Project Webhooks") | |||||
.description(DESCRIPTION) | |||||
.onlyOnQualifiers(Qualifiers.PROJECT) | |||||
.fields( | |||||
PropertyFieldDefinition.build(NAME_FIELD) | |||||
.name("Name") | |||||
.type(PropertyType.STRING) | |||||
.build(), | |||||
PropertyFieldDefinition.build(URL_FIELD) | |||||
.name("URL") | |||||
.type(PropertyType.STRING) | |||||
.build()) | |||||
.build()); | |||||
} | |||||
} |
property.category.localization=Localization | property.category.localization=Localization | ||||
property.category.server_id=Server ID | property.category.server_id=Server ID | ||||
property.category.exclusions=Analysis Scope | property.category.exclusions=Analysis Scope | ||||
property.category.webhooks=Webhooks | |||||
property.sonar.inclusions.name=Source File Inclusions | property.sonar.inclusions.name=Source File Inclusions | ||||
property.sonar.inclusions.description=Patterns used to include some source files and only these ones in analysis. | property.sonar.inclusions.description=Patterns used to include some source files and only these ones in analysis. | ||||
property.sonar.test.inclusions.name=Test File Inclusions | property.sonar.test.inclusions.name=Test File Inclusions |
@Test | @Test | ||||
public void all() { | public void all() { | ||||
List<PropertyDefinition> defs = CorePropertyDefinitions.all(); | List<PropertyDefinition> defs = CorePropertyDefinitions.all(); | ||||
assertThat(defs.size()).isGreaterThan(9); | |||||
assertThat(defs).hasSize(64); | |||||
} | } | ||||
@Test | @Test |