]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21421 System page adopts the new UI
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 17 Jan 2024 15:35:15 +0000 (16:35 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 19 Jan 2024 20:02:55 +0000 (20:02 +0000)
18 files changed:
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx
server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
server/sonar-web/src/main/js/apps/system/components/PageActions.tsx
server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx
server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx
server/sonar-web/src/main/js/apps/system/components/SystemApp.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx
server/sonar-web/src/main/js/components/common/StatusIndicator.tsx
server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx
server/sonar-web/src/main/js/components/upgrade/__tests__/SystemUpgrade-test.tsx

index 85fb6dfe6eef0e8244fb907b36cac6680a074a19..92661b36e237bddb1e2bb7c174885286e096da17 100644 (file)
@@ -87,7 +87,7 @@ export * from './TutorialStepList';
 export * from './avatar/Avatar';
 export * from './avatar/GenericAvatar';
 export * from './buttons';
-export { ClipboardIconButton } from './clipboard';
+export { ClipboardButton, ClipboardIconButton } from './clipboard';
 export * from './code-line/LineCoverage';
 export * from './code-line/LineFinding';
 export * from './code-line/LineIssuesIndicatorIcon';
index 62382535deae011e074bd2d1164ef74c00bcbdbe..9e7b58e11f7525973e3fc12c70ff57c6b9b3e7c6 100644 (file)
@@ -79,6 +79,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [
   '/project/background_tasks',
   '/admin/background_tasks',
   '/admin/groups',
+  '/admin/system',
   '/admin/users',
   '/admin/settings/encryption',
   '/admin/extension/license/support',
index 9b186130f0341e66f150f716518d081428d0d648..04cc3b0c81ad687eb290567d14ede2a985510c5a 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { Variant } from 'design-system';
 import { groupBy, isEmpty, mapValues } from 'lodash';
 import * as React from 'react';
 import { getSystemUpgrades } from '../../../api/system';
-import { Alert, AlertVariant } from '../../../components/ui/Alert';
+import { Alert } from '../../../components/ui/Alert';
 import DismissableAlert from '../../../components/ui/DismissableAlert';
 import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton';
 import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils';
@@ -41,7 +42,7 @@ type GroupedSystemUpdate = {
   [x: string]: Dict<SystemUpgrade[]>;
 };
 
-const MAP_VARIANT: Dict<AlertVariant> = {
+const MAP_VARIANT: Dict<Variant> = {
   [UpdateUseCase.NewMinorVersion]: 'info',
   [UpdateUseCase.NewPatch]: 'warning',
   [UpdateUseCase.PreLTS]: 'warning',
index 19d92519d18d9cd5563b2dd6cf0b1c69557c4437..cf638be0286f86e3f0655ec902cc8501bf838259 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { ButtonPrimary, FlagMessage, Modal, RadioButton } from 'design-system';
 import * as React from 'react';
 import { setLogLevel } from '../../../api/system';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import Modal from '../../../components/controls/Modal';
-import { Alert } from '../../../components/ui/Alert';
 import { translate } from '../../../helpers/l10n';
 import { LOGS_LEVELS } from '../utils';
 
@@ -37,6 +35,7 @@ interface State {
   updating: boolean;
 }
 
+const FORM_ID = 'set-log-level-form';
 export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
@@ -55,55 +54,50 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
     }
   };
 
-  handleLevelChange = (event: React.ChangeEvent<HTMLInputElement>) =>
-    this.setState({ newLevel: event.currentTarget.value });
+  handleLevelChange = (value: string) => this.setState({ newLevel: value });
 
   render() {
     const { updating, newLevel } = this.state;
     const header = translate('system.set_log_level');
     return (
-      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
-        <form id="set-log-level-form" onSubmit={this.handleFormSubmit}>
-          <div className="modal-head">
-            <h2>{header}</h2>
-          </div>
-          <div className="modal-body">
+      <Modal
+        headerTitle={header}
+        onClose={this.props.onClose}
+        body={
+          <form id={FORM_ID} onSubmit={this.handleFormSubmit}>
             {LOGS_LEVELS.map((level) => (
-              <p className="spacer-bottom" key={level}>
-                <input
-                  checked={level === newLevel}
-                  className="spacer-right text-middle"
-                  id={`loglevel-${level}`}
-                  name="system.log_levels"
-                  onChange={this.handleLevelChange}
-                  type="radio"
-                  value={level}
-                />
+              <RadioButton
+                key={level}
+                checked={level === newLevel}
+                className="sw-mb-2"
+                id={`loglevel-${level}`}
+                name="system.log_levels"
+                onCheck={this.handleLevelChange}
+                value={level}
+              >
                 <label className="text-middle" htmlFor={`loglevel-${level}`}>
                   {level}
                 </label>
-              </p>
+              </RadioButton>
             ))}
-            <Alert className="spacer-top" variant="info">
+            <FlagMessage className="sw-mt-2" variant="info">
               {this.props.infoMsg}
-            </Alert>
+            </FlagMessage>
             {newLevel !== 'INFO' && (
-              <Alert className="spacer-top" variant="warning">
+              <FlagMessage className="sw-mt-2" variant="warning">
                 {translate('system.log_level.warning')}
-              </Alert>
+              </FlagMessage>
             )}
-          </div>
-          <div className="modal-foot">
-            {updating && <i className="spinner spacer-right" />}
-            <SubmitButton disabled={updating} id="set-log-level-submit">
-              {translate('save')}
-            </SubmitButton>
-            <ResetButtonLink id="set-log-level-cancel" onClick={this.props.onClose}>
-              {translate('cancel')}
-            </ResetButtonLink>
-          </div>
-        </form>
-      </Modal>
+          </form>
+        }
+        primaryButton={
+          <ButtonPrimary disabled={updating} id="set-log-level-submit" type="submit" form={FORM_ID}>
+            {translate('save')}
+          </ButtonPrimary>
+        }
+        secondaryButtonLabel={translate('cancel')}
+        loading={updating}
+      />
     );
   }
 }
index 09bf3fbb78239421cbd93a5f3d2706d9c7444cf0..67a7e68d375283fe90fc1f5452665adfd6d48f84 100644 (file)
@@ -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.
  */
+import { Note, UnorderedList } from 'design-system';
 import { sortBy } from 'lodash';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
@@ -38,12 +39,15 @@ interface Props {
   toggleCard: (toggledCard: string) => void;
 }
 
-export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
+export default function ClusterSysInfos({
+  expandedCards,
+  sysInfoData,
+  toggleCard,
+}: Readonly<Props>) {
   const mainCardName = 'System';
   return (
-    <ul>
+    <UnorderedList className="sw-flex sw-flex-col sw-gap-4">
       <HealthCard
-        biggerHealth
         health={getHealth(sysInfoData)}
         healthCauses={getHealthCauses(sysInfoData)}
         name={mainCardName}
@@ -51,8 +55,8 @@ export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard
         open={expandedCards.includes(mainCardName)}
         sysInfoData={ignoreInfoFields(getClusterMainCardSection(sysInfoData))}
       />
-      <li className="note system-info-health-title">
-        {translate('system.application_nodes_title')}
+      <li>
+        <Note>{translate('system.application_nodes_title')}</Note>
       </li>
       {sortBy(getAppNodes(sysInfoData), getNodeName).map((node: SysInfoAppNode) => (
         <HealthCard
@@ -65,7 +69,9 @@ export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard
           sysInfoData={ignoreInfoFields(node)}
         />
       ))}
-      <li className="note system-info-health-title">{translate('system.search_nodes_title')}</li>
+      <li>
+        <Note>{translate('system.search_nodes_title')}</Note>
+      </li>
       {sortBy(getSearchNodes(sysInfoData), getNodeName).map((node: SysInfoSearchNode) => (
         <HealthCard
           key={getNodeName(node)}
@@ -75,6 +81,6 @@ export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard
           sysInfoData={ignoreInfoFields(node)}
         />
       ))}
-    </ul>
+    </UnorderedList>
   );
 }
index 26150d57b2064010ed240bca11009664c2ecaf1c..09cb66272d4b2ad093f6506280c3894dde1836f1 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import {
+  ButtonPrimary,
+  ChevronDownIcon,
+  DownloadButton,
+  Dropdown,
+  InteractiveIcon,
+  ItemDownload,
+  PencilIcon,
+} from 'design-system';
 import * as React from 'react';
-import { Button, EditButton } from '../../../components/controls/buttons';
-import Dropdown from '../../../components/controls/Dropdown';
-import DropdownIcon from '../../../components/icons/DropdownIcon';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
 import { getFileNameSuffix } from '../utils';
@@ -67,101 +73,60 @@ export default class PageActions extends React.PureComponent<Props, State> {
     const infoUrl = getBaseUrl() + '/api/system/info';
     const logsUrl = getBaseUrl() + '/api/system/logs';
     return (
-      <div className="page-actions">
-        <span>
-          <span className="text-middle">
+      <div className="sw-flex sw-items-center sw-gap-2">
+        <div className="sw-flex sw-items-center">
+          <span>
             {translate('system.logs_level')}
-            {':'}
-            <strong className="little-spacer-left">{this.props.logLevel}</strong>
+            {': '}
+            <strong>{this.props.logLevel}</strong>
           </span>
-          <EditButton
-            className="spacer-left button-small"
+          <InteractiveIcon
+            className="sw-ml-1"
+            Icon={PencilIcon}
             id="edit-logs-level-button"
             onClick={this.handleLogsLevelOpen}
             aria-label={translate('system.logs_level.change')}
           />
-        </span>
+        </div>
         {this.props.canDownloadLogs && (
           <Dropdown
-            className="display-inline-block spacer-left"
+            id="system-logs-download"
             overlay={
-              <ul className="menu">
-                <li>
-                  <a
-                    download="sonarqube_app.log"
-                    href={logsUrl + '?name=app'}
-                    id="logs-link"
-                    rel="noopener noreferrer"
-                    target="_blank"
-                  >
-                    Main Process
-                  </a>
-                </li>
-                <li>
-                  <a
-                    download="sonarqube_ce.log"
-                    href={logsUrl + '?name=ce'}
-                    id="ce-logs-link"
-                    rel="noopener noreferrer"
-                    target="_blank"
-                  >
-                    Compute Engine
-                  </a>
-                </li>
-                <li>
-                  <a
-                    download="sonarqube_es.log"
-                    href={logsUrl + '?name=es'}
-                    id="es-logs-link"
-                    rel="noopener noreferrer"
-                    target="_blank"
-                  >
-                    Search Engine
-                  </a>
-                </li>
-                <li>
-                  <a
-                    download="sonarqube_web.log"
-                    href={logsUrl + '?name=web'}
-                    id="web-logs-link"
-                    rel="noopener noreferrer"
-                    target="_blank"
-                  >
-                    Web Server
-                  </a>
-                </li>
-                <li>
-                  <a
-                    download="sonarqube_access.log"
-                    href={logsUrl + '?name=access'}
-                    id="access-logs-link"
-                    rel="noopener noreferrer"
-                    target="_blank"
-                  >
-                    Access Logs
-                  </a>
-                </li>
-                <li>
-                  <a
-                    download="sonarqube_deprecation.log"
-                    href={logsUrl + '?name=deprecation'}
-                    rel="noopener noreferrer"
-                    target="_blank"
-                  >
-                    Deprecation Logs
-                  </a>
-                </li>
-              </ul>
+              <>
+                <ItemDownload download="sonarqube_app.log" href={logsUrl + '?name=app'}>
+                  Main Process
+                </ItemDownload>
+                <ItemDownload download="sonarqube_ce.log" href={logsUrl + '?name=ce'}>
+                  Compute Engine
+                </ItemDownload>
+                <ItemDownload download="sonarqube_es.log" href={logsUrl + '?name=es'}>
+                  Search Engine
+                </ItemDownload>
+
+                <ItemDownload download="sonarqube_web.log" href={logsUrl + '?name=web'}>
+                  Web Server
+                </ItemDownload>
+
+                <ItemDownload download="sonarqube_access.log" href={logsUrl + '?name=access'}>
+                  Access Logs
+                </ItemDownload>
+
+                <ItemDownload
+                  download="sonarqube_deprecation.log"
+                  href={logsUrl + '?name=deprecation'}
+                >
+                  Deprecation Logs
+                </ItemDownload>
+              </>
             }
           >
-            <Button>
+            <ButtonPrimary>
               {translate('system.download_logs')}
-              <DropdownIcon className="little-spacer-left" />
-            </Button>
+              <ChevronDownIcon className="sw-ml-1" />
+            </ButtonPrimary>
           </Dropdown>
         )}
-        <a
-          className="button spacer-left"
+        <DownloadButton
           download={`sonarqube-system-info-${getFileNameSuffix(this.props.serverId)}.json`}
           href={infoUrl}
           id="download-link"
@@ -170,7 +135,7 @@ export default class PageActions extends React.PureComponent<Props, State> {
           target="_blank"
         >
           {translate('system.download_system_info')}
-        </a>
+        </DownloadButton>
         {this.state.openLogsLevelForm && (
           <ChangeLogLevelForm
             infoMsg={translate(
index d49a7b62fc46abb1410b9e9576fb543baadf3d01..9bf4545eb352f221cde14472fd2ab473679c35d0 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { Card, ClipboardButton, FlagMessage, Spinner, Title } from 'design-system';
 import * as React from 'react';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import { ClipboardButton } from '../../../components/controls/clipboard';
-import { Alert } from '../../../components/ui/Alert';
 import { toShortISO8601String } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
 import { AppState } from '../../../types/appstate';
@@ -36,63 +35,58 @@ export interface Props {
   version?: string;
 }
 
-function PageHeader(props: Props) {
+function PageHeader(props: Readonly<Props>) {
   const { isCluster, loading, logLevel, serverId, version, appState } = props;
   return (
-    <header className="page-header">
-      <h1 className="page-title">{translate('system_info.page')}</h1>
-      <PageActions
-        canDownloadLogs={!isCluster}
-        cluster={isCluster}
-        logLevel={logLevel}
-        onLogLevelChange={props.onLogLevelChange}
-        serverId={serverId}
-      />
-      {loading && (
-        <div className="page-actions">
-          <i className="spinner" />
+    <header className="sw-mt-8">
+      <div className="sw-flex sw-items-start sw-justify-between">
+        <Title>{translate('system_info.page')}</Title>
+
+        <div className="sw-flex sw-items-center">
+          <Spinner className="sw-mr-4 sw-mt-1" loading={loading} />
+
+          <PageActions
+            canDownloadLogs={!isCluster}
+            cluster={isCluster}
+            logLevel={logLevel}
+            onLogLevelChange={props.onLogLevelChange}
+            serverId={serverId}
+          />
         </div>
-      )}
+      </div>
+
       {serverId && version && (
-        <div className="system-info-copy-paste-id-info boxed-group ">
+        <Card className="sw-max-w-1/2 sw-mb-8">
           {!appState.productionDatabase && (
-            <Alert className="width-100" variant="warning">
+            <FlagMessage className="sw-mb-2" variant="warning">
               {translate('system.not_production_database_warning')}
-            </Alert>
+            </FlagMessage>
           )}
-          <div className="display-flex-center">
-            <div className="flex-1">
-              <table className="width-100">
-                <tbody>
-                  <tr>
-                    <th>
-                      <strong>{translate('system.server_id')}</strong>
-                    </th>
-                    <td>
-                      <code>{serverId}</code>
-                    </td>
-                  </tr>
-                  <tr>
-                    <th>
-                      <strong>{translate('system.version')}</strong>
-                    </th>
-                    <td>{version}</td>
-                  </tr>
-                </tbody>
-              </table>
+          <div className="sw-flex sw-items-center sw-justify-between">
+            <div>
+              <div className="sw-flex sw-items-center">
+                <strong className="sw-w-32">{translate('system.server_id')}</strong>
+                <span className="sw-code">{serverId}</span>
+              </div>
+              <div className="sw-flex sw-items-center">
+                <strong className="sw-w-32">{translate('system.version')}</strong>
+                <span>{version}</span>
+              </div>
             </div>
             <ClipboardButton
-              className="flex-0"
+              className="sw-ml-4"
               copyValue={`SonarQube ID information
 Server ID: ${serverId}
 Version: ${version}
 Date: ${toShortISO8601String(Date.now())}
 `}
             >
-              {translate('system.copy_id_info')}
+              <span className="sw-ml-1 sw-whitespace-nowrap">
+                {translate('system.copy_id_info')}
+              </span>
             </ClipboardButton>
           </div>
-        </div>
+        </Card>
       )}
     </header>
   );
index 4807d2fa76380b2af275a2b4b5cb83447003aaee..6a8acbe7f7af13d8efc54fc8cc8a379926b582c8 100644 (file)
@@ -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.
  */
+import { UnorderedList } from 'design-system';
 import { map } from 'lodash';
 import * as React from 'react';
 import { SysInfoStandalone } from '../../../types/types';
@@ -35,12 +36,16 @@ interface Props {
   toggleCard: (toggledCard: string) => void;
 }
 
-export default function StandAloneSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
-  const mainCardName = 'System';
+const mainCardName = 'System';
+
+export default function StandAloneSysInfos({
+  expandedCards,
+  sysInfoData,
+  toggleCard,
+}: Readonly<Props>) {
   return (
-    <ul>
+    <UnorderedList className="sw-flex sw-flex-col sw-gap-4">
       <HealthCard
-        biggerHealth
         health={getHealth(sysInfoData)}
         healthCauses={getHealthCauses(sysInfoData)}
         name={mainCardName}
@@ -57,6 +62,6 @@ export default function StandAloneSysInfos({ expandedCards, sysInfoData, toggleC
           sysInfoData={ignoreInfoFields(section)}
         />
       ))}
-    </ul>
+    </UnorderedList>
   );
 }
index 30baa93cd470fa1dfd8ff453b1ac239fa47448fa..2d8b64933490dc7cee85ebfcce7f015d84e158f2 100644 (file)
@@ -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.
  */
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { getSystemInfo } from '../../../api/system';
@@ -27,13 +28,13 @@ import { translate } from '../../../helpers/l10n';
 import { SysInfoCluster, SysInfoStandalone } from '../../../types/types';
 import '../styles.css';
 import {
+  Query,
   getClusterVersion,
   getServerId,
   getSystemLogsLevel,
   getVersion,
   isCluster,
   parseQuery,
-  Query,
   serializeQuery,
 } from '../utils';
 import ClusterSysInfos from './ClusterSysInfos';
@@ -123,26 +124,28 @@ class SystemApp extends React.PureComponent<Props, State> {
   render() {
     const { loading, sysInfoData } = this.state;
     return (
-      <main className="page page-limited">
+      <LargeCenteredLayout as="main">
         <Suggestions suggestions="system_info" />
         <Helmet defer={false} title={translate('system_info.page')} />
-        <div className="page-notifs">
-          <UpdateNotification dismissable={false} />
-        </div>
-        {sysInfoData && (
-          <PageHeader
-            isCluster={isCluster(sysInfoData)}
-            loading={loading}
-            logLevel={getSystemLogsLevel(sysInfoData)}
-            onLogLevelChange={this.fetchSysInfo}
-            serverId={getServerId(sysInfoData)}
-            version={
-              isCluster(sysInfoData) ? getClusterVersion(sysInfoData) : getVersion(sysInfoData)
-            }
-          />
-        )}
-        {this.renderSysInfo()}
-      </main>
+        <PageContentFontWrapper className="sw-body-sm sw-pb-8">
+          <div>
+            <UpdateNotification dismissable={false} />
+          </div>
+          {sysInfoData && (
+            <PageHeader
+              isCluster={isCluster(sysInfoData)}
+              loading={loading}
+              logLevel={getSystemLogsLevel(sysInfoData)}
+              onLogLevelChange={this.fetchSysInfo}
+              serverId={getServerId(sysInfoData)}
+              version={
+                isCluster(sysInfoData) ? getClusterVersion(sysInfoData) : getVersion(sysInfoData)
+              }
+            />
+          )}
+          {this.renderSysInfo()}
+        </PageContentFontWrapper>
+      </LargeCenteredLayout>
     );
   }
 }
index d2b885e135564dde177a7f1ec4a8d25b9f20d86d..fc45c6112b68a04222a223d59658a071af2c8421 100644 (file)
@@ -76,8 +76,8 @@ describe('System Info Standalone', () => {
       'Web Server',
       'Access Logs',
       'Deprecation Logs',
-    ].forEach((link) => {
-      expect(screen.getByRole('link', { name: link })).toBeInTheDocument();
+    ].forEach((name) => {
+      expect(screen.getByRole('menuitem', { name })).toBeInTheDocument();
     });
     expect(ui.downloadSystemInfoButton.get()).toBeInTheDocument();
   });
@@ -122,7 +122,7 @@ function getPageObjects() {
     pageHeading: byRole('heading', { name: 'system_info.page' }),
     downloadLogsButton: byRole('button', { name: 'system.download_logs' }),
     downloadSystemInfoButton: byRole('link', { name: 'system.download_system_info' }),
-    copyIdInformation: byRole('button', { name: 'copy_to_clipboard' }),
+    copyIdInformation: byRole('button', { name: 'system.copy_id_info' }),
     sectionButton: (name: string) => byRole('button', { name }),
     changeLogLevelButton: byRole('button', { name: 'system.logs_level.change' }),
     logLevelsRadioButton: (name: LogsLevels) => byRole('radio', { name }),
index a7857689da406d6191290138758e65c1dd437cf9..73551380dc5ab3cefc979e01a75f323b1d965023 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { Accordion, FlagMessage, SubHeadingHighlight } from 'design-system';
 import { map } from 'lodash';
 import * as React from 'react';
-import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion';
-import { Alert } from '../../../../components/ui/Alert';
 import { translate } from '../../../../helpers/l10n';
 import { HealthTypes, SysInfoValueObject } from '../../../../types/types';
-import { getLogsLevel, groupSections, LogsLevels } from '../../utils';
+import { LogsLevels, getLogsLevel, groupSections } from '../../utils';
 import HealthItem from './HealthItem';
 import Section from './Section';
 
 interface Props {
-  biggerHealth?: boolean;
   health?: HealthTypes;
   healthCauses?: string[];
   onClick: (toggledCard: string) => void;
@@ -38,50 +36,44 @@ interface Props {
 }
 
 export default function HealthCard({
-  biggerHealth,
   health,
   healthCauses,
   onClick,
   open,
   name,
   sysInfoData,
-}: Props) {
+}: Readonly<Props>) {
   const { mainSection, sections } = groupSections(sysInfoData);
   const showFields = open && mainSection && Object.keys(mainSection).length > 0;
   const showSections = open && sections;
   const logLevel = getLogsLevel(sysInfoData);
   const showLogLevelWarning = logLevel && logLevel !== LogsLevels.INFO;
+
   return (
-    <BoxedGroupAccordion
+    <Accordion
       data={name}
       onClick={onClick}
       open={open}
-      renderHeader={() => (
+      header={
         <>
-          {showLogLevelWarning && (
-            <Alert
-              className="boxed-group-accordion-alert spacer-left"
-              display="inline"
-              variant="warning"
-            >
-              {translate('system.log_level.warning.short')}
-            </Alert>
-          )}
-          {health && (
-            <HealthItem
-              biggerHealth={biggerHealth}
-              health={health}
-              healthCauses={healthCauses}
-              name={name}
-            />
-          )}
+          <div className="sw-flex-1 sw-flex sw-items-center">
+            <SubHeadingHighlight as="h2" className="sw-mb-0">
+              {name}
+            </SubHeadingHighlight>
+            {showLogLevelWarning && (
+              <FlagMessage className="sw-ml-4" variant="warning">
+                {translate('system.log_level.warning.short')}
+              </FlagMessage>
+            )}
+          </div>
+          {health && <HealthItem health={health} healthCauses={healthCauses} name={name} />}
         </>
-      )}
-      title={name}
+      }
+      ariaLabel={name}
     >
       {showFields && <Section items={mainSection} />}
       {showSections &&
         map(sections, (section, name) => <Section items={section} key={name} name={name} />)}
-    </BoxedGroupAccordion>
+    </Accordion>
   );
 }
index 08fc16e80e2eb409a219c6c6dd86e0c1a448bae6..f41956f80132ad57305c4378e88bb42612a08a21 100644 (file)
@@ -18,8 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import classNames from 'classnames';
+import { FlagMessage } from 'design-system';
 import * as React from 'react';
-import { Alert } from '../../../../components/ui/Alert';
 import { HealthTypes } from '../../../../types/types';
 
 interface Props {
@@ -28,14 +28,13 @@ interface Props {
   healthCause: string;
 }
 
-export default function HealthCauseItem({ className, health, healthCause }: Props) {
+export default function HealthCauseItem({ className, health, healthCause }: Readonly<Props>) {
   return (
-    <Alert
-      className={classNames('boxed-group-accordion-alert', className)}
-      display="inline"
+    <FlagMessage
+      className={classNames('-sw-my-2', className)}
       variant={health === HealthTypes.RED ? 'error' : 'warning'}
     >
       {healthCause}
-    </Alert>
+    </FlagMessage>
   );
 }
index 90b3352a8be36caadb59f73b88b6b2fdb4ab982e..357e8dca6fc5bf801ec3f4859f18e8238b6aa105 100644 (file)
@@ -26,32 +26,28 @@ import { HealthTypes } from '../../../../types/types';
 import HealthCauseItem from './HealthCauseItem';
 
 interface Props {
-  biggerHealth?: boolean;
   name?: string;
   className?: string;
   health: HealthTypes;
   healthCauses?: string[];
 }
 
-export default function HealthItem({ biggerHealth, className, name, health, healthCauses }: Props) {
+export default function HealthItem({ className, name, health, healthCauses }: Readonly<Props>) {
   const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthTypes.GREEN;
 
-  const statusIndicator = (
-    <StatusIndicator color={health.toLowerCase()} size={biggerHealth ? 'big' : undefined} />
-  );
+  const statusIndicator = <StatusIndicator color={health} />;
   return (
-    <div className={classNames('system-info-health-info', className)}>
+    <div className={classNames('sw-flex sw-items-center', className)}>
       {hasHealthCauses &&
-        healthCauses.map((cause, idx) => (
-          <HealthCauseItem className="spacer-right" health={health} healthCause={cause} key={idx} />
+        healthCauses.map((cause) => (
+          <HealthCauseItem className="sw-mr-2" health={health} healthCause={cause} key={cause} />
         ))}
-      {name ? (
-        <Tooltip overlay={translateWithParameters('system.current_health_of_x', name)}>
-          <span>{statusIndicator}</span>
-        </Tooltip>
-      ) : (
-        statusIndicator
-      )}
+
+      <Tooltip
+        overlay={name ? translateWithParameters('system.current_health_of_x', name) : undefined}
+      >
+        <span>{statusIndicator}</span>
+      </Tooltip>
     </div>
   );
 }
index 1b1d3de69a8f9f2b4555843c55c4cbd863362776..6c333478732e6a2f78f2b98c36c02c7d2d8cd9f7 100644 (file)
@@ -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.
  */
+import { ContentCell, SubHeading, Table, TableRow } from 'design-system';
 import { map } from 'lodash';
 import * as React from 'react';
 import { SysInfoValueObject } from '../../../../types/types';
@@ -27,26 +28,26 @@ interface Props {
   items: SysInfoValueObject;
 }
 
-export default function Section({ name, items }: Props) {
+const COLUMNS = ['0', 'auto'];
+
+export default function Section({ name, items }: Readonly<Props>) {
   return (
-    <div className="system-info-section">
-      {name && <h4 className="spacer-bottom">{name}</h4>}
-      <table className="data zebra" id={name}>
-        <tbody>
-          {map(items, (value, name) => {
-            return (
-              <tr key={name}>
-                <td className="thin">
-                  <div className="system-info-section-item-name">{name}</div>
-                </td>
-                <td style={{ wordBreak: 'break-all' }}>
+    <div className="it__system-info-section">
+      {name !== undefined && <SubHeading>{name}</SubHeading>}
+      <Table id={name} columnCount={COLUMNS.length} columnWidths={COLUMNS}>
+        {map(items, (value, name) => {
+          return (
+            <TableRow key={name}>
+              <ContentCell className="it__system-info-section-item-name">{name}</ContentCell>
+              <ContentCell>
+                <span className="sw-break-all">
                   <SysInfoItem name={name} value={value} />
-                </td>
-              </tr>
-            );
-          })}
-        </tbody>
-      </table>
+                </span>
+              </ContentCell>
+            </TableRow>
+          );
+        })}
+      </Table>
     </div>
   );
 }
index fa880ca83c13ebdfa9158a89871ed4b12fe6ce12..bbbeef434a4b28559bea2caa7f9cadf78fe8fa4e 100644 (file)
@@ -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.
  */
+import { ContentCell, Table, TableRow } from 'design-system';
 import { map } from 'lodash';
 import * as React from 'react';
 import { translate } from '../../../../helpers/l10n';
@@ -29,32 +30,32 @@ export interface Props {
   value: SysInfoValue;
 }
 
-export default function SysInfoItem({ name, value }: Props) {
+const COLUMNS = [0, 'auto'];
+
+export default function SysInfoItem({ name, value }: Readonly<Props>) {
   if (name === HEALTH_FIELD || name === STATE_FIELD) {
-    return <HealthItem className="no-margin" health={value as HealthTypes} />;
+    return <HealthItem health={value as HealthTypes} />;
   }
   if (value instanceof Array) {
-    return <code>{JSON.stringify(value)}</code>;
+    return <span className="sw-code">{JSON.stringify(value)}</span>;
   }
   switch (typeof value) {
     case 'boolean':
       return <>{translate(value ? 'yes' : 'no')}</>;
     case 'object':
       return (
-        <table className="data">
-          <tbody>
-            {map(value, (v, n) => (
-              <tr key={n}>
-                <td className="thin nowrap">{n}</td>
-                <td>
-                  <SysInfoItem name={n} value={v} />
-                </td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
+        <Table columnCount={COLUMNS.length} columnWidths={COLUMNS}>
+          {map(value, (v, n) => (
+            <TableRow key={n}>
+              <ContentCell className="sw-whitespace-nowrap">{n}</ContentCell>
+              <ContentCell>
+                <SysInfoItem name={n} value={v} />
+              </ContentCell>
+            </TableRow>
+          ))}
+        </Table>
       );
     default:
-      return <code>{value}</code>;
+      return <span className="sw-code">{value}</span>;
   }
 }
index 8f7964bd8f49f3d28d65ca63e101a39a3fbc0743..c6cac3e2379d109f5155f33e50c75f181d4c80a5 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import classNames from 'classnames';
+import { CheckIcon, FlagErrorIcon, FlagWarningIcon } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../helpers/l10n';
+import { HealthTypes } from '../../types/types';
 import './StatusIndicator.css';
 
 export interface StatusIndicatorProps {
-  className?: string;
-  color?: string;
-  size?: string;
+  color: HealthTypes;
 }
 
-export default function StatusIndicator({ className, color, size }: StatusIndicatorProps) {
+const ICON_MAP = {
+  [HealthTypes.GREEN]: CheckIcon,
+  [HealthTypes.YELLOW]: FlagWarningIcon,
+  [HealthTypes.RED]: FlagErrorIcon,
+};
+
+export default function StatusIndicator({ color }: Readonly<StatusIndicatorProps>) {
+  const Icon = ICON_MAP[color];
+
   return (
     <>
-      {color ? translate('system.current_health', color) : undefined}
-      <i
-        className={classNames(
-          'spacer-left',
-          'status-indicator',
-          color,
-          {
-            'small-status-indicator': size === 'small',
-            'big-status-indicator': size === 'big',
-          },
-          className,
-        )}
-      />
+      {translate('system.current_health', color.toLowerCase())}
+      <Icon aria-hidden className="sw-ml-2" />
     </>
   );
 }
index ed1b3573c3be918daec59e35a64b69ea8522aeb1..b028aa6c3c6c4fc7c109949e55fcf466e6cebc53 100644 (file)
@@ -30,38 +30,32 @@ interface Props {
   updateUseCase?: UpdateUseCase;
 }
 
-interface State {
-  openSystemUpgradeForm: boolean;
-}
+export default function SystemUpgradeButton(props: Readonly<Props>) {
+  const { latestLTS, systemUpgrades, updateUseCase } = props;
 
-export default class SystemUpgradeButton extends React.PureComponent<Props, State> {
-  state: State = { openSystemUpgradeForm: false };
+  const [isSystemUpgradeFormOpen, setSystemUpgradeFormOpen] = React.useState(false);
 
-  handleOpenSystemUpgradeForm = () => {
-    this.setState({ openSystemUpgradeForm: true });
-  };
+  const openSystemUpgradeForm = React.useCallback(() => {
+    setSystemUpgradeFormOpen(true);
+  }, [setSystemUpgradeFormOpen]);
 
-  handleCloseSystemUpgradeForm = () => {
-    this.setState({ openSystemUpgradeForm: false });
-  };
+  const closeSystemUpgradeForm = React.useCallback(() => {
+    setSystemUpgradeFormOpen(false);
+  }, [setSystemUpgradeFormOpen]);
 
-  render() {
-    const { latestLTS, systemUpgrades, updateUseCase } = this.props;
-    const { openSystemUpgradeForm } = this.state;
-    return (
-      <>
-        <Button className="spacer-left" onClick={this.handleOpenSystemUpgradeForm}>
-          {translate('learn_more')}
-        </Button>
-        {openSystemUpgradeForm && (
-          <SystemUpgradeForm
-            onClose={this.handleCloseSystemUpgradeForm}
-            systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))}
-            latestLTS={latestLTS}
-            updateUseCase={updateUseCase}
-          />
-        )}
-      </>
-    );
-  }
+  return (
+    <>
+      <Button className="sw-ml-2" onClick={openSystemUpgradeForm}>
+        {translate('learn_more')}
+      </Button>
+      {isSystemUpgradeFormOpen && (
+        <SystemUpgradeForm
+          onClose={closeSystemUpgradeForm}
+          systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))}
+          latestLTS={latestLTS}
+          updateUseCase={updateUseCase}
+        />
+      )}
+    </>
+  );
 }
index 1e571b1d5dd24437222b13929d22408b6268b032..29a1c4d35f283ac10f69b18edde886b62fd1d942 100644 (file)
@@ -69,7 +69,7 @@ it('should render properly for new patch', async () => {
 });
 
 function renderSystemUpgradeButton(
-  props: Partial<SystemUpgradeButton['props']> = {},
+  props: Partial<React.ComponentPropsWithoutRef<typeof SystemUpgradeButton>> = {},
   version = '9.7',
 ) {
   renderComponent(