import { getJSON, post, postJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-export type SysValue = boolean | string | number | HealthType | SysValueObject | SysValueArray;
-export interface SysValueObject {
- [key: string]: SysValue;
-}
-export interface SysValueArray extends Array<SysValue> {}
-
-export interface SysInfoSection {
- [sectionName: string]: SysValueObject;
-}
-
-export enum HealthType {
- RED = 'RED',
- YELLOW = 'YELLOW',
- GREEN = 'GREEN'
-}
-
-export interface NodeInfo extends SysValueObject {
- 'Compute Engine Logging': { 'Logs Level': string };
- Health: HealthType;
- 'Health Causes': string[];
- Name: string;
- 'Web Logging': { 'Logs Level': string };
-}
-
-export interface SysInfo extends SysValueObject {
- Health: HealthType;
- 'Health Causes': string[];
- System: {
- 'High Availability': boolean;
- 'Logs Level': string;
- 'Server ID': string;
- };
-}
-
-export interface ClusterSysInfo extends SysInfo {
- 'Application Nodes': NodeInfo[];
- 'Search Nodes': NodeInfo[];
-}
-
-export interface SystemUpgrade {
- version: string;
- description: string;
- releaseDate: string;
- changeLogUrl: string;
- downloadUrl: string;
- plugins: any;
-}
-
export function setLogLevel(level: string): Promise<void | Response> {
return post('/api/system/change_log_level', { level }).catch(throwGlobalError);
}
-export function getSystemInfo(): Promise<SysInfo> {
+export function getSystemInfo(): Promise<T.SysInfoCluster | T.SysInfoStandalone> {
return getJSON('/api/system/info').catch(throwGlobalError);
}
}
export function getSystemUpgrades(): Promise<{
- upgrades: SystemUpgrade[];
+ upgrades: T.SystemUpgrade[];
updateCenterRefresh: string;
}> {
return getJSON('/api/system/upgrades');
name: string;
}
+ export type HealthType = 'RED' | 'YELLOW' | 'GREEN';
+
export type HomePage =
| { type: 'APPLICATION'; branch: string | undefined; component: string }
| { type: 'ISSUES' }
export type RuleType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT' | 'UNKNOWN';
- export type Status = 'ERROR' | 'OK';
-
export type Setting = SettingValue & { definition: SettingDefinition };
export type SettingType =
export type StandardType = 'owaspTop10' | 'sansTop25' | 'cwe' | 'sonarsourceSecurity';
+ export type Status = 'ERROR' | 'OK';
+
export interface SubscriptionPlan {
maxNcloc: number;
price: number;
text: string;
}
+ export interface SysInfoAppNode extends SysInfoBase {
+ 'Compute Engine Logging': SysInfoLogging;
+ Name: string;
+ 'Web Logging': SysInfoLogging;
+ }
+
+ export interface SysInfoBase extends SysInfoValueObject {
+ Health: HealthType;
+ 'Health Causes': string[];
+ Plugins?: Dict<string>;
+ System: {
+ Version: string;
+ };
+ }
+
+ export interface SysInfoCluster extends SysInfoBase {
+ 'Application Nodes': SysInfoAppNode[];
+ 'Search Nodes': SysInfoSearchNode[];
+ Settings: Dict<string>;
+ Statistics?: {
+ ncloc: number;
+ };
+ System: {
+ 'High Availability': true;
+ 'Server ID': string;
+ Version: string;
+ };
+ }
+
+ export interface SysInfoLogging extends Dict<string> {
+ 'Logs Level': string;
+ }
+
+ export interface SysInfoSearchNode extends SysInfoValueObject {
+ Name: string;
+ }
+
+ export interface SysInfoSection extends Dict<SysInfoValueObject> {}
+
+ export interface SysInfoStandalone extends SysInfoBase {
+ 'Compute Engine Logging': SysInfoLogging;
+ Settings: Dict<string>;
+ Statistics?: {
+ ncloc: number;
+ } & Dict<string | number>;
+ System: {
+ 'High Availability': false;
+ 'Server ID': string;
+ Version: string;
+ };
+ 'Web Logging': SysInfoLogging;
+ }
+
+ export type SysInfoValue =
+ | boolean
+ | string
+ | number
+ | undefined
+ | HealthType
+ | SysInfoValueObject
+ | SysInfoValueArray;
+
+ export interface SysInfoValueArray extends Array<SysInfoValue> {}
+
+ export interface SysInfoValueObject extends Dict<SysInfoValue> {}
+
+ export interface SystemUpgrade {
+ version: string;
+ description: string;
+ releaseDate: string;
+ changeLogUrl: string;
+ downloadUrl: string;
+ }
+
export interface Task {
analysisId?: string;
branch?: string;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+/* eslint-disable sonarjs/no-duplicate-string */
import * as u from '../utils';
-import { ClusterSysInfo, SysInfo, SystemUpgrade } from '../../../api/system';
+import { mockClusterSysInfo, mockStandaloneSysInfo } from '../../../helpers/testMocks';
describe('parseQuery', () => {
it('should correctly parse the expand array', () => {
});
describe('getSystemLogsLevel', () => {
- it('should correctly return log level for standalone mode', () => {
- expect(u.getSystemLogsLevel({ System: { 'Logs Level': 'FOO' } } as SysInfo)).toBe('FOO');
- expect(u.getSystemLogsLevel({} as SysInfo)).toBe('INFO');
- expect(u.getSystemLogsLevel()).toBe('INFO');
+ it('should correctly return the worst log level for standalone mode', () => {
+ expect(u.getSystemLogsLevel(mockStandaloneSysInfo())).toBe('DEBUG');
});
it('should return the worst log level for cluster mode', () => {
- expect(
- u.getSystemLogsLevel({
- System: { 'High Availability': true },
- 'Application Nodes': [
- {
- 'Compute Engine Logging': { 'Logs Level': 'DEBUG' },
- 'Web Logging': { 'Logs Level': 'INFO' }
- },
- {
- 'Compute Engine Logging': { 'Logs Level': 'INFO' },
- 'Web Logging': { 'Logs Level': 'INFO' }
- }
- ]
- } as ClusterSysInfo)
- ).toBe('DEBUG');
+ expect(u.getSystemLogsLevel(mockClusterSysInfo())).toBe('DEBUG');
});
it('should not fail if the log informations are not there yet', () => {
expect(
- u.getSystemLogsLevel({
- System: { 'High Availability': true },
- 'Application Nodes': [{ Name: 'App 1' }, { Name: 'App 2' }]
- } as ClusterSysInfo)
+ u.getSystemLogsLevel(
+ mockClusterSysInfo({
+ 'Application Nodes': [{ Name: 'App 1' }, { Name: 'App 2' }]
+ })
+ )
).toBe('INFO');
expect(
- u.getSystemLogsLevel({
- System: { 'High Availability': true },
- 'Application Nodes': [{ 'Compute Engine Logging': {} }, { Name: 'App 2' }]
- } as any)
+ u.getSystemLogsLevel(
+ mockClusterSysInfo({
+ 'Application Nodes': [{ 'Compute Engine Logging': {} }, { Name: 'App 2' }]
+ })
+ )
).toBe('INFO');
- expect(u.getSystemLogsLevel({ System: {} } as SysInfo)).toBe('INFO');
+ expect(u.getSystemLogsLevel({} as T.SysInfoStandalone)).toBe('INFO');
});
});
{ version: '5.10' },
{ version: '5.1' },
{ version: '5.4' }
- ] as SystemUpgrade[])
+ ] as T.SystemUpgrade[])
).toEqual([{ version: '5.10' }, { version: '5.4.2' }, { version: '5.4' }, { version: '5.1' }]);
expect(
u.sortUpgrades([
{ version: '5.1.2' },
{ version: '6.0' },
{ version: '6.9' }
- ] as SystemUpgrade[])
+ ] as T.SystemUpgrade[])
).toEqual([{ version: '6.9' }, { version: '6.0' }, { version: '5.10' }, { version: '5.1.2' }]);
});
});
{ version: '5.4.2' },
{ version: '5.4' },
{ version: '5.1' }
- ] as SystemUpgrade[])
+ ] as T.SystemUpgrade[])
).toEqual([
[{ version: '5.10' }, { version: '5.4.2' }, { version: '5.4' }, { version: '5.1' }]
]);
{ version: '6.0' },
{ version: '5.10' },
{ version: '5.4.2' }
- ] as SystemUpgrade[])
+ ] as T.SystemUpgrade[])
).toEqual([
[{ version: '6.9' }, { version: '6.7' }, { version: '6.0' }],
[{ version: '5.10' }, { version: '5.4.2' }]
]);
});
});
+
+describe('isCluster', () => {
+ it('should return the correct information', () => {
+ expect(u.isCluster(mockClusterSysInfo())).toBe(true);
+ expect(u.isCluster(mockStandaloneSysInfo())).toBe(false);
+ });
+});
+
+describe('isLogInfoBlock', () => {
+ it('should return the correct information', () => {
+ expect(u.isLogInfoBlock(mockStandaloneSysInfo().System)).toBe(false);
+ expect(u.isLogInfoBlock(mockStandaloneSysInfo()['Web Logging'])).toBe(true);
+ });
+});
+
+describe('hasLoggingInfo', () => {
+ it('should return the correct information', () => {
+ expect(u.hasLoggingInfo(mockStandaloneSysInfo())).toBe(true);
+ expect(u.hasLoggingInfo(mockClusterSysInfo()['Application Nodes'][0])).toBe(true);
+ expect(u.hasLoggingInfo(mockClusterSysInfo())).toBe(false);
+ });
+});
+
+describe('getStandaloneSecondarySections', () => {
+ it('should return the correct information', () => {
+ expect(Object.keys(u.getStandaloneSecondarySections(mockStandaloneSysInfo()))).toEqual(
+ expect.arrayContaining(['Compute Engine', 'Search Engine', 'Web'])
+ );
+ expect(Object.keys(u.getStandaloneSecondarySections(mockClusterSysInfo()))).toEqual(
+ expect.arrayContaining(['Compute Engine', 'Search Engine', 'Web'])
+ );
+ });
+});
+
+describe('getStandaloneMainSections', () => {
+ it('should return the correct information', () => {
+ expect(Object.keys(u.getStandaloneMainSections(mockStandaloneSysInfo()))).toEqual(
+ expect.arrayContaining([
+ 'Server ID',
+ 'High Availability',
+ 'Health',
+ 'Health Causes',
+ 'Database'
+ ])
+ );
+ });
+});
+
+describe('getClusterMainCardSection', () => {
+ it('should return the correct information', () => {
+ expect(Object.keys(u.getClusterMainCardSection(mockClusterSysInfo()))).toEqual(
+ expect.arrayContaining([
+ 'Server ID',
+ 'High Availability',
+ 'Lines of Code',
+ 'Health',
+ 'Health Causes',
+ 'Database',
+ 'Compute Engine Tasks',
+ 'Search State',
+ 'Search Indexes'
+ ])
+ );
+ });
+});
+
+describe('getSearchNodes', () => {
+ it('should return the correct information', () => {
+ expect(
+ u.getSearchNodes(
+ mockClusterSysInfo({
+ 'Search Nodes': [{ Name: 'searchnode1' }]
+ })
+ )
+ ).toEqual([{ Name: 'searchnode1' }]);
+ });
+});
+
+describe('getAppNodes', () => {
+ it('should return the correct information', () => {
+ expect(
+ u.getAppNodes(
+ mockClusterSysInfo({
+ 'Application Nodes': [{ Name: 'appnode1' }]
+ })
+ )
+ ).toEqual([{ Name: 'appnode1' }]);
+ });
+});
+
+describe('getNodeName', () => {
+ it('should return the correct information', () => {
+ expect(u.getNodeName({ Name: 'Foo' })).toEqual('Foo');
+ });
+});
+
+describe('getHealthCauses', () => {
+ it('should return the correct information', () => {
+ expect(u.getHealthCauses({ 'Health Causes': ['Foo'] } as T.SysInfoBase)).toEqual(['Foo']);
+ });
+});
+
+describe('getHealth', () => {
+ it('should return the correct information', () => {
+ expect(u.getHealth({ Health: 'GREEN' } as T.SysInfoBase)).toEqual('GREEN');
+ });
+});
+
+describe('getLogsLevel', () => {
+ it('should return the correct information, if available', () => {
+ expect(u.getLogsLevel({ 'Compute Engine Logging': { 'Logs Level': 'TRACE' } })).toEqual(
+ 'TRACE'
+ );
+ });
+
+ it('should return the worst level', () => {
+ expect(
+ u.getLogsLevel({
+ 'Web Logging': { 'Logs Level': 'DEBUG' },
+ 'Compute Engine Logging': { 'Logs Level': 'TRACE' }
+ })
+ ).toEqual('TRACE');
+ });
+
+ it('should return the default level if no information is provided', () => {
+ expect(u.getLogsLevel()).toEqual('INFO');
+ });
+});
+
+describe('getServerId', () => {
+ it('should return the correct information, if available', () => {
+ expect(u.getServerId(mockStandaloneSysInfo({ System: { 'Server ID': 'foo-bar' } }))).toEqual(
+ 'foo-bar'
+ );
+ });
+
+ it('should return undefined if no information is available', () => {
+ expect(u.getServerId(mockStandaloneSysInfo({ System: {} }))).toBeUndefined();
+ });
+});
+
+describe('getVersion', () => {
+ it('should return the correct information, if available', () => {
+ expect(u.getVersion(mockStandaloneSysInfo({ System: { Version: '1.0' } }))).toEqual('1.0');
+ });
+
+ it('should return undefined if no information is available', () => {
+ expect(u.getVersion(mockStandaloneSysInfo({ System: {} }))).toBeUndefined();
+ });
+});
+
+describe('getClusterVersion', () => {
+ it('should return the correct information, if available', () => {
+ expect(
+ u.getClusterVersion(
+ mockClusterSysInfo({
+ 'Application Nodes': [{ System: { Version: '1.0' } }]
+ })
+ )
+ ).toEqual('1.0');
+ });
+
+ it('should return undefined if no information is available', () => {
+ expect(
+ u.getClusterVersion(mockClusterSysInfo({ 'Application Nodes': [{ System: {} }] }))
+ ).toBeUndefined();
+ expect(
+ u.getClusterVersion(
+ mockClusterSysInfo({
+ 'Application Nodes': [],
+ System: { Version: '1.0' }
+ })
+ )
+ ).toBeUndefined();
+ });
+});
import SystemUpgradeNotif from './system-upgrade/SystemUpgradeNotif';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { translate } from '../../../helpers/l10n';
-import { ClusterSysInfo, getSystemInfo, SysInfo } from '../../../api/system';
+import { getSystemInfo } from '../../../api/system';
import {
getServerId,
getSystemLogsLevel,
isCluster,
parseQuery,
Query,
- serializeQuery
+ serializeQuery,
+ getVersion,
+ getClusterVersion
} from '../utils';
import '../styles.css';
interface State {
loading: boolean;
- sysInfoData?: SysInfo;
+ sysInfoData?: T.SysInfoCluster | T.SysInfoStandalone;
}
class App extends React.PureComponent<Props, State> {
fetchSysInfo = () => {
this.setState({ loading: true });
getSystemInfo().then(
- (sysInfoData: SysInfo) => {
+ sysInfoData => {
if (this.mounted) {
this.setState({ loading: false, sysInfoData });
}
return (
<ClusterSysInfos
expandedCards={query.expandedCards}
- sysInfoData={sysInfoData as ClusterSysInfo}
+ sysInfoData={sysInfoData}
toggleCard={this.toggleSysInfoCards}
/>
);
<Suggestions suggestions="system_info" />
<Helmet title={translate('system_info.page')} />
<SystemUpgradeNotif />
- <PageHeader
- isCluster={isCluster(sysInfoData)}
- loading={loading}
- logLevel={getSystemLogsLevel(sysInfoData)}
- onLogLevelChange={this.fetchSysInfo}
- serverId={getServerId(sysInfoData)}
- showActions={sysInfoData !== undefined}
- />
+ {sysInfoData && (
+ <PageHeader
+ isCluster={isCluster(sysInfoData)}
+ loading={loading}
+ logLevel={getSystemLogsLevel(sysInfoData)}
+ onLogLevelChange={this.fetchSysInfo}
+ serverId={getServerId(sysInfoData)}
+ showActions={sysInfoData !== undefined}
+ version={
+ isCluster(sysInfoData) ? getClusterVersion(sysInfoData) : getVersion(sysInfoData)
+ }
+ />
+ )}
{this.renderSysInfo()}
</div>
);
import { sortBy } from 'lodash';
import HealthCard from './info-items/HealthCard';
import { translate } from '../../../helpers/l10n';
-import { ClusterSysInfo } from '../../../api/system';
import {
getAppNodes,
getHealth,
interface Props {
expandedCards: string[];
- sysInfoData: ClusterSysInfo;
+ sysInfoData: T.SysInfoCluster;
toggleCard: (toggledCard: string) => void;
}
<li className="note system-info-health-title">
{translate('system.application_nodes_title')}
</li>
- {sortBy(getAppNodes(sysInfoData), getNodeName).map(node => (
+ {sortBy(getAppNodes(sysInfoData), getNodeName).map((node: T.SysInfoAppNode) => (
<HealthCard
health={getHealth(node)}
healthCauses={getHealthCauses(node)}
/>
))}
<li className="note system-info-health-title">{translate('system.search_nodes_title')}</li>
- {sortBy(getSearchNodes(sysInfoData), getNodeName).map(node => (
+ {sortBy(getSearchNodes(sysInfoData), getNodeName).map((node: T.SysInfoSearchNode) => (
<HealthCard
- health={getHealth(node)}
- healthCauses={getHealthCauses(node)}
key={getNodeName(node)}
name={getNodeName(node)}
onClick={toggleCard}
*/
import * as React from 'react';
import PageActions from './PageActions';
+import ClipboardButton from '../../../components/controls/ClipboardButton';
import { translate } from '../../../helpers/l10n';
+import { toShortNotSoISOString } from '../../../helpers/dates';
-interface Props {
+export interface Props {
isCluster: boolean;
loading: boolean;
logLevel: string;
onLogLevelChange: () => void;
serverId?: string;
showActions: boolean;
+ version?: string;
}
export default function PageHeader(props: Props) {
+ const { isCluster, loading, logLevel, serverId, showActions, version } = props;
return (
<header className="page-header">
<h1 className="page-title">{translate('system_info.page')}</h1>
- {props.showActions && (
+ {showActions && (
<PageActions
- canDownloadLogs={!props.isCluster}
- canRestart={!props.isCluster}
- cluster={props.isCluster}
- logLevel={props.logLevel}
+ canDownloadLogs={!isCluster}
+ canRestart={!isCluster}
+ cluster={isCluster}
+ logLevel={logLevel}
onLogLevelChange={props.onLogLevelChange}
- serverId={props.serverId}
+ serverId={serverId}
/>
)}
- {props.loading && (
+ {loading && (
<div className="page-actions">
<i className="spinner" />
</div>
)}
+ {serverId && version && (
+ <div className="system-info-copy-paste-id-info boxed-group display-flex-center">
+ <div className="flex-1">
+ <table className="width-100">
+ <tbody>
+ <tr>
+ <th>
+ <strong>{translate('system.server_id')}</strong>
+ </th>
+ <td>{serverId}</td>
+ </tr>
+ <tr>
+ <th>
+ <strong>{translate('system.version')}</strong>
+ </th>
+ <td>{version}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <ClipboardButton
+ className="flex-0"
+ copyValue={`SonarQube ID information
+Server ID: ${serverId}
+Version: ${version}
+Date: ${toShortNotSoISOString(Date.now())}
+`}
+ label={translate('system.copy_id_info')}
+ />
+ </div>
+ )}
</header>
);
}
import * as React from 'react';
import { map } from 'lodash';
import HealthCard from './info-items/HealthCard';
-import { SysInfo } from '../../../api/system';
import {
getHealth,
getHealthCauses,
interface Props {
expandedCards: string[];
- sysInfoData: SysInfo;
+ sysInfoData: T.SysInfoStandalone;
toggleCard: (toggledCard: string) => void;
}
import * as React from 'react';
import { shallow } from 'enzyme';
import ClusterSysInfos from '../ClusterSysInfos';
-import { ClusterSysInfo, HealthType } from '../../../../api/system';
-
-const sysInfoData: ClusterSysInfo = {
- Health: HealthType.RED,
- 'Health Causes': ['Database down'],
- 'Application Nodes': [
- {
- Name: 'Bar',
- Health: HealthType.GREEN,
- 'Health Causes': [],
- 'Compute Engine Logging': { 'Logs Level': 'INFO' },
- 'Web Logging': { 'Logs Level': 'INFO' }
- }
- ],
- 'Search Nodes': [
- {
- Name: 'Baz',
- Health: HealthType.YELLOW,
- 'Health Causes': [],
- 'Compute Engine Logging': { 'Logs Level': 'INFO' },
- 'Web Logging': { 'Logs Level': 'INFO' }
- }
- ],
- System: {
- 'High Availability': true,
- 'Logs Level': 'INFO',
- 'Server ID': 'MyServerId'
- }
-};
+import { mockClusterSysInfo } from '../../../../helpers/testMocks';
it('should render correctly', () => {
expect(
- getWrapper({
- sysInfoData: {
- ...sysInfoData,
+ shallowRender(
+ mockClusterSysInfo({
+ sysInfoData: {
+ Health: 'RED',
+ 'Health Causes': ['Database down'],
+ 'Application Nodes': [
+ {
+ Name: 'Foo',
+ Health: 'GREEN',
+ 'Health Causes': [],
+ 'Compute Engine Logging': { 'Logs Level': 'INFO' },
+ 'Web Logging': { 'Logs Level': 'INFO' }
+ },
+ {
+ Name: 'Bar',
+ Health: 'RED',
+ 'Health Causes': [],
+ 'Compute Engine Logging': { 'Logs Level': 'INFO' },
+ 'Web Logging': { 'Logs Level': 'DEBUG' }
+ },
+ {
+ Name: 'Baz',
+ Health: 'YELLOW',
+ 'Health Causes': [],
+ 'Compute Engine Logging': { 'Logs Level': 'TRACE' },
+ 'Web Logging': { 'Logs Level': 'DEBUG' }
+ }
+ ]
+ }
+ })
+ ).find('HealthCard')
+ ).toHaveLength(4);
+});
+
+it('should support more than two nodes', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props = {}) {
+ return shallow(
+ <ClusterSysInfos
+ expandedCards={['System', 'Foo']}
+ sysInfoData={mockClusterSysInfo({
+ Health: 'RED',
+ 'Health Causes': ['Database down'],
'Application Nodes': [
- {
- Name: 'Foo',
- Health: HealthType.GREEN,
- 'Health Causes': [],
- 'Compute Engine Logging': { 'Logs Level': 'INFO' },
- 'Web Logging': { 'Logs Level': 'INFO' }
- },
{
Name: 'Bar',
- Health: HealthType.RED,
+ Health: 'GREEN',
'Health Causes': [],
'Compute Engine Logging': { 'Logs Level': 'INFO' },
- 'Web Logging': { 'Logs Level': 'DEBUG' }
- },
+ 'Web Logging': { 'Logs Level': 'INFO' }
+ }
+ ],
+ 'Search Nodes': [
{
Name: 'Baz',
- Health: HealthType.YELLOW,
+ Health: 'YELLOW',
'Health Causes': [],
- 'Compute Engine Logging': { 'Logs Level': 'TRACE' },
- 'Web Logging': { 'Logs Level': 'DEBUG' }
+ 'Compute Engine Logging': { 'Logs Level': 'INFO' },
+ 'Web Logging': { 'Logs Level': 'INFO' }
}
]
- }
- }).find('HealthCard')
- ).toHaveLength(5);
-});
-
-it('should support more than two nodes', () => {
- expect(getWrapper()).toMatchSnapshot();
-});
-
-function getWrapper(props = {}) {
- return shallow(
- <ClusterSysInfos
- expandedCards={['System', 'Foo']}
- sysInfoData={sysInfoData}
+ })}
toggleCard={() => {}}
{...props}
/>
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import PageHeader from '../PageHeader';
+import PageHeader, { Props } from '../PageHeader';
+
+jest.mock('../../../../helpers/dates', () => ({
+ toShortNotSoISOString: () => '2019-01-01'
+}));
it('should render correctly', () => {
- expect(
- shallow(
- <PageHeader
- isCluster={true}
- loading={false}
- logLevel="INFO"
- onLogLevelChange={() => {}}
- showActions={true}
- />
- )
- ).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot();
+ expect(shallowRender({ loading: true, showActions: false })).toMatchSnapshot();
+ expect(shallowRender({ serverId: 'foo-bar', version: '7.7.0.1234' })).toMatchSnapshot();
});
-it('should show a loading spinner and no actions', () => {
- expect(
- shallow(
- <PageHeader
- isCluster={true}
- loading={true}
- logLevel="INFO"
- onLogLevelChange={() => {}}
- showActions={false}
- />
- )
- ).toMatchSnapshot();
-});
+function shallowRender(props: Partial<Props> = {}) {
+ return shallow(
+ <PageHeader
+ isCluster={true}
+ loading={false}
+ logLevel="INFO"
+ onLogLevelChange={jest.fn()}
+ showActions={true}
+ {...props}
+ />
+ );
+}
import * as React from 'react';
import { shallow } from 'enzyme';
import StandaloneSysInfos from '../StandaloneSysInfos';
-import { HealthType, SysInfo } from '../../../../api/system';
-
-const sysInfoData: SysInfo = {
- Health: HealthType.RED,
- 'Health Causes': ['Database down'],
- 'Web JVM': { 'Max Memory': '2Gb' },
- 'Compute Engine': { Pending: 4 },
- Search: { 'Number of Nodes': 1 },
- System: {
- 'High Availability': true,
- 'Logs Level': 'DEBUG',
- 'Server ID': 'MyServerId'
- }
-};
+import { mockStandaloneSysInfo } from '../../../../helpers/testMocks';
it('should render correctly', () => {
expect(getWrapper()).toMatchSnapshot();
return shallow(
<StandaloneSysInfos
expandedCards={['Compute Engine', 'Foo']}
- sysInfoData={sysInfoData}
+ sysInfoData={mockStandaloneSysInfo({ Health: 'RED', 'Health Causes': ['Database down'] })}
toggleCard={() => {}}
{...props}
/>
open={true}
sysInfoData={
Object {
+ "Compute Engine Tasks": Object {
+ "Total In Progress": 0,
+ "Total Pending": 0,
+ },
+ "Database": Object {
+ "Database": "PostgreSQL",
+ "Database Version": "10.3",
+ "Driver": "PostgreSQL JDBC Driver",
+ "Driver Version": "42.2.5",
+ "URL": "jdbc:postgresql://localhost/sonar",
+ "Username": "sonar",
+ },
"High Availability": true,
- "Logs Level": "INFO",
- "Server ID": "MyServerId",
+ "Lines of Code": "989,880",
+ "Search Indexes": Object {
+ "Index components - Docs": 30445,
+ "Index components - Shards": 10,
+ },
+ "Search State": Object {
+ "Nodes": 3,
+ "State": "GREEN",
+ },
}
}
/>
system.search_nodes_title
</li>
<HealthCard
- health="YELLOW"
- healthCauses={Array []}
key="Baz"
name="Baz"
onClick={[Function]}
canRestart={false}
cluster={true}
logLevel="INFO"
- onLogLevelChange={[Function]}
+ onLogLevelChange={[MockFunction]}
/>
</header>
`;
-exports[`should show a loading spinner and no actions 1`] = `
+exports[`should render correctly 2`] = `
<header
className="page-header"
>
</div>
</header>
`;
+
+exports[`should render correctly 3`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ system_info.page
+ </h1>
+ <PageActions
+ canDownloadLogs={false}
+ canRestart={false}
+ cluster={true}
+ logLevel="INFO"
+ onLogLevelChange={[MockFunction]}
+ serverId="foo-bar"
+ />
+ <div
+ className="system-info-copy-paste-id-info boxed-group display-flex-center"
+ >
+ <div
+ className="flex-1"
+ >
+ <table
+ className="width-100"
+ >
+ <tbody>
+ <tr>
+ <th>
+ <strong>
+ system.server_id
+ </strong>
+ </th>
+ <td>
+ foo-bar
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <strong>
+ system.version
+ </strong>
+ </th>
+ <td>
+ 7.7.0.1234
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <ClipboardButton
+ className="flex-0"
+ copyValue="SonarQube ID information
+Server ID: foo-bar
+Version: 7.7.0.1234
+Date: 2019-01-01
+"
+ label="system.copy_id_info"
+ />
+ </div>
+</header>
+`;
open={false}
sysInfoData={
Object {
- "High Availability": true,
- "Logs Level": "DEBUG",
- "Server ID": "MyServerId",
+ "Database": Object {
+ "Database": "PostgreSQL",
+ "Database Version": "10.3",
+ "Driver": "PostgreSQL JDBC Driver",
+ "Driver Version": "42.2.5",
+ "URL": "jdbc:postgresql://localhost/sonar",
+ "Username": "sonar",
+ },
+ "High Availability": false,
}
}
/>
open={false}
sysInfoData={
Object {
- "Web JVM": Object {
- "Max Memory": "2Gb",
+ "Web Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Max Connections": 60,
+ },
+ "Web JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Web JVM State": Object {
+ "Free Memory (MB)": 111,
+ "Max Memory (MB)": 1024,
+ },
+ "Web Logging": Object {
+ "Logs Dir": "/logs",
+ "Logs Level": "INFO",
},
}
}
open={true}
sysInfoData={
Object {
- "Compute Engine": Object {
- "Pending": 4,
+ "Compute Engine Database Connection": Object {
+ "Pool Active Connections": 0,
+ "Pool Initial Size": 0,
+ },
+ "Compute Engine JVM Properties": Object {
+ "file.encoding": "UTF-8",
+ "file.separator": "/",
+ },
+ "Compute Engine JVM State": Object {
+ "Free Memory (MB)": 89,
+ "Max Memory (MB)": 1024,
+ },
+ "Compute Engine Logging": Object {
+ "Logs Dir": "/logs",
+ "Logs Level": "DEBUG",
+ },
+ "Compute Engine Tasks": Object {
+ "In Progress": 0,
+ "Pending": 0,
},
}
}
open={false}
sysInfoData={
Object {
- "Search": Object {
- "Number of Nodes": 1,
+ "Search Indexes": Object {
+ "Index components - Docs": 30445,
+ "Index components - Shards": 10,
+ },
+ "Search State": Object {
+ "Nodes": 3,
+ "State": "GREEN",
},
}
}
import { map } from 'lodash';
import HealthItem from './HealthItem';
import Section from './Section';
+import { Alert } from '../../../../components/ui/Alert';
import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion';
-import { HealthType, SysValueObject } from '../../../../api/system';
import { LOGS_LEVELS, groupSections, getLogsLevel } from '../../utils';
import { translate } from '../../../../helpers/l10n';
-import { Alert } from '../../../../components/ui/Alert';
interface Props {
biggerHealth?: boolean;
- health?: HealthType;
+ health?: T.HealthType;
healthCauses?: string[];
onClick: (toggledCard: string) => void;
open: boolean;
name: string;
- sysInfoData: SysValueObject;
+ sysInfoData: T.SysInfoValueObject;
}
export default function HealthCard({
*/
import * as React from 'react';
import * as classNames from 'classnames';
-import { HealthType } from '../../../../api/system';
import { Alert } from '../../../../components/ui/Alert';
interface Props {
className?: string;
- health: HealthType;
+ health: T.HealthType;
healthCause: string;
}
<Alert
className={classNames('boxed-group-accordion-alert', className)}
display="inline"
- variant={health === HealthType.RED ? 'error' : 'warning'}>
+ variant={health === 'RED' ? 'error' : 'warning'}>
{healthCause}
</Alert>
);
import HealthCauseItem from './HealthCauseItem';
import StatusIndicator from '../../../../components/common/StatusIndicator';
import Tooltip from '../../../../components/controls/Tooltip';
-import { HealthType } from '../../../../api/system';
import { translateWithParameters } from '../../../../helpers/l10n';
interface Props {
biggerHealth?: boolean;
name?: string;
className?: string;
- health: HealthType;
+ health: T.HealthType;
healthCauses?: string[];
}
export default function HealthItem({ biggerHealth, className, name, health, healthCauses }: Props) {
- const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN;
+ const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== 'GREEN';
const statusIndicator = (
<StatusIndicator color={health.toLowerCase()} size={biggerHealth ? 'big' : undefined} />
);
import * as React from 'react';
import { map } from 'lodash';
import SysInfoItem from './SysInfoItem';
-import { SysValueObject } from '../../../../api/system';
interface Props {
name?: string;
- items: SysValueObject;
+ items: T.SysInfoValueObject;
}
export default function Section({ name, items }: Props) {
import HealthItem from './HealthItem';
import AlertErrorIcon from '../../../../components/icons-components/AlertErrorIcon';
import AlertSuccessIcon from '../../../../components/icons-components/AlertSuccessIcon';
-import { HealthType, SysValue, SysValueObject } from '../../../../api/system';
-import { HEALTH_FIELD } from '../../utils';
+import { HEALTH_FIELD, STATE_FIELD } from '../../utils';
export interface Props {
name: string;
- value: SysValue;
+ value: T.SysInfoValue;
}
export default function SysInfoItem({ name, value }: Props): JSX.Element {
- if (name === HEALTH_FIELD || name === 'State') {
- return <HealthItem className="no-margin" health={value as HealthType} />;
+ if (name === HEALTH_FIELD || name === STATE_FIELD) {
+ return <HealthItem className="no-margin" health={value as T.HealthType} />;
}
if (value instanceof Array) {
return <code>{JSON.stringify(value)}</code>;
}
}
-function ObjectItem({ value }: { value: SysValueObject }) {
+function ObjectItem({ value }: { value: T.SysInfoValueObject }) {
return (
<table className="data">
<tbody>
import * as React from 'react';
import { shallow } from 'enzyme';
import HealthCard from '../HealthCard';
-import { HealthType } from '../../../../../api/system';
it('should render correctly', () => {
expect(getWrapper()).toMatchSnapshot();
return shallow(
<HealthCard
biggerHealth={false}
- health={HealthType.RED}
+ health="RED"
healthCauses={['foo']}
name="Foobar"
onClick={() => {}}
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="foo" />)).toMatchSnapshot();
- expect(
- shallow(<HealthCauseItem health={HealthType.YELLOW} healthCause="foo" />)
- ).toMatchSnapshot();
+ expect(shallow(<HealthCauseItem health="RED" healthCause="foo" />)).toMatchSnapshot();
+ expect(shallow(<HealthCauseItem health="YELLOW" healthCause="foo" />)).toMatchSnapshot();
});
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 biggerHealth={true} health={HealthType.RED} healthCauses={['foo']} name="Foo" />
- )
+ shallow(<HealthItem biggerHealth={true} health="RED" healthCauses={['foo']} name="Foo" />)
).toMatchSnapshot();
});
it('should not render health causes', () => {
- expect(
- shallow(<HealthItem health={HealthType.GREEN} healthCauses={['foo']} />)
- ).toMatchSnapshot();
- expect(shallow(<HealthItem health={HealthType.YELLOW} healthCauses={[]} />)).toMatchSnapshot();
+ expect(shallow(<HealthItem health="GREEN" healthCauses={['foo']} />)).toMatchSnapshot();
+ expect(shallow(<HealthItem health="YELLOW" healthCauses={[]} />)).toMatchSnapshot();
});
it('should render multiple health causes', () => {
- expect(
- shallow(<HealthItem health={HealthType.YELLOW} healthCauses={['foo', 'bar']} />)
- ).toMatchSnapshot();
+ expect(shallow(<HealthItem health="YELLOW" healthCauses={['foo', 'bar']} />)).toMatchSnapshot();
});
*/
import * as React from 'react';
import SystemUpgradeItem from './SystemUpgradeItem';
-import { SystemUpgrade } from '../../../../api/system';
import Modal from '../../../../components/controls/Modal';
import { translate } from '../../../../helpers/l10n';
import { ResetButtonLink } from '../../../../components/ui/buttons';
interface Props {
- systemUpgrades: SystemUpgrade[][];
+ systemUpgrades: T.SystemUpgrade[][];
onClose: () => void;
}
import DateFormatter from '../../../../components/intl/DateFormatter';
import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
import { ButtonLink } from '../../../../components/ui/buttons';
-import { SystemUpgrade } from '../../../../api/system';
import { translate } from '../../../../helpers/l10n';
interface Props {
className?: string;
- upgrades: SystemUpgrade[];
+ upgrades: T.SystemUpgrade[];
}
interface State {
import { FormattedMessage } from 'react-intl';
import SystemUpgradeIntermediate from './SystemUpgradeIntermediate';
import DateFormatter from '../../../../components/intl/DateFormatter';
-import { SystemUpgrade } from '../../../../api/system';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
interface Props {
type?: string;
- systemUpgrades: SystemUpgrade[];
+ systemUpgrades: T.SystemUpgrade[];
}
export default function SystemUpgradeItem({ type, systemUpgrades }: Props) {
import * as React from 'react';
import SystemUpgradeForm from './SystemUpgradeForm';
import { sortUpgrades, groupUpgrades } from '../../utils';
-import { getSystemUpgrades, SystemUpgrade } from '../../../../api/system';
+import { getSystemUpgrades } from '../../../../api/system';
import { Button } from '../../../../components/ui/buttons';
import { translate } from '../../../../helpers/l10n';
import { Alert } from '../../../../components/ui/Alert';
interface State {
- systemUpgrades: SystemUpgrade[][];
+ systemUpgrades: T.SystemUpgrade[][];
openSystemUpgradeForm: boolean;
}
margin-top: -12px;
}
+.system-info-copy-paste-id-info {
+ max-width: 550px;
+ padding: var(--gridSize) calc(2 * var(--gridSize));
+ clear: both;
+ line-height: 1.8;
+}
+
.system-info-health-info .status-indicator {
position: relative;
top: 8px;
RawQuery,
serializeStringArray
} from '../../helpers/query';
-import {
- ClusterSysInfo,
- HealthType,
- NodeInfo,
- SysInfo,
- SysInfoSection,
- SysValueObject,
- SystemUpgrade
-} from '../../api/system';
import { formatMeasure } from '../../helpers/measures';
export interface Query {
}
export const LOGS_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
+const DEFAULT_LOG_LEVEL = LOGS_LEVELS[0];
+
+export const APP_NODES_FIELD = 'Application Nodes';
+export const CE_FIELD_PREFIX = 'Compute Engine';
+export const CE_LOGGING_FIELD = 'Compute Engine Logging';
export const HA_FIELD = 'High Availability';
+export const HEALTH_CAUSES_FIELD = 'Health Causes';
export const HEALTH_FIELD = 'Health';
-export const HEALTHCAUSES_FIELD = 'Health Causes';
+export const LOGS_LEVEL_FIELD = 'Logs Level';
+export const NAME_FIELD = 'Name';
+export const NCLOC_FIELD = 'ncloc';
export const PLUGINS_FIELD = 'Plugins';
+export const SEARCH_NODES_FIELD = 'Search Nodes';
+export const SEARCH_PREFIX = 'Search';
+export const SERVER_ID_FIELD = 'Server ID';
export const SETTINGS_FIELD = 'Settings';
-
-export function ignoreInfoFields(sysInfoObject: SysValueObject): SysValueObject {
+export const STATE_FIELD = 'State';
+export const STATS_FIELD = 'Statistics';
+export const SYSTEM_FIELD = 'System';
+export const VERSION_FIELD = 'Version';
+export const WEB_LOGGING_FIELD = 'Web Logging';
+export const WEB_PREFIX = 'Web';
+
+export function ignoreInfoFields(sysInfoObject: T.SysInfoValueObject) {
return omit(sysInfoObject, [
HEALTH_FIELD,
- HEALTHCAUSES_FIELD,
- 'Name',
+ HEALTH_CAUSES_FIELD,
+ NAME_FIELD,
PLUGINS_FIELD,
- SETTINGS_FIELD
- ]) as SysValueObject;
+ SETTINGS_FIELD,
+ SERVER_ID_FIELD,
+ VERSION_FIELD
+ ]);
}
-export function getHealth(sysInfoObject: SysValueObject): HealthType {
- return sysInfoObject[HEALTH_FIELD] as HealthType;
+export function getHealth(sysInfoObject: T.SysInfoBase) {
+ return sysInfoObject[HEALTH_FIELD];
}
-export function getHealthCauses(sysInfoObject: SysValueObject): string[] {
- return sysInfoObject[HEALTHCAUSES_FIELD] as string[];
+export function getHealthCauses(sysInfoObject: T.SysInfoBase) {
+ return sysInfoObject[HEALTH_CAUSES_FIELD];
}
-export function getLogsLevel(sysInfoObject?: SysValueObject): string {
- if (!sysInfoObject) {
- return LOGS_LEVELS[0];
- }
- if (sysInfoObject['Web Logging'] || sysInfoObject['Compute Engine Logging']) {
- return sortBy(
- [
- getLogsLevel((sysInfoObject as NodeInfo)['Web Logging']),
- getLogsLevel((sysInfoObject as NodeInfo)['Compute Engine Logging'])
- ],
- logLevel => LOGS_LEVELS.indexOf(logLevel)
- )[1];
- }
- if (sysInfoObject['System']) {
- return getLogsLevel((sysInfoObject as SysInfo)['System']);
+export function getLogsLevel(sysInfoObject?: T.SysInfoValueObject): string {
+ if (sysInfoObject !== undefined) {
+ if (isLogInfoBlock(sysInfoObject)) {
+ return sysInfoObject[LOGS_LEVEL_FIELD];
+ } else if (hasLoggingInfo(sysInfoObject)) {
+ return sortBy(
+ [
+ getLogsLevel(sysInfoObject[WEB_LOGGING_FIELD]),
+ getLogsLevel(sysInfoObject[CE_LOGGING_FIELD])
+ ],
+ logLevel => LOGS_LEVELS.indexOf(logLevel)
+ )[1];
+ }
}
- return (sysInfoObject['Logs Level'] || LOGS_LEVELS[0]) as string;
+ return DEFAULT_LOG_LEVEL;
}
-export function getAppNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
- return sysInfoData['Application Nodes'];
+export function getAppNodes(sysInfoData: T.SysInfoCluster): T.SysInfoAppNode[] {
+ return sysInfoData[APP_NODES_FIELD];
}
-export function getSearchNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
- return sysInfoData['Search Nodes'];
+export function getSearchNodes(sysInfoData: T.SysInfoCluster): T.SysInfoSearchNode[] {
+ return sysInfoData[SEARCH_NODES_FIELD];
}
-export function isCluster(sysInfoData?: SysInfo): boolean {
- return (
- sysInfoData !== undefined && sysInfoData['System'] && sysInfoData['System'][HA_FIELD] === true
- );
+export function isCluster(
+ sysInfoData: T.SysInfoCluster | T.SysInfoStandalone
+): sysInfoData is T.SysInfoCluster {
+ return sysInfoData[SYSTEM_FIELD] && sysInfoData[SYSTEM_FIELD][HA_FIELD] === true;
}
-export function getServerId(sysInfoData?: SysInfo): string | undefined {
- return sysInfoData && sysInfoData['System']['Server ID'];
+export function isLogInfoBlock(
+ sysInfoObject: T.SysInfoValueObject
+): sysInfoObject is T.SysInfoLogging {
+ return sysInfoObject[LOGS_LEVEL_FIELD] !== undefined;
}
-export function getSystemLogsLevel(sysInfoData?: SysInfo): string {
- const defaultLevel = LOGS_LEVELS[0];
- if (!sysInfoData) {
- return defaultLevel;
- }
+export function hasLoggingInfo(
+ sysInfoObject: T.SysInfoValueObject
+): sysInfoObject is T.SysInfoStandalone | T.SysInfoAppNode {
+ return Boolean(sysInfoObject[WEB_LOGGING_FIELD] || sysInfoObject[CE_LOGGING_FIELD]);
+}
+
+export function getServerId(sysInfoData: T.SysInfoCluster | T.SysInfoStandalone): string {
+ return sysInfoData && sysInfoData[SYSTEM_FIELD][SERVER_ID_FIELD];
+}
+
+export function getVersion(sysInfoData: T.SysInfoStandalone): string | undefined {
+ return sysInfoData && sysInfoData[SYSTEM_FIELD][VERSION_FIELD];
+}
+
+export function getClusterVersion(sysInfoData: T.SysInfoCluster): string | undefined {
+ const appNodes = getAppNodes(sysInfoData);
+ return appNodes.length > 0 ? appNodes[0][SYSTEM_FIELD][VERSION_FIELD] : undefined;
+}
+
+export function getSystemLogsLevel(sysInfoData: T.SysInfoCluster | T.SysInfoStandalone): string {
if (isCluster(sysInfoData)) {
- const logLevels = sortBy(
- getAppNodes(sysInfoData as ClusterSysInfo).map(getLogsLevel),
- logLevel => LOGS_LEVELS.indexOf(logLevel)
+ const logLevels = sortBy(getAppNodes(sysInfoData).map(getLogsLevel), logLevel =>
+ LOGS_LEVELS.indexOf(logLevel)
);
- return logLevels.length > 0 ? logLevels[logLevels.length - 1] : defaultLevel;
+ return logLevels.length > 0 ? logLevels[logLevels.length - 1] : DEFAULT_LOG_LEVEL;
} else {
return getLogsLevel(sysInfoData);
}
}
-export function getNodeName(nodeInfo: NodeInfo): string {
- return nodeInfo['Name'];
+export function getNodeName(nodeInfo: T.SysInfoAppNode | T.SysInfoSearchNode): string {
+ return nodeInfo[NAME_FIELD];
}
-function getSystemData(sysInfoData: SysInfo): SysValueObject {
- const statData: SysValueObject = {};
- const statistics = sysInfoData['Statistics'] as SysValueObject;
+function getSystemData(sysInfoData: T.SysInfoBase): T.SysInfoValueObject {
+ const statData: T.SysInfoValueObject = {};
+ const statistics = sysInfoData[STATS_FIELD] as T.SysInfoValueObject; // TODO
if (statistics) {
- statData['Lines of Code'] = formatMeasure(statistics['ncloc'] as number, 'INT');
+ statData['Lines of Code'] = formatMeasure(statistics[NCLOC_FIELD] as number, 'INT');
}
- return { ...sysInfoData['System'], ...statData };
+ return { ...sysInfoData[SYSTEM_FIELD], ...statData };
}
-export function getClusterMainCardSection(sysInfoData: ClusterSysInfo): SysValueObject {
+export function getClusterMainCardSection(sysInfoData: T.SysInfoCluster): T.SysInfoValueObject {
return {
...getSystemData(sysInfoData),
- ...(omit(sysInfoData, [
- 'Application Nodes',
+ ...omit(sysInfoData, [
+ APP_NODES_FIELD,
PLUGINS_FIELD,
- 'Search Nodes',
+ SEARCH_NODES_FIELD,
SETTINGS_FIELD,
- 'Statistics',
- 'System'
- ]) as SysValueObject)
+ STATS_FIELD,
+ SYSTEM_FIELD
+ ])
};
}
-export function getStandaloneMainSections(sysInfoData: SysInfo): SysValueObject {
+export function getStandaloneMainSections(sysInfoData: T.SysInfoBase): T.SysInfoValueObject {
return {
...getSystemData(sysInfoData),
...(omitBy(
sysInfoData,
(value, key) =>
value == null ||
- [PLUGINS_FIELD, SETTINGS_FIELD, 'Statistics', 'System'].includes(key) ||
- key.startsWith('Compute Engine') ||
- key.startsWith('Search') ||
- key.startsWith('Web')
- ) as SysValueObject)
+ [PLUGINS_FIELD, SETTINGS_FIELD, STATS_FIELD, SYSTEM_FIELD].includes(key) ||
+ key.startsWith(CE_FIELD_PREFIX) ||
+ key.startsWith(SEARCH_PREFIX) ||
+ key.startsWith(WEB_PREFIX)
+ ) as T.SysInfoValueObject)
};
}
-export function getStandaloneSecondarySections(sysInfoData: SysInfo): SysInfoSection {
+export function getStandaloneSecondarySections(sysInfoData: T.SysInfoBase): T.SysInfoSection {
return {
- Web: pickBy(sysInfoData, (_, key) => key.startsWith('Web')) as SysValueObject,
+ Web: pickBy(sysInfoData, (_, key) => key.startsWith(WEB_PREFIX)) as T.SysInfoValueObject,
'Compute Engine': pickBy(sysInfoData, (_, key) =>
- key.startsWith('Compute Engine')
- ) as SysValueObject,
- 'Search Engine': pickBy(sysInfoData, (_, key) => key.startsWith('Search')) as SysValueObject
+ key.startsWith(CE_FIELD_PREFIX)
+ ) as T.SysInfoValueObject,
+ 'Search Engine': pickBy(sysInfoData, (_, key) =>
+ key.startsWith(SEARCH_PREFIX)
+ ) as T.SysInfoValueObject
};
}
);
}
-export function groupSections(sysInfoData: SysValueObject) {
- const mainSection: SysValueObject = {};
- const sections: SysInfoSection = {};
+export function groupSections(sysInfoData: T.SysInfoValueObject) {
+ const mainSection: T.SysInfoValueObject = {};
+ const sections: T.SysInfoSection = {};
each(sysInfoData, (item, key) => {
if (typeof item !== 'object' || item instanceof Array) {
mainSection[key] = item;
})
);
-export function sortUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[] {
+export function sortUpgrades(upgrades: T.SystemUpgrade[]): T.SystemUpgrade[] {
return sortBy(upgrades, [
- (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[0]),
- (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[1] || 0),
- (upgrade: SystemUpgrade) => -Number(upgrade.version.split('.')[2] || 0)
+ (upgrade: T.SystemUpgrade) => -Number(upgrade.version.split('.')[0]),
+ (upgrade: T.SystemUpgrade) => -Number(upgrade.version.split('.')[1] || 0),
+ (upgrade: T.SystemUpgrade) => -Number(upgrade.version.split('.')[2] || 0)
]);
}
-export function groupUpgrades(upgrades: SystemUpgrade[]): SystemUpgrade[][] {
+export function groupUpgrades(upgrades: T.SystemUpgrade[]): T.SystemUpgrade[][] {
const groupedVersions = groupBy(upgrades, upgrade => upgrade.version.split('.')[0]);
const sortedMajor = sortBy(Object.keys(groupedVersions), key => -Number(key));
return sortedMajor.map(key => groupedVersions[key]);
interface Props {
className?: string;
copyValue: string;
+ label?: string;
}
interface State {
className={classNames('js-copy-to-clipboard no-select', this.props.className)}
data-clipboard-text={this.props.copyValue}
innerRef={node => (this.copyButton = node)}>
- {translate('copy')}
+ {this.props.label ? this.props.label : translate('copy')}
</Button>
</Tooltip>
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { shallow } from 'enzyme';
+import { shallow, mount } from 'enzyme';
import ClipboardButton from '../ClipboardButton';
+const constructor = jest.fn();
+const destroy = jest.fn();
+const on = jest.fn();
+
+jest.mock(
+ 'clipboard',
+ () =>
+ function(...args: any) {
+ constructor(...args);
+ return {
+ destroy,
+ on
+ };
+ }
+);
+
jest.useFakeTimers();
it('should display correctly', () => {
- const wrapper = shallow(<ClipboardButton copyValue="foo" />);
+ const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
- (wrapper.instance() as ClipboardButton).showTooltip();
+ wrapper.instance().showTooltip();
wrapper.update();
expect(wrapper).toMatchSnapshot();
jest.runAllTimers();
wrapper.update();
expect(wrapper).toMatchSnapshot();
});
+
+it('should render a custom label if provided', () => {
+ expect(shallowRender({ label: 'Foo Bar' })).toMatchSnapshot();
+});
+
+it('should allow its content to be copied', () => {
+ const wrapper = mountRender();
+ const button = wrapper.find('button').getDOMNode();
+ const instance = wrapper.instance();
+
+ expect(constructor).toBeCalledWith(button);
+ expect(on).toBeCalledWith('success', instance.showTooltip);
+
+ jest.clearAllMocks();
+
+ wrapper.setProps({ label: 'Some new label' });
+ expect(destroy).toBeCalled();
+ expect(constructor).toBeCalledWith(button);
+ expect(on).toBeCalledWith('success', instance.showTooltip);
+
+ jest.clearAllMocks();
+
+ wrapper.unmount();
+ expect(destroy).toBeCalled();
+});
+
+function shallowRender(props: Partial<ClipboardButton['props']> = {}) {
+ return shallow<ClipboardButton>(createComponent(props));
+}
+
+function mountRender(props: Partial<ClipboardButton['props']> = {}) {
+ return mount<ClipboardButton>(createComponent(props));
+}
+
+function createComponent(props: Partial<ClipboardButton['props']> = {}) {
+ return <ClipboardButton copyValue="foo" {...props} />;
+}
</Button>
</Tooltip>
`;
+
+exports[`should render a custom label if provided 1`] = `
+<Tooltip
+ overlay="copied_action"
+ visible={false}
+>
+ <Button
+ className="js-copy-to-clipboard no-select"
+ data-clipboard-text="foo"
+ innerRef={[Function]}
+ >
+ Foo Bar
+ </Button>
+</Tooltip>
+`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+/* eslint-disable sonarjs/no-duplicate-string */
import { InjectedRouter } from 'react-router';
import { Store, createStore } from 'redux';
import { Location } from 'history';
};
}
+export function mockBaseSysInfo(overrides: Partial<any> = {}): T.SysInfoBase {
+ return {
+ Health: 'GREEN' as T.HealthType,
+ 'Health Causes': [],
+ System: {
+ Version: '7.8'
+ },
+ Database: {
+ Database: 'PostgreSQL',
+ 'Database Version': '10.3',
+ Username: 'sonar',
+ URL: 'jdbc:postgresql://localhost/sonar',
+ Driver: 'PostgreSQL JDBC Driver',
+ 'Driver Version': '42.2.5'
+ },
+ 'Compute Engine Tasks': {
+ 'Total Pending': 0,
+ 'Total In Progress': 0
+ },
+ 'Search State': { State: 'GREEN', Nodes: 3 },
+ 'Search Indexes': {
+ 'Index components - Docs': 30445,
+ 'Index components - Shards': 10
+ },
+ ...overrides
+ };
+}
+
+export function mockClusterSysInfo(overrides: Partial<any> = {}): T.SysInfoCluster {
+ const baseInfo = mockBaseSysInfo(overrides);
+ return {
+ ...baseInfo,
+ System: {
+ ...baseInfo.System,
+ 'High Availability': true,
+ 'Server ID': 'asd564-asd54a-5dsfg45'
+ },
+ Settings: {
+ 'sonar.cluster.enabled': 'true',
+ 'sonar.cluster.node.name': 'server9.example.com'
+ },
+ 'Application Nodes': [
+ {
+ Name: 'server9.example.com',
+ Host: '10.0.0.0',
+ Health: 'GREEN' as T.HealthType,
+ 'Health Causes': [],
+ System: {
+ Version: '7.8'
+ },
+ Plugins: {
+ java: '5.13.0.17924 [SonarJava]'
+ },
+ 'Web JVM State': {
+ 'Max Memory (MB)': 1024,
+ 'Free Memory (MB)': 122
+ },
+ 'Web Database Connection': {
+ 'Pool Active Connections': 1
+ },
+ 'Web Logging': { 'Logs Level': 'DEBUG' },
+ 'Web JVM Properties': {
+ 'file.encoding': 'UTF-8',
+ 'file.separator': '/'
+ },
+ 'Compute Engine Tasks': {
+ Pending: 0,
+ 'In Progress': 0
+ },
+ 'Compute Engine JVM State': {
+ 'Max Memory (MB)': 1024,
+ 'Free Memory (MB)': 78
+ },
+ 'Compute Engine Database Connection': {
+ 'Pool Initial Size': 0,
+ 'Pool Active Connections': 0
+ },
+ 'Compute Engine Logging': {
+ 'Logs Level': 'INFO'
+ },
+ 'Compute Engine JVM Properties': {
+ 'file.encoding': 'UTF-8',
+ 'file.separator': '/'
+ }
+ },
+ {
+ Name: 'server9.example.com',
+ Host: '10.0.0.0',
+ Health: 'GREEN' as T.HealthType,
+ 'Health Causes': [],
+ System: {
+ Version: '7.8'
+ },
+ Plugins: {
+ java: '5.13.0.17924 [SonarJava]'
+ },
+ 'Web JVM State': {
+ 'Max Memory (MB)': 1024,
+ 'Free Memory (MB)': 111
+ },
+ 'Web Database Connection': {
+ 'Pool Active Connections': 0,
+ 'Pool Max Connections': 60
+ },
+ 'Web Logging': { 'Logs Level': 'INFO' },
+ 'Web JVM Properties': {
+ 'file.encoding': 'UTF-8',
+ 'file.separator': '/'
+ },
+ 'Compute Engine Tasks': {
+ Pending: 0,
+ 'In Progress': 0
+ },
+ 'Compute Engine JVM State': {
+ 'Max Memory (MB)': 1024,
+ 'Free Memory (MB)': 89
+ },
+ 'Compute Engine Database Connection': {
+ 'Pool Initial Size': 0,
+ 'Pool Active Connections': 0
+ },
+ 'Compute Engine Logging': {
+ 'Logs Level': 'INFO'
+ },
+ 'Compute Engine JVM Properties': {
+ 'file.encoding': 'UTF-8',
+ 'file.separator': '/'
+ }
+ }
+ ],
+ 'Search Nodes': [
+ {
+ Name: 'server.example.com',
+ Host: '10.0.0.0',
+ 'Search State': {
+ 'CPU Usage (%)': 0,
+ 'Disk Available': '93 GB'
+ }
+ },
+ {
+ Name: 'server.example.com',
+ Host: '10.0.0.0',
+ 'Search State': {
+ 'CPU Usage (%)': 0,
+ 'Disk Available': '93 GB'
+ }
+ },
+ {
+ Name: 'server.example.com',
+ Host: '10.0.0.0',
+ 'Search State': {
+ 'CPU Usage (%)': 0,
+ 'Disk Available': '93 GB'
+ }
+ }
+ ],
+ Statistics: {
+ ncloc: 989880
+ },
+ ...overrides
+ };
+}
+
export function mockComponent(overrides: Partial<T.Component> = {}): T.Component {
return {
breadcrumbs: [],
};
}
+export function mockStandaloneSysInfo(overrides: Partial<any> = {}): T.SysInfoStandalone {
+ const baseInfo = mockBaseSysInfo(overrides);
+ return {
+ ...baseInfo,
+ System: {
+ ...baseInfo.System,
+ 'High Availability': false,
+ 'Server ID': 'asd564-asd54a-5dsfg45'
+ },
+ Settings: {
+ 'sonar.cluster.enabled': 'true',
+ 'sonar.cluster.node.name': 'server9.example.com'
+ },
+ 'Web JVM State': {
+ 'Max Memory (MB)': 1024,
+ 'Free Memory (MB)': 111
+ },
+ 'Web Database Connection': {
+ 'Pool Active Connections': 0,
+ 'Pool Max Connections': 60
+ },
+ 'Web Logging': { 'Logs Level': 'INFO', 'Logs Dir': '/logs' },
+ 'Web JVM Properties': {
+ 'file.encoding': 'UTF-8',
+ 'file.separator': '/'
+ },
+ 'Compute Engine Tasks': {
+ Pending: 0,
+ 'In Progress': 0
+ },
+ 'Compute Engine JVM State': {
+ 'Max Memory (MB)': 1024,
+ 'Free Memory (MB)': 89
+ },
+ 'Compute Engine Database Connection': {
+ 'Pool Initial Size': 0,
+ 'Pool Active Connections': 0
+ },
+ 'Compute Engine Logging': {
+ 'Logs Level': 'DEBUG',
+ 'Logs Dir': '/logs'
+ },
+ 'Compute Engine JVM Properties': {
+ 'file.encoding': 'UTF-8',
+ 'file.separator': '/'
+ },
+ ...overrides
+ };
+}
+
export function mockLongLivingBranch(
overrides: Partial<T.LongLivingBranch> = {}
): T.LongLivingBranch {
system.application_nodes_title=Application Nodes
system.are_you_sure_to_restart=Are you sure you want to restart the server?
system.cluster_log_level.info=Your selection affect all Application nodes but not the Search nodes.
+system.copy_id_info=Copy ID information
system.current_health_of_x=Current health status of {0}
system.download_logs=Download Logs
system.download_system_info=Download System Info
system.restart_server=Restart Server
system.search_nodes_title=Search Nodes
system.see_sonarqube_downloads=See All SonarQube Downloads
+system.server_id=Server ID
system.set_log_level=Set logs level
system.show_intermediate_versions=Show intermediate versions
system.system_upgrade=System Upgrade
+system.version=Version
system.version_is_availble={version} is available