aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/system/components
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-09-11 17:01:34 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2017-09-26 23:49:37 +0200
commit74c8a8ac5e83144fab7291b9ad57c14742fe8bbf (patch)
tree191311ab4f4df1faf2c1eac16e2f018b1c77be5a /server/sonar-web/src/main/js/apps/system/components
parentd7df6d3c31db2ca875d8dec2c0b873cc04b7738e (diff)
downloadsonarqube-74c8a8ac5e83144fab7291b9ad57c14742fe8bbf.tar.gz
sonarqube-74c8a8ac5e83144fab7291b9ad57c14742fe8bbf.zip
SONAR-9802 Add nodes to system info in cluster mode
Diffstat (limited to 'server/sonar-web/src/main/js/apps/system/components')
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/App.tsx125
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx114
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx82
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageActions.tsx163
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx55
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx34
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap194
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap59
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap126
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap38
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx84
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx42
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx64
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx60
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap132
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap68
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap125
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap180
29 files changed, 2202 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx
new file mode 100644
index 00000000000..440b117929a
--- /dev/null
+++ b/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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
new file mode 100644
index 00000000000..3065a0be66d
--- /dev/null
+++ b/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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
new file mode 100644
index 00000000000..7f68dc6771a
--- /dev/null
+++ b/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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
new file mode 100644
index 00000000000..1b2ef92d34a
--- /dev/null
+++ b/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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
new file mode 100644
index 00000000000..c9d84569e30
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
@@ -0,0 +1,50 @@
+/*
+ * 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 PageActions from './PageActions';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ isCluster: boolean;
+ loading: boolean;
+ logLevel: string;
+ showActions: boolean;
+}
+
+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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx
new file mode 100644
index 00000000000..2a142c58a0e
--- /dev/null
+++ b/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>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx
new file mode 100644
index 00000000000..08c177e418b
--- /dev/null
+++ b/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();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
new file mode 100644
index 00000000000..5b703a1a214
--- /dev/null
+++ b/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}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx
new file mode 100644
index 00000000000..098e897ec92
--- /dev/null
+++ b/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);
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx
new file mode 100644
index 00000000000..f5255c7228e
--- /dev/null
+++ b/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();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
new file mode 100644
index 00000000000..04cfc11d49b
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
new file mode 100644
index 00000000000..20ed8d541f6
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap
new file mode 100644
index 00000000000..9d84700539f
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
new file mode 100644
index 00000000000..2945b6df932
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
new file mode 100644
index 00000000000..b45205dccba
--- /dev/null
+++ b/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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
new file mode 100644
index 00000000000..49b22b717bb
--- /dev/null
+++ b/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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
new file mode 100644
index 00000000000..a0793a81831
--- /dev/null
+++ b/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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx
new file mode 100644
index 00000000000..3da339cbdf2
--- /dev/null
+++ b/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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx
new file mode 100644
index 00000000000..12876d75940
--- /dev/null
+++ b/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>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
new file mode 100644
index 00000000000..c080f444dec
--- /dev/null
+++ b/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}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
new file mode 100644
index 00000000000..24504f09a91
--- /dev/null
+++ b/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();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
new file mode 100644
index 00000000000..f77622b9e92
--- /dev/null
+++ b/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();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx
new file mode 100644
index 00000000000..2778dc855b8
--- /dev/null
+++ b/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();
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx
new file mode 100644
index 00000000000..2761a2ddbf9
--- /dev/null
+++ b/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);
+});
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
new file mode 100644
index 00000000000..724001990fa
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap
new file mode 100644
index 00000000000..3202387cf1c
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
new file mode 100644
index 00000000000..d9fa53a2956
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap
new file mode 100644
index 00000000000..20fce92a9b7
--- /dev/null
+++ b/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>
+`;
diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap
new file mode 100644
index 00000000000..a1ff67c3506
--- /dev/null
+++ b/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>,
+]
+`;