ソースを参照

SONAR-9802 Add nodes to system info in cluster mode

tags/6.6-RC1
Grégoire Aubert 6年前
コミット
74c8a8ac5e
78個のファイルの変更3375行の追加789行の削除
  1. 2
    2
      server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java
  2. 3
    3
      server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java
  3. 3
    3
      server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java
  4. 21
    10
      server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java
  5. 22
    16
      server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java
  6. 64
    0
      server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java
  7. 16
    9
      server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java
  8. 3
    3
      server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java
  9. 8
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java
  10. 33
    33
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java
  11. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java
  12. 0
    140
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java
  13. 119
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java
  14. 2
    3
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java
  15. 13
    19
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java
  16. 10
    12
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java
  17. 25
    32
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java
  18. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java
  19. 0
    104
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java
  20. 103
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java
  21. 59
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
  22. 13
    20
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  23. 26
    30
      server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
  24. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java
  25. 12
    10
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java
  26. 22
    33
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java
  27. 4
    5
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java
  28. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java
  29. 12
    18
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java
  30. 16
    12
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java
  31. 36
    32
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java
  32. 44
    0
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java
  33. 10
    8
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java
  34. 9
    10
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java
  35. 18
    17
      server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
  36. 0
    37
      server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java
  37. 41
    6
      server/sonar-web/src/main/js/api/system.ts
  38. 43
    0
      server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
  39. 125
    0
      server/sonar-web/src/main/js/apps/system/components/App.tsx
  40. 114
    0
      server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
  41. 82
    0
      server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
  42. 163
    0
      server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
  43. 28
    19
      server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
  44. 30
    0
      server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx
  45. 38
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx
  46. 47
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
  47. 55
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
  48. 34
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx
  49. 194
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
  50. 59
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
  51. 126
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap
  52. 38
    0
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
  53. 84
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
  54. 41
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
  55. 42
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
  56. 52
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx
  57. 71
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx
  58. 64
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
  59. 32
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
  60. 47
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
  61. 32
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx
  62. 60
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx
  63. 132
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
  64. 17
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap
  65. 68
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
  66. 125
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap
  67. 180
    0
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap
  68. 7
    5
      server/sonar-web/src/main/js/apps/system/main.js
  69. 8
    2
      server/sonar-web/src/main/js/apps/system/routes.ts
  70. 81
    0
      server/sonar-web/src/main/js/apps/system/styles.css
  71. 99
    0
      server/sonar-web/src/main/js/apps/system/utils.ts
  72. 93
    0
      server/sonar-web/src/main/js/components/common/RestartForm.tsx
  73. 3
    25
      server/sonar-web/src/main/js/components/facet/FacetHeader.js
  74. 15
    95
      server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap
  75. 44
    0
      server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx
  76. 2
    0
      server/sonar-web/src/main/js/components/icons-components/icons.js
  77. 14
    8
      server/sonar-web/src/main/js/helpers/urls.ts
  78. 12
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 2
- 2
server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java ファイルの表示

@@ -22,7 +22,7 @@ package org.sonar.ce;
import org.sonar.ce.configuration.CeConfigurationImpl;
import org.sonar.ce.log.CeLogging;
import org.sonar.core.platform.Module;
import org.sonar.process.systeminfo.ProcessStateSystemInfo;
import org.sonar.process.systeminfo.JvmStateSection;
import org.sonar.ce.monitoring.CeDatabaseMBeanImpl;

public class CeConfigurationModule extends Module {
@@ -32,6 +32,6 @@ public class CeConfigurationModule extends Module {
CeConfigurationImpl.class,
CeLogging.class,
CeDatabaseMBeanImpl.class,
new ProcessStateSystemInfo("Compute Engine State"));
new JvmStateSection("Compute Engine JVM State"));
}
}

+ 3
- 3
server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java ファイルの表示

@@ -26,7 +26,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.ce.httpd.HttpAction;
import org.sonar.process.systeminfo.ProcessStateSystemInfo;
import org.sonar.process.systeminfo.JvmStateSection;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

@@ -44,8 +44,8 @@ public class SystemInfoHttpActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

private SystemInfoSection stateProvider1 = new ProcessStateSystemInfo("state1");
private SystemInfoSection stateProvider2 = new ProcessStateSystemInfo("state2");
private SystemInfoSection stateProvider1 = new JvmStateSection("state1");
private SystemInfoSection stateProvider2 = new JvmStateSection("state2");
private SystemInfoHttpAction underTest;

@Before

+ 3
- 3
server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java ファイルの表示

@@ -59,15 +59,15 @@ public class DistributedAnswer<T> {
return members;
}

