From 2f02c40ef393c1cd20adc69d53409d31fc739ef8 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 7 May 2019 13:01:01 +0200 Subject: [PATCH] SONAR-11938 Remove Server ID and Version from system information --- server/sonar-web/src/main/js/api/system.ts | 52 +--- server/sonar-web/src/main/js/app/types.d.ts | 80 +++++- .../js/apps/system/__tests__/utils-test.ts | 229 +++++++++++++++--- .../main/js/apps/system/components/App.tsx | 33 ++- .../system/components/ClusterSysInfos.tsx | 9 +- .../js/apps/system/components/PageHeader.tsx | 51 +++- .../system/components/StandaloneSysInfos.tsx | 3 +- .../__tests__/ClusterSysInfos-test.tsx | 114 +++++---- .../components/__tests__/PageHeader-test.tsx | 45 ++-- .../__tests__/StandaloneSysInfos-test.tsx | 17 +- .../ClusterSysInfos-test.tsx.snap | 25 +- .../__snapshots__/PageHeader-test.tsx.snap | 67 ++++- .../StandaloneSysInfos-test.tsx.snap | 59 ++++- .../components/info-items/HealthCard.tsx | 7 +- .../components/info-items/HealthCauseItem.tsx | 5 +- .../components/info-items/HealthItem.tsx | 5 +- .../system/components/info-items/Section.tsx | 3 +- .../components/info-items/SysInfoItem.tsx | 11 +- .../info-items/__tests__/HealthCard-test.tsx | 3 +- .../__tests__/HealthCauseItem-test.tsx | 7 +- .../info-items/__tests__/HealthItem-test.tsx | 15 +- .../system-upgrade/SystemUpgradeForm.tsx | 3 +- .../SystemUpgradeIntermediate.tsx | 3 +- .../system-upgrade/SystemUpgradeItem.tsx | 3 +- .../system-upgrade/SystemUpgradeNotif.tsx | 4 +- .../src/main/js/apps/system/styles.css | 7 + .../src/main/js/apps/system/utils.ts | 202 ++++++++------- .../components/controls/ClipboardButton.tsx | 3 +- .../__tests__/ClipboardButton-test.tsx | 59 ++++- .../ClipboardButton-test.tsx.snap | 15 ++ .../src/main/js/helpers/testMocks.ts | 214 ++++++++++++++++ .../resources/org/sonar/l10n/core.properties | 3 + 32 files changed, 992 insertions(+), 364 deletions(-) diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts index 05542d79939..83b78eab993 100644 --- a/server/sonar-web/src/main/js/api/system.ts +++ b/server/sonar-web/src/main/js/api/system.ts @@ -20,59 +20,11 @@ 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 {} - -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 { return post('/api/system/change_log_level', { level }).catch(throwGlobalError); } -export function getSystemInfo(): Promise { +export function getSystemInfo(): Promise { return getJSON('/api/system/info').catch(throwGlobalError); } @@ -81,7 +33,7 @@ export function getSystemStatus(): Promise<{ id: string; version: string; status } export function getSystemUpgrades(): Promise<{ - upgrades: SystemUpgrade[]; + upgrades: T.SystemUpgrade[]; updateCenterRefresh: string; }> { return getJSON('/api/system/upgrades'); diff --git a/server/sonar-web/src/main/js/app/types.d.ts b/server/sonar-web/src/main/js/app/types.d.ts index 27fde4e2538..7ee7ed04cdd 100644 --- a/server/sonar-web/src/main/js/app/types.d.ts +++ b/server/sonar-web/src/main/js/app/types.d.ts @@ -301,6 +301,8 @@ declare namespace T { name: string; } + export type HealthType = 'RED' | 'YELLOW' | 'GREEN'; + export type HomePage = | { type: 'APPLICATION'; branch: string | undefined; component: string } | { type: 'ISSUES' } @@ -743,8 +745,6 @@ declare namespace T { export type RuleType = 'BUG' | 'VULNERABILITY' | 'CODE_SMELL' | 'SECURITY_HOTSPOT' | 'UNKNOWN'; - export type Status = 'ERROR' | 'OK'; - export type Setting = SettingValue & { definition: SettingDefinition }; export type SettingType = @@ -850,6 +850,8 @@ declare namespace T { export type StandardType = 'owaspTop10' | 'sansTop25' | 'cwe' | 'sonarsourceSecurity'; + export type Status = 'ERROR' | 'OK'; + export interface SubscriptionPlan { maxNcloc: number; price: number; @@ -861,6 +863,80 @@ declare namespace T { 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; + System: { + Version: string; + }; + } + + export interface SysInfoCluster extends SysInfoBase { + 'Application Nodes': SysInfoAppNode[]; + 'Search Nodes': SysInfoSearchNode[]; + Settings: Dict; + Statistics?: { + ncloc: number; + }; + System: { + 'High Availability': true; + 'Server ID': string; + Version: string; + }; + } + + export interface SysInfoLogging extends Dict { + 'Logs Level': string; + } + + export interface SysInfoSearchNode extends SysInfoValueObject { + Name: string; + } + + export interface SysInfoSection extends Dict {} + + export interface SysInfoStandalone extends SysInfoBase { + 'Compute Engine Logging': SysInfoLogging; + Settings: Dict; + Statistics?: { + ncloc: number; + } & Dict; + 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 {} + + export interface SysInfoValueObject extends Dict {} + + export interface SystemUpgrade { + version: string; + description: string; + releaseDate: string; + changeLogUrl: string; + downloadUrl: string; + } + export interface Task { analysisId?: string; branch?: string; diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts index d94d51599ec..22247758f54 100644 --- a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts @@ -17,8 +17,9 @@ * 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', () => { @@ -44,44 +45,30 @@ describe('groupSections', () => { }); 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'); }); }); @@ -93,7 +80,7 @@ describe('sortUpgrades', () => { { 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([ @@ -101,7 +88,7 @@ describe('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' }]); }); }); @@ -114,7 +101,7 @@ describe('groupUpgrades', () => { { 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' }] ]); @@ -125,10 +112,186 @@ describe('groupUpgrades', () => { { 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(); + }); +}); 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 index b90b28493f3..07cec002e46 100644 --- a/server/sonar-web/src/main/js/apps/system/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx @@ -26,14 +26,16 @@ import StandaloneSysInfos from './StandaloneSysInfos'; 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'; @@ -41,7 +43,7 @@ type Props = WithRouterProps; interface State { loading: boolean; - sysInfoData?: SysInfo; + sysInfoData?: T.SysInfoCluster | T.SysInfoStandalone; } class App extends React.PureComponent { @@ -60,7 +62,7 @@ class App extends React.PureComponent { fetchSysInfo = () => { this.setState({ loading: true }); getSystemInfo().then( - (sysInfoData: SysInfo) => { + sysInfoData => { if (this.mounted) { this.setState({ loading: false, sysInfoData }); } @@ -100,7 +102,7 @@ class App extends React.PureComponent { return ( ); @@ -121,14 +123,19 @@ class App extends React.PureComponent { - + {sysInfoData && ( + + )} {this.renderSysInfo()} ); 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 index 7231c753b25..2a86c77b1c3 100644 --- a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx @@ -21,7 +21,6 @@ import * as React from 'react'; import { sortBy } from 'lodash'; import HealthCard from './info-items/HealthCard'; import { translate } from '../../../helpers/l10n'; -import { ClusterSysInfo } from '../../../api/system'; import { getAppNodes, getHealth, @@ -34,7 +33,7 @@ import { interface Props { expandedCards: string[]; - sysInfoData: ClusterSysInfo; + sysInfoData: T.SysInfoCluster; toggleCard: (toggledCard: string) => void; } @@ -54,7 +53,7 @@ export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard
  • {translate('system.application_nodes_title')}
  • - {sortBy(getAppNodes(sysInfoData), getNodeName).map(node => ( + {sortBy(getAppNodes(sysInfoData), getNodeName).map((node: T.SysInfoAppNode) => ( ))}
  • {translate('system.search_nodes_title')}
  • - {sortBy(getSearchNodes(sysInfoData), getNodeName).map(node => ( + {sortBy(getSearchNodes(sysInfoData), getNodeName).map((node: T.SysInfoSearchNode) => ( void; serverId?: string; showActions: boolean; + version?: string; } export default function PageHeader(props: Props) { + const { isCluster, loading, logLevel, serverId, showActions, version } = props; return (

    {translate('system_info.page')}

    - {props.showActions && ( + {showActions && ( )} - {props.loading && ( + {loading && (
    )} + {serverId && version && ( +
    +
    + + + + + + + + + + + +
    + {translate('system.server_id')} + {serverId}
    + {translate('system.version')} + {version}
    +
    + +
    + )}
    ); } 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 index e0ba16fe754..adb9db52ec5 100644 --- a/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx @@ -20,7 +20,6 @@ import * as React from 'react'; import { map } from 'lodash'; import HealthCard from './info-items/HealthCard'; -import { SysInfo } from '../../../api/system'; import { getHealth, getHealthCauses, @@ -31,7 +30,7 @@ import { interface Props { expandedCards: string[]; - sysInfoData: SysInfo; + sysInfoData: T.SysInfoStandalone; toggleCard: (toggledCard: string) => void; } 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 index ed0b62b7dc3..395f5bf30ac 100644 --- 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 @@ -20,78 +20,74 @@ 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( + { - expect(getWrapper()).toMatchSnapshot(); -}); - -function getWrapper(props = {}) { - return shallow( - {}} {...props} /> 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 index 0164cc044eb..382169ee3b9 100644 --- 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 @@ -19,32 +19,27 @@ */ 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( - {}} - 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( - {}} - showActions={false} - /> - ) - ).toMatchSnapshot(); -}); +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx index 69a40917cb5..e7bec64929e 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx @@ -20,20 +20,7 @@ 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(); @@ -43,7 +30,7 @@ function getWrapper(props = {}) { return shallow( {}} {...props} /> 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 index 580f6979534..a186f4110d3 100644 --- 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 @@ -15,9 +15,28 @@ exports[`should support more than two nodes 1`] = ` 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", + }, } } /> @@ -50,8 +69,6 @@ exports[`should support more than two nodes 1`] = ` system.search_nodes_title `; -exports[`should show a loading spinner and no actions 1`] = ` +exports[`should render correctly 2`] = `
    @@ -37,3 +37,66 @@ exports[`should show a loading spinner and no actions 1`] = `
    `; + +exports[`should render correctly 3`] = ` +
    +

    + system_info.page +

    + +
    +
    + + + + + + + + + + + +
    + + system.server_id + + + foo-bar +
    + + system.version + + + 7.7.0.1234 +
    +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap index b71406e8fba..0d5a69d87e1 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap @@ -15,9 +15,15 @@ exports[`should render correctly 1`] = ` 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, } } /> @@ -28,8 +34,21 @@ exports[`should render correctly 1`] = ` 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", }, } } @@ -41,8 +60,25 @@ exports[`should render correctly 1`] = ` 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, }, } } @@ -54,8 +90,13 @@ exports[`should render correctly 1`] = ` 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", }, } } 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 index f6e21f1b189..95e3d72aef6 100644 --- 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 @@ -21,20 +21,19 @@ import * as React from 'react'; 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({ 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 index 04c416e1338..79ebc2e1ca4 100644 --- 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 @@ -19,12 +19,11 @@ */ 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; } @@ -33,7 +32,7 @@ export default function HealthCauseItem({ className, health, healthCause }: Prop + variant={health === 'RED' ? 'error' : 'warning'}> {healthCause} ); 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 index 1b71f37fce3..e5291e8c1be 100644 --- 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 @@ -22,19 +22,18 @@ import * as classNames from 'classnames'; 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 = ( ); 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 index a40845a12a3..b548e94afa9 100644 --- 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 @@ -20,11 +20,10 @@ 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) { 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 index 3389df8bb61..fc004f88d99 100644 --- 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 @@ -22,17 +22,16 @@ import { map } from 'lodash'; 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 ; + if (name === HEALTH_FIELD || name === STATE_FIELD) { + return ; } if (value instanceof Array) { return {JSON.stringify(value)}; @@ -55,7 +54,7 @@ function BooleanItem({ value }: { value: boolean }) { } } -function ObjectItem({ value }: { value: SysValueObject }) { +function ObjectItem({ value }: { value: T.SysInfoValueObject }) { return ( 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 index c8eaa8d45ef..f96d0245c37 100644 --- 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 @@ -20,7 +20,6 @@ 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(); @@ -48,7 +47,7 @@ function getWrapper(props = {}) { return shallow( {}} 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 index 1c415360938..52ca0760f4c 100644 --- 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 @@ -20,11 +20,8 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import HealthCauseItem from '../HealthCauseItem'; -import { HealthType } from '../../../../../api/system'; it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); - expect( - shallow() - ).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); + expect(shallow()).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 index 9857ee9b8f9..a17f1140b98 100644 --- 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 @@ -20,25 +20,18 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import HealthItem from '../HealthItem'; -import { HealthType } from '../../../../../api/system'; it('should render correctly', () => { expect( - shallow( - - ) + shallow() ).toMatchSnapshot(); }); it('should not render health causes', () => { - expect( - shallow() - ).toMatchSnapshot(); - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); it('should render multiple health causes', () => { - expect( - shallow() - ).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx index 65712b193e7..9bc6c42f038 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeForm.tsx @@ -19,13 +19,12 @@ */ 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; } diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx index cd49bba50d1..14c65114dbd 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeIntermediate.tsx @@ -21,12 +21,11 @@ import * as React from 'react'; 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 { diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx index 93e9e7b48e6..5881085f99d 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeItem.tsx @@ -21,12 +21,11 @@ import * as React from 'react'; 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) { diff --git a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx index 87677c1b39a..c4800cccafa 100644 --- a/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/system-upgrade/SystemUpgradeNotif.tsx @@ -20,13 +20,13 @@ 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; } diff --git a/server/sonar-web/src/main/js/apps/system/styles.css b/server/sonar-web/src/main/js/apps/system/styles.css index ec695d0194f..33f60e68f48 100644 --- a/server/sonar-web/src/main/js/apps/system/styles.css +++ b/server/sonar-web/src/main/js/apps/system/styles.css @@ -26,6 +26,13 @@ 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; diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts index e2d26559342..85f34adb82b 100644 --- a/server/sonar-web/src/main/js/apps/system/utils.ts +++ b/server/sonar-web/src/main/js/apps/system/utils.ts @@ -25,15 +25,6 @@ import { RawQuery, serializeStringArray } from '../../helpers/query'; -import { - ClusterSysInfo, - HealthType, - NodeInfo, - SysInfo, - SysInfoSection, - SysValueObject, - SystemUpgrade -} from '../../api/system'; import { formatMeasure } from '../../helpers/measures'; export interface Query { @@ -41,132 +32,167 @@ 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 }; } @@ -179,9 +205,9 @@ export function getFileNameSuffix(suffix?: string) { ); } -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; @@ -205,15 +231,15 @@ export const serializeQuery = memoize( }) ); -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]); diff --git a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx index e29f25b6289..94d9f2e017b 100644 --- a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx @@ -27,6 +27,7 @@ import { translate } from '../../helpers/l10n'; interface Props { className?: string; copyValue: string; + label?: string; } interface State { @@ -82,7 +83,7 @@ export default class ClipboardButton extends React.PureComponent { 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')} ); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx index a5e98cc79fb..b9bd71c1fca 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ClipboardButton-test.tsx @@ -18,18 +18,71 @@ * 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(); + 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 = {}) { + return shallow(createComponent(props)); +} + +function mountRender(props: Partial = {}) { + return mount(createComponent(props)); +} + +function createComponent(props: Partial = {}) { + return ; +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap index d25ba109237..5b8c90e9169 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ClipboardButton-test.tsx.snap @@ -44,3 +44,18 @@ exports[`should display correctly 3`] = ` `; + +exports[`should render a custom label if provided 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index 7ae22739944..66c6a639e6f 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -17,6 +17,7 @@ * 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'; @@ -98,6 +99,169 @@ export function mockAppState(overrides: Partial = {}): T.AppState { }; } +export function mockBaseSysInfo(overrides: Partial = {}): 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 = {}): 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 { return { breadcrumbs: [], @@ -488,6 +652,56 @@ export function mockSourceViewerFile( }; } +export function mockStandaloneSysInfo(overrides: Partial = {}): 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 { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 391a892c458..7c99cfb0977 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2378,6 +2378,7 @@ background_tasks.failing_count=Count of projects where processing of most recent 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 @@ -2397,9 +2398,11 @@ system.released_x=Released {0} 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 -- 2.39.5