]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21018 - Import / Export adopts the new UI
authorKevin Silva <kevin.silva@sonarsource.com>
Mon, 13 Nov 2023 18:14:31 +0000 (19:14 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 15 Nov 2023 20:02:39 +0000 (20:02 +0000)
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/apps/projectDump/ProjectDumpApp.tsx
server/sonar-web/src/main/js/apps/projectDump/components/Export.tsx
server/sonar-web/src/main/js/apps/projectDump/components/Import.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index f8c8b6c0ace6a6a5f836768ad3956298bdcdbb6a..0fad836990205a4327bf84f381a1633eb136479a 100644 (file)
@@ -62,6 +62,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [
   '/project/key',
   '/project/deletion',
   '/project/links',
+  '/project/import_export',
 ];
 
 export default function GlobalContainer() {
index f2f793c6256abd82a48479f5045be3d7d269bd7e..c16e59f84ccaad870492ff6241d71d3b9e2c53bf 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 {
+  BasicSeparator,
+  LargeCenteredLayout,
+  PageContentFontWrapper,
+  Spinner,
+  Title,
+} from 'design-system';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { getActivity } from '../../api/ce';
@@ -158,42 +165,41 @@ export class ProjectDumpApp extends React.Component<Props, State> {
     const { lastAnalysisTask, lastExportTask, lastImportTask, status } = this.state;
 
     return (
-      <div className="page page-limited" id="project-dump">
-        <Helmet defer={false} title={translate('project_dump.page')} />
-        <header className="page-header">
-          <h1 className="page-title">{translate('project_dump.page')}</h1>
-          <div className="page-description">
-            {projectImportFeatureEnabled
-              ? translate('project_dump.page.description')
-              : translate('project_dump.page.description_without_import')}
-          </div>
-        </header>
+      <LargeCenteredLayout id="project-dump">
+        <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+          <header className="sw-mb-5">
+            <Helmet defer={false} title={translate('project_dump.page')} />
+            <Title className="sw-mb-4">{translate('project_dump.page')}</Title>
+            <p>
+              {projectImportFeatureEnabled
+                ? translate('project_dump.page.description')
+                : translate('project_dump.page.description_without_import')}
+            </p>
+          </header>
 
-        {status === undefined ? (
-          <i className="spinner" />
-        ) : (
-          <div className="columns">
-            <div className="column-half">
-              <Export
-                componentKey={component.key}
-                loadStatus={this.poll}
-                status={status}
-                task={lastExportTask}
-              />
-            </div>
-            <div className="column-half">
-              <Import
-                importEnabled={!!projectImportFeatureEnabled}
-                analysis={lastAnalysisTask}
-                componentKey={component.key}
-                loadStatus={this.poll}
-                status={status}
-                task={lastImportTask}
-              />
-            </div>
-          </div>
-        )}
-      </div>
+          <Spinner loading={status === undefined}>
+            {status && (
+              <>
+                <Export
+                  componentKey={component.key}
+                  loadStatus={this.poll}
+                  status={status}
+                  task={lastExportTask}
+                />
+                <BasicSeparator className="sw-my-8" />
+                <Import
+                  importEnabled={!!projectImportFeatureEnabled}
+                  analysis={lastAnalysisTask}
+                  componentKey={component.key}
+                  loadStatus={this.poll}
+                  status={status}
+                  task={lastImportTask}
+                />
+              </>
+            )}
+          </Spinner>
+        </PageContentFontWrapper>
+      </LargeCenteredLayout>
     );
   }
 }
index ea57b000c5070708b7d9df32549416b4fc3d8efa..b32bad35a38ede0ccdbf2066093c9ba5bef2d0c7 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 { ButtonSecondary, FlagMessage, Link, Spinner } from 'design-system';
 import * as React from 'react';
 import { doExport } from '../../../api/project-dump';
-import Link from '../../../components/common/Link';
-import { Button } from '../../../components/controls/buttons';
 import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
-import { Alert } from '../../../components/ui/Alert';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { DumpStatus, DumpTask } from '../../../types/project-dump';
 
@@ -34,57 +32,57 @@ interface Props {
   task?: DumpTask;
 }
 
-export default class Export extends React.Component<Props> {
-  handleExport = () => {
-    doExport(this.props.componentKey).then(this.props.loadStatus, () => {
+export default function Export(props: Readonly<Props>) {
+  const handleExport = async () => {
+    try {
+      await doExport(props.componentKey);
+      props.loadStatus();
+    } catch (error) {
       /* no catch needed */
-    });
+    }
   };
 
-  renderHeader() {
+  function renderHeader() {
     return (
-      <div className="boxed-group-header">
-        <h2>{translate('project_dump.export')}</h2>
+      <div className="sw-mb-4">
+        <span className="sw-heading-md">{translate('project_dump.export')}</span>
       </div>
     );
   }
 
-  renderWhenCanNotExport() {
+  function renderWhenCanNotExport() {
     return (
-      <div className="boxed-group" id="project-export">
-        {this.renderHeader()}
-        <div className="boxed-group-inner">
-          <Alert id="export-not-possible" variant="warning">
-            {translate('project_dump.can_not_export')}
-          </Alert>
-        </div>
-      </div>
+      <>
+        {renderHeader()}
+        <FlagMessage className="sw-mb-4" variant="warning">
+          {translate('project_dump.can_not_export')}
+        </FlagMessage>
+      </>
     );
   }
 
-  renderWhenExportPending(task: DumpTask) {
+  function renderWhenExportPending(task: DumpTask) {
     return (
-      <div className="boxed-group" id="project-export">
-        {this.renderHeader()}
-        <div className="boxed-group-inner" id="export-pending">
-          <i className="spinner spacer-right" />
+      <>
+        {renderHeader()}
+        <div>
+          <Spinner />
           <DateTimeFormatter date={task.submittedAt}>
             {(formatted) => (
               <span>{translateWithParameters('project_dump.pending_export', formatted)}</span>
             )}
           </DateTimeFormatter>
         </div>
-      </div>
+      </>
     );
   }
 
-  renderWhenExportInProgress(task: DumpTask) {
+  function renderWhenExportInProgress(task: DumpTask) {
     return (
-      <div className="boxed-group" id="project-export">
-        {this.renderHeader()}
-
-        <div className="boxed-group-inner" id="export-in-progress">
-          <i className="spinner spacer-right" />
+      <>
+        {renderHeader()}
+        <div>
+          <Spinner />
           {task.startedAt && (
             <DateFromNow date={task.startedAt}>
               {(fromNow) => (
@@ -93,96 +91,100 @@ export default class Export extends React.Component<Props> {
             </DateFromNow>
           )}
         </div>
-      </div>
+      </>
     );
   }
 
-  renderWhenExportFailed() {
-    const { componentKey } = this.props;
+  function renderWhenExportFailed() {
+    const { componentKey } = props;
     const detailsUrl = `/project/background_tasks?id=${encodeURIComponent(
       componentKey,
     )}&status=FAILED&taskType=PROJECT_EXPORT`;
 
     return (
-      <div className="boxed-group" id="project-export">
-        {this.renderHeader()}
-
-        <div className="boxed-group-inner">
-          <Alert id="export-in-progress" variant="error">
+      <>
+        {renderHeader()}
+        <div>
+          <FlagMessage className="sw-mb-4" variant="error">
             {translate('project_dump.failed_export')}
-            <Link className="spacer-left" to={detailsUrl}>
+            <Link className="sw-ml-1" to={detailsUrl}>
               {translate('project_dump.see_details')}
             </Link>
-          </Alert>
+          </FlagMessage>
 
-          {this.renderExport()}
+          {renderExport()}
         </div>
-      </div>
+      </>
     );
   }
 
-  renderDump(task?: DumpTask) {
-    const { status } = this.props;
+  function renderDump(task?: DumpTask) {
+    const { status } = props;
 
     return (
-      <Alert className="export-dump" variant="success">
-        {task && task.executedAt && (
-          <DateTimeFormatter date={task.executedAt}>
-            {(formatted) => (
-              <div className="export-dump-message">
-                {translateWithParameters('project_dump.latest_export_available', formatted)}
-              </div>
-            )}
-          </DateTimeFormatter>
-        )}
-        {!task && (
-          <div className="export-dump-message">{translate('project_dump.export_available')}</div>
-        )}
-        <div className="export-dump-path">
-          <code tabIndex={0}>{status.exportedDump}</code>
+      <FlagMessage className="sw-mb-4" variant="success">
+        <div>
+          {task && task.executedAt && (
+            <DateTimeFormatter date={task.executedAt}>
+              {(formatted) => (
+                <div>
+                  {translateWithParameters('project_dump.latest_export_available', formatted)}
+                </div>
+              )}
+            </DateTimeFormatter>
+          )}
+          <div>
+            {!task && <div>{translate('project_dump.export_available')}</div>}
+
+            <code tabIndex={0}>{status.exportedDump}</code>
+          </div>
         </div>
-      </Alert>
+      </FlagMessage>
     );
   }
 
-  renderExport() {
+  function renderExport() {
     return (
-      <div>
-        <div className="spacer-bottom">{translate('project_dump.export_form_description')}</div>
-        <Button onClick={this.handleExport}>{translate('project_dump.do_export')}</Button>
-      </div>
+      <>
+        <div>{translate('project_dump.export_form_description')}</div>
+        <ButtonSecondary
+          aria-label={translate('project_dump.do_export')}
+          className="sw-mt-4"
+          onClick={handleExport}
+        >
+          {translate('project_dump.do_export')}
+        </ButtonSecondary>
+      </>
     );
   }
 
-  render() {
-    const { status, task } = this.props;
+  const { task, status } = props;
 
-    if (!status.canBeExported) {
-      return this.renderWhenCanNotExport();
-    }
+  if (!status.canBeExported) {
+    return renderWhenCanNotExport();
+  }
 
-    if (task && task.status === 'PENDING') {
-      return this.renderWhenExportPending(task);
-    }
+  if (task && task.status === 'PENDING') {
+    return renderWhenExportPending(task);
+  }
 
-    if (task && task.status === 'IN_PROGRESS') {
-      return this.renderWhenExportInProgress(task);
-    }
+  if (task && task.status === 'IN_PROGRESS') {
+    return renderWhenExportInProgress(task);
+  }
 
-    if (task && task.status === 'FAILED') {
-      return this.renderWhenExportFailed();
-    }
+  if (task && task.status === 'FAILED') {
+    return renderWhenExportFailed();
+  }
 
-    const isDumpAvailable = Boolean(status.exportedDump);
+  const isDumpAvailable = Boolean(status.exportedDump);
 
-    return (
-      <div className="boxed-group" id="project-export">
-        {this.renderHeader()}
-        <div className="boxed-group-inner">
-          {isDumpAvailable && this.renderDump(task)}
-          {this.renderExport()}
-        </div>
+  return (
+    <>
+      {renderHeader()}
+      <div>
+        {isDumpAvailable && renderDump(task)}
+        {renderExport()}
       </div>
-    );
-  }
+    </>
+  );
 }
index a4782ae5f862545e77d9029afc3222bae9afffca..e9ca3fcceb73f93a633ee2da284090bacd38ca3c 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 { ButtonSecondary, FlagMessage, Link, Spinner } from 'design-system';
 import * as React from 'react';
 import { doImport } from '../../../api/project-dump';
-import Link from '../../../components/common/Link';
-import { Button } from '../../../components/controls/buttons';
 import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
-import { Alert } from '../../../components/ui/Alert';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getComponentBackgroundTaskUrl } from '../../../helpers/urls';
 import { DumpStatus, DumpTask } from '../../../types/project-dump';
@@ -39,73 +36,71 @@ interface Props {
   task?: DumpTask;
 }
 
-export default class Import extends React.Component<Props> {
-  handleImport = () => {
-    doImport(this.props.componentKey).then(this.props.loadStatus, () => {
+export default function Import(props: Readonly<Props>) {
+  const handleImport = () => {
+    doImport(props.componentKey).then(props.loadStatus, () => {
       /* no catch needed */
     });
   };
 
-  renderWhenCanNotImport() {
-    return (
-      <div className="boxed-group-inner" id="import-not-possible">
-        {translate('project_dump.can_not_import')}
-      </div>
-    );
+  function renderWhenCanNotImport() {
+    return <span>{translate('project_dump.can_not_import')}</span>;
   }
 
-  renderWhenNoDump() {
+  function renderWhenNoDump() {
     return (
-      <div className="boxed-group-inner">
-        <Alert id="import-no-file" variant="warning">
-          {translate('project_dump.no_file_to_import')}
-        </Alert>
-      </div>
+      <FlagMessage variant="warning">{translate('project_dump.no_file_to_import')}</FlagMessage>
     );
   }
 
-  renderImportForm() {
+  function renderImportForm() {
     return (
-      <div>
-        <div className="spacer-bottom">{translate('project_dump.import_form_description')}</div>
-        <Button onClick={this.handleImport}>{translate('project_dump.do_import')}</Button>
-      </div>
+      <>
+        <div className="sw-mt-4">{translate('project_dump.import_form_description')}</div>
+        <ButtonSecondary
+          aria-label={translate('project_dump.do_import')}
+          className="sw-mt-4"
+          onClick={handleImport}
+        >
+          {translate('project_dump.do_import')}
+        </ButtonSecondary>
+      </>
     );
   }
 
-  renderWhenImportSuccess(task: DumpTask) {
+  function renderWhenImportSuccess(task: DumpTask) {
     return (
-      <div className="boxed-group-inner">
+      <>
         {task.executedAt && (
           <DateTimeFormatter date={task.executedAt}>
             {(formatted) => (
-              <Alert variant="success">
+              <FlagMessage variant="success">
                 {translateWithParameters('project_dump.import_success', formatted)}
-              </Alert>
+              </FlagMessage>
             )}
           </DateTimeFormatter>
         )}
-      </div>
+      </>
     );
   }
 
-  renderWhenImportPending(task: DumpTask) {
+  function renderWhenImportPending(task: DumpTask) {
     return (
-      <div className="boxed-group-inner" id="import-pending">
-        <i className="spinner spacer-right" />
+      <>
+        <Spinner />
         <DateTimeFormatter date={task.submittedAt}>
           {(formatted) => (
             <span>{translateWithParameters('project_dump.pending_import', formatted)}</span>
           )}
         </DateTimeFormatter>
-      </div>
+      </>
     );
   }
 
-  renderWhenImportInProgress(task: DumpTask) {
+  function renderWhenImportInProgress(task: DumpTask) {
     return (
-      <div className="boxed-group-inner" id="import-in-progress">
-        <i className="spinner spacer-right" />
+      <>
+        <Spinner />
         {task.startedAt && (
           <DateFromNow date={task.startedAt}>
             {(fromNow) => (
@@ -113,12 +108,12 @@ export default class Import extends React.Component<Props> {
             )}
           </DateFromNow>
         )}
-      </div>
+      </>
     );
   }
 
-  renderWhenImportFailed() {
-    const { componentKey } = this.props;
+  function renderWhenImportFailed() {
+    const { componentKey } = props;
     const detailsUrl = getComponentBackgroundTaskUrl(
       componentKey,
       TaskStatuses.Failed,
@@ -126,56 +121,55 @@ export default class Import extends React.Component<Props> {
     );
 
     return (
-      <div className="boxed-group-inner">
-        <Alert id="export-in-progress" variant="error">
+      <div>
+        <FlagMessage variant="error">
           {translate('project_dump.failed_import')}
-          <Link className="spacer-left" to={detailsUrl}>
+          <Link className="sw-ml-1" to={detailsUrl}>
             {translate('project_dump.see_details')}
           </Link>
-        </Alert>
+        </FlagMessage>
 
-        {this.renderImportForm()}
+        {renderImportForm()}
       </div>
     );
   }
 
-  render() {
-    const { importEnabled, status, task, analysis } = this.props;
+  const { importEnabled, status, task, analysis } = props;
 
-    let content: React.ReactNode = null;
-    if (task && task.status === TaskStatuses.Success && !analysis) {
-      content = this.renderWhenImportSuccess(task);
-    } else if (task && task.status === TaskStatuses.Pending) {
-      content = this.renderWhenImportPending(task);
-    } else if (task && task.status === TaskStatuses.InProgress) {
-      content = this.renderWhenImportInProgress(task);
-    } else if (task && task.status === TaskStatuses.Failed) {
-      content = this.renderWhenImportFailed();
-    } else if (!status.canBeImported) {
-      content = this.renderWhenCanNotImport();
-    } else if (!status.dumpToImport) {
-      content = this.renderWhenNoDump();
-    } else {
-      content = <div className="boxed-group-inner">{this.renderImportForm()}</div>;
+  function renderContent(): React.ReactNode {
+    switch (task?.status) {
+      case TaskStatuses.Success:
+        if (!analysis) {
+          return renderWhenImportSuccess(task);
+        }
+        break;
+      case TaskStatuses.Pending:
+        return renderWhenImportPending(task);
+      case TaskStatuses.InProgress:
+        return renderWhenImportInProgress(task);
+      case TaskStatuses.Failed:
+        return renderWhenImportFailed();
+      default:
+        if (!status.canBeImported) {
+          return renderWhenCanNotImport();
+        } else if (!status.dumpToImport) {
+          return renderWhenNoDump();
+        }
+        return <div>{renderImportForm()}</div>;
     }
-    return (
-      <div
-        className={classNames('boxed-group', {
-          'import-disabled text-muted': !importEnabled,
-        })}
-        id="project-import"
-      >
-        <div className="boxed-group-header">
-          <h2>{translate('project_dump.import')}</h2>
-        </div>
-        {importEnabled ? (
-          content
-        ) : (
-          <div className="boxed-group-inner">
-            {translate('project_dump.import_form_description_disabled')}
-          </div>
-        )}
-      </div>
-    );
   }
+
+  return (
+    <>
+      <div className="sw-my-4">
+        <span className="sw-heading-md">{translate('project_dump.import')}</span>
+      </div>
+
+      {importEnabled ? (
+        renderContent()
+      ) : (
+        <div>{translate('project_dump.import_form_description_disabled')}</div>
+      )}
+    </>
+  );
 }
index 11517663e4b86905717f22c20e3e54a6cfde7497..1d505aed1a7accef5a18904585fbd95f39520bae 100644 (file)
@@ -3707,7 +3707,7 @@ project_dump.page.description_without_import=Export project issues, measures and
 project_dump.refresh=Refresh
 project_dump.see_details=See Details
 project_dump.export=Export
-project_dump.do_export=Export
+project_dump.do_export=Export Project
 project_dump.can_not_export=This project cannot be exported as no analysis has been run so far.
 project_dump.pending_export=Export was scheduled on {0}, waiting to be processed.
 project_dump.in_progress_export=Export is in progress, started {0}.
@@ -3716,7 +3716,7 @@ project_dump.latest_export_available=Latest project dump was generated on {0}. I
 project_dump.export_available=Project dump was generated. It can be found on the server, in the following directory:
 project_dump.export_form_description=Export the project to the file system. The export file will need to be manually copied to the target filesystem.
 project_dump.import=Import
-project_dump.do_import=Import
+project_dump.do_import=Import Project
 project_dump.import_success=The project has been successfully imported on {0}.
 project_dump.can_not_import=This project can not be imported because it already contains some data.
 project_dump.no_file_to_import=This project can not be imported because the dump file is not found.