void setAnswer(Member member, T answer) {
public void setAnswer(Member member, T answer) {
this.answers.put(member, answer);
}

void setTimedOut(Member member) {
public void setTimedOut(Member member) {
this.timedOutMembers.add(member);
}

void setFailed(Member member, Exception e) {
public void setFailed(Member member, Exception e) {
failedMembers.put(member, e);
}
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java → server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java ファイルの表示

@@ -17,24 +17,35 @@
* 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.monitoring;
package org.sonar.process.systeminfo;

import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

public class JvmPropsMonitor implements Monitor {
@Override
public String name() {
return "JvmProperties";
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

/**
* Dumps {@link System#getProperties()}
*/
public class JvmPropertiesSection implements SystemInfoSection {

private final String name;

public JvmPropertiesSection(String name) {
this.name = name;
}

@Override
public Map<String, Object> attributes() {
Map<String, Object> sortedProps = new TreeMap<>();
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName(name);

for (Map.Entry<Object, Object> systemProp : System.getProperties().entrySet()) {
sortedProps.put(Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue()));
if (systemProp.getValue() != null) {
setAttribute(protobuf, Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue()));
}
}
return sortedProps;
return protobuf.build();
}
}

server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java → server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java ファイルの表示

@@ -25,11 +25,16 @@ import java.lang.management.MemoryUsage;
import java.lang.management.ThreadMXBean;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

public class ProcessStateSystemInfo implements SystemInfoSection {
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

/**
* Dumps state of JVM (memory, threads)
*/
public class JvmStateSection implements SystemInfoSection {
private static final long MEGABYTE = 1024L * 1024L;
private final String name;

public ProcessStateSystemInfo(String name) {
public JvmStateSection(String name) {
this.name = name;
}

@@ -40,26 +45,27 @@ public class ProcessStateSystemInfo implements SystemInfoSection {

// Visible for testing
ProtobufSystemInfo.Section toProtobuf(MemoryMXBean memoryBean) {
ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder();
builder.setName(name);
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName(name);
MemoryUsage heap = memoryBean.getHeapMemoryUsage();
addAttributeInMb(builder, "Heap Committed (MB)", heap.getCommitted());
addAttributeInMb(builder, "Heap Init (MB)", heap.getInit());
addAttributeInMb(builder, "Heap Max (MB)", heap.getMax());
addAttributeInMb(builder, "Heap Used (MB)", heap.getUsed());
addAttributeInMb(protobuf, "Heap Committed (MB)", heap.getCommitted());
addAttributeInMb(protobuf, "Heap Init (MB)", heap.getInit());
addAttributeInMb(protobuf, "Heap Max (MB)", heap.getMax());
addAttributeInMb(protobuf, "Heap Used (MB)", heap.getUsed());
MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage();
addAttributeInMb(builder, "Non Heap Committed (MB)", nonHeap.getCommitted());
addAttributeInMb(builder, "Non Heap Init (MB)", nonHeap.getInit());
addAttributeInMb(builder, "Non Heap Max (MB)", nonHeap.getMax());
addAttributeInMb(builder, "Non Heap Used (MB)", nonHeap.getUsed());
addAttributeInMb(protobuf, "Non Heap Committed (MB)", nonHeap.getCommitted());
addAttributeInMb(protobuf, "Non Heap Init (MB)", nonHeap.getInit());
addAttributeInMb(protobuf, "Non Heap Max (MB)", nonHeap.getMax());
addAttributeInMb(protobuf, "Non Heap Used (MB)", nonHeap.getUsed());
ThreadMXBean thread = ManagementFactory.getThreadMXBean();
builder.addAttributesBuilder().setKey("Thread Count").setLongValue(thread.getThreadCount()).build();
return builder.build();
setAttribute(protobuf, "Thread Count", thread.getThreadCount());

return protobuf.build();
}

private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder builder, String key, long valueInBytes) {
private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder protobuf, String key, long valueInBytes) {
if (valueInBytes >= 0L) {
builder.addAttributesBuilder().setKey(key).setLongValue(valueInBytes / MEGABYTE).build();
setAttribute(protobuf, key, valueInBytes / MEGABYTE);
}
}
}

+ 64
- 0
server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java ファイルの表示

@@ -0,0 +1,64 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.process.systeminfo;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

public class SystemInfoUtils {

private SystemInfoUtils() {
// prevent instantiation
}

public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, @Nullable String value) {
if (value != null) {
section.addAttributesBuilder()
.setKey(key)
.setStringValue(value)
.build();
}
}

public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, boolean value) {
section.addAttributesBuilder()
.setKey(key)
.setBooleanValue(value)
.build();
}

public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, long value) {
section.addAttributesBuilder()
.setKey(key)
.setLongValue(value)
.build();
}

@CheckForNull
public static ProtobufSystemInfo.Attribute attribute(ProtobufSystemInfo.Section section, String key) {
for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
if (attribute.getKey().equals(key)) {
return attribute;
}
}
return null;
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java → server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java ファイルの表示

@@ -17,22 +17,29 @@
* 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.ws;
package org.sonar.process.systeminfo;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;

public class InfoActionModuleTest {
@Test
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
public class JvmPropertiesSectionTest {

new InfoActionModule().configure(container);
private JvmPropertiesSection underTest = new JvmPropertiesSection("Web JVM Properties");

assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
@Test
public void name_is_not_empty() {
assertThat(underTest.toProtobuf().getName()).isEqualTo("Web JVM Properties");
}

@Test
public void test_toProtobuf() {
ProtobufSystemInfo.Section section = underTest.toProtobuf();

Assertions.assertThat(attribute(section, "java.vm.vendor").getStringValue()).isNotEmpty();
Assertions.assertThat(attribute(section, "os.name").getStringValue()).isNotEmpty();
}
}

server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java → server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java ファイルの表示

@@ -28,13 +28,13 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ProcessStateSystemInfoTest {
public class JvmStateSectionTest {

public static final String PROCESS_NAME = "the process name";

@Test
public void toSystemInfoSection() {
ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME);
JvmStateSection underTest = new JvmStateSection(PROCESS_NAME);
ProtobufSystemInfo.Section section = underTest.toProtobuf();

assertThat(section.getName()).isEqualTo(PROCESS_NAME);
@@ -47,7 +47,7 @@ public class ProcessStateSystemInfoTest {
MemoryMXBean memoryBean = mock(MemoryMXBean.class, Mockito.RETURNS_DEEP_STUBS);
when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(-1L);

ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME);
JvmStateSection underTest = new JvmStateSection(PROCESS_NAME);
ProtobufSystemInfo.Section section = underTest.toProtobuf(memoryBean);

assertThat(section.getAttributesList()).extracting("key").doesNotContain("Heap Committed (MB)");

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java ファイルの表示

@@ -21,12 +21,13 @@ package org.sonar.server.platform.monitoring;

import org.picocontainer.Startable;
import org.sonar.process.Jmx;
import org.sonar.process.systeminfo.SystemInfoSection;

/**
* Base implementation of a {@link org.sonar.server.platform.monitoring.Monitor}
* Base implementation of a {@link SystemInfoSection}
* that is exported as a JMX bean
*/
public abstract class BaseMonitorMBean implements Monitor, Startable {
public abstract class BaseSectionMBean implements SystemInfoSection, Startable {

/**
* Auto-registers to MBean server
@@ -47,4 +48,9 @@ public abstract class BaseMonitorMBean implements Monitor, Startable {
String objectName() {
return "SonarQube:name=" + name();
}

/**
* Name of section in System Info page
*/
abstract String name();
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java ファイルの表示

@@ -19,25 +19,25 @@
*/
package org.sonar.server.platform.monitoring;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.dbcp.BasicDataSource;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;

import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

/**
* Information about database and connection pool
*/
public class DatabaseMonitor extends BaseMonitorMBean implements DatabaseMonitorMBean {
public class DatabaseSection extends BaseSectionMBean implements DatabaseSectionMBean {

private final DatabaseVersion dbVersion;
private final DbClient dbClient;

public DatabaseMonitor(DatabaseVersion dbVersion, DbClient dbClient) {
public DatabaseSection(DatabaseVersion dbVersion, DbClient dbClient) {
this.dbVersion = dbVersion;
this.dbClient = dbClient;
}
@@ -98,40 +98,40 @@ public class DatabaseMonitor extends BaseMonitorMBean implements DatabaseMonitor
}

@Override
public Map<String, Object> attributes() {
Map<String, Object> attributes = new LinkedHashMap<>();
completeDbAttributes(attributes);
completePoolAttributes(attributes);
return attributes;
}

private void completePoolAttributes(Map<String, Object> attributes) {
attributes.put("Pool Active Connections", getPoolActiveConnections());
attributes.put("Pool Max Connections", getPoolMaxActiveConnections());
attributes.put("Pool Initial Size", getPoolInitialSize());
attributes.put("Pool Idle Connections", getPoolIdleConnections());
attributes.put("Pool Min Idle Connections", getPoolMinIdleConnections());
attributes.put("Pool Max Idle Connections", getPoolMaxIdleConnections());
attributes.put("Pool Max Wait (ms)", getPoolMaxWaitMillis());
attributes.put("Pool Remove Abandoned", getPoolRemoveAbandoned());
attributes.put("Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
public Section toProtobuf() {
Section.Builder protobuf = Section.newBuilder();
protobuf.setName(name());
completeDbAttributes(protobuf);
completePoolAttributes(protobuf);
return protobuf.build();
}

private void completePoolAttributes(Section.Builder protobuf) {
setAttribute(protobuf, "Pool Active Connections", getPoolActiveConnections());
setAttribute(protobuf, "Pool Max Connections", getPoolMaxActiveConnections());
setAttribute(protobuf, "Pool Initial Size", getPoolInitialSize());
setAttribute(protobuf, "Pool Idle Connections", getPoolIdleConnections());
setAttribute(protobuf, "Pool Min Idle Connections", getPoolMinIdleConnections());
setAttribute(protobuf, "Pool Max Idle Connections", getPoolMaxIdleConnections());
setAttribute(protobuf, "Pool Max Wait (ms)", getPoolMaxWaitMillis());
setAttribute(protobuf, "Pool Remove Abandoned", getPoolRemoveAbandoned());
setAttribute(protobuf, "Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
}

private BasicDataSource commonsDbcp() {
return (BasicDataSource) dbClient.getDatabase().getDataSource();
}

private void completeDbAttributes(Map<String, Object> attributes) {
try (DbSession dbSession = dbClient.openSession(false);
Connection connection = dbSession.getConnection()) {
DatabaseMetaData metadata = connection.getMetaData();
attributes.put("Database", metadata.getDatabaseProductName());
attributes.put("Database Version", metadata.getDatabaseProductVersion());
attributes.put("Username", metadata.getUserName());
attributes.put("URL", metadata.getURL());
attributes.put("Driver", metadata.getDriverName());
attributes.put("Driver Version", metadata.getDriverVersion());
attributes.put("Version Status", getMigrationStatus());
private void completeDbAttributes(Section.Builder protobuf) {
try (DbSession dbSession = dbClient.openSession(false)) {
DatabaseMetaData metadata = dbSession.getConnection().getMetaData();
setAttribute(protobuf, "Database", metadata.getDatabaseProductName());
setAttribute(protobuf, "Database Version", metadata.getDatabaseProductVersion());
setAttribute(protobuf, "Username", metadata.getUserName());
setAttribute(protobuf, "URL", metadata.getURL());
setAttribute(protobuf, "Driver", metadata.getDriverName());
setAttribute(protobuf, "Driver Version", metadata.getDriverVersion());
setAttribute(protobuf, "Version Status", getMigrationStatus());
} catch (SQLException e) {
throw new IllegalStateException("Fail to get DB metadata", e);
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java ファイルの表示

@@ -19,7 +19,7 @@
*/
package org.sonar.server.platform.monitoring;

public interface DatabaseMonitorMBean {
public interface DatabaseSectionMBean {

/**
* Is database schema up-to-date or should it be upgraded ?

+ 0
- 140
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java ファイルの表示

@@ -1,140 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.monitoring;

import java.util.LinkedHashMap;
import java.util.Map;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
import org.elasticsearch.action.admin.indices.stats.IndexStats;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.es.EsClient;

import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;

public class EsMonitor extends BaseMonitorMBean implements EsMonitorMBean {

private final EsClient esClient;

public EsMonitor(EsClient esClient) {
this.esClient = esClient;
}

@Override
public String name() {
return "Elasticsearch";
}

/**
* MXBean does not allow to return enum {@link ClusterHealthStatus}, so
* returning String.
*/
@Override
public String getState() {
return getStateAsEnum().name();
}

private ClusterHealthStatus getStateAsEnum() {
return clusterStats().getStatus();
}

@Override
public int getNumberOfNodes() {
return clusterStats().getNodesStats().getCounts().getTotal();
}

@Override
public Map<String, Object> attributes() {
try {
Map<String, Object> attributes = new LinkedHashMap<>();
attributes.put("State", getStateAsEnum());
attributes.put("Indices", indexAttributes());
attributes.put("Number of Nodes", getNumberOfNodes());
attributes.put("Nodes", nodeAttributes());
return attributes;
} catch (Exception es) {
Loggers.get(EsMonitor.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
Map<String, Object> attributes = new LinkedHashMap<>();
attributes.put("State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
return attributes;
}
}

private LinkedHashMap<String, LinkedHashMap<String, Object>> indexAttributes() {
LinkedHashMap<String, LinkedHashMap<String, Object>> indices = new LinkedHashMap<>();
IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();

for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
indices.put(indexStats.getKey(), attributes);
attributes.put("Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
attributes.put("Shards", indexStats.getValue().getShards().length);
attributes.put("Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
}
return indices;
}

/**
* map of {node name -> node attributes}
*/
private LinkedHashMap<String, LinkedHashMap<String, Object>> nodeAttributes() {
LinkedHashMap<String, LinkedHashMap<String, Object>> nodes = new LinkedHashMap<>();
NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
for (Map.Entry<String, NodeStats> entry : nodesStats.getNodesMap().entrySet()) {

LinkedHashMap<String, Object> nodeAttributes = new LinkedHashMap<>();
NodeStats stats = entry.getValue();
DiscoveryNode node = stats.getNode();
nodes.put(node.getName(), nodeAttributes);
nodeAttributes.put("Address", node.getAddress().toString());
nodeAttributes.put("Type", node.isMasterNode() ? "Master" : "Slave");
nodeAttributes.put("Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
nodeAttributes.put("Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
nodeAttributes.put("Open Files", stats.getProcess().getOpenFileDescriptors());
nodeAttributes.put("JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
nodeAttributes.put("JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
nodeAttributes.put("JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
nodeAttributes.put("JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
nodeAttributes.put("JVM Threads", stats.getJvm().getThreads().getCount());
nodeAttributes.put("Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
nodeAttributes.put("Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
nodeAttributes.put("Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
nodeAttributes.put("Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
nodeAttributes.put("Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
nodeAttributes.put("Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
nodeAttributes.put("Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
}
return nodes;
}

private ClusterStatsResponse clusterStats() {
return esClient.prepareClusterStats().get();
}

private static String formatPercent(long amount) {
return String.format("%.1f%%", 100 * amount * 1.0D / 100L);
}
}

+ 119
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java ファイルの表示

@@ -0,0 +1,119 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.monitoring;

import java.util.Map;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
import org.elasticsearch.action.admin.indices.stats.IndexStats;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.sonar.api.utils.log.Loggers;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.es.EsClient;

import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

public class EsSection extends BaseSectionMBean implements EsSectionMBean {

private final EsClient esClient;

public EsSection(EsClient esClient) {
this.esClient = esClient;
}

@Override
public String name() {
return "Elasticsearch";
}

/**
* MXBean does not allow to return enum {@link ClusterHealthStatus}, so
* returning String.
*/
@Override
public String getState() {
return getStateAsEnum().name();
}

private ClusterHealthStatus getStateAsEnum() {
return clusterStats().getStatus();
}

@Override
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName(name());
try {
setAttribute(protobuf, "State", getStateAsEnum().name());
completeNodeAttributes(protobuf);
completeIndexAttributes(protobuf);

} catch (Exception es) {
Loggers.get(EsSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es);
setAttribute(protobuf, "State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage());
}
return protobuf.build();
}

private void completeIndexAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
IndicesStatsResponse indicesStats = esClient.prepareStats().all().get();
for (Map.Entry<String, IndexStats> indexStats : indicesStats.getIndices().entrySet()) {
String prefix = "Index " + indexStats.getKey() + " - ";
setAttribute(protobuf, prefix + "Docs", indexStats.getValue().getPrimaries().getDocs().getCount());
setAttribute(protobuf, prefix + "Shards", indexStats.getValue().getShards().length);
setAttribute(protobuf, prefix + "Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes()));
}
}

private void completeNodeAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
if (!nodesStats.getNodes().isEmpty()) {
NodeStats stats = nodesStats.getNodes().get(0);
setAttribute(protobuf, "Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
setAttribute(protobuf, "Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
setAttribute(protobuf, "Open Files", stats.getProcess().getOpenFileDescriptors());
setAttribute(protobuf, "JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
setAttribute(protobuf, "JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
setAttribute(protobuf, "JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
setAttribute(protobuf, "JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
setAttribute(protobuf, "JVM Threads", stats.getJvm().getThreads().getCount());
setAttribute(protobuf, "Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
setAttribute(protobuf, "Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
setAttribute(protobuf, "Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
setAttribute(protobuf, "Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
setAttribute(protobuf, "Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
setAttribute(protobuf, "Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
setAttribute(protobuf, "Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
}
}

private ClusterStatsResponse clusterStats() {
return esClient.prepareClusterStats().get();
}

private static String formatPercent(long amount) {
return String.format("%.1f%%", 100 * amount * 1.0D / 100L);
}
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java ファイルの表示

@@ -20,10 +20,9 @@
package org.sonar.server.platform.monitoring;

/**
* The public attributes of {@link org.sonar.server.platform.monitoring.EsMonitor}
* The public attributes of {@link EsSection}
* to be exported in JMX bean.
*/
public interface EsMonitorMBean {
public interface EsSectionMBean {
String getState();
int getNumberOfNodes();
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java ファイルの表示

@@ -19,39 +19,33 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.LinkedHashMap;
import java.util.Map;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.updatecenter.common.Version;

/**
* Installed plugins (excluding core plugins)
*/
public class PluginsMonitor implements Monitor {
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

public class PluginsSection implements SystemInfoSection {
private final PluginRepository repository;

public PluginsMonitor(PluginRepository repository) {
public PluginsSection(PluginRepository repository) {
this.repository = repository;
}

@Override
public String name() {
return "Plugins";
}

@Override
public Map<String, Object> attributes() {
Map<String, Object> attributes = new LinkedHashMap<>();
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName("Plugins");
for (PluginInfo plugin : repository.getPluginInfos()) {
LinkedHashMap<String, Object> pluginAttributes = new LinkedHashMap<>();
pluginAttributes.put("Name", plugin.getName());
String label = "[" + plugin.getName() + "]";
Version version = plugin.getVersion();
if (version != null) {
pluginAttributes.put("Version", version.getName());
label = version.getName() + " " + label;
}
attributes.put(plugin.getKey(), pluginAttributes);
setAttribute(protobuf, plugin.getKey(), label);
}
return attributes;
return protobuf.build();
}
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java ファイルの表示

@@ -19,41 +19,39 @@
*/
package org.sonar.server.platform.monitoring;

import com.google.common.collect.ImmutableSortedMap;
import java.util.Map;
import java.util.SortedMap;
import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

import static org.apache.commons.lang.StringUtils.abbreviate;
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

public class SettingsMonitor implements Monitor {
public class SettingsSection implements SystemInfoSection {

static final int MAX_VALUE_LENGTH = 500;
private final Settings settings;

public SettingsMonitor(Settings settings) {
public SettingsSection(Settings settings) {
this.settings = settings;
}

@Override
public String name() {
return "Settings";
}
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName("Settings");

@Override
public SortedMap<String, Object> attributes() {
PropertyDefinitions definitions = settings.getDefinitions();
ImmutableSortedMap.Builder<String, Object> builder = ImmutableSortedMap.naturalOrder();
for (Map.Entry<String, String> prop : settings.getProperties().entrySet()) {
String key = prop.getKey();
PropertyDefinition def = definitions.get(key);
if (def == null || def.type() != PropertyType.PASSWORD) {
builder.put(key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH));
setAttribute(protobuf, key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH));
}
}
return builder.build();
return protobuf.build();
}
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java ファイルの表示

@@ -21,9 +21,7 @@ package org.sonar.server.platform.monitoring;

import com.google.common.base.Joiner;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.CoreProperties;
@@ -33,12 +31,15 @@ import org.sonar.api.security.SecurityRealm;
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.process.ProcessProperties;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.authentication.IdentityProviderRepository;
import org.sonar.server.platform.ServerIdLoader;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.user.SecurityRealmFactory;

public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonitorMBean {
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

public class SonarQubeSection extends BaseSectionMBean implements SonarQubeSectionMBean {

private static final Joiner COMMA_JOINER = Joiner.on(", ");

@@ -51,7 +52,7 @@ public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonit
private final ServerLogging serverLogging;
private final ServerIdLoader serverIdLoader;

public SonarQubeMonitor(Configuration config, SecurityRealmFactory securityRealmFactory,
public SonarQubeSection(Configuration config, SecurityRealmFactory securityRealmFactory,
IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
ServerIdLoader serverIdLoader) {
this.config = config;
@@ -118,39 +119,31 @@ public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonit
}

@Override
public Map<String, Object> attributes() {
Map<String, Object> attributes = new LinkedHashMap<>();
completeWithServerIdAttributes(attributes);
attributes.put("Version", getVersion());
addIfNotNull("External User Authentication", getExternalUserAuthentication(), attributes);
addIfNotEmpty("Accepted external identity providers", getEnabledIdentityProviders(), attributes);
addIfNotEmpty("External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders(), attributes);
attributes.put("Force authentication", getForceAuthentication());
attributes.put("Official Distribution", isOfficialDistribution());
attributes.put("Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
attributes.put("Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
attributes.put("Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
attributes.put("Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
attributes.put("Logs Level", getLogLevel());
return attributes;
}
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName(name());

private void completeWithServerIdAttributes(Map<String, Object> attributes) {
serverIdLoader.get().ifPresent(serverId -> {
attributes.put("Server ID", serverId.getId());
attributes.put("Server ID validated", serverId.isValid());
setAttribute(protobuf, "Server ID", serverId.getId());
setAttribute(protobuf, "Server ID validated", serverId.isValid());
});
setAttribute(protobuf, "Version", getVersion());
setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication());
addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders());
addIfNotEmpty(protobuf, "External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders());
setAttribute(protobuf, "Force authentication", getForceAuthentication());
setAttribute(protobuf, "Official Distribution", isOfficialDistribution());
setAttribute(protobuf, "Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
setAttribute(protobuf, "Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
setAttribute(protobuf, "Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
setAttribute(protobuf, "Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
setAttribute(protobuf, "Logs Level", getLogLevel());
return protobuf.build();
}

private static void addIfNotNull(String key, @Nullable String value, Map<String, Object> attributes) {
if (value != null) {
attributes.put(key, value);
}
}

private static void addIfNotEmpty(String key, List<String> values, Map<String, Object> attributes) {
if (!values.isEmpty()) {
attributes.put(key, COMMA_JOINER.join(values));
private static void addIfNotEmpty(ProtobufSystemInfo.Section.Builder protobuf, String key, @Nullable List<String> values) {
if (values != null && !values.isEmpty()) {
setAttribute(protobuf, key, COMMA_JOINER.join(values));
}
}
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java ファイルの表示

@@ -21,7 +21,7 @@ package org.sonar.server.platform.monitoring;

import javax.annotation.CheckForNull;

public interface SonarQubeMonitorMBean {
public interface SonarQubeSectionMBean {
@CheckForNull
String getServerId();


+ 0
- 104
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java ファイルの表示

@@ -1,104 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.monitoring;

import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.sonar.api.utils.System2;

import static org.sonar.api.utils.DateUtils.formatDateTime;

/**
* JVM runtime information. Not exported as a MXBean because these informations
* are natively provided.
*/
public class SystemMonitor implements Monitor {
private final System2 system;

public SystemMonitor() {
this(System2.INSTANCE);
}

SystemMonitor(System2 system) {
this.system = system;
}

@Override
public String name() {
return "System";
}

@Override
public Map<String, Object> attributes() {
Map<String, Object> attributes = new LinkedHashMap<>();
attributes.put("System Date", formatDateTime(new Date(system.now())));
attributes.put("Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
attributes.put("JVM Vendor", runtimeMXBean().getVmVendor());
attributes.put("JVM Name", runtimeMXBean().getVmName());
attributes.put("JVM Version", runtimeMXBean().getVmVersion());
attributes.put("Processors", runtime().availableProcessors());
attributes.put("System Classpath", runtimeMXBean().getClassPath());
attributes.put("BootClassPath", runtimeMXBean().getBootClassPath());
attributes.put("Library Path", runtimeMXBean().getLibraryPath());
attributes.put("Total Memory", formatMemory(runtime().totalMemory()));
attributes.put("Free Memory", formatMemory(runtime().freeMemory()));
attributes.put("Max Memory", formatMemory(runtime().maxMemory()));
attributes.put("Heap", memoryMXBean().getHeapMemoryUsage().toString());
attributes.put("Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
attributes.put("System Load Average", String.format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
attributes.put("Loaded Classes", classLoadingMXBean().getLoadedClassCount());
attributes.put("Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
attributes.put("Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
attributes.put("Threads", threadMXBean().getThreadCount());
attributes.put("Threads Peak", threadMXBean().getPeakThreadCount());
attributes.put("Daemon Thread", threadMXBean().getDaemonThreadCount());
return attributes;
}

private static RuntimeMXBean runtimeMXBean() {
return ManagementFactory.getRuntimeMXBean();
}

private static Runtime runtime() {
return Runtime.getRuntime();
}

private static MemoryMXBean memoryMXBean() {
return ManagementFactory.getMemoryMXBean();
}

private static ClassLoadingMXBean classLoadingMXBean() {
return ManagementFactory.getClassLoadingMXBean();
}

private static ThreadMXBean threadMXBean() {
return ManagementFactory.getThreadMXBean();
}

private static String formatMemory(long memoryInBytes) {
return String.format("%d MB", memoryInBytes / 1_000_000);
}
}

+ 103
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java ファイルの表示

@@ -0,0 +1,103 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.monitoring;

import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.util.Date;
import org.sonar.api.utils.System2;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

import static java.lang.String.format;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

/**
* JVM runtime information. Not exported as a MXBean because these information
* are natively provided.
*/
public class SystemSection implements SystemInfoSection {
private final System2 system;

public SystemSection() {
this(System2.INSTANCE);
}

SystemSection(System2 system) {
this.system = system;
}

@Override
public ProtobufSystemInfo.Section toProtobuf() {
ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
protobuf.setName("System");

setAttribute(protobuf, "System Date", formatDateTime(new Date(system.now())));
setAttribute(protobuf, "Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime())));
setAttribute(protobuf, "JVM Vendor", runtimeMXBean().getVmVendor());
setAttribute(protobuf, "JVM Name", runtimeMXBean().getVmName());
setAttribute(protobuf, "JVM Version", runtimeMXBean().getVmVersion());
setAttribute(protobuf, "Processors", runtime().availableProcessors());
setAttribute(protobuf, "System Classpath", runtimeMXBean().getClassPath());
setAttribute(protobuf, "BootClassPath", runtimeMXBean().getBootClassPath());
setAttribute(protobuf, "Library Path", runtimeMXBean().getLibraryPath());
setAttribute(protobuf, "Total Memory", formatMemory(runtime().totalMemory()));
setAttribute(protobuf, "Free Memory", formatMemory(runtime().freeMemory()));
setAttribute(protobuf, "Max Memory", formatMemory(runtime().maxMemory()));
setAttribute(protobuf, "Heap", memoryMXBean().getHeapMemoryUsage().toString());
setAttribute(protobuf, "Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString());
setAttribute(protobuf, "System Load Average", format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0));
setAttribute(protobuf, "Loaded Classes", classLoadingMXBean().getLoadedClassCount());
setAttribute(protobuf, "Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount());
setAttribute(protobuf, "Unloaded Classes", classLoadingMXBean().getUnloadedClassCount());
setAttribute(protobuf, "Threads", threadMXBean().getThreadCount());
setAttribute(protobuf, "Threads Peak", threadMXBean().getPeakThreadCount());
setAttribute(protobuf, "Daemon Thread", threadMXBean().getDaemonThreadCount());
return protobuf.build();
}

private static RuntimeMXBean runtimeMXBean() {
return ManagementFactory.getRuntimeMXBean();
}

private static Runtime runtime() {
return Runtime.getRuntime();
}

private static MemoryMXBean memoryMXBean() {
return ManagementFactory.getMemoryMXBean();
}

private static ClassLoadingMXBean classLoadingMXBean() {
return ManagementFactory.getClassLoadingMXBean();
}

private static ThreadMXBean threadMXBean() {
return ManagementFactory.getThreadMXBean();
}

private static String formatMemory(long memoryInBytes) {
return format("%d MB", memoryInBytes / 1_000_000);
}
}

+ 59
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java ファイルの表示

@@ -0,0 +1,59 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.monitoring;

import org.sonar.process.systeminfo.JvmPropertiesSection;
import org.sonar.process.systeminfo.JvmStateSection;
import org.sonar.server.platform.ws.ClusterInfoAction;
import org.sonar.server.platform.ws.StandaloneInfoAction;

public class WebSystemInfoModule {

private WebSystemInfoModule() {
// do not instantiate
}

public static Object[] forStandaloneMode() {
return new Object[] {
new JvmPropertiesSection("Web JVM Properties"),
new JvmStateSection("Web JVM State"),
DatabaseSection.class,
EsSection.class,
PluginsSection.class,
SettingsSection.class,
SonarQubeSection.class,
SystemSection.class,

StandaloneInfoAction.class
};
}

public static Object[] forClusterMode() {
return new Object[] {
new JvmPropertiesSection("Web JVM Properties"),
new JvmStateSection("Web JVM State"),
DatabaseSection.class,
EsSection.class,
PluginsSection.class,

ClusterInfoAction.class
};
}
}

+ 13
- 20
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java ファイルの表示

@@ -108,19 +108,13 @@ import org.sonar.server.platform.BackendCleanup;
import org.sonar.server.platform.PersistentSettings;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.platform.SettingsChangeNotifier;
import org.sonar.server.platform.monitoring.DatabaseMonitor;
import org.sonar.server.platform.monitoring.EsMonitor;
import org.sonar.server.platform.monitoring.JvmPropsMonitor;
import org.sonar.server.platform.monitoring.PluginsMonitor;
import org.sonar.server.platform.monitoring.SettingsMonitor;
import org.sonar.server.platform.monitoring.SonarQubeMonitor;
import org.sonar.server.platform.monitoring.SystemMonitor;
import org.sonar.server.platform.monitoring.WebSystemInfoModule;
import org.sonar.server.platform.web.WebPagesFilter;
import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
import org.sonar.server.platform.ws.ChangeLogLevelAction;
import org.sonar.server.platform.ws.DbMigrationStatusAction;
import org.sonar.server.platform.ws.HealthActionModule;
import org.sonar.server.platform.ws.InfoActionModule;
import org.sonar.server.platform.ws.InfoAction;
import org.sonar.server.platform.ws.L10nWs;
import org.sonar.server.platform.ws.LogsAction;
import org.sonar.server.platform.ws.MigrateDbAction;
@@ -192,7 +186,9 @@ import org.sonar.server.source.ws.LinesAction;
import org.sonar.server.source.ws.RawAction;
import org.sonar.server.source.ws.ScmAction;
import org.sonar.server.source.ws.SourcesWs;
import org.sonar.server.telemetry.TelemetryModule;
import org.sonar.server.telemetry.TelemetryClient;
import org.sonar.server.telemetry.TelemetryDaemon;
import org.sonar.server.telemetry.TelemetryDataLoader;
import org.sonar.server.test.index.TestIndex;
import org.sonar.server.test.index.TestIndexDefinition;
import org.sonar.server.test.index.TestIndexer;
@@ -486,17 +482,9 @@ public class PlatformLevel4 extends PlatformLevel {
// System
ServerLogging.class,
RestartAction.class,
InfoActionModule.class,
PingAction.class,
UpgradesAction.class,
StatusAction.class,
SystemMonitor.class,
SettingsMonitor.class,
SonarQubeMonitor.class,
EsMonitor.class,
PluginsMonitor.class,
JvmPropsMonitor.class,
DatabaseMonitor.class,
MigrateDbAction.class,
LogsAction.class,
ChangeLogLevelAction.class,
@@ -548,9 +536,14 @@ public class PlatformLevel4 extends PlatformLevel {

RecoveryIndexer.class,
ProjectIndexersImpl.class);
addIfStartupLeader(
// Telemetry
TelemetryModule.class);

// telemetry
add(TelemetryDataLoader.class);
addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class);

// system info
add(InfoAction.class);
addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode());

addAll(level4AddedComponents);
}

+ 26
- 30
server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java ファイルの表示

@@ -19,15 +19,15 @@
*/
package org.sonar.server.platform.ws;

import java.util.Map;
import java.util.Arrays;
import java.util.Optional;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.ce.http.CeHttpClient;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.platform.monitoring.Monitor;
import org.sonar.server.telemetry.TelemetryDataLoader;
import org.sonar.server.user.UserSession;

@@ -40,14 +40,14 @@ public class InfoAction implements SystemWsAction {

private final UserSession userSession;
private final CeHttpClient ceHttpClient;
private final Monitor[] monitors;
private final SystemInfoSection[] systemInfoSections;
private final TelemetryDataLoader statistics;

public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, Monitor... monitors) {
public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, SystemInfoSection... systemInfoSections) {
this.userSession = userSession;
this.ceHttpClient = ceHttpClient;
this.statistics = statistics;
this.monitors = monitors;
this.systemInfoSections = systemInfoSections;
}

@Override
@@ -66,32 +66,19 @@ public class InfoAction implements SystemWsAction {
public void handle(Request request, Response response) {
userSession.checkIsSystemAdministrator();

JsonWriter json = response.newJsonWriter();
writeJson(json);
json.close();
try (JsonWriter json = response.newJsonWriter()) {
writeJson(json);
}
}

private void writeJson(JsonWriter json) {
json.beginObject();
for (Monitor monitor : monitors) {
Map<String, Object> attributes = monitor.attributes();
json.name(monitor.name());
json.beginObject();
for (Map.Entry<String, Object> attribute : attributes.entrySet()) {
json.name(attribute.getKey()).valueObject(attribute.getValue());
}
json.endObject();
}
Arrays.stream(systemInfoSections)
.map(SystemInfoSection::toProtobuf)
.forEach(section -> sectionToJson(section, json));
Optional<ProtobufSystemInfo.SystemInfo> ceSysInfo = ceHttpClient.retrieveSystemInfo();
if (ceSysInfo.isPresent()) {
for (ProtobufSystemInfo.Section section : ceSysInfo.get().getSectionsList()) {
json.name(section.getName());
json.beginObject();
for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
writeAttribute(json, attribute);
}
json.endObject();
}
ceSysInfo.get().getSectionsList().forEach(section -> sectionToJson(section, json));
}
writeStatistics(json);
json.endObject();
@@ -102,19 +89,28 @@ public class InfoAction implements SystemWsAction {
writeTelemetryData(json, statistics.load());
}

private static void writeAttribute(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
private static void sectionToJson(ProtobufSystemInfo.Section section, JsonWriter json) {
json.name(section.getName());
json.beginObject();
for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
attributeToJson(json, attribute);
}
json.endObject();
}

private static void attributeToJson(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
switch (attribute.getValueCase()) {
case BOOLEAN_VALUE:
json.name(attribute.getKey()).valueObject(attribute.getBooleanValue());
json.prop(attribute.getKey(), attribute.getBooleanValue());
break;
case LONG_VALUE:
json.name(attribute.getKey()).valueObject(attribute.getLongValue());
json.prop(attribute.getKey(), attribute.getLongValue());
break;
case DOUBLE_VALUE:
json.name(attribute.getKey()).valueObject(attribute.getDoubleValue());
json.prop(attribute.getKey(), attribute.getDoubleValue());
break;
case STRING_VALUE:
json.name(attribute.getKey()).valueObject(attribute.getStringValue());
json.prop(attribute.getKey(), attribute.getStringValue());
break;
case VALUE_NOT_SET:
break;

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java ファイルの表示

@@ -28,9 +28,9 @@ import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class BaseMonitorMBeanTest {
public class BaseSectionMBeanTest {

FakeMonitor underTest = new FakeMonitor();
private FakeSection underTest = new FakeSection();

@Test
public void test_registration() throws Exception {

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java ファイルの表示

@@ -19,25 +19,27 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;

public class DatabaseMonitorTest {
public class DatabaseSectionTest {

@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);

private DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
private DatabaseMonitor underTest = new DatabaseMonitor(databaseVersion, dbTester.getDbClient());
private DatabaseSection underTest = new DatabaseSection(databaseVersion, dbTester.getDbClient());

@Before
public void setUp() throws Exception {
@@ -51,16 +53,16 @@ public class DatabaseMonitorTest {

@Test
public void db_info() {
Map<String, Object> attributes = underTest.attributes();
assertThat(attributes.get("Database")).isEqualTo("H2");
assertThat(attributes.get("Database Version").toString()).startsWith("1.");
assertThat(attributes.get("Username")).isEqualTo("SONAR");
assertThat(attributes.get("Driver Version").toString()).startsWith("1.");
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThatAttributeIs(section, "Database", "H2");
assertThat(attribute(section, "Database Version").getStringValue()).startsWith("1.");
assertThatAttributeIs(section, "Username", "SONAR");
assertThat(attribute(section, "Driver Version").getStringValue()).startsWith("1.");
}

@Test
public void pool_info() {
Map<String, Object> attributes = underTest.attributes();
assertThat((int) attributes.get("Pool Max Connections")).isGreaterThan(0);
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThat(attribute(section, "Pool Max Connections").getLongValue()).isGreaterThan(0L);
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java ファイルの表示

@@ -19,12 +19,12 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.Map;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.issue.index.IssueIndexDefinition;
@@ -32,13 +32,15 @@ import org.sonar.server.issue.index.IssueIndexDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;

public class EsMonitorTest {
public class EsSectionTest {

@Rule
public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));

private EsMonitor underTest = new EsMonitor(esTester.client());
private EsSection underTest = new EsSection(esTester.client());

@Test
public void name() {
@@ -46,67 +48,54 @@ public class EsMonitorTest {
}

@Test
public void cluster_attributes() {
Map<String, Object> attributes = underTest.attributes();
public void es_state() {
assertThat(underTest.getState()).isEqualTo(ClusterHealthStatus.GREEN.name());
assertThat(attributes.get("State")).isEqualTo(ClusterHealthStatus.GREEN);
assertThat(attributes.get("Number of Nodes")).isEqualTo(1);
assertThatAttributeIs(underTest.toProtobuf(), "State", ClusterHealthStatus.GREEN.name());
}

@Test
public void node_attributes() {
Map<String, Object> attributes = underTest.attributes();
Map nodesAttributes = (Map) attributes.get("Nodes");

// one node
assertThat(nodesAttributes).hasSize(1);
Map nodeAttributes = (Map) nodesAttributes.values().iterator().next();
assertThat(nodeAttributes.get("Type")).isEqualTo("Master");
assertThat(nodeAttributes.get("Store Size")).isNotNull();
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThat(attribute(section, "Store Size")).isNotNull();
}

@Test
public void index_attributes() {
Map<String, Object> attributes = underTest.attributes();
Map indicesAttributes = (Map) attributes.get("Indices");
ProtobufSystemInfo.Section section = underTest.toProtobuf();

// one index "issues"
Map indexAttributes = (Map) indicesAttributes.get(IssueIndexDefinition.INDEX_TYPE_ISSUE.getIndex());
assertThat(indexAttributes.get("Docs")).isEqualTo(0L);
assertThat((int) indexAttributes.get("Shards")).isGreaterThan(0);
assertThat(indexAttributes.get("Store Size")).isNotNull();
assertThat(attribute(section, "Index issues - Docs").getLongValue()).isEqualTo(0L);
assertThat(attribute(section, "Index issues - Shards").getLongValue()).isGreaterThan(0);
assertThat(attribute(section, "Index issues - Store Size").getStringValue()).isNotNull();
}

@Test
public void attributes_displays_exception_message_when_cause_null_when_client_fails() {
EsClient esClientMock = mock(EsClient.class);
EsMonitor underTest = new EsMonitor(esClientMock);
EsSection underTest = new EsSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with no cause"));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).hasSize(1);
assertThat(attributes.get("State")).isEqualTo("RuntimeException with no cause");
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThatAttributeIs(section, "State", "RuntimeException with no cause");
}

@Test
public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() {
EsClient esClientMock = mock(EsClient.class);
EsMonitor underTest = new EsMonitor(esClientMock);
EsSection underTest = new EsSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message")));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).hasSize(1);
assertThat(attributes.get("State")).isEqualTo("RuntimeException with cause not ES");
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThatAttributeIs(section, "State", "RuntimeException with cause not ES");
}

@Test
public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() {
EsClient esClientMock = mock(EsClient.class);
EsMonitor underTest = new EsMonitor(esClientMock);
EsSection underTest = new EsSection(esClientMock);
when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message")));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).hasSize(1);
assertThat(attributes.get("State")).isEqualTo("some cause message");
ProtobufSystemInfo.Section section = underTest.toProtobuf();
assertThatAttributeIs(section, "State", "some cause message");
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java ファイルの表示

@@ -19,10 +19,9 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.Collections;
import java.util.Map;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

public class FakeMonitor extends BaseMonitorMBean implements FakeMonitorMBean {
public class FakeSection extends BaseSectionMBean implements FakeSectionMBean {

@Override
public int getFake() {
@@ -35,7 +34,7 @@ public class FakeMonitor extends BaseMonitorMBean implements FakeMonitorMBean {
}

@Override
public Map<String, Object> attributes() {
return Collections.emptyMap();
public ProtobufSystemInfo.Section toProtobuf() {
return ProtobufSystemInfo.Section.newBuilder().build();
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java ファイルの表示

@@ -19,6 +19,6 @@
*/
package org.sonar.server.platform.monitoring;

public interface FakeMonitorMBean {
public interface FakeSectionMBean {
int getFake();
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java ファイルの表示

@@ -20,49 +20,43 @@
package org.sonar.server.platform.monitoring;

import java.util.Arrays;
import java.util.Map;
import org.junit.Test;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.updatecenter.common.Version;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;

public class PluginsMonitorTest {
public class PluginsSectionTest {

PluginRepository repo = mock(PluginRepository.class);
PluginsMonitor underTest = new PluginsMonitor(repo);
private PluginRepository repo = mock(PluginRepository.class);
private PluginsSection underTest = new PluginsSection(repo);

@Test
public void name() {
assertThat(underTest.name()).isEqualTo("Plugins");
assertThat(underTest.toProtobuf().getName()).isEqualTo("Plugins");
}

@Test
public void plugin_name_and_version() {
when(repo.getPluginInfos()).thenReturn(Arrays.asList(
new PluginInfo("key-1")
.setName("plugin-1")
.setName("Plugin 1")
.setVersion(Version.create("1.1")),
new PluginInfo("key-2")
.setName("plugin-2")
.setName("Plugin 2")
.setVersion(Version.create("2.2")),
new PluginInfo("no-version")
.setName("No Version")));

Map<String, Object> attributes = underTest.attributes();
ProtobufSystemInfo.Section section = underTest.toProtobuf();

assertThat(attributes).containsKeys("key-1", "key-2");
assertThat((Map) attributes.get("key-1"))
.containsEntry("Name", "plugin-1")
.containsEntry("Version", "1.1");
assertThat((Map) attributes.get("key-2"))
.containsEntry("Name", "plugin-2")
.containsEntry("Version", "2.2");
assertThat((Map) attributes.get("no-version"))
.containsEntry("Name", "No Version")
.doesNotContainKey("Version");
assertThatAttributeIs(section, "key-1", "1.1 [Plugin 1]");
assertThatAttributeIs(section, "key-2", "2.2 [Plugin 2]");
assertThatAttributeIs(section, "no-version", "[No Version]");
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java ファイルの表示

@@ -19,53 +19,57 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.SortedMap;
import org.junit.Test;
import org.sonar.api.PropertyType;
import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

import static org.apache.commons.lang.StringUtils.repeat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;

public class SettingsMonitorTest {
public class SettingsSectionTest {

private static final String PASSWORD_PROPERTY = "sonar.password";

PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build());
Settings settings = new MapSettings(defs);
SettingsMonitor underTest = new SettingsMonitor(settings);
private PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build());
private Settings settings = new MapSettings(defs);
private SettingsSection underTest = new SettingsSection(settings);

@Test
public void return_properties_and_sort_by_key() {
settings.setProperty("foo", "foo value");
settings.setProperty("bar", "bar value");

SortedMap<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsExactly(entry("bar", "bar value"), entry("foo", "foo value"));
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "bar", "bar value");
assertThatAttributeIs(protobuf, "foo", "foo value");
}

@Test
public void truncate_long_property_values() {
settings.setProperty("foo", repeat("abcde", 1_000));

String value = (String) underTest.attributes().get("foo");
assertThat(value).hasSize(SettingsMonitor.MAX_VALUE_LENGTH).startsWith("abcde");
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
String value = attribute(protobuf, "foo").getStringValue();
assertThat(value).hasSize(SettingsSection.MAX_VALUE_LENGTH).startsWith("abcde");
}

@Test
public void exclude_password_properties() {
settings.setProperty(PASSWORD_PROPERTY, "abcde");

assertThat(underTest.attributes()).isEmpty();
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThat(attribute(protobuf, PASSWORD_PROPERTY)).isNull();
}

@Test
public void test_monitor_name() {
assertThat(underTest.name()).isEqualTo("Settings");
assertThat(underTest.toProtobuf().getName()).isEqualTo("Settings");

}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java ファイルの表示

@@ -20,7 +20,6 @@
package org.sonar.server.platform.monitoring;

import java.io.File;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
@@ -31,6 +30,7 @@ import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.platform.Server;
import org.sonar.api.security.SecurityRealm;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.authentication.IdentityProviderRepositoryRule;
import org.sonar.server.authentication.TestIdentityProvider;
import org.sonar.server.platform.ServerId;
@@ -39,11 +39,12 @@ import org.sonar.server.platform.ServerLogging;
import org.sonar.server.user.SecurityRealmFactory;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs;

public class SonarQubeMonitorTest {
public class SonarQubeSectionTest {

private static final String SERVER_ID_PROPERTY = "Server ID";
private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated";
@@ -54,13 +55,13 @@ public class SonarQubeMonitorTest {
@Rule
public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule();

MapSettings settings = new MapSettings();
Server server = mock(Server.class);
ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
ServerLogging serverLogging = mock(ServerLogging.class);
SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
private MapSettings settings = new MapSettings();
private Server server = mock(Server.class);
private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
private ServerLogging serverLogging = mock(ServerLogging.class);
private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);

SonarQubeMonitor underTest = new SonarQubeMonitor(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
private SonarQubeSection underTest = new SonarQubeSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
serverLogging, serverIdLoader);

@Before
@@ -80,7 +81,7 @@ public class SonarQubeMonitorTest {
when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC"));
assertThat(underTest.getServerId()).isEqualTo("ABC");

when(serverIdLoader.getRaw()).thenReturn(Optional.<String>empty());
when(serverIdLoader.getRaw()).thenReturn(Optional.empty());
assertThat(underTest.getServerId()).isNull();
}

@@ -88,35 +89,38 @@ public class SonarQubeMonitorTest {
public void attributes_contain_information_about_valid_server_id() {
when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true)));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, true));
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, true);
}

@Test
public void attributes_contain_information_about_non_valid_server_id() {
when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", false)));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, false));
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC");
assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, false);
}

@Test
public void attributes_do_not_contain_information_about_server_id_if_absent() {
when(serverIdLoader.get()).thenReturn(Optional.<ServerId>empty());
when(serverIdLoader.get()).thenReturn(Optional.empty());

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).doesNotContainKeys(SERVER_ID_PROPERTY, SERVER_ID_VALIDATED_PROPERTY);
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThat(attribute(protobuf, SERVER_ID_PROPERTY)).isNull();
assertThat(attribute(protobuf, SERVER_ID_VALIDATED_PROPERTY)).isNull();
}

@Test
public void official_distribution() throws Exception {
File rootDir = temp.newFolder();
FileUtils.write(new File(rootDir, SonarQubeMonitor.BRANDING_FILE_PATH), "1.2");
FileUtils.write(new File(rootDir, SonarQubeSection.BRANDING_FILE_PATH), "1.2");

when(server.getRootDir()).thenReturn(rootDir);

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsEntry("Official Distribution", Boolean.TRUE);
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "Official Distribution", true);
}

@Test
@@ -125,14 +129,14 @@ public class SonarQubeMonitorTest {
// branding file is missing
when(server.getRootDir()).thenReturn(rootDir);

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsEntry("Official Distribution", Boolean.FALSE);
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "Official Distribution", false);
}

@Test
public void get_log_level() throws Exception {
Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsEntry("Logs Level", "DEBUG");
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "Logs Level", "DEBUG");
}

@Test
@@ -141,16 +145,16 @@ public class SonarQubeMonitorTest {
when(realm.getName()).thenReturn("LDAP");
when(securityRealmFactory.getRealm()).thenReturn(realm);

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsEntry("External User Authentication", "LDAP");
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "External User Authentication", "LDAP");
}

@Test
public void no_realm() throws Exception {
when(securityRealmFactory.getRealm()).thenReturn(null);

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).doesNotContainKey("External User Authentication");
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThat(attribute(protobuf, "External User Authentication")).isNull();
}

@Test
@@ -168,8 +172,8 @@ public class SonarQubeMonitorTest {
.setName("Disabled")
.setEnabled(false));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsEntry("Accepted external identity providers", "Bitbucket, GitHub");
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "Accepted external identity providers", "Bitbucket, GitHub");
}

@Test
@@ -190,7 +194,7 @@ public class SonarQubeMonitorTest {
.setEnabled(false)
.setAllowsUsersToSignUp(true));

Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsEntry("External identity providers whose users are allowed to sign themselves up", "GitHub");
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
}
}

+ 44
- 0
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java ファイルの表示

@@ -0,0 +1,44 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.monitoring;

import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;

public class SystemInfoTesting {

private SystemInfoTesting() {
// do not instantiate
}

public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, String expectedValue) {
ProtobufSystemInfo.Attribute value = attribute(section, key);
assertThat(value).as(key).isNotNull();
assertThat(value.getStringValue()).isEqualTo(expectedValue);
}

public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, boolean expectedValue) {
ProtobufSystemInfo.Attribute value = attribute(section, key);
assertThat(value).as(key).isNotNull();
assertThat(value.getBooleanValue()).isEqualTo(expectedValue);
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java ファイルの表示

@@ -19,24 +19,26 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.Map;
import org.junit.Test;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;

public class JvmPropsMonitorTest {
public class SystemSectionTest {

JvmPropsMonitor underTest = new JvmPropsMonitor();
private SystemSection underTest = new SystemSection();

@Test
public void name_is_not_empty() {
assertThat(underTest.name()).isNotEmpty();
public void name() {
assertThat(underTest.toProtobuf().getName()).isEqualTo("System");
}

@Test
public void attributes() {
Map<String, Object> attributes = underTest.attributes();
public void system_properties() {
ProtobufSystemInfo.Section section = underTest.toProtobuf();

assertThat(attributes).containsKeys("java.vm.vendor", "os.name");
assertThat(attribute(section, "System Date").getStringValue()).isNotEmpty();
assertThat(attribute(section, "Processors").getLongValue()).isGreaterThan(0);
}
}

server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java → server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java ファイルの表示

@@ -19,24 +19,23 @@
*/
package org.sonar.server.platform.monitoring;

import java.util.Map;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class SystemMonitorTest {

SystemMonitor underTest = new SystemMonitor();
public class WebSystemInfoModuleTest {

@Test
public void name() {
assertThat(underTest.name()).isEqualTo("System");
public void test_forStandaloneMode() {
assertThat(WebSystemInfoModule.forStandaloneMode())
.isNotEmpty()
.doesNotContainNull();
}

@Test
public void system_properties() {
Map<String, Object> attributes = underTest.attributes();
assertThat(attributes).containsKeys("System Date", "Processors");
public void test_forClusterMode() {
assertThat(WebSystemInfoModule.forClusterMode())
.isNotEmpty()
.doesNotContainNull();
}
}

+ 18
- 17
server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java ファイルの表示

@@ -19,8 +19,6 @@
*/
package org.sonar.server.platform.ws;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
@@ -28,8 +26,9 @@ import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.sonar.ce.http.CeHttpClient;
import org.sonar.ce.http.CeHttpClientImpl;
import org.sonar.process.systeminfo.SystemInfoSection;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.platform.monitoring.Monitor;
import org.sonar.server.telemetry.TelemetryData;
import org.sonar.server.telemetry.TelemetryDataLoader;
import org.sonar.server.tester.UserSessionRule;
@@ -40,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;

public class InfoActionTest {
@Rule
@@ -48,12 +48,12 @@ public class InfoActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

private Monitor monitor1 = mock(Monitor.class);
private Monitor monitor2 = mock(Monitor.class);
private SystemInfoSection section1 = mock(SystemInfoSection.class);
private SystemInfoSection section2 = mock(SystemInfoSection.class);
private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
private TelemetryDataLoader statistics = mock(TelemetryDataLoader.class);

private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, monitor1, monitor2);
private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, section1, section2);
private WsActionTester ws = new WsActionTester(underTest);

@Test
@@ -84,22 +84,23 @@ public class InfoActionTest {
public void write_json() {
logInAsSystemAdministrator();

Map<String, Object> attributes1 = new LinkedHashMap<>();
attributes1.put("foo", "bar");
Map<String, Object> attributes2 = new LinkedHashMap<>();
attributes2.put("one", 1);
attributes2.put("two", 2);
when(monitor1.name()).thenReturn("Monitor One");
when(monitor1.attributes()).thenReturn(attributes1);
when(monitor2.name()).thenReturn("Monitor Two");
when(monitor2.attributes()).thenReturn(attributes2);
ProtobufSystemInfo.Section.Builder attributes1 = ProtobufSystemInfo.Section.newBuilder()
.setName("Section One");
setAttribute(attributes1, "foo", "bar");
when(section1.toProtobuf()).thenReturn(attributes1.build());

ProtobufSystemInfo.Section.Builder attributes2 = ProtobufSystemInfo.Section.newBuilder()
.setName("Section Two");
setAttribute(attributes2, "one", 1);
setAttribute(attributes2, "two", 2);
when(section2.toProtobuf()).thenReturn(attributes2.build());
when(ceHttpClient.retrieveSystemInfo()).thenReturn(Optional.empty());
when(statistics.load()).thenReturn(mock(TelemetryData.class));

TestResponse response = ws.newRequest().execute();
// response does not contain empty "Monitor Three"
// response does not contain empty "Section Three"
verify(statistics).load();
assertThat(response.getInput()).isEqualTo("{\"Monitor One\":{\"foo\":\"bar\"},\"Monitor Two\":{\"one\":1,\"two\":2}," +
assertThat(response.getInput()).isEqualTo("{\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
"\"Statistics\":{\"plugins\":{},\"userCount\":0,\"projectCount\":0,\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":{},\"nclocByLanguage\":{}}}");
}


+ 0
- 37
server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java ファイルの表示

@@ -1,37 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.telemetry;

import org.junit.Test;
import org.sonar.core.platform.ComponentContainer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER;

public class TelemetryModuleTest {
@Test
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();

new TelemetryModule().configure(container);

assertThat(container.size()).isEqualTo(2 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER);
}
}

+ 41
- 6
server/sonar-web/src/main/js/api/system.ts ファイルの表示

@@ -18,21 +18,56 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, post } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';

export function setLogLevel(level: string): Promise<void> {
return post('/api/system/change_log_level', { level });
export type SysValue = boolean | string | number | HealthType | SysValueObject | SysValueArray;
export interface SysValueObject {
[key: string]: SysValue;
}
export interface SysValueArray extends Array<SysValue> {}

export function getSystemInfo(): Promise<any> {
return getJSON('/api/system/info');
export interface SysInfoSection {
[sectionName: string]: SysValueObject;
}

export enum HealthType {
RED = 'RED',
YELLOW = 'YELLOW',
GREEN = 'GREEN'
}

export interface HealthCause extends SysValueObject {
message: string;
}

export interface NodeInfo extends SysValueObject {
Name: string;
Health: HealthType;
'Health Causes': HealthCause[];
}

export interface SysInfo extends SysValueObject {
Cluster: boolean;
Health: HealthType;
'Health Causes': HealthCause[];
'Application Nodes': NodeInfo[];
'Search Nodes': NodeInfo[];
}

export function setLogLevel(level: string): Promise<void | Response> {
return post('/api/system/change_log_level', { level }).catch(throwGlobalError);
}

export function getSystemInfo(): Promise<SysInfo> {
return getJSON('/api/system/info').catch(throwGlobalError);
}

export function getSystemStatus(): Promise<any> {
return getJSON('/api/system/status');
}

export function restart(): Promise<void> {
return post('/api/system/restart');
export function restart(): Promise<void | Response> {
return post('/api/system/restart').catch(throwGlobalError);
}

const POLLING_INTERVAL = 2000;

+ 43
- 0
server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts ファイルの表示

@@ -0,0 +1,43 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as utils from '../utils';

describe('parseQuery', () => {
it('should correctly parse the expand array', () => {
expect(utils.parseQuery({})).toEqual({ expandedCards: [] });
expect(utils.parseQuery({ expand: 'foo,bar' })).toEqual({ expandedCards: ['foo', 'bar'] });
});
});

describe('serializeQuery', () => {
it('should correctly serialize the expand array', () => {
expect(utils.serializeQuery({ expandedCards: [] })).toEqual({});
expect(utils.serializeQuery({ expandedCards: ['foo', 'bar'] })).toEqual({ expand: 'foo,bar' });
});
});

describe('groupSections', () => {
it('should correctly group the root field into a main section', () => {
expect(utils.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({
mainSection: { foo: 'Foo', bar: 3 },
sections: { baz: { a: 'a' } }
});
});
});

+ 125
- 0
server/sonar-web/src/main/js/apps/system/components/App.tsx ファイルの表示

@@ -0,0 +1,125 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import ClusterSysInfos from './ClusterSysInfos';
import PageHeader from './PageHeader';
import StandAloneSysInfos from './StandAloneSysInfos';
import { translate } from '../../../helpers/l10n';
import { getSystemInfo, SysInfo } from '../../../api/system';
import { isCluster, parseQuery, Query, serializeQuery } from '../utils';
import { RawQuery } from '../../../helpers/query';
import '../styles.css';

interface Props {
location: { pathname: string; query: RawQuery };
}

interface State {
loading: boolean;
sysInfoData?: SysInfo;
}

export default class App extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: true };

static contextTypes = {
router: PropTypes.object
};

componentDidMount() {
this.mounted = true;
this.fetchSysInfo();
}

componentWillUnmount() {
this.mounted = false;
}

fetchSysInfo = () => {
this.setState({ loading: true });
getSystemInfo().then(
(sysInfoData: SysInfo) => {
if (this.mounted) {
this.setState({ loading: false, sysInfoData });
}
},
() => {
if (this.mounted) {
this.setState({ loading: false });
}
}
);
};

toggleSysInfoCards = (toggledCard: string) => {
const query = parseQuery(this.props.location.query);
let expandedCards;
if (query.expandedCards.includes(toggledCard)) {
expandedCards = query.expandedCards.filter(card => card !== toggledCard);
} else {
expandedCards = query.expandedCards.concat(toggledCard);
}
this.updateQuery({ expandedCards });
};

updateQuery = (newQuery: Query) => {
const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery });
this.context.router.push({ pathname: this.props.location.pathname, query });
};

renderSysInfo() {
const { sysInfoData } = this.state;
if (!sysInfoData) {
return null;
}

const query = parseQuery(this.props.location.query);
if (isCluster(sysInfoData)) {
return (
<ClusterSysInfos
sysInfoData={sysInfoData}
expandedCards={query.expandedCards}
toggleCard={this.toggleSysInfoCards}
/>
);
}
return <StandAloneSysInfos sysInfoData={sysInfoData} />;
}

render() {
const { loading, sysInfoData } = this.state;
// TODO Correctly get logLevel, we are not sure yet how we want to do it for cluster mode
return (
<div className="page page-limited">
<Helmet title={translate('system_info.page')} />
<PageHeader
loading={loading}
isCluster={isCluster(sysInfoData)}
logLevel="INFO"
showActions={sysInfoData != undefined}
/>
{this.renderSysInfo()}
</div>
);
}
}

+ 114
- 0
server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx ファイルの表示

@@ -0,0 +1,114 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import Modal from 'react-modal';
import { setLogLevel } from '../../../api/system';
import { translate } from '../../../helpers/l10n';

interface Props {
infoMsg: string;
logLevel: string;
onChange: (level: string) => void;
onClose: () => void;
}

interface State {
newLevel: string;
updating: boolean;
}

const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE'];

export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = { newLevel: props.logLevel, updating: false };
}

handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
const { newLevel } = this.state;
if (!this.state.updating && newLevel !== this.props.logLevel) {
this.setState({ updating: true });
setLogLevel(newLevel).then(
() => this.props.onChange(newLevel),
() => this.setState({ updating: false })
);
}
};

handleLevelChange = (event: React.ChangeEvent<HTMLInputElement>) =>
this.setState({ newLevel: event.currentTarget.value });

render() {
const { updating, newLevel } = this.state;
const header = translate('system.set_log_level');
const disableSubmit = updating || newLevel === this.props.logLevel;
return (
<Modal
isOpen={true}
contentLabel={header}
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.props.onClose}>
<form id="set-log-level-form" onSubmit={this.handleFormSubmit}>
<div className="modal-head">
<h2>{header}</h2>
</div>
<div className="modal-body">
{LOG_LEVELS.map(level => (
<p key={level} className="spacer-bottom">
<input
type="radio"
className="spacer-right text-middle"
name="system.log_levels"
value={level}
checked={level === newLevel}
onChange={this.handleLevelChange}
/>
{level}
</p>
))}
<div className="alert alert-info spacer-top">{this.props.infoMsg}</div>
{newLevel !== 'INFO' && (
<div className="alert alert-danger spacer-top">
{translate('system.log_level.warning')}
</div>
)}
</div>
<div className="modal-foot">
{updating && <i className="spinner spacer-right" />}
<button disabled={disableSubmit} id="set-log-level-submit">
{translate('save')}
</button>
<a href="#" id="set-log-level-cancel" onClick={this.handleCancelClick}>
{translate('cancel')}
</a>
</div>
</form>
</Modal>
);
}
}

+ 82
- 0
server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx ファイルの表示

@@ -0,0 +1,82 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { sortBy } from 'lodash';
import HealthCard from './info-items/HealthCard';
import { translate } from '../../../helpers/l10n';
import {
getAppNodes,
getHealth,
getHealthCauses,
getMainCardSection,
getNodeName,
getSearchNodes,
ignoreInfoFields
} from '../utils';
import { SysInfo } from '../../../api/system';

interface Props {
expandedCards: string[];
sysInfoData: SysInfo;
toggleCard: (toggledCard: string) => void;
}

export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
const mainCardName = 'System';
return (
<ul>
<HealthCard
biggerHealth={true}
health={getHealth(sysInfoData)}
healthCauses={getHealthCauses(sysInfoData)}
name={mainCardName}
onClick={toggleCard}
open={expandedCards.includes(mainCardName)}
sysInfoData={ignoreInfoFields(getMainCardSection(sysInfoData))}
/>
<li className="note system-info-health-title">
{translate('system.application_nodes_title')}
</li>
{sortBy(getAppNodes(sysInfoData), 'name').map(node => (
<HealthCard
key={getNodeName(node)}
health={getHealth(node)}
healthCauses={getHealthCauses(node)}
name={getNodeName(node)}
onClick={toggleCard}
open={expandedCards.includes(getNodeName(node))}
sysInfoData={ignoreInfoFields(node)}
/>
))}
<li className="note system-info-health-title">{translate('system.search_nodes_title')}</li>
{sortBy(getSearchNodes(sysInfoData), 'name').map(node => (
<HealthCard
key={getNodeName(node)}
health={getHealth(node)}
healthCauses={getHealthCauses(node)}
name={getNodeName(node)}
onClick={toggleCard}
open={expandedCards.includes(getNodeName(node))}
sysInfoData={ignoreInfoFields(node)}
/>
))}
</ul>
);
}

+ 163
- 0
server/sonar-web/src/main/js/apps/system/components/PageActions.tsx ファイルの表示

@@ -0,0 +1,163 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import ChangeLogLevelForm from './ChangeLogLevelForm';
import RestartForm from '../../../components/common/RestartForm';
import { getBaseUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';

interface Props {
canDownloadLogs: boolean;
canRestart: boolean;
cluster: boolean;
logLevel: string;
}

interface State {
logLevel: string;
openLogsLevelForm: boolean;
openRestartForm: boolean;
}

export default class PageActions extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
openLogsLevelForm: false,
openRestartForm: false,
logLevel: props.logLevel
};
}

componentWillReceiveProps(nextProps: Props) {
if (nextProps.logLevel !== this.state.logLevel) {
this.setState({ logLevel: nextProps.logLevel });
}
}

handleLogsLevelOpen = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ openLogsLevelForm: true });
};

handleLogsLevelChange = (logLevel: string) => {
this.setState({ logLevel });
this.handleLogsLevelClose();
};

handleLogsLevelClose = () => this.setState({ openLogsLevelForm: false });

handleServerRestartOpen = () => this.setState({ openRestartForm: true });
handleServerRestartClose = () => this.setState({ openRestartForm: false });

render() {
const infoUrl = getBaseUrl() + '/api/system/info';
const logsUrl = getBaseUrl() + '/api/system/logs';
return (
<div className="page-actions">
<span>
{translate('system.logs_level')}
{':'}
<strong className="little-spacer-left">{this.state.logLevel}</strong>
<a
id="edit-logs-level-button"
className="spacer-left icon-edit"
href="#"
onClick={this.handleLogsLevelOpen}
/>
</span>
{this.props.canDownloadLogs && (
<div className="display-inline-block dropdown spacer-left">
<button data-toggle="dropdown">
{translate('system.download_logs')}
<i className="icon-dropdown little-spacer-left" />
</button>
<ul className="dropdown-menu">
<li>
<a
href={logsUrl + '?process=app'}
id="logs-link"
download="sonarqube_app.log"
target="_blank">
Compute Engine
</a>
</li>
<li>
<a
href={logsUrl + '?process=ce'}
id="ce-logs-link"
download="sonarqube_ce.log"
target="_blank">
Main Process
</a>
</li>
<li>
<a
href={logsUrl + '?process=es'}
id="es-logs-link"
download="sonarqube_es.log"
target="_blank">
Elasticsearch
</a>
</li>
<li>
<a
href={logsUrl + '?process=web'}
id="web-logs-link"
download="sonarqube_web.log"
target="_blank">
Web Server
</a>
</li>
</ul>
</div>
)}
<a
href={infoUrl}
id="download-link"
className="button spacer-left"
download="sonarqube_system_info.json"
target="_blank">
{translate('system.download_system_info')}
</a>
{this.props.canRestart && (
<button
id="restart-server-button"
className="spacer-left"
onClick={this.handleServerRestartOpen}>
{translate('system.restart_server')}
</button>
)}
{this.props.canRestart &&
this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
{this.state.openLogsLevelForm && (
<ChangeLogLevelForm
infoMsg={translate(
this.props.cluster ? 'system.cluster_log_level.info' : 'system.log_level.info'
)}
logLevel={this.state.logLevel}
onChange={this.handleLogsLevelChange}
onClose={this.handleLogsLevelClose}
/>
)}
</div>
);
}
}

server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java → server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx ファイルの表示

@@ -17,25 +17,34 @@
* 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.monitoring;
import * as React from 'react';
import PageActions from './PageActions';
import { translate } from '../../../helpers/l10n';

import java.util.Map;
import org.sonar.api.server.ServerSide;
import org.sonar.server.platform.ws.InfoAction;

/**
* Any component that is involved in the information returned by the web service api/system/info
*/
@ServerSide
public interface Monitor {
/**
* Name of section in System Info page
*/
String name();
interface Props {
isCluster: boolean;
loading: boolean;
logLevel: string;
showActions: boolean;
}

/**
* Type of attribute values must be supported by {@link org.sonar.api.utils.text.JsonWriter#valueObject(Object)}
* because of JSON export by {@link InfoAction}.
*/
Map<String, Object> attributes();
export default function PageHeader({ isCluster, loading, logLevel, showActions }: Props) {
return (
<header className="page-header">
<h1 className="page-title">{translate('system_info.page')}</h1>
{showActions && (
<PageActions
canDownloadLogs={!isCluster}
canRestart={!isCluster}
cluster={isCluster}
logLevel={logLevel}
/>
)}
{loading && (
<div className="page-actions">
<i className="spinner" />
</div>
)}
</header>
);
}

+ 30
- 0
server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx ファイルの表示

@@ -0,0 +1,30 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';

interface Props {
sysInfoData: object;
}

export default class StandAloneSysInfos extends React.PureComponent<Props> {
render() {
return <div>StandAloneSysInfos</div>;
}
}

+ 38
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx ファイルの表示

@@ -0,0 +1,38 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import ChangeLogLevelForm from '../ChangeLogLevelForm';

it('should render correctly', () => {
expect(
shallow(
<ChangeLogLevelForm infoMsg="Foo" logLevel="INFO" onChange={() => {}} onClose={() => {}} />
)
).toMatchSnapshot();
});

it('should display some warning messages for non INFO levels', () => {
expect(
shallow(
<ChangeLogLevelForm infoMsg="Foo" logLevel="DEBUG" onChange={() => {}} onClose={() => {}} />
)
).toMatchSnapshot();
});

+ 47
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx ファイルの表示

@@ -0,0 +1,47 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import ClusterSysInfos from '../ClusterSysInfos';
import { HealthType, SysInfo } from '../../../../api/system';

const sysInfoData: SysInfo = {
Cluster: true,
Health: HealthType.RED,
Name: 'Foo',
'Health Causes': [{ message: 'Database down' }],
'Application Nodes': [{ Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [] }],
'Search Nodes': [{ Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [] }]
};

it('should render correctly', () => {
expect(getWrapper()).toMatchSnapshot();
});

function getWrapper(props = {}) {
return shallow(
<ClusterSysInfos
expandedCards={['System', 'Foo']}
sysInfoData={sysInfoData}
toggleCard={() => {}}
{...props}
/>
);
}

+ 55
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx ファイルの表示

@@ -0,0 +1,55 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import PageActions from '../PageActions';
import { click } from '../../../../helpers/testUtils';

it('should render correctly', () => {
expect(
shallow(
<PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
)
).toMatchSnapshot();
});

it('should render without restart and log download', () => {
expect(
shallow(
<PageActions canDownloadLogs={false} canRestart={false} cluster={true} logLevel="INFO" />
)
).toMatchSnapshot();
});

it('should open restart modal', () => {
const wrapper = shallow(
<PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
);
click(wrapper.find('#restart-server-button'));
expect(wrapper.find('RestartForm')).toHaveLength(1);
});

it('should open change log level modal', () => {
const wrapper = shallow(
<PageActions canDownloadLogs={true} canRestart={true} cluster={false} logLevel="INFO" />
);
click(wrapper.find('#edit-logs-level-button'));
expect(wrapper.find('ChangeLogLevelForm')).toHaveLength(1);
});

+ 34
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx ファイルの表示

@@ -0,0 +1,34 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import PageHeader from '../PageHeader';

it('should render correctly', () => {
expect(
shallow(<PageHeader isCluster={true} loading={false} logLevel="INFO" showActions={true} />)
).toMatchSnapshot();
});

it('should show a loading spinner and no actions', () => {
expect(
shallow(<PageHeader isCluster={true} loading={true} logLevel="INFO" showActions={false} />)
).toMatchSnapshot();
});

+ 194
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap ファイルの表示

@@ -0,0 +1,194 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display some warning messages for non INFO levels 1`] = `
<Modal
ariaHideApp={true}
bodyOpenClassName="ReactModal__Body--open"
className="modal"
closeTimeoutMS={0}
contentLabel="system.set_log_level"
isOpen={true}
onRequestClose={[Function]}
overlayClassName="modal-overlay"
parentSelector={[Function]}
portalClassName="ReactModalPortal"
shouldCloseOnOverlayClick={true}
>
<form
id="set-log-level-form"
onSubmit={[Function]}
>
<div
className="modal-head"
>
<h2>
system.set_log_level
</h2>
</div>
<div
className="modal-body"
>
<p
className="spacer-bottom"
>
<input
checked={false}
className="spacer-right text-middle"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="INFO"
/>
INFO
</p>
<p
className="spacer-bottom"
>
<input
checked={true}
className="spacer-right text-middle"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="DEBUG"
/>
DEBUG
</p>
<p
className="spacer-bottom"
>
<input
checked={false}
className="spacer-right text-middle"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="TRACE"
/>
TRACE
</p>
<div
className="alert alert-info spacer-top"
>
Foo
</div>
<div
className="alert alert-danger spacer-top"
>
system.log_level.warning
</div>
</div>
<div
className="modal-foot"
>
<button
disabled={true}
id="set-log-level-submit"
>
save
</button>
<a
href="#"
id="set-log-level-cancel"
onClick={[Function]}
>
cancel
</a>
</div>
</form>
</Modal>
`;

exports[`should render correctly 1`] = `
<Modal
ariaHideApp={true}
bodyOpenClassName="ReactModal__Body--open"
className="modal"
closeTimeoutMS={0}
contentLabel="system.set_log_level"
isOpen={true}
onRequestClose={[Function]}
overlayClassName="modal-overlay"
parentSelector={[Function]}
portalClassName="ReactModalPortal"
shouldCloseOnOverlayClick={true}
>
<form
id="set-log-level-form"
onSubmit={[Function]}
>
<div
className="modal-head"
>
<h2>
system.set_log_level
</h2>
</div>
<div
className="modal-body"
>
<p
className="spacer-bottom"
>
<input
checked={true}
className="spacer-right text-middle"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="INFO"
/>
INFO
</p>
<p
className="spacer-bottom"
>
<input
checked={false}
className="spacer-right text-middle"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="DEBUG"
/>
DEBUG
</p>
<p
className="spacer-bottom"
>
<input
checked={false}
className="spacer-right text-middle"
name="system.log_levels"
onChange={[Function]}
type="radio"
value="TRACE"
/>
TRACE
</p>
<div
className="alert alert-info spacer-top"
>
Foo
</div>
</div>
<div
className="modal-foot"
>
<button
disabled={true}
id="set-log-level-submit"
>
save
</button>
<a
href="#"
id="set-log-level-cancel"
onClick={[Function]}
>
cancel
</a>
</div>
</form>
</Modal>
`;

+ 59
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap ファイルの表示

@@ -0,0 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ul>
<HealthCard
biggerHealth={true}
health="RED"
healthCauses={
Array [
Object {
"message": "Database down",
},
]
}
name="System"
onClick={[Function]}
open={true}
sysInfoData={
Object {
"Name": "Foo",
}
}
/>
<li
className="note system-info-health-title"
>
system.application_nodes_title
</li>
<HealthCard
health="GREEN"
healthCauses={Array []}
name="Bar"
onClick={[Function]}
open={false}
sysInfoData={
Object {
"Name": "Bar",
}
}
/>
<li
className="note system-info-health-title"
>
system.search_nodes_title
</li>
<HealthCard
health="YELLOW"
healthCauses={Array []}
name="Baz"
onClick={[Function]}
open={false}
sysInfoData={
Object {
"Name": "Baz",
}
}
/>
</ul>
`;

+ 126
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap ファイルの表示

@@ -0,0 +1,126 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="page-actions"
>
<span>
system.logs_level
:
<strong
className="little-spacer-left"
>
INFO
</strong>
<a
className="spacer-left icon-edit"
href="#"
id="edit-logs-level-button"
onClick={[Function]}
/>
</span>
<div
className="display-inline-block dropdown spacer-left"
>
<button
data-toggle="dropdown"
>
system.download_logs
<i
className="icon-dropdown little-spacer-left"
/>
</button>
<ul
className="dropdown-menu"
>
<li>
<a
download="sonarqube_app.log"
href="/api/system/logs?process=app"
id="logs-link"
target="_blank"
>
Compute Engine
</a>
</li>
<li>
<a
download="sonarqube_ce.log"
href="/api/system/logs?process=ce"
id="ce-logs-link"
target="_blank"
>
Main Process
</a>
</li>
<li>
<a
download="sonarqube_es.log"
href="/api/system/logs?process=es"
id="es-logs-link"
target="_blank"
>
Elasticsearch
</a>
</li>
<li>
<a
download="sonarqube_web.log"
href="/api/system/logs?process=web"
id="web-logs-link"
target="_blank"
>
Web Server
</a>
</li>
</ul>
</div>
<a
className="button spacer-left"
download="sonarqube_system_info.json"
href="/api/system/info"
id="download-link"
target="_blank"
>
system.download_system_info
</a>
<button
className="spacer-left"
id="restart-server-button"
onClick={[Function]}
>
system.restart_server
</button>
</div>
`;

exports[`should render without restart and log download 1`] = `
<div
className="page-actions"
>
<span>
system.logs_level
:
<strong
className="little-spacer-left"
>
INFO
</strong>
<a
className="spacer-left icon-edit"
href="#"
id="edit-logs-level-button"
onClick={[Function]}
/>
</span>
<a
className="button spacer-left"
download="sonarqube_system_info.json"
href="/api/system/info"
id="download-link"
target="_blank"
>
system.download_system_info
</a>
</div>
`;

+ 38
- 0
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap ファイルの表示

@@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<header
className="page-header"
>
<h1
className="page-title"
>
system_info.page
</h1>
<PageActions
canDownloadLogs={false}
canRestart={false}
cluster={true}
logLevel="INFO"
/>
</header>
`;

exports[`should show a loading spinner and no actions 1`] = `
<header
className="page-header"
>
<h1
className="page-title"
>
system_info.page
</h1>
<div
className="page-actions"
>
<i
className="spinner"
/>
</div>
</header>
`;

+ 84
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx ファイルの表示

@@ -0,0 +1,84 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import * as classNames from 'classnames';
import { map } from 'lodash';
import HealthItem from './HealthItem';
import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon';
import Section from './Section';
import { HealthType, HealthCause, SysValueObject } from '../../../../api/system';
import { groupSections } from '../../utils';

interface Props {
biggerHealth?: boolean;
health: HealthType;
healthCauses: HealthCause[];
onClick: (toggledCard: string) => void;
open: boolean;
name: string;
sysInfoData: SysValueObject;
}

interface State {
hoveringDetail: boolean;
}

export default class HealthCard extends React.PureComponent<Props, State> {
state: State = { hoveringDetail: false };

handleClick = () => this.props.onClick(this.props.name);
onDetailEnter = () => this.setState({ hoveringDetail: true });
onDetailLeave = () => this.setState({ hoveringDetail: false });

render() {
const { open, sysInfoData } = this.props;
const { mainSection, sections } = groupSections(sysInfoData);
const showFields = open && mainSection && Object.keys(mainSection).length > 0;
const showSections = open && sections;
return (
<li
className={classNames('boxed-group system-info-health-card', {
'no-hover': this.state.hoveringDetail
})}>
<div className="boxed-group-header" onClick={this.handleClick}>
<span className="system-info-health-card-title">
<OpenCloseIcon className="little-spacer-right" open={open} />
{this.props.name}
</span>
<HealthItem
className={classNames('pull-right', { 'big-dot': this.props.biggerHealth })}
health={this.props.health}
healthCauses={this.props.healthCauses}
/>
</div>
{open && (
<div
className="boxed-group-inner"
onMouseEnter={this.onDetailEnter}
onMouseLeave={this.onDetailLeave}>
{showFields && <Section items={mainSection} />}
{showSections &&
map(sections, (section, name) => <Section key={name} items={section} name={name} />)}
</div>
)}
</li>
);
}
}

+ 41
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx ファイルの表示

@@ -0,0 +1,41 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import * as classNames from 'classnames';
import { HealthCause, HealthType } from '../../../../api/system';

interface Props {
className?: string;
health: HealthType;
healthCause: HealthCause;
}

export default function HealthCauseItem({ className, health, healthCause }: Props) {
return (
<span
className={classNames(
'alert',
health === HealthType.RED ? 'alert-danger' : 'alert-warning',
className
)}>
{healthCause.message}
</span>
);
}

+ 42
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx ファイルの表示

@@ -0,0 +1,42 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import * as classNames from 'classnames';
import HealthCauseItem from './HealthCauseItem';
import { HealthType, HealthCause } from '../../../../api/system';

interface Props {
className?: string;
health: HealthType;
healthCauses?: HealthCause[];
}

export default function HealthItem({ className, health, healthCauses }: Props) {
const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN;
return (
<div className={classNames('system-info-health-info', className)}>
{hasHealthCauses &&
healthCauses!.map((cause, idx) => (
<HealthCauseItem key={idx} className="spacer-right" health={health} healthCause={cause} />
))}
<span className={classNames('system-info-health-dot', health)} />
</div>
);
}

+ 52
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx ファイルの表示

@@ -0,0 +1,52 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { map } from 'lodash';
import SysInfoItem from './SysInfoItem';
import { SysValueObject } from '../../../../api/system';

interface Props {
name?: string;
items: SysValueObject;
}

export default function Section({ name, items }: Props) {
return (
<div className="system-info-section">
{name && <h4 className="spacer-bottom">{name}</h4>}
<table className="data zebra" id={name}>
<tbody>
{map(items, (value, name) => {
return (
<tr key={name}>
<td className="thin">
<div className="system-info-section-item-name">{name}</div>
</td>
<td style={{ wordBreak: 'break-all' }}>
<SysInfoItem name={name} value={value} />
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}

+ 71
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx ファイルの表示

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { map } from 'lodash';
import HealthItem from './HealthItem';
import { HealthType, SysValue, SysValueObject } from '../../../../api/system';
import { HEALTH_FIELD } from '../../utils';

interface Props {
name: string;
value: SysValue;
}

export default function SysInfoItem({ name, value }: Props): JSX.Element {
if (name === HEALTH_FIELD) {
return <HealthItem className="no-margin" health={value as HealthType} />;
}
if (value instanceof Array) {
return <code>{JSON.stringify(value)}</code>;
}
switch (typeof value) {
case 'boolean':
return <BooleanItem value={value as boolean} />;
case 'object':
return <ObjectItem value={value as SysValueObject} />;
default:
return <code>{value}</code>;
}
}

function BooleanItem({ value }: { value: boolean }) {
if (value) {
return <i className="icon-check" />;
} else {
return <i className="icon-delete" />;
}
}

function ObjectItem({ value }: { value: SysValueObject }) {
return (
<table className="data">
<tbody>
{map(value, (value, name) => (
<tr key={name}>
<td className="thin nowrap">{name}</td>
<td>
<SysInfoItem name={name} value={value} />
</td>
</tr>
))}
</tbody>
</table>
);
}

+ 64
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx ファイルの表示

@@ -0,0 +1,64 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import HealthCard from '../HealthCard';
import { click } from '../../../../../helpers/testUtils';
import { HealthType } from '../../../../../api/system';

it('should render correctly', () => {
expect(getShallowWrapper()).toMatchSnapshot();
});

it('should display the sysinfo detail', () => {
expect(getShallowWrapper({ biggerHealth: true, open: true })).toMatchSnapshot();
});

it('should show the sysinfo detail when the card is clicked', () => {
const onClick = jest.fn();
click(getShallowWrapper({ onClick }).find('.boxed-group-header'));
expect(onClick).toBeCalled();
expect(onClick).toBeCalledWith('Foobar');
});

it('should show a main section and multiple sub sections', () => {
const sysInfoData = {
Name: 'foo',
bar: 'Bar',
Database: { db: 'test' },
Elasticseach: { Elastic: 'search' }
};
expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot();
});

function getShallowWrapper(props = {}) {
return shallow(
<HealthCard
biggerHealth={false}
health={HealthType.RED}
healthCauses={[{ message: 'foo' }]}
name="Foobar"
onClick={() => {}}
open={false}
sysInfoData={{}}
{...props}
/>
);
}

+ 32
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx ファイルの表示

@@ -0,0 +1,32 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import HealthCauseItem from '../HealthCauseItem';
import { HealthType } from '../../../../../api/system';

it('should render correctly', () => {
expect(
shallow(<HealthCauseItem health={HealthType.RED} healthCause={{ message: 'foo' }} />)
).toMatchSnapshot();
expect(
shallow(<HealthCauseItem health={HealthType.YELLOW} healthCause={{ message: 'foo' }} />)
).toMatchSnapshot();
});

+ 47
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx ファイルの表示

@@ -0,0 +1,47 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import HealthItem from '../HealthItem';
import { HealthType } from '../../../../../api/system';

it('should render correctly', () => {
expect(
shallow(<HealthItem health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />)
).toMatchSnapshot();
});

it('should not render health causes', () => {
expect(
shallow(<HealthItem health={HealthType.GREEN} healthCauses={[{ message: 'foo' }]} />)
).toMatchSnapshot();
expect(shallow(<HealthItem health={HealthType.YELLOW} healthCauses={[]} />)).toMatchSnapshot();
});

it('should render multiple health causes', () => {
expect(
shallow(
<HealthItem
health={HealthType.YELLOW}
healthCauses={[{ message: 'foo' }, { message: 'bar' }]}
/>
)
).toMatchSnapshot();
});

+ 32
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx ファイルの表示

@@ -0,0 +1,32 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import Section from '../Section';

it('should render correctly', () => {
expect(
shallow(<Section name="foo" items={{ foo: 1, bar: 'Bar', baz: false }} />)
).toMatchSnapshot();
});

it('should not render a title', () => {
expect(shallow(<Section items={{ foo: 'bar' }} />)).toMatchSnapshot();
});

+ 60
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx ファイルの表示

@@ -0,0 +1,60 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import { shallow, mount } from 'enzyme';
import SysInfoItem from '../SysInfoItem';

it('should render string', () => {
const wrapper = shallow(<SysInfoItem name="foo" value="/some/path/as/an/example" />);
expect(wrapper.find('code').text()).toBe('/some/path/as/an/example');
});

it('should render object', () => {
const wrapper = shallow(<SysInfoItem name="foo" value={{ bar: 'baz' }} />);
expect(wrapper.find('ObjectItem').prop('value')).toEqual({ bar: 'baz' });
});

it('should render boolean', () => {
const wrapper = shallow(<SysInfoItem name="foo" value={true} />);
expect(wrapper.find('BooleanItem').prop('value')).toBe(true);
});

it('should render health item', () => {
const wrapper = shallow(<SysInfoItem name="Health" value="GREEN" />);
expect(wrapper.find('HealthItem').prop('health')).toBe('GREEN');
});

it('should render object correctly', () => {
expect(
mount(
<SysInfoItem name="test" value={{ foo: 'Far', bar: { a: 1, b: 'b' }, baz: true }} />
).find('ObjectItem')
).toMatchSnapshot();
});

it('should render `true`', () => {
const wrapper = mount(<SysInfoItem name="test" value={true} />);
expect(wrapper.find('.icon-check')).toHaveLength(1);
});

it('should render `false`', () => {
const wrapper = mount(<SysInfoItem name="test" value={false} />);
expect(wrapper.find('.icon-delete')).toHaveLength(1);
});

+ 132
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap ファイルの表示

@@ -0,0 +1,132 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display the sysinfo detail 1`] = `
<li
className="boxed-group system-info-health-card"
>
<div
className="boxed-group-header"
onClick={[Function]}
>
<span
className="system-info-health-card-title"
>
<OpenCloseIcon
className="little-spacer-right"
open={true}
/>
Foobar
</span>
<HealthItem
className="pull-right big-dot"
health="RED"
healthCauses={
Array [
Object {
"message": "foo",
},
]
}
/>
</div>
<div
className="boxed-group-inner"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
/>
</li>
`;

exports[`should render correctly 1`] = `
<li
className="boxed-group system-info-health-card"
>
<div
className="boxed-group-header"
onClick={[Function]}
>
<span
className="system-info-health-card-title"
>
<OpenCloseIcon
className="little-spacer-right"
open={false}
/>
Foobar
</span>
<HealthItem
className="pull-right"
health="RED"
healthCauses={
Array [
Object {
"message": "foo",
},
]
}
/>
</div>
</li>
`;

exports[`should show a main section and multiple sub sections 1`] = `
<li
className="boxed-group system-info-health-card"
>
<div
className="boxed-group-header"
onClick={[Function]}
>
<span
className="system-info-health-card-title"
>
<OpenCloseIcon
className="little-spacer-right"
open={true}
/>
Foobar
</span>
<HealthItem
className="pull-right"
health="RED"
healthCauses={
Array [
Object {
"message": "foo",
},
]
}
/>
</div>
<div
className="boxed-group-inner"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<Section
items={
Object {
"Name": "foo",
"bar": "Bar",
}
}
/>
<Section
items={
Object {
"db": "test",
}
}
name="Database"
/>
<Section
items={
Object {
"Elastic": "search",
}
}
name="Elasticseach"
/>
</div>
</li>
`;

+ 17
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap ファイルの表示

@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<span
className="alert alert-danger"
>
foo
</span>
`;

exports[`should render correctly 2`] = `
<span
className="alert alert-warning"
>
foo
</span>
`;

+ 68
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap ファイルの表示

@@ -0,0 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should not render health causes 1`] = `
<div
className="system-info-health-info"
>
<span
className="system-info-health-dot GREEN"
/>
</div>
`;

exports[`should not render health causes 2`] = `
<div
className="system-info-health-info"
>
<span
className="system-info-health-dot YELLOW"
/>
</div>
`;

exports[`should render correctly 1`] = `
<div
className="system-info-health-info"
>
<HealthCauseItem
className="spacer-right"
health="RED"
healthCause={
Object {
"message": "foo",
}
}
/>
<span
className="system-info-health-dot RED"
/>
</div>
`;

exports[`should render multiple health causes 1`] = `
<div
className="system-info-health-info"
>
<HealthCauseItem
className="spacer-right"
health="YELLOW"
healthCause={
Object {
"message": "foo",
}
}
/>
<HealthCauseItem
className="spacer-right"
health="YELLOW"
healthCause={
Object {
"message": "bar",
}
}
/>
<span
className="system-info-health-dot YELLOW"
/>
</div>
`;

+ 125
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap ファイルの表示

@@ -0,0 +1,125 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should not render a title 1`] = `
<div
className="system-info-section"
>
<table
className="data zebra"
>
<tbody>
<tr>
<td
className="thin"
>
<div
className="system-info-section-item-name"
>
foo
</div>
</td>
<td
style={
Object {
"wordBreak": "break-all",
}
}
>
<SysInfoItem
name="foo"
value="bar"
/>
</td>
</tr>
</tbody>
</table>
</div>
`;

exports[`should render correctly 1`] = `
<div
className="system-info-section"
>
<h4
className="spacer-bottom"
>
foo
</h4>
<table
className="data zebra"
id="foo"
>
<tbody>
<tr>
<td
className="thin"
>
<div
className="system-info-section-item-name"
>
foo
</div>
</td>
<td
style={
Object {
"wordBreak": "break-all",
}
}
>
<SysInfoItem
name="foo"
value={1}
/>
</td>
</tr>
<tr>
<td
className="thin"
>
<div
className="system-info-section-item-name"
>
bar
</div>
</td>
<td
style={
Object {
"wordBreak": "break-all",
}
}
>
<SysInfoItem
name="bar"
value="Bar"
/>
</td>
</tr>
<tr>
<td
className="thin"
>
<div
className="system-info-section-item-name"
>
baz
</div>
</td>
<td
style={
Object {
"wordBreak": "break-all",
}
}
>
<SysInfoItem
name="baz"
value={false}
/>
</td>
</tr>
</tbody>
</table>
</div>
`;

+ 180
- 0
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap ファイルの表示

@@ -0,0 +1,180 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render object correctly 1`] = `
Array [
<ObjectItem
value={
Object {
"bar": Object {
"a": 1,
"b": "b",
},
"baz": true,
"foo": "Far",
}
}
>
<table
className="data"
>
<tbody>
<tr>
<td
className="thin nowrap"
>
foo
</td>
<td>
<SysInfoItem
name="foo"
value="Far"
>
<code>
Far
</code>
</SysInfoItem>
</td>
</tr>
<tr>
<td
className="thin nowrap"
>
bar
</td>
<td>
<SysInfoItem
name="bar"
value={
Object {
"a": 1,
"b": "b",
}
}
>
<ObjectItem
value={
Object {
"a": 1,
"b": "b",
}
}
>
<table
className="data"
>
<tbody>
<tr>
<td
className="thin nowrap"
>
a
</td>
<td>
<SysInfoItem
name="a"
value={1}
>
<code>
1
</code>
</SysInfoItem>
</td>
</tr>
<tr>
<td
className="thin nowrap"
>
b
</td>
<td>
<SysInfoItem
name="b"
value="b"
>
<code>
b
</code>
</SysInfoItem>
</td>
</tr>
</tbody>
</table>
</ObjectItem>
</SysInfoItem>
</td>
</tr>
<tr>
<td
className="thin nowrap"
>
baz
</td>
<td>
<SysInfoItem
name="baz"
value={true}
>
<BooleanItem
value={true}
>
<i
className="icon-check"
/>
</BooleanItem>
</SysInfoItem>
</td>
</tr>
</tbody>
</table>
</ObjectItem>,
<ObjectItem
value={
Object {
"a": 1,
"b": "b",
}
}
>
<table
className="data"
>
<tbody>
<tr>
<td
className="thin nowrap"
>
a
</td>
<td>
<SysInfoItem
name="a"
value={1}
>
<code>
1
</code>
</SysInfoItem>
</td>
</tr>
<tr>
<td
className="thin nowrap"
>
b
</td>
<td>
<SysInfoItem
name="b"
value="b"
>
<code>
b
</code>
</SysInfoItem>
</td>
</tr>
</tbody>
</table>
</ObjectItem>,
]
`;

+ 7
- 5
server/sonar-web/src/main/js/apps/system/main.js ファイルの表示

@@ -23,7 +23,7 @@ import { sortBy } from 'lodash';
import { getSystemInfo } from '../../api/system';
import Section from './section';
import { translate } from '../../helpers/l10n';
import RestartModal from '../../components/RestartModal';
import RestartForm from '../../components/common/RestartForm';

const SECTIONS_ORDER = [
'SonarQube',
@@ -38,6 +38,8 @@ const SECTIONS_ORDER = [
];

export default class Main extends React.PureComponent {
state = { openRestartForm: false };

componentDidMount() {
getSystemInfo().then(info => this.setState({ sections: this.parseSections(info) }));
}
@@ -60,9 +62,8 @@ export default class Main extends React.PureComponent {

orderItems = items => sortBy(items, 'name');

handleServerRestart = () => {
new RestartModal().render();
};
handleServerRestartOpen = () => this.setState({ openRestartForm: true });
handleServerRestartClose = () => this.setState({ openRestartForm: false });

render() {
let sections = null;
@@ -113,9 +114,10 @@ export default class Main extends React.PureComponent {
<button
id="restart-server-button"
className="big-spacer-left"
onClick={this.handleServerRestart}>
onClick={this.handleServerRestartOpen}>
Restart Server
</button>
{this.state.openRestartForm && <RestartForm onClose={this.handleServerRestartClose} />}
</div>
</header>
{sections}

+ 8
- 2
server/sonar-web/src/main/js/apps/system/routes.ts ファイルの表示

@@ -17,12 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';
import { RouterState, RouteComponent, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./main').then(i => callback(null, { component: (i as any).default }));
import('./components/App').then(i => callback(null, { component: i.default }));
}
},
{
path: 'old',
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./main').then(i => callback(null, (i as any).default));
}
}
];

+ 81
- 0
server/sonar-web/src/main/js/apps/system/styles.css ファイルの表示

@@ -0,0 +1,81 @@
.system-info-health-title {
margin-top: 24px;
margin-bottom: 16px;
}

.system-info-health-card {
margin-bottom: 8px;
transition: border-color 0.3s ease;
}

.system-info-health-card:not(.no-hover):hover {
border-color: #4b9fd5;
}

.system-info-health-card:not(.no-hover):hover .system-info-health-card-title {
color: #4b9fd5;
}

.system-info-health-card .boxed-group-header {
cursor: pointer;
padding-bottom: 15px;
}

.system-info-health-card .boxed-group-inner {
padding-top: 0;
}

.system-info-health-card-title {
font-weight: bold;
}

.system-info-health-info {
margin-top: -4px;
}

.system-info-health-dot {
display: inline-block;
width: 16px;
height: 16px;
margin: 4px;
border-radius: 16px;
box-sizing: border-box;
}

.system-info-health-dot.GREEN {
background-color: #00aa00;
}

.system-info-health-dot.YELLOW {
background-color: #eabe06;
}
.system-info-health-dot.RED {
background-color: #d4333f;
}

.system-info-health-info .alert {
display: inline-block;
position: relative;
top: -8px;
}

.system-info-health-info.big-dot .system-info-health-dot {
width: 24px;
height: 24px;
margin: 0;
border-radius: 24px;
}

.system-info-health-info.no-margin .system-info-health-dot {
margin: 0;
}

.system-info-section ~ .system-info-section {
margin-top: 16px;
}

.system-info-section-item-name {
width: 25vw;
overflow: hidden;
text-overflow: ellipsis;
}

+ 99
- 0
server/sonar-web/src/main/js/apps/system/utils.ts ファイルの表示

@@ -0,0 +1,99 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import { each, omit, memoize } from 'lodash';
import {
cleanQuery,
parseAsArray,
parseAsString,
RawQuery,
serializeStringArray
} from '../../helpers/query';
import {
HealthCause,
HealthType,
NodeInfo,
SysInfo,
SysInfoSection,
SysValueObject
} from '../../api/system';

export interface Query {
expandedCards: string[];
}

export const HEALTH_FIELD = 'Health';
export const HEALTHCAUSES_FIELD = 'Health Causes';

export function ignoreInfoFields(sysInfoObject: SysValueObject): SysValueObject {
return omit(sysInfoObject, ['Cluster', HEALTH_FIELD, HEALTHCAUSES_FIELD]);
}

export function getAppNodes(sysInfoData: SysInfo): NodeInfo[] {
return sysInfoData['Application Nodes'];
}

export function getHealth(sysInfoObject: SysValueObject): HealthType {
return sysInfoObject[HEALTH_FIELD] as HealthType;
}

export function getHealthCauses(sysInfoObject: SysValueObject): HealthCause[] {
return sysInfoObject[HEALTHCAUSES_FIELD] as HealthCause[];
}

export function getMainCardSection(sysInfoData: SysInfo): SysValueObject {
return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
}

export function getNodeName(nodeInfo: NodeInfo): string {
return nodeInfo['Name'];
}

export function getSearchNodes(sysInfoData: SysInfo): NodeInfo[] {
return sysInfoData['Search Nodes'];
}

export function groupSections(sysInfoData: SysValueObject) {
let mainSection: SysValueObject = {};
let sections: SysInfoSection = {};
each(sysInfoData, (item, key) => {
if (typeof item !== 'object' || item instanceof Array) {
mainSection[key] = item;
} else {
sections[key] = item;
}
});
return { mainSection, sections };
}

export function isCluster(sysInfoData?: SysInfo): boolean {
return sysInfoData != undefined && sysInfoData['Cluster'];
}

export const parseQuery = memoize((urlQuery: RawQuery): Query => {
return {
expandedCards: parseAsArray(urlQuery.expand, parseAsString)
};
});

export const serializeQuery = memoize((query: Query): RawQuery => {
return cleanQuery({
expand: serializeStringArray(query.expandedCards)
});
});

+ 93
- 0
server/sonar-web/src/main/js/components/common/RestartForm.tsx ファイルの表示

@@ -0,0 +1,93 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';
import * as classNames from 'classnames';
import Modal from 'react-modal';
import { restartAndWait } from '../../api/system';
import { translate } from '../../helpers/l10n';

interface Props {
onClose: () => void;
}

interface State {
restarting: boolean;
}

export default class RestartForm extends React.PureComponent<Props, State> {
state: State = { restarting: false };

handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
if (!this.state.restarting) {
this.setState({ restarting: true });
restartAndWait().then(
() => document.location.reload(),
() => this.setState({ restarting: false })
);
}
};

render() {
const { restarting } = this.state;
const header = translate('system.restart_server');
return (
<Modal
isOpen={true}
contentLabel={header}
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.props.onClose}>
<form id="restart-form" onSubmit={this.handleFormSubmit}>
<div className="modal-head">
<h2>{header}</h2>
</div>
<div className="modal-body">
<p className={classNames('spacer-top spacer-bottom', { 'text-center': restarting })}>
{translate(restarting ? 'system.is_restarting' : 'system.are_you_sure_to_restart')}
</p>
{restarting && (
<p className="big-spacer-top spacer-bottom text-center">
<i className="spinner" />
</p>
)}
</div>
{!restarting && (
<div className="modal-foot">
<button id="restart-server-submit">{translate('restart')}</button>
<a
href="#"
className="js-modal-close"
id="restart-server-cancel"
onClick={this.handleCancelClick}>
{translate('cancel')}
</a>
</div>
)}
</form>
</Modal>
);
}
}

+ 3
- 25
server/sonar-web/src/main/js/components/facet/FacetHeader.js ファイルの表示

@@ -20,8 +20,9 @@
// @flow
/* eslint-disable max-len */
import React from 'react';
import Tooltip from '../controls/Tooltip';
import OpenCloseIcon from '../icons-components/OpenCloseIcon';
import HelpIcon from '../icons-components/HelpIcon';
import Tooltip from '../controls/Tooltip';
import { translate } from '../../helpers/l10n';

/*::
@@ -58,29 +59,6 @@ export default class FacetHeader extends React.PureComponent {
}
};

renderCheckbox() {
return (
<svg
className="little-spacer-right"
viewBox="0 0 1792 1792"
width="10"
height="10"
style={{ paddingTop: 3 }}>
{this.props.open ? (
<path
style={{ fill: 'currentColor ' }}
d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
/>
) : (
<path
style={{ fill: 'currentColor ' }}
d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
/>
)}
</svg>
);
}

renderHelper() {
if (!this.props.helper) {
return null;
@@ -119,7 +97,7 @@ export default class FacetHeader extends React.PureComponent {
{this.props.onClick ? (
<span className="search-navigator-facet-header">
<a href="#" onClick={this.handleClick}>
{this.renderCheckbox()}
<OpenCloseIcon className="little-spacer-right" open={this.props.open} />
{this.props.name}
</a>
{this.renderHelper()}

+ 15
- 95
server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap ファイルの表示

@@ -15,26 +15,10 @@ exports[`should clear 1`] = `
href="#"
onClick={[Function]}
>
<svg
<OpenCloseIcon
className="little-spacer-right"
height="10"
style={
Object {
"paddingTop": 3,
}
}
viewBox="0 0 1792 1792"
width="10"
>
<path
d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
style={
Object {
"fill": "currentColor ",
}
}
/>
</svg>
open={false}
/>
foo
</a>
<span
@@ -55,26 +39,10 @@ exports[`should render closed facet with value 1`] = `
href="#"
onClick={[Function]}
>
<svg
<OpenCloseIcon
className="little-spacer-right"
height="10"
style={
Object {
"paddingTop": 3,
}
}
viewBox="0 0 1792 1792"
width="10"
>
<path
d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
style={
Object {
"fill": "currentColor ",
}
}
/>
</svg>
open={false}
/>
foo
</a>
<span
@@ -95,26 +63,10 @@ exports[`should render closed facet without value 1`] = `
href="#"
onClick={[Function]}
>
<svg
<OpenCloseIcon
className="little-spacer-right"
height="10"
style={
Object {
"paddingTop": 3,
}
}
viewBox="0 0 1792 1792"
width="10"
>
<path
d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"
style={
Object {
"fill": "currentColor ",
}
}
/>
</svg>
open={false}
/>
foo
</a>
</span>
@@ -130,26 +82,10 @@ exports[`should render open facet with value 1`] = `
href="#"
onClick={[Function]}
>
<svg
<OpenCloseIcon
className="little-spacer-right"
height="10"
style={
Object {
"paddingTop": 3,
}
}
viewBox="0 0 1792 1792"
width="10"
>
<path
d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
style={
Object {
"fill": "currentColor ",
}
}
/>
</svg>
open={true}
/>
foo
</a>
</span>
@@ -165,26 +101,10 @@ exports[`should render open facet without value 1`] = `
href="#"
onClick={[Function]}
>
<svg
<OpenCloseIcon
className="little-spacer-right"
height="10"
style={
Object {
"paddingTop": 3,
}
}
viewBox="0 0 1792 1792"
width="10"
>
<path
d="M1683 808l-742 741q-19 19-45 19t-45-19l-742-741q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"
style={
Object {
"fill": "currentColor ",
}
}
/>
</svg>
open={true}
/>
foo
</a>
</span>

+ 44
- 0
server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx ファイルの表示

@@ -0,0 +1,44 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
import * as React from 'react';

interface Props {
className?: string;
open: boolean;
size?: number;
}

export default function OpenCloseIcon({ className, open, size = 14 }: Props) {
return (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
width={size}
height={size}
style={{ fill: 'currentColor' }}>
{open ? (
<path d="M13.506 8.539l-5.191 5.184q-0.133 0.133-0.315 0.133t-0.315-0.133l-5.191-5.184q-0.133-0.133-0.133-0.318t0.133-0.318l1.161-1.154q0.133-0.133 0.315-0.133t0.315 0.133l3.715 3.715 3.715-3.715q0.133-0.133 0.315-0.133t0.315 0.133l1.161 1.154q0.133 0.133 0.133 0.318t-0.133 0.318z" />
) : (
<path d="M13.527 8.318l-5.244 5.244q-0.134 0.134-0.318 0.134t-0.318-0.134l-1.173-1.173q-0.134-0.134-0.134-0.318t0.134-0.318l3.753-3.753-3.753-3.753q-0.134-0.134-0.134-0.318t0.134-0.318l1.173-1.173q0.134-0.134 0.318-0.134t0.318 0.134l5.244 5.244q0.134 0.134 0.134 0.318t-0.134 0.318z" />
)}
</svg>
);
}

+ 2
- 0
server/sonar-web/src/main/js/components/icons-components/icons.js ファイルの表示

@@ -30,6 +30,7 @@ import _HelpIcon from './HelpIcon';
import _HistoryIcon from './HistoryIcon';
import _LinkIcon from './LinkIcon';
import _ListIcon from './ListIcon';
import _OpenCloseIcon from './OpenCloseIcon';
import _OrganizationIcon from './OrganizationIcon';
import _ProjectEventIcon from './ProjectEventIcon';
import _QualifierIcon from './QualifierIcon';
@@ -51,6 +52,7 @@ export const HelpIcon = _HelpIcon;
export const HistoryIcon = _HistoryIcon;
export const LinkIcon = _LinkIcon;
export const ListIcon = _ListIcon;
export const OpenCloseIcon = _OpenCloseIcon;
export const OrganizationIcon = _OrganizationIcon;
export const ProjectEventIcon = _ProjectEventIcon;
export const QualifierIcon = _QualifierIcon;

+ 14
- 8
server/sonar-web/src/main/js/helpers/urls.ts ファイルの表示

@@ -31,21 +31,23 @@ interface Location {
query?: Query;
}

export function getBaseUrl(): string {
return (window as any).baseUrl;
}

/**
* Generate URL for a component's home page
*/
export function getComponentUrl(componentKey: string, branch?: string): string {
const branchQuery = branch ? `&branch=${encodeURIComponent(branch)}` : '';
return (
(window as any).baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery
);
return getBaseUrl() + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery;
}

export function getProjectUrl(key: string, branch?: string): Location {
return { pathname: '/dashboard', query: { id: key, branch } };
}

export function getProjectBranchUrl(key: string, branch: Branch) {
export function getProjectBranchUrl(key: string, branch: Branch): Location {
if (isShortLivingBranch(branch)) {
return {
pathname: '/project/issues',
@@ -74,13 +76,17 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca

export function getComponentIssuesUrlAsString(componentKey: string, query?: Query): string {
const path = getComponentIssuesUrl(componentKey, query);
return `${(window as any).baseUrl}${path.pathname}?${stringify(path.query)}`;
return `${getBaseUrl()}${path.pathname}?${stringify(path.query)}`;
}

/**
* Generate URL for a component's drilldown page
*/
export function getComponentDrilldownUrl(componentKey: string, metric: string, branch?: string) {
export function getComponentDrilldownUrl(
componentKey: string,
metric: string,
branch?: string
): Location {
return { pathname: '/component_measures', query: { id: componentKey, metric, branch } };
}

@@ -159,9 +165,9 @@ export function getDeprecatedActiveRulesUrl(query = {}, organization?: string |
}

export function getProjectsUrl(): string {
return (window as any).baseUrl + '/projects';
return getBaseUrl() + '/projects';
}

export function getMarkdownHelpUrl(): string {
return (window as any).baseUrl + '/markdown/help';
return getBaseUrl() + '/markdown/help';
}

+ 12
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties ファイルの表示

@@ -146,6 +146,7 @@ remove=Remove
rename=Rename
reset_verb=Reset
resolution=Resolution
restart=Restart
restore=Restore
result=Result
results=Results
@@ -2840,8 +2841,18 @@ background_tasks.add_more_with_governance=Add more with Governance
# SYSTEM
#
#------------------------------------------------------------------------------
system.application_nodes_title=Application Nodes
system.are_you_sure_to_restart=Are you sure you want to restart the server?
system.cluster_log_level.info=Changes apply to all Application nodes but not to Search nodes.
system.download_logs=Download Logs
system.download_system_info=Download System Info
system.is_restarting=Server is restarting. This page will be automatically refreshed.
system.log_level.warning=Current level has performance impacts, please make sure to get back to INFO level once your investigation is done. Please note that when the server is restarted, logging will revert to the level configured in sonar.properties.

system.log_level.info=Changes don't apply to Search.
system.logs_level=Logs level
system.restart_server=Restart Server
system.search_nodes_title=Search Nodes
system.set_log_level=Set logs level


#------------------------------------------------------------------------------

読み込み中…
キャンセル
保存