Ver código fonte

SONAR-22017 Add versionEOL to GlobalAction

tags/10.5.0.89998
OrlovAlexander 1 mês atrás
pai
commit
65962904f7

+ 13
- 4
server/sonar-webserver-webapi/src/main/java/org/sonar/server/ui/ws/GlobalAction.java Ver arquivo

@@ -28,9 +28,11 @@ import org.sonar.api.config.Configuration;
import org.sonar.api.platform.Server;
import org.sonar.api.resources.ResourceType;
import org.sonar.api.resources.ResourceTypes;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService.NewController;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.api.web.page.Page;
import org.sonar.core.documentation.DocumentationLinkGenerator;
@@ -48,6 +50,7 @@ import org.sonar.server.user.UserSession;

import static org.sonar.api.CoreProperties.DEVELOPER_AGGREGATED_INFO_DISABLED;
import static org.sonar.api.CoreProperties.RATING_GRID;
import static org.sonar.api.internal.MetadataLoader.loadSqVersionEol;
import static org.sonar.core.config.WebConstants.SONAR_LF_ENABLE_GRAVATAR;
import static org.sonar.core.config.WebConstants.SONAR_LF_GRAVATAR_SERVER_URL;
import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_URL;
@@ -80,9 +83,9 @@ public class GlobalAction implements NavigationWsAction, Startable {
private final DocumentationLinkGenerator documentationLinkGenerator;

public GlobalAction(PageRepository pageRepository, Configuration config, ResourceTypes resourceTypes, Server server,
NodeInformation nodeInformation, DbClient dbClient, UserSession userSession, PlatformEditionProvider editionProvider,
WebAnalyticsLoader webAnalyticsLoader, IssueIndexSyncProgressChecker issueIndexSyncChecker,
DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier, DocumentationLinkGenerator documentationLinkGenerator) {
NodeInformation nodeInformation, DbClient dbClient, UserSession userSession, PlatformEditionProvider editionProvider,
WebAnalyticsLoader webAnalyticsLoader, IssueIndexSyncProgressChecker issueIndexSyncChecker,
DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier, DocumentationLinkGenerator documentationLinkGenerator) {
this.pageRepository = pageRepository;
this.config = config;
this.resourceTypes = resourceTypes;
@@ -115,7 +118,8 @@ public class GlobalAction implements NavigationWsAction, Startable {
.setHandler(this)
.setInternal(true)
.setResponseExample(getClass().getResource("global-example.json"))
.setSince("5.2");
.setSince("5.2")
.setChangelog(new Change("10.5", "Field 'versionEOL' added, to indicate the end of support of installed version."));
}

@Override
@@ -128,6 +132,7 @@ public class GlobalAction implements NavigationWsAction, Startable {
writeDeprecatedLogoProperties(json);
writeQualifiers(json);
writeVersion(json);
writeVersionEol(json);
writeDatabaseProduction(json);
writeInstanceUsesDefaultAdminCredentials(json);
editionProvider.get().ifPresent(e -> json.prop("edition", e.name().toLowerCase(Locale.ENGLISH)));
@@ -179,6 +184,10 @@ public class GlobalAction implements NavigationWsAction, Startable {
json.prop("version", displayVersion);
}

private void writeVersionEol(JsonWriter json) {
json.prop("versionEOL", loadSqVersionEol(System2.INSTANCE));
}

private void writeDatabaseProduction(JsonWriter json) {
json.prop("productionDatabase", !dbClient.getDatabase().getDialect().getId().equals(H2.ID));
}

+ 1
- 0
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/ui/ws/global-example.json Ver arquivo

@@ -24,6 +24,7 @@
"POL"
],
"version": "6.2",
"versionEOL": "2025-01-01",
"productionDatabase": true,
"canAdmin": false,
"standalone": true,

+ 52
- 33
server/sonar-webserver-webapi/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java Ver arquivo

@@ -19,10 +19,13 @@
*/
package org.sonar.server.ui.ws;

import java.util.Objects;
import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.MockedStatic;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.internal.MetadataLoader;
import org.sonar.api.platform.Server;
import org.sonar.api.resources.ResourceType;
import org.sonar.api.resources.ResourceTypeTree;
@@ -51,13 +54,14 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.sonar.test.JsonAssert.assertJson;

public class GlobalActionTest {
class GlobalActionTest {

@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@RegisterExtension
private final UserSessionRule userSession = UserSessionRule.standalone();

private final MapSettings settings = new MapSettings();

@@ -73,7 +77,7 @@ public class GlobalActionTest {
private WsActionTester ws;

@Test
public void empty_call() {
void empty_call() {
init();

assertJson(call()).isSimilarTo("{" +
@@ -84,8 +88,8 @@ public class GlobalActionTest {
}

@Test
public void return_qualifiers() {
init(new Page[] {}, new ResourceTypeTree[] {
void return_qualifiers() {
init(new Page[]{}, new ResourceTypeTree[]{
ResourceTypeTree.builder()
.addType(ResourceType.builder("POL").build())
.addType(ResourceType.builder("LOP").build())
@@ -104,7 +108,7 @@ public class GlobalActionTest {
}

@Test
public void return_settings() {
void return_settings() {
settings.setProperty("sonar.lf.logoUrl", "http://example.com/my-custom-logo.png");
settings.setProperty("sonar.lf.logoWidthPx", 135);
settings.setProperty("sonar.lf.gravatarServerUrl", "https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon");
@@ -130,7 +134,7 @@ public class GlobalActionTest {
}

@Test
public void return_developer_info_disabled_setting() {
void return_developer_info_disabled_setting() {
init();
settings.setProperty("sonar.developerAggregatedInfo.disabled", true);

@@ -142,7 +146,7 @@ public class GlobalActionTest {
}

@Test
public void return_deprecated_logo_settings() {
void return_deprecated_logo_settings() {
init();
settings.setProperty("sonar.lf.logoUrl", "http://example.com/my-custom-logo.png");
settings.setProperty("sonar.lf.logoWidthPx", 135);
@@ -158,8 +162,8 @@ public class GlobalActionTest {
}

@Test
public void the_returned_global_pages_do_not_include_administration_pages() {
init(createPages(), new ResourceTypeTree[] {});
void the_returned_global_pages_do_not_include_administration_pages() {
init(createPages(), new ResourceTypeTree[]{});

assertJson(call()).isSimilarTo("{" +
" \"globalPages\": [" +
@@ -176,7 +180,7 @@ public class GlobalActionTest {
}

@Test
public void return_sonarqube_version() {
void return_sonarqube_version() {
init();
when(server.getVersion()).thenReturn("6.2");

@@ -186,7 +190,16 @@ public class GlobalActionTest {
}

@Test
public void functional_version_when_4_digits() {
void execute_shouldReturnVersionEol() {
init();
try (MockedStatic<MetadataLoader> mocked = mockStatic(MetadataLoader.class)) {
mocked.when(() -> MetadataLoader.loadSqVersionEol(any())).thenReturn("2025-01-01");
assertThat(call()).contains("\"versionEOL\":\"2025-01-01\"");
}
}

@Test
void functional_version_when_4_digits() {
init();
when(server.getVersion()).thenReturn("6.3.1.1234");

@@ -196,7 +209,7 @@ public class GlobalActionTest {
}

@Test
public void functional_version_when_third_digit_is_0() {
void functional_version_when_third_digit_is_0() {
init();
when(server.getVersion()).thenReturn("6.3.0.1234");

@@ -206,7 +219,7 @@ public class GlobalActionTest {
}

@Test
public void return_if_production_database_or_not() {
void return_if_production_database_or_not() {
init();
when(dbClient.getDatabase().getDialect()).thenReturn(new PostgreSql());

@@ -216,7 +229,7 @@ public class GlobalActionTest {
}

@Test
public void return_need_issue_sync() {
void return_need_issue_sync() {
init();
when(indexSyncProgressChecker.isIssueSyncInProgress(any())).thenReturn(true);
assertJson(call()).isSimilarTo("{\"needIssueSync\": true}");
@@ -226,7 +239,7 @@ public class GlobalActionTest {
}

@Test
public void instance_uses_default_admin_credentials() {
void instance_uses_default_admin_credentials() {
init();

when(defaultAdminCredentialsVerifier.hasDefaultCredentialUser()).thenReturn(true);
@@ -242,7 +255,7 @@ public class GlobalActionTest {
}

@Test
public void standalone_flag() {
void standalone_flag() {
init();
userSession.logIn().setSystemAdministrator();
when(nodeInformation.isStandalone()).thenReturn(true);
@@ -251,7 +264,7 @@ public class GlobalActionTest {
}

@Test
public void not_standalone_flag() {
void not_standalone_flag() {
init();
userSession.logIn().setSystemAdministrator();
when(nodeInformation.isStandalone()).thenReturn(false);
@@ -260,14 +273,14 @@ public class GlobalActionTest {
}

@Test
public void test_example_response() {
void test_example_response() {
settings.setProperty("sonar.lf.logoUrl", "http://example.com/my-custom-logo.png");
settings.setProperty("sonar.lf.logoWidthPx", 135);
settings.setProperty("sonar.lf.gravatarServerUrl", "http://some-server.tld/logo.png");
settings.setProperty("sonar.lf.enableGravatar", true);
settings.setProperty("sonar.updatecenter.activate", false);
settings.setProperty("sonar.technicalDebt.ratingGrid", "0.05,0.1,0.2,0.5");
init(createPages(), new ResourceTypeTree[] {
init(createPages(), new ResourceTypeTree[]{
ResourceTypeTree.builder()
.addType(ResourceType.builder("POL").build())
.addType(ResourceType.builder("LOP").build())
@@ -285,12 +298,18 @@ public class GlobalActionTest {
when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY));
when(documentationLinkGenerator.getDocumentationLink(null)).thenReturn("http://docs.example.com/10.0");

String result = call();
assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());

try (MockedStatic<MetadataLoader> mocked = mockStatic(MetadataLoader.class)) {
mocked.when(() -> MetadataLoader.loadSqVersionEol(any())).thenReturn("2025-01-01");

String result = call();

assertJson(result).isSimilarTo(Objects.requireNonNull(ws.getDef().responseExampleAsString()));
}
}

@Test
public void edition_is_not_returned_if_not_defined() {
void edition_is_not_returned_if_not_defined() {
init();
when(editionProvider.get()).thenReturn(Optional.empty());

@@ -299,7 +318,7 @@ public class GlobalActionTest {
}

@Test
public void edition_is_returned_if_defined() {
void edition_is_returned_if_defined() {
init();
when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.DEVELOPER));

@@ -308,7 +327,7 @@ public class GlobalActionTest {
}

@Test
public void web_analytics_js_path_is_not_returned_if_not_defined() {
void web_analytics_js_path_is_not_returned_if_not_defined() {
init();
when(webAnalyticsLoader.getUrlPathToJs()).thenReturn(Optional.empty());

@@ -317,7 +336,7 @@ public class GlobalActionTest {
}

@Test
public void web_analytics_js_path_is_returned_if_defined() {
void web_analytics_js_path_is_returned_if_defined() {
init();
String path = "static/googleanalytics/analytics.js";
when(webAnalyticsLoader.getUrlPathToJs()).thenReturn(Optional.of(path));
@@ -327,7 +346,7 @@ public class GlobalActionTest {
}

@Test
public void call_shouldReturnDocumentationUrl() {
void call_shouldReturnDocumentationUrl() {
init();
String url = "https://docs.sonarsource.com/sonarqube/10.0";
when(documentationLinkGenerator.getDocumentationLink(null)).thenReturn(url);
@@ -337,7 +356,7 @@ public class GlobalActionTest {
}

private void init() {
init(new org.sonar.api.web.page.Page[] {}, new ResourceTypeTree[] {});
init(new org.sonar.api.web.page.Page[]{}, new ResourceTypeTree[]{});
}

private void init(org.sonar.api.web.page.Page[] pages, ResourceTypeTree[] resourceTypeTrees) {
@@ -348,7 +367,7 @@ public class GlobalActionTest {
when(pluginRepository.getPluginInfo(any())).thenReturn(new PluginInfo("unused").setVersion(Version.create("1.0")));
CoreExtensionRepository coreExtensionRepository = mock(CoreExtensionRepository.class);
when(coreExtensionRepository.isInstalled(any())).thenReturn(false);
PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[] {context -> {
PageRepository pageRepository = new PageRepository(pluginRepository, coreExtensionRepository, new PageDefinition[]{context -> {
for (Page page : pages) {
context.addPage(page);
}
@@ -370,6 +389,6 @@ public class GlobalActionTest {
Page anotherPage = Page.builder("another_plugin/page").setName("My Another Page").build();
Page adminPage = Page.builder("my_plugin/admin_page").setName("Admin Page").setAdmin(true).build();

return new Page[] {page, anotherPage, adminPage};
return new Page[]{page, anotherPage, adminPage};
}
}

+ 9
- 0
sonar-plugin-api-impl/build.gradle Ver arquivo

@@ -21,9 +21,14 @@ dependencies {
testImplementation 'com.google.guava:guava'
testImplementation 'com.tngtech.java:junit-dataprovider'
testImplementation 'junit:junit'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.mockito:mockito-junit-jupiter'
testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures'

testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
}

import org.apache.tools.ant.filters.ReplaceTokens
@@ -47,3 +52,7 @@ publishing {
}
}
}
test {
// Enabling the JUnit Platform (see https://github.com/junit-team/junit5-samples/tree/master/junit5-migration-gradle)
useJUnitPlatform()
}

+ 18
- 8
sonar-plugin-api-impl/src/main/java/org/sonar/api/internal/MetadataLoader.java Ver arquivo

@@ -28,6 +28,7 @@ import org.sonar.api.SonarEdition;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.Version;

import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

/**
@@ -40,6 +41,8 @@ public class MetadataLoader {
private static final String SQ_VERSION_FILE_PATH = "/sq-version.txt";
private static final String SONAR_API_VERSION_FILE_PATH = "/sonar-api-version.txt";
private static final String EDITION_FILE_PATH = "/sonar-edition.txt";
private static final String SQ_VERSION_EOL_FILE_PATH = "/sq-version-eol.txt";
public static final String CAN_NOT_LOAD_FROM_CLASSPATH = "Can not load %s from classpath";

private MetadataLoader() {
// only static methods
@@ -48,18 +51,25 @@ public class MetadataLoader {
public static Version loadApiVersion(System2 system) {
return getVersion(system, SONAR_API_VERSION_FILE_PATH);
}

public static Version loadSQVersion(System2 system) {
return getVersion(system, SQ_VERSION_FILE_PATH);
}

public static String loadSqVersionEol(System2 system) {
return getParamFromFile(system, SQ_VERSION_EOL_FILE_PATH);
}

private static Version getVersion(System2 system, String versionFilePath) {
URL url = system.getResource(versionFilePath);
return Version.parse(getParamFromFile(system, versionFilePath));
}

try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8.name())) {
String versionInFile = scanner.nextLine();
return Version.parse(versionInFile);
private static String getParamFromFile(System2 system, String filePath) {
URL url = system.getResource(filePath);
try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8)) {
return scanner.nextLine();
} catch (IOException e) {
throw new IllegalStateException("Can not load " + versionFilePath + " from classpath ", e);
throw new IllegalStateException(format(CAN_NOT_LOAD_FROM_CLASSPATH, filePath), e);
}
}

@@ -68,11 +78,11 @@ public class MetadataLoader {
if (url == null) {
return SonarEdition.COMMUNITY;
}
try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8.name())) {
try (Scanner scanner = new Scanner(url.openStream(), StandardCharsets.UTF_8)) {
String editionInFile = scanner.nextLine();
return parseEdition(editionInFile);
} catch (IOException e) {
throw new IllegalStateException("Can not load " + EDITION_FILE_PATH + " from classpath", e);
throw new IllegalStateException(format(CAN_NOT_LOAD_FROM_CLASSPATH, EDITION_FILE_PATH), e);
}
}

@@ -81,7 +91,7 @@ public class MetadataLoader {
try {
return SonarEdition.valueOf(str);
} catch (IllegalArgumentException e) {
throw new IllegalStateException(String.format("Invalid edition found in '%s': '%s'", EDITION_FILE_PATH, str));
throw new IllegalStateException(format("Invalid edition found in '%s': '%s'", EDITION_FILE_PATH, str));
}
}
}

+ 21
- 8
sonar-plugin-api-impl/src/test/java/org/sonar/api/internal/MetadataLoaderTest.java Ver arquivo

@@ -21,7 +21,7 @@ package org.sonar.api.internal;

import java.io.File;
import java.net.MalformedURLException;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.sonar.api.SonarEdition;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.Version;
@@ -32,45 +32,45 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MetadataLoaderTest {
class MetadataLoaderTest {
private final System2 system = mock(System2.class);

@Test
public void load_api_version_from_file_in_classpath() {
void load_api_version_from_file_in_classpath() {
Version version = MetadataLoader.loadApiVersion(System2.INSTANCE);
assertThat(version).isNotNull();
assertThat(version.major()).isGreaterThanOrEqualTo(5);
}

@Test
public void load_sq_version_from_file_in_classpath() {
void load_sq_version_from_file_in_classpath() {
Version version = MetadataLoader.loadSQVersion(System2.INSTANCE);
assertThat(version).isNotNull();
assertThat(version.major()).isGreaterThanOrEqualTo(5);
}

@Test
public void load_edition_from_file_in_classpath() {
void load_edition_from_file_in_classpath() {
SonarEdition edition = MetadataLoader.loadEdition(System2.INSTANCE);
assertThat(edition).isNotNull();
}

@Test
public void load_edition_defaults_to_community_if_file_not_found() throws MalformedURLException {
void load_edition_defaults_to_community_if_file_not_found() throws MalformedURLException {
when(system.getResource(anyString())).thenReturn(new File("target/unknown").toURI().toURL());
SonarEdition edition = MetadataLoader.loadEdition(System2.INSTANCE);
assertThat(edition).isEqualTo(SonarEdition.COMMUNITY);
}

@Test
public void throw_ISE_if_edition_is_invalid() {
void throw_ISE_if_edition_is_invalid() {
assertThatThrownBy(() -> MetadataLoader.parseEdition("trash"))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Invalid edition found in '/sonar-edition.txt': 'TRASH'");
}

@Test
public void throw_ISE_if_fail_to_load_version() throws Exception {
void throw_ISE_if_fail_to_load_version() throws Exception {
when(system.getResource(anyString())).thenReturn(new File("target/unknown").toURI().toURL());

assertThatThrownBy(() -> MetadataLoader.loadApiVersion(system))
@@ -78,4 +78,17 @@ public class MetadataLoaderTest {
.hasMessageContaining("Can not load /sonar-api-version.txt from classpath");
}

@Test
void loadSqVersionEol_shouldLoadCorrectEol() {
String eol = MetadataLoader.loadSqVersionEol(System2.INSTANCE);
assertThat(eol).isNotNull();
}

@Test
void loadSqVersionEol_whenFileNotFound_shouldThrowException() throws MalformedURLException {
when(system.getResource(anyString())).thenReturn(new File("target/unknown").toURI().toURL());
assertThatThrownBy(() -> MetadataLoader.loadSqVersionEol(system))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Can not load /sq-version-eol.txt from classpath");
}
}

Carregando…
Cancelar
Salvar