diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-09-11 17:01:34 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-09-26 23:49:37 +0200 |
commit | 74c8a8ac5e83144fab7291b9ad57c14742fe8bbf (patch) | |
tree | 191311ab4f4df1faf2c1eac16e2f018b1c77be5a /server/sonar-web/src/main/js/apps/system/components | |
parent | d7df6d3c31db2ca875d8dec2c0b873cc04b7738e (diff) | |
download | sonarqube-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')
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>, +] +`; |