]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19904 New UI for Other/local analysis tutorials
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 12 Jul 2023 13:55:28 +0000 (15:55 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 21 Jul 2023 20:03:16 +0000 (20:03 +0000)
31 files changed:
server/sonar-web/design-system/src/components/CodeSnippet.tsx
server/sonar-web/design-system/src/components/TutorialStep.tsx
server/sonar-web/design-system/src/components/__tests__/CodeSnippet-test.tsx
server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-it.tsx
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/components/CompilationInfo.tsx
server/sonar-web/src/main/js/components/tutorials/components/GradleBuildSelection.tsx
server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/tutorials/components/ProjectTokenScopeInfo.tsx
server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx
server/sonar-web/src/main/js/components/tutorials/components/Step.css [deleted file]
server/sonar-web/src/main/js/components/tutorials/components/Step.tsx
server/sonar-web/src/main/js/components/tutorials/other/BuildToolForm.tsx
server/sonar-web/src/main/js/components/tutorials/other/DoneNextSteps.tsx
server/sonar-web/src/main/js/components/tutorials/other/OtherTutorial.tsx
server/sonar-web/src/main/js/components/tutorials/other/ProjectAnalysisStep.tsx
server/sonar-web/src/main/js/components/tutorials/other/TokenStep.tsx
server/sonar-web/src/main/js/components/tutorials/other/__tests__/OtherTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetCore.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetExecute.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/DotNetFramework.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/DownloadBuildWrapper.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/DownloadScanner.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/ExecBuildWrapper.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/ExecScanner.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/JavaGradle.tsx
server/sonar-web/src/main/js/components/tutorials/other/commands/JavaMaven.tsx
server/sonar-web/src/main/js/components/tutorials/test-utils.ts
server/sonar-web/src/main/js/helpers/docs.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 25a184d0803d394f3b6279160a62b7856cdde166..4e8b57066d915396925673dab92b2373ec61a70e 100644 (file)
@@ -57,8 +57,11 @@ export function CodeSnippet(props: Props) {
 
   return (
     <Wrapper
+      as={isSimpleOneLine ? 'span' : undefined}
       className={classNames(
+        'sw-code',
         {
+          'sw-py-6': isOneLine && !noCopy,
           'code-snippet-highlighted-oneline': isOneLine,
           'code-snippet-simple-oneline': isSimpleOneLine,
         },
@@ -103,5 +106,5 @@ const StyledClipboardButton = styled(ClipboardButton)`
 `;
 
 const StyledSingleLineClipboardButton = styled(StyledClipboardButton)`
-  ${tw`sw-top-6 sw-bottom-6`}
+  ${tw`sw-top-4 sw-bottom-4`}
 `;
index b7cb8b626e15abdcec09132d4c769717de79dc87..7caf334ab8bc7c6ecbc870cd53a323cb38edc804 100644 (file)
@@ -23,14 +23,15 @@ import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
 
 interface Props {
   children: React.ReactNode;
+  stepNumber?: number;
   title: React.ReactNode;
 }
 
-export function TutorialStep(props: Props) {
+export function TutorialStep({ children, title, stepNumber }: Props) {
   return (
-    <Step>
-      <Title>{props.title}</Title>
-      <StepDetails>{props.children}</StepDetails>
+    <Step stepNumber={stepNumber}>
+      <Title>{title}</Title>
+      <StepDetails>{children}</StepDetails>
     </Step>
   );
 }
@@ -61,9 +62,9 @@ const Title = styled.h2`
   color: ${themeColor('pageTitle')};
 `;
 
-const Step = styled.li`
+const Step = styled.li<{ stepNumber?: number }>`
   list-style: none;
-  counter-increment: li;
+  counter-increment: li ${(props) => props.stepNumber};
 
   ${tw`sw-mt-10`}
 
index 87bca8a7bdc53638eb2466a1109c199ce2b48b9b..8b0aad26b4c6f912d349e65d4b8bcd72494eeb68 100644 (file)
@@ -33,7 +33,7 @@ it('should show full size when multiline with no editting', () => {
 it('should show reduced size when single line with no editting', () => {
   const { container } = setupWithProps({ isOneLine: true, snippet: 'foobar' });
   const copyButton = screen.getByRole('button', { name: 'Copy' });
-  expect(copyButton).toHaveStyle('top: 1.5rem');
+  expect(copyButton).toHaveStyle('top: 1rem');
   expect(container).toMatchSnapshot();
 });
 
index d857ec0c8cb6176e6ab31fb0215dc2a565335074..797de70738c5b974f9e4ef3055df22c720931c38 100644 (file)
@@ -168,7 +168,7 @@ exports[`should show full size when multiline with no editting 1`] = `
 
 <div>
   <div
-    class="fs-mask emotion-0 emotion-1"
+    class="sw-code fs-mask emotion-0 emotion-1"
   >
     <button
       aria-describedby="tooltip-1"
@@ -270,8 +270,8 @@ exports[`should show reduced size when single line with no editting 1`] = `
   right: 1.5rem;
   top: 1.5rem;
   position: absolute;
-  bottom: 1.5rem;
-  top: 1.5rem;
+  bottom: 1rem;
+  top: 1rem;
 }
 
 .emotion-4:hover,
@@ -379,7 +379,7 @@ exports[`should show reduced size when single line with no editting 1`] = `
 
 <div>
   <div
-    class="code-snippet-highlighted-oneline fs-mask emotion-0 emotion-1"
+    class="sw-code sw-py-6 code-snippet-highlighted-oneline fs-mask emotion-0 emotion-1"
   >
     <button
       aria-describedby="tooltip-2"
index d8404cc1e4b1ec7886c603c841527a4b646eb3fb..72f9263314824919656c89d090270f5771772c88 100644 (file)
@@ -187,7 +187,7 @@ async function startLocalTutorial(user: UserEvent) {
   await user.click(ui.chooseTutorialLink(TutorialModes.Local).get());
   await user.click(screen.getByRole('button', { name: 'onboarding.token.generate' }));
   await user.click(screen.getByRole('button', { name: 'continue' }));
-  await user.click(screen.getByRole('button', { name: 'onboarding.build.maven' }));
+  await user.click(screen.getByRole('radio', { name: 'onboarding.build.maven' }));
 }
 
 async function startJenkinsTutorial(user: UserEvent) {
index 9781cccea86df74a3d4bd60136c929829af5ec06..6080a73c7be649702208d8c59ff278f3219306d4 100644 (file)
@@ -27,7 +27,7 @@ import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
 import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils';
 import { Permissions } from '../../../../types/permissions';
 import { TokenType } from '../../../../types/token';
-import { getCopyToClipboardValue } from '../../test-utils';
+import { getCopyToClipboardValue, getTutorialBuildButtons } from '../../test-utils';
 import { OSs } from '../../types';
 import AzurePipelinesTutorial, { AzurePipelinesTutorialProps } from '../AzurePipelinesTutorial';
 
@@ -82,32 +82,32 @@ it('should render correctly and allow navigating between the different steps', a
   await goToNextStep(user);
 
   //// Analysis step: .NET
-  await clickButton(user, 'onboarding.build.dotnet');
+  await user.click(getTutorialBuildButtons().dotnetBuildButton.get());
   assertDotNetStepIsCorrectlyRendered();
 
   //// Analysis step: Maven
-  await clickButton(user, 'onboarding.build.maven');
+  await user.click(getTutorialBuildButtons().mavenBuildButton.get());
   assertMavenStepIsCorrectlyRendered();
 
   //// Analysis step: Gradle
-  await clickButton(user, 'onboarding.build.gradle');
+  await user.click(getTutorialBuildButtons().gradleBuildButton.get());
   assertGradleStepIsCorrectlyRendered();
 
   //// Analysis step: C Family
-  await clickButton(user, 'onboarding.build.cfamily');
+  await user.click(getTutorialBuildButtons().cFamilyBuildButton.get());
 
   // OS's
-  await clickButton(user, `onboarding.build.other.os.${OSs.Linux}`);
+  await user.click(getTutorialBuildButtons().linuxButton.get());
   assertCFamilyStepIsCorrectlyRendered(OSs.Linux);
 
-  await clickButton(user, `onboarding.build.other.os.${OSs.Windows}`);
+  await user.click(getTutorialBuildButtons().windowsButton.get());
   assertCFamilyStepIsCorrectlyRendered(OSs.Windows);
 
-  await clickButton(user, `onboarding.build.other.os.${OSs.MacOS}`);
+  await user.click(getTutorialBuildButtons().macosButton.get());
   assertCFamilyStepIsCorrectlyRendered(OSs.MacOS);
 
   //// Analysis step: Other
-  await clickButton(user, 'onboarding.build.other');
+  await user.click(getTutorialBuildButtons().otherBuildButton.get());
   assertOtherStepIsCorrectlyRendered();
 
   //// Finish tutorial
@@ -122,7 +122,7 @@ it('allows to navigate back to a previous step', async () => {
   // No clickable steps.
   expect(
     screen.queryByRole('button', {
-      name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
+      name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
     })
   ).not.toBeInTheDocument();
 
@@ -133,22 +133,22 @@ it('allows to navigate back to a previous step', async () => {
   // The first 2 steps become clickable.
   expect(
     screen.getByRole('button', {
-      name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
+      name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
     })
   ).toBeInTheDocument();
   expect(
     screen.getByRole('button', {
-      name: 'onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title',
+      name: 'onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.title',
     })
   ).toBeInTheDocument();
 
   // Navigate back to the first step.
-  await clickButton(user, 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title');
+  await clickButton(user, 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title');
 
   // No more clickable steps.
   expect(
     screen.queryByRole('button', {
-      name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
+      name: 'onboarding.tutorial.with.azure_pipelines.ExtensionInstallation.title',
     })
   ).not.toBeInTheDocument();
 });
@@ -161,10 +161,8 @@ it('should not offer CFamily analysis if the language is not available', async (
   await goToNextStep(user);
   await goToNextStep(user);
 
-  expect(screen.getByRole('button', { name: 'onboarding.build.dotnet' })).toBeInTheDocument();
-  expect(
-    screen.queryByRole('button', { name: 'onboarding.build.cfamily' })
-  ).not.toBeInTheDocument();
+  expect(getTutorialBuildButtons().dotnetBuildButton.get()).toBeInTheDocument();
+  expect(getTutorialBuildButtons().cFamilyBuildButton.query()).not.toBeInTheDocument();
 });
 
 function assertDefaultStepIsCorrectlyRendered() {
index d566e3f131e770d16a023ea29759eb19aa7b88cc..790508a38d151b32701e3c01449b021bdebf586b 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 { FlagMessage, Link } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { Alert } from '../../../components/ui/Alert';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
-import DocLink from '../../common/DocLink';
 
 export interface CompilationInfoProps {
   className?: string;
 }
 
-export function CompilationInfo({ className = 'spacer-top spacer-bottom' }: CompilationInfoProps) {
+export function CompilationInfo({ className = 'sw-my-2' }: CompilationInfoProps) {
+  const docUrl = useDocUrl();
+
   return (
-    <Alert className={className} variant="info">
-      <p className="spacer-bottom">
-        <FormattedMessage
-          id="onboarding.tutorial.cfamilly.compilation_database_info"
-          defaultMessage={translate('onboarding.tutorial.cfamilly.compilation_database_info')}
-          values={{
-            link: (
-              <DocLink to="/analyzing-source-code/languages/c-family/">
-                {translate('onboarding.tutorial.cfamilly.compilation_database_info.link')}
-              </DocLink>
-            ),
-          }}
-        />
-      </p>
-      <p>
-        <FormattedMessage
-          id="onboarding.tutorial.cfamilly.speed_caching"
-          defaultMessage={translate('onboarding.tutorial.cfamilly.speed_caching')}
-          values={{
-            link: (
-              <DocLink to="/analyzing-source-code/languages/c-family/#analysis-cache">
-                {translate('onboarding.tutorial.cfamilly.speed_caching.link')}
-              </DocLink>
-            ),
-          }}
-        />
-      </p>
-    </Alert>
+    <FlagMessage className={className} variant="info">
+      <div>
+        <p className="sw-mb-2">
+          <FormattedMessage
+            id="onboarding.tutorial.cfamilly.compilation_database_info"
+            defaultMessage={translate('onboarding.tutorial.cfamilly.compilation_database_info')}
+            values={{
+              link: (
+                <Link to={docUrl('/analyzing-source-code/languages/c-family/')}>
+                  {translate('onboarding.tutorial.cfamilly.compilation_database_info.link')}
+                </Link>
+              ),
+            }}
+          />
+        </p>
+        <p>
+          <FormattedMessage
+            id="onboarding.tutorial.cfamilly.speed_caching"
+            defaultMessage={translate('onboarding.tutorial.cfamilly.speed_caching')}
+            values={{
+              link: (
+                <Link to={docUrl('/analyzing-source-code/languages/c-family/#analysis-cache')}>
+                  {translate('onboarding.tutorial.cfamilly.speed_caching.link')}
+                </Link>
+              ),
+            }}
+          />
+        </p>
+      </div>
+    </FlagMessage>
   );
 }
index 4ed7e7639c1b53e898741bb0c3bd7b0ae7167454..bf5c67b4cd12c950de12e5f08687ec76d8b8fd21 100644 (file)
@@ -17,8 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { ToggleButton } from 'design-system';
 import React from 'react';
-import ButtonToggle from '../../controls/ButtonToggle';
 import { GradleBuildDSL } from '../types';
 
 interface Props {
@@ -37,10 +37,10 @@ export default function GradleBuildSelection({ children, className }: Props) {
   return (
     <>
       <div className={className}>
-        <ButtonToggle
+        <ToggleButton
           options={buildOptions}
           value={build}
-          onCheck={(value: GradleBuildDSL) => setBuild(value)}
+          onChange={(value: GradleBuildDSL) => setBuild(value)}
         />
       </div>
       {children(build)}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx b/server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx
new file mode 100644 (file)
index 0000000..d84392f
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { CodeSnippet } from 'design-system';
+import * as React from 'react';
+import { FCProps } from '../../../types/misc';
+
+export function InlineSnippet({ snippet }: Pick<FCProps<typeof CodeSnippet>, 'snippet'>) {
+  return (
+    <CodeSnippet className="sw-code sw-inline-block sw-px-1" noCopy isOneLine snippet={snippet} />
+  );
+}
index 12664f75a6f38b7ef348a5f164b014ea67aaee49..dd66c815e6d6db29085dbced31d47d2ea483c891 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import classNames from 'classnames';
+import { FlagMessage, Link } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
-import DocLink from '../../common/DocLink';
-import Link from '../../common/Link';
-import { Alert } from '../../ui/Alert';
 
 export interface ProjectTokenScopeInfoProps {
   className?: string;
 }
 
 export default function ProjectTokenScopeInfo({ className }: ProjectTokenScopeInfoProps) {
+  const docUrl = useDocUrl('/user-guide/user-account/generating-and-using-tokens/');
+
   return (
-    <Alert variant="info" className={classNames('spacer-top', className)}>
+    <FlagMessage variant="info" className={classNames('sw-mt-2', className)}>
       <FormattedMessage
+        tagName="span"
         defaultMessage={translate('onboarding.token.warning_project_token_scope')}
         id="onboarding.token.warning_project_token_scope"
         values={{
@@ -41,13 +43,9 @@ export default function ProjectTokenScopeInfo({ className }: ProjectTokenScopeIn
               {translate('onboarding.token.text.user_account')}
             </Link>
           ),
-          doc_link: (
-            <DocLink to="/user-guide/user-account/generating-and-using-tokens/">
-              {translate('documentation')}
-            </DocLink>
-          ),
+          doc_link: <Link to={docUrl}>{translate('documentation')}</Link>,
         }}
       />
-    </Alert>
+    </FlagMessage>
   );
 }
index 8eb27fa490b03944750d0f902dfe4ba41af2abd1..301008e4cbbc9f8edcf3dc262293f36185677344 100644 (file)
@@ -17,9 +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.
  */
+import { ToggleButton } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
-import ButtonToggle from '../../controls/ButtonToggle';
 
 export interface RenderOptionsProps {
   checked: string | undefined;
@@ -39,12 +39,12 @@ export default function RenderOptions({
   titleLabelKey,
 }: RenderOptionsProps) {
   return (
-    <div className="big-spacer-top">
-      {titleLabelKey && <h4 className="spacer-bottom">{translate(titleLabelKey)}</h4>}
+    <div className="sw-mt-4">
+      {titleLabelKey && <label className="sw-block sw-mb-1">{translate(titleLabelKey)}</label>}
 
-      <ButtonToggle
+      <ToggleButton
         label={label}
-        onCheck={onCheck}
+        onChange={onCheck}
         options={options.map((build) => ({
           label: translate(optionLabelKey, build),
           value: build,
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/Step.css b/server/sonar-web/src/main/js/components/tutorials/components/Step.css
deleted file mode 100644 (file)
index 2ba5339..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-.onboarding-step {
-  position: relative;
-  padding-left: 34px;
-  margin-bottom: var(--gridSize);
-}
-
-.onboarding-step.no-step-number {
-  padding-left: 0;
-}
-
-.onboarding-step:not(.is-open):not(.is-finished) {
-  opacity: 0.4;
-}
-
-.onboarding-step .boxed-group-actions {
-  height: var(--controlHeight);
-  line-height: var(--controlHeight);
-}
-
-.onboarding-step hr {
-  margin-left: -72px;
-}
-
-.onboarding-step-number {
-  position: absolute;
-  top: 15px;
-  left: 15px;
-  width: 24px;
-  height: 24px;
-  line-height: 24px;
-  border-radius: 24px;
-  background-color: #b9b9b9;
-  color: #fff;
-  font-size: var(--mediumFontSize);
-  text-align: center;
-}
-
-.onboarding-step.is-open .onboarding-step-number {
-  background-color: var(--darkBlue);
-}
-
-.onboarding-step.is-finished {
-  cursor: pointer;
-  outline: none;
-}
-
-.onboarding-step .markdown {
-  line-height: inherit;
-}
-
-.onboarding-step ol.list-styled,
-.onboarding-step ul.list-styled {
-  padding-left: 18px;
-}
-
-.onboarding-step ul.list-styled li {
-  margin-top: var(--gridSize);
-  margin-bottom: var(--gridSize);
-}
-
-.onboarding-step ol.list-styled > li {
-  position: relative;
-  counter-increment: li;
-  margin-bottom: calc(2 * var(--gridSize));
-}
-
-.onboarding-step ol.list-roman {
-  list-style: lower-roman;
-}
-.onboarding-step ol.list-roman > li::marker {
-  font-weight: 400;
-}
-
-.onboarding-step ul.list-alpha {
-  list-style: lower-alpha;
-}
-.onboarding-step ol.list-styled:not(.list-roman) > li::marker,
-.onboarding-step ul.list-alpha > li::marker {
-  font-weight: 500;
-}
-
-.onboarding-step ul.list-alpha > li,
-.onboarding-step ol.list-roman > li {
-  margin-bottom: var(--gridSize);
-}
index c1d371bb3e60e095327688e1075b79c063ef78bf..195e3261a7fc74974a1c607babda5deec192759e 100644 (file)
@@ -18,9 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-tabindex */
-import classNames from 'classnames';
+import styled from '@emotion/styled';
+import { Card, TutorialStep, TutorialStepList, themeBorder, themeColor } from 'design-system';
 import * as React from 'react';
-import './Step.css';
 
 interface Props {
   finished?: boolean;
@@ -32,13 +32,10 @@ interface Props {
   stepTitle: React.ReactNode;
 }
 
+const CLOSED_STEP_OPACITY = 0.4;
+
 export default function Step(props: Props) {
   const { finished, open, stepNumber, stepTitle } = props;
-  const className = classNames('boxed-group', 'onboarding-step', {
-    'is-open': open,
-    'is-finished': finished,
-    'no-step-number': stepNumber === undefined,
-  });
 
   const clickable = !open && finished && props.onOpen !== undefined;
 
@@ -50,18 +47,40 @@ export default function Step(props: Props) {
   };
 
   return (
-    <div
-      className={className}
+    <StyledCard
+      clickable={Boolean(clickable)}
+      className="sw-mb-2 sw-p-0"
       onClick={clickable ? handleClick : undefined}
       role={clickable ? 'button' : undefined}
       tabIndex={clickable ? 0 : undefined}
     >
-      {stepNumber !== undefined && <div className="onboarding-step-number">{stepNumber}</div>}
-      {!open && props.renderResult && props.renderResult()}
-      <div className="boxed-group-header">
-        <h2>{stepTitle}</h2>
+      <div
+        style={{ opacity: !open && !finished ? CLOSED_STEP_OPACITY : undefined }}
+        className="sw-flex sw-items-center sw-justify-between sw-px-6"
+      >
+        <TutorialStepList className="sw-flex-1">
+          <TutorialStep title={stepTitle} stepNumber={stepNumber}>
+            {open ? <div>{props.renderForm()}</div> : <div className="boxed-group-inner" />}
+          </TutorialStep>
+        </TutorialStepList>
+        {!open && props.renderResult && props.renderResult()}
       </div>
-      {open ? <div>{props.renderForm()}</div> : <div className="boxed-group-inner" />}
-    </div>
+    </StyledCard>
   );
 }
+
+const StyledCard = styled(Card)<{ clickable: boolean }>`
+  --focus: ${themeColor('buttonSecondaryBorder')};
+
+  ${({ clickable, theme }) =>
+    clickable &&
+    `
+    cursor: pointer; 
+
+
+    &:focus,
+    &:active {
+      outline: ${themeBorder('focus', 'var(--focus)')({ theme })}
+    }
+`};
+`;
index 75f9ddf9e9a41edbc0dd9dbf2062d73b9a120616..889231e06af68ff35cae1f422478c21263d6846e 100644 (file)
@@ -17,9 +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.
  */
+import { ToggleButton } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
-import ButtonToggle from '../../controls/ButtonToggle';
 import { withCLanguageFeature } from '../../hoc/withCLanguageFeature';
 import GithubCFamilyExampleRepositories from '../components/GithubCFamilyExampleRepositories';
 import RenderOptions from '../components/RenderOptions';
@@ -70,10 +70,10 @@ export class BuildToolForm extends React.PureComponent<Props, State> {
     return (
       <>
         <div>
-          <h4 className="spacer-bottom">{translate('onboarding.build')}</h4>
-          <ButtonToggle
+          <label className="sw-block sw-mb-1">{translate('onboarding.build')}</label>
+          <ToggleButton
             label={translate('onboarding.build')}
-            onCheck={this.handleBuildToolChange}
+            onChange={this.handleBuildToolChange}
             options={buildTools.map((tool) => ({
               label: translate('onboarding.build', tool),
               value: tool,
@@ -95,7 +95,7 @@ export class BuildToolForm extends React.PureComponent<Props, State> {
 
         {config.buildTool === BuildTools.CFamily && config.os && (
           <GithubCFamilyExampleRepositories
-            className="big-spacer-top abs-width-600"
+            className="sw-mt-4"
             os={config.os}
             ci={TutorialModes.Local}
           />
index a69a6e32bb5c264a947e9ad8e1b2d08c2c16d4ec..6f788da72e8c18fad38662007247345a3c9d99c2 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, FlagVisual, Link } from 'design-system';
 import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
 import { Component } from '../../../types/types';
-import DocLink from '../../common/DocLink';
 
 export interface DoneNextStepsProps {
   component: Component;
@@ -30,43 +30,49 @@ export interface DoneNextStepsProps {
 export default function DoneNextSteps({ component }: DoneNextStepsProps) {
   const isProjectAdmin = component.configuration?.showSettings;
 
+  const docUrl = useDocUrl();
+
   return (
     <>
-      <hr className="big-spacer-top big-spacer-bottom" />
+      <BasicSeparator className="sw-my-8" />
+
+      <div className="sw-flex sw-justify-center sw-mb-12">
+        <FlagVisual />
+      </div>
 
       <p>
-        <strong>{translate('onboarding.analysis.auto_refresh_after_analysis.done')}</strong>{' '}
+        <strong className="sw-font-semibold sw-mr-1">
+          {translate('onboarding.analysis.auto_refresh_after_analysis.done')}
+        </strong>
         {translate('onboarding.analysis.auto_refresh_after_analysis.auto_refresh')}
       </p>
-      <p className="big-spacer-top">
+      <p className="sw-mt-4">
         {isProjectAdmin
           ? translate('onboarding.analysis.auto_refresh_after_analysis.set_up_pr_deco_and_ci.admin')
           : translate('onboarding.analysis.auto_refresh_after_analysis.set_up_pr_deco_and_ci')}
       </p>
-      <p className="big-spacer-top">
-        <FormattedMessage
-          defaultMessage={translate(
-            'onboarding.analysis.auto_refresh_after_analysis.check_these_links'
-          )}
-          id="onboarding.analysis.auto_refresh_after_analysis.check_these_links"
-          values={{
-            link_branches: (
-              <DocLink to="/analyzing-source-code/branches/branch-analysis/">
-                {translate(
-                  'onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches'
-                )}
-              </DocLink>
-            ),
-            link_pr_analysis: (
-              <DocLink to="/analyzing-source-code/pull-request-analysis">
-                {translate(
-                  'onboarding.analysis.auto_refresh_after_analysis.check_these_links.pr_analysis'
-                )}
-              </DocLink>
-            ),
-          }}
-        />
-      </p>
+      <div className="sw-mt-4">
+        <span>
+          {translate('onboarding.analysis.auto_refresh_after_analysis.check_these_links')}
+        </span>
+        <ul className="sw-flex sw-flex-col sw-gap-2 sw-mt-2">
+          <li>
+            <Link to={docUrl('/analyzing-source-code/branches/branch-analysis/')}>
+              {translate(
+                'onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches'
+              )}
+            </Link>
+          </li>
+
+          <li>
+            <Link to={docUrl('/analyzing-source-code/pull-request-analysis')}>
+              {translate(
+                'onboarding.analysis.auto_refresh_after_analysis.check_these_links.pr_analysis'
+              )}
+            </Link>
+          </li>
+        </ul>
+      </div>
     </>
   );
 }
index 9e72b543429f43e3484d069cb2167a8bc0f38a18..eeaf570462f7ba8e2c8c7943a374c4af76f5c28f 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 { LightLabel, PageContentFontWrapper, Title } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 import { Component } from '../../../types/types';
 import { LoggedInUser } from '../../../types/users';
-import InstanceMessage from '../../common/InstanceMessage';
 import ProjectAnalysisStep from './ProjectAnalysisStep';
 import TokenStep from './TokenStep';
 
@@ -58,12 +58,10 @@ export default class OtherTutorial extends React.PureComponent<Props, State> {
     const { step, token } = this.state;
 
     return (
-      <>
-        <div className="page-header big-spacer-bottom">
-          <h2 className="page-title">{translate('onboarding.project_analysis.header')}</h2>
-          <p className="page-description">
-            <InstanceMessage message={translate('onboarding.project_analysis.description')} />
-          </p>
+      <PageContentFontWrapper className="sw-body-sm">
+        <div className="sw-mb-4">
+          <Title>{translate('onboarding.project_analysis.header')} </Title>
+          <LightLabel>{translate('onboarding.project_analysis.description')}</LightLabel>
         </div>
 
         <TokenStep
@@ -85,7 +83,7 @@ export default class OtherTutorial extends React.PureComponent<Props, State> {
           token={token}
           stepNumber={2}
         />
-      </>
+      </PageContentFontWrapper>
     );
   }
 }
index 2a39f6d634c1e5b3bae62b31f58ae4bccebe619b..c3d4d1726d3e5ced29bd232d687a203af7531057 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 { noop } from 'lodash';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 import { Component } from '../../../types/types';
@@ -53,38 +54,31 @@ export default class ProjectAnalysisStep extends React.PureComponent<Props, Stat
   renderForm = () => {
     const { component, baseUrl, isLocal, token } = this.props;
     return (
-      <div className="boxed-group-inner">
-        <div className="display-flex-column">
-          <BuildToolForm onDone={this.handleBuildToolSelect} />
+      <div className="sw-pb-4">
+        <BuildToolForm onDone={this.handleBuildToolSelect} />
 
-          {this.state.config && (
-            <div className="big-spacer-top">
-              <AnalysisCommand
-                component={component}
-                baseUrl={baseUrl}
-                isLocal={isLocal}
-                languageConfig={this.state.config}
-                token={token}
-              />
-            </div>
-          )}
-        </div>
+        {this.state.config && (
+          <div className="sw-mt-4">
+            <AnalysisCommand
+              component={component}
+              baseUrl={baseUrl}
+              isLocal={isLocal}
+              languageConfig={this.state.config}
+              token={token}
+            />
+          </div>
+        )}
       </div>
     );
   };
 
-  renderResult = () => null;
-
   render() {
     return (
       <Step
         finished={false}
-        onOpen={() => {
-          /* noop */
-        }}
+        onOpen={noop}
         open={this.props.open}
         renderForm={this.renderForm}
-        renderResult={this.renderResult}
         stepNumber={this.props.stepNumber}
         stepTitle={translate('onboarding.analysis.header')}
       />
index 9eb8e33cb6c01665439bd62ca68d1e7a0c8b3b9c..6766b7bc2790cd3b56c8e134fd5262f1ab4e34ac 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,
+  ButtonSecondary,
+  DeferredSpinner,
+  DestructiveIcon,
+  FlagMessage,
+  FlagSuccessIcon,
+  HelperHintIcon,
+  Highlight,
+  InputField,
+  InputSelect,
+  LabelValueSelectOption,
+  Link,
+  Note,
+  RadioButton,
+  TrashIcon,
+} from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { SingleValue } from 'react-select';
 import { generateToken, getTokens, revokeToken } from '../../../api/user-tokens';
 import { translate } from '../../../helpers/l10n';
 import {
@@ -29,12 +47,6 @@ import {
 import { TokenExpiration, TokenType, UserToken } from '../../../types/token';
 import { LoggedInUser } from '../../../types/users';
 import DocumentationTooltip from '../../common/DocumentationTooltip';
-import Link from '../../common/Link';
-import Radio from '../../controls/Radio';
-import Select from '../../controls/Select';
-import { Button, DeleteButton, SubmitButton } from '../../controls/buttons';
-import AlertErrorIcon from '../../icons/AlertErrorIcon';
-import AlertSuccessIcon from '../../icons/AlertSuccessIcon';
 import ProjectTokenScopeInfo from '../components/ProjectTokenScopeInfo';
 import Step from '../components/Step';
 import { getUniqueTokenName } from '../utils';
@@ -63,6 +75,11 @@ interface State {
 
 const TOKEN_FORMAT_REGEX = /^[_a-z0-9]+$/;
 
+enum TokenUse {
+  GENERATE = 'generate',
+  EXISTING = 'use-existing',
+}
+
 export default class TokenStep extends React.PureComponent<Props, State> {
   mounted = false;
 
@@ -71,7 +88,7 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     this.state = {
       existingToken: '',
       loading: false,
-      selection: 'generate',
+      selection: TokenUse.GENERATE,
       tokenName: props.initialTokenName,
       tokenExpiration: TokenExpiration.OneMonth,
       tokenExpirationOptions: EXPIRATION_OPTIONS,
@@ -105,14 +122,14 @@ export default class TokenStep extends React.PureComponent<Props, State> {
   }
 
   getToken = () =>
-    this.state.selection === 'generate' ? this.state.token : this.state.existingToken;
+    this.state.selection === TokenUse.GENERATE ? this.state.token : this.state.existingToken;
 
   canContinue = () => {
     const { existingToken, selection, token } = this.state;
     const validExistingToken = TOKEN_FORMAT_REGEX.exec(existingToken) != null;
     return (
-      (selection === 'generate' && token != null) ||
-      (selection === 'use-existing' && existingToken && validExistingToken)
+      (selection === TokenUse.GENERATE && token != null) ||
+      (selection === TokenUse.EXISTING && existingToken && validExistingToken)
     );
   };
 
@@ -120,8 +137,10 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     this.setState({ tokenName: event.target.value });
   };
 
-  handleTokenExpirationChange = ({ value }: { value: TokenExpiration }) => {
-    this.setState({ tokenExpiration: value });
+  handleTokenExpirationChange = (option: SingleValue<LabelValueSelectOption<TokenExpiration>>) => {
+    if (option) {
+      this.setState({ tokenExpiration: option.value });
+    }
   };
 
   handleTokenGenerate = async (event: React.FormEvent<HTMLFormElement>) => {
@@ -188,24 +207,24 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     return (
       <div>
         {tokens !== undefined && tokens.length > 0 ? (
-          <Radio
-            checked={selection === 'generate'}
+          <RadioButton
+            checked={selection === TokenUse.GENERATE}
             onCheck={this.handleModeChange}
-            value="generate"
+            value={TokenUse.GENERATE}
           >
             {translate('onboarding.token.generate', TokenType.Project)}
-          </Radio>
+          </RadioButton>
         ) : (
           translate('onboarding.token.generate', TokenType.Project)
         )}
-        {selection === 'generate' && (
-          <div className="big-spacer-top">
-            <form className="display-flex-center" onSubmit={this.handleTokenGenerate}>
-              <div className="display-flex-column">
-                <label className="h3" htmlFor="generate-token-input">
+        {selection === TokenUse.GENERATE && (
+          <div className="sw-mt-4">
+            <form className="sw-flex sw-items-center" onSubmit={this.handleTokenGenerate}>
+              <div className="sw-flex sw-flex-col">
+                <HighlightLabel className="sw-mb-2" htmlFor="generate-token-input">
                   {translate('onboarding.token.name.label')}
                   <DocumentationTooltip
-                    className="spacer-left"
+                    className="sw-ml-2"
                     content={translate('onboarding.token.name.help')}
                     links={[
                       {
@@ -213,41 +232,44 @@ export default class TokenStep extends React.PureComponent<Props, State> {
                         label: translate('learn_more'),
                       },
                     ]}
-                  />
-                </label>
-                <input
+                  >
+                    <HelperHintIcon />
+                  </DocumentationTooltip>
+                </HighlightLabel>
+                <InputField
                   id="generate-token-input"
                   autoFocus
-                  className="input-super-large spacer-right spacer-top text-middle"
                   onChange={this.handleTokenNameChange}
                   required
+                  size="large"
                   type="text"
                   value={tokenName ?? ''}
                 />
               </div>
-              <div className="display-flex-column spacer-left big-spacer-right">
-                <label htmlFor="token-select-expiration" className="h3">
+              <div className="sw-flex sw-flex-col sw-ml-4">
+                <HighlightLabel className="sw-mb-2" htmlFor="token-select-expiration">
                   {translate('users.tokens.expires_in')}
-                </label>
-                <div className="display-flex-center">
-                  <Select
+                </HighlightLabel>
+                <div className="sw-flex sw-items-center">
+                  <InputSelect
                     id="token-select-expiration"
-                    className="spacer-top abs-width-100 spacer-right"
+                    className="sw-w-abs-150 sw-mr-4"
                     isSearchable={false}
                     onChange={this.handleTokenExpirationChange}
                     options={tokenExpirationOptions}
+                    size="full"
                     value={tokenExpirationOptions.find(
                       (option) => option.value === tokenExpiration
                     )}
                   />
 
-                  {loading ? (
-                    <i className="spinner text-middle" />
-                  ) : (
-                    <SubmitButton className="text-middle spacer-top" disabled={!tokenName}>
-                      {translate('onboarding.token.generate')}
-                    </SubmitButton>
-                  )}
+                  <ButtonSecondary
+                    type="submit"
+                    disabled={!tokenName || loading}
+                    icon={<DeferredSpinner className="sw-mr-1" loading={loading} />}
+                  >
+                    {translate('onboarding.token.generate')}
+                  </ButtonSecondary>
                 </div>
               </div>
             </form>
@@ -263,20 +285,20 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     const validInput = !existingToken || TOKEN_FORMAT_REGEX.exec(existingToken) != null;
 
     return (
-      <div className="big-spacer-top">
-        <Radio
-          checked={this.state.selection === 'use-existing'}
+      <div className="sw-mt-4">
+        <RadioButton
+          checked={this.state.selection === TokenUse.EXISTING}
           onCheck={this.handleModeChange}
-          value="use-existing"
+          value={TokenUse.EXISTING}
         >
           {translate('onboarding.token.use_existing_token')}
-        </Radio>
-        {this.state.selection === 'use-existing' && (
-          <div className="big-spacer-top display-flex-column">
-            <label className="h3" htmlFor="existing-token-input">
+        </RadioButton>
+        {this.state.selection === TokenUse.EXISTING && (
+          <div className="sw-flex sw-flex-col sw-mt-4">
+            <HighlightLabel className="sw-mb-2" htmlFor="existing-token-input">
               {translate('onboarding.token.use_existing_token.label')}
               <DocumentationTooltip
-                className="spacer-left"
+                className="sw-ml-2"
                 content={translate('onboarding.token.use_existing_token.help')}
                 links={[
                   {
@@ -284,22 +306,24 @@ export default class TokenStep extends React.PureComponent<Props, State> {
                     label: translate('learn_more'),
                   },
                 ]}
-              />
-            </label>
-            <input
+              >
+                <HelperHintIcon />
+              </DocumentationTooltip>
+            </HighlightLabel>
+            <InputField
               id="existing-token-input"
               autoFocus
-              className="input-super-large spacer-right spacer-top text-middle"
               onChange={this.handleExisingTokenChange}
               required
+              isInvalid={!validInput}
+              size="large"
               type="text"
               value={this.state.existingToken}
             />
             {!validInput && (
-              <span className="text-danger">
-                <AlertErrorIcon className="little-spacer-right text-text-top" />
+              <FlagMessage className="sw-mt-2 sw-w-fit" variant="error">
                 {translate('onboarding.token.invalid_format')}
-              </span>
+              </FlagMessage>
             )}
           </div>
         )}
@@ -312,23 +336,23 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     const canUseExisting = tokens !== undefined && tokens.length > 0;
 
     return (
-      <div className="boxed-group-inner">
+      <div className="sw-p-4">
         {token != null ? (
-          <form onSubmit={this.handleTokenRevoke}>
-            <span className="text-middle">
+          <form className="sw-flex sw-items-center" onSubmit={this.handleTokenRevoke}>
+            <span>
               {tokenName}
               {': '}
+              <strong className="sw-font-semibold">{token}</strong>
             </span>
-            <strong className="spacer-right text-middle">{token}</strong>
-            {loading ? (
-              <i className="spinner text-middle" />
-            ) : (
-              <DeleteButton
-                className="button-small text-middle"
+
+            <DeferredSpinner className="sw-ml-3 sw-my-2" loading={loading}>
+              <DestructiveIcon
+                className="sw-ml-1"
+                Icon={TrashIcon}
                 aria-label={translate('onboarding.token.delete')}
                 onClick={this.handleTokenRevoke}
               />
-            )}
+            </DeferredSpinner>
           </form>
         ) : (
           <div>
@@ -337,7 +361,7 @@ export default class TokenStep extends React.PureComponent<Props, State> {
           </div>
         )}
 
-        <div className="note big-spacer-top width-50">
+        <Note as="div" className="sw-mt-6 sw-w-1/2">
           <FormattedMessage
             defaultMessage={translate('onboarding.token.text')}
             id="onboarding.token.text"
@@ -349,13 +373,13 @@ export default class TokenStep extends React.PureComponent<Props, State> {
               ),
             }}
           />
-        </div>
+        </Note>
 
         {this.canContinue() && (
-          <div className="big-spacer-top">
-            <Button className="js-continue" onClick={this.handleContinueClick}>
+          <div className="sw-mt-4">
+            <ButtonPrimary onClick={this.handleContinueClick}>
               {translate('continue')}
-            </Button>
+            </ButtonPrimary>
           </div>
         )}
       </div>
@@ -371,10 +395,12 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     }
 
     return (
-      <div className="boxed-group-actions display-flex-center">
-        <AlertSuccessIcon className="spacer-right" />
-        {selection === 'generate' && tokenName && `${tokenName}: `}
-        <strong>{token}</strong>
+      <div className="sw-flex sw-items-center">
+        <FlagSuccessIcon className="sw-mr-2" />
+        <span>
+          {selection === TokenUse.GENERATE && tokenName && `${tokenName}: `}
+          <strong className="sw-ml-1">{token}</strong>
+        </span>
       </div>
     );
   };
@@ -393,3 +419,8 @@ export default class TokenStep extends React.PureComponent<Props, State> {
     );
   }
 }
+
+// We need to pass 'htmlFor' to the label, but
+// using 'as' doesn't dynamically change the allowed props
+// https://github.com/emotion-js/emotion/issues/2266
+const HighlightLabel = Highlight.withComponent('label');
index e0cc60ca7840201bb623acb28e01e71ff97348d5..9a95eb678aa7fbd1fc82495fffbc3a3d74d3d697 100644 (file)
@@ -102,54 +102,62 @@ it('can choose build tools and copy provided settings', async () => {
 
   // Maven
   await user.click(ui.mavenBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('maven: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('maven: execute scanner');
 
   // Gradle
   await user.click(ui.gradleBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('gradle: sonarqube plugin');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('gradle: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('gradle: sonarqube plugin');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('gradle: execute scanner');
 
   // Dotnet - Core
   await user.click(ui.dotnetBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('dotnet core: install scanner globally');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('dotnet core: execute command 1');
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('dotnet core: execute command 2');
-  expect(getCopyToClipboardValue(3)).toMatchSnapshot('dotnet core: execute command 3');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(
+    'dotnet core: install scanner globally'
+  );
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('dotnet core: execute command 1');
+  expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot('dotnet core: execute command 2');
+  expect(getCopyToClipboardValue(3, 'Copy')).toMatchSnapshot('dotnet core: execute command 3');
 
   // Dotnet - Framework
   await user.click(ui.dotnetFrameworkButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('dotnet framework: execute command 1');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('dotnet framework: execute command 2');
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('dotnet framework: execute command 3');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('dotnet framework: execute command 1');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('dotnet framework: execute command 2');
+  expect(getCopyToClipboardValue(2, 'Copy')).toMatchSnapshot('dotnet framework: execute command 3');
 
   // C Family - Linux
   await user.click(ui.cFamilyBuildButton.get());
   await user.click(ui.linuxButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('cfamily linux: execute build wrapper');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('cfamily linux: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(
+    'cfamily linux: execute build wrapper'
+  );
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('cfamily linux: execute scanner');
 
   // C Family - Windows
   await user.click(ui.windowsButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('cfamily windows: execute build wrapper');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('cfamily windows: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(
+    'cfamily windows: execute build wrapper'
+  );
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('cfamily windows: execute scanner');
 
   // C Family - MacOS
   await user.click(ui.macosButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('cfamily macos: execute build wrapper');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('cfamily macos: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(
+    'cfamily macos: execute build wrapper'
+  );
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('cfamily macos: execute scanner');
 
   // Other - Linux
   await user.click(ui.otherBuildButton.get());
   await user.click(ui.linuxButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('other linux: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('other linux: execute scanner');
 
   // Other - Windows
   await user.click(ui.windowsButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('other windows: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('other windows: execute scanner');
 
   // Other - MacOS
   await user.click(ui.macosButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('other macos: execute scanner');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('other macos: execute scanner');
 });
 
 function renderOtherTutorial({
index ef3fae9e94308cb4ad96015a986fdf4a615cc207..16a472f38f5e304b45f20a89438b27086b7ff3fa 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 { CodeSnippet, FlagMessage, SubHeading } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../../helpers/l10n';
-import CodeSnippet from '../../../common/CodeSnippet';
-import { Alert } from '../../../ui/Alert';
 import { DotNetProps } from './DotNet';
 import DotNetExecute from './DotNetExecute';
 
@@ -35,16 +34,18 @@ export default function DotNetCore(props: DotNetProps) {
 
   return (
     <div>
-      <h4 className="huge-spacer-top spacer-bottom">
+      <SubHeading className="sw-mt-8 sw-mb-2">
         {translate('onboarding.analysis.dotnetcore.global')}
-      </h4>
-      <p className="big-spacer-top markdown">
-        {translate('onboarding.analysis.dotnetcore.global.text')}
-      </p>
-      <CodeSnippet snippet="dotnet tool install --global dotnet-sonarscanner" />
-      <Alert className="spacer-top" variant="info">
+      </SubHeading>
+      <p className="sw-mt-4">{translate('onboarding.analysis.dotnetcore.global.text')}</p>
+      <CodeSnippet
+        className="sw-px-4"
+        isOneLine
+        snippet="dotnet tool install --global dotnet-sonarscanner"
+      />
+      <FlagMessage className="sw-mt-2" variant="info">
         {translate('onboarding.analysis.dotnetcore.global.text.path')}
-      </Alert>
+      </FlagMessage>
       <DotNetExecute commands={commands} component={component} />
     </div>
   );
index 05b55dc4a299c398fabc05d2e9de3f2c56125f69..ab3b502e91791d3f4a311d200743ad8f76b69cc3 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 { CodeSnippet, Link, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
 import { Component } from '../../../../types/types';
-import CodeSnippet from '../../../common/CodeSnippet';
-import DocLink from '../../../common/DocLink';
 import InstanceMessage from '../../../common/InstanceMessage';
 import DoneNextSteps from '../DoneNextSteps';
 
@@ -32,27 +32,36 @@ export interface DotNetExecuteProps {
 }
 
 export default function DotNetExecute({ commands, component }: DotNetExecuteProps) {
+  const docUrl = useDocUrl();
+
   return (
     <>
-      <h4 className="huge-spacer-top spacer-bottom">
+      <SubHeading className="sw-mt-8 sw-mb-2">
         {translate('onboarding.analysis.sq_scanner.execute')}
-      </h4>
+      </SubHeading>
 
       <InstanceMessage message={translate('onboarding.analysis.msbuild.execute.text')}>
-        {(transformedMessage) => <p className="spacer-bottom markdown">{transformedMessage}</p>}
+        {(transformedMessage) => <p className="sw-mb-2">{transformedMessage}</p>}
       </InstanceMessage>
-      {commands.map((command, index) => (
-        <CodeSnippet key={index} snippet={command} />
+      {commands.map((command) => (
+        <CodeSnippet
+          className="sw-px-4"
+          key={command}
+          language="bash"
+          isOneLine
+          wrap
+          snippet={command}
+        />
       ))}
-      <p className="big-spacer-top markdown">
+      <p className="sw-mt-4">
         <FormattedMessage
           defaultMessage={translate('onboarding.analysis.docs')}
           id="onboarding.analysis.docs"
           values={{
             link: (
-              <DocLink to="/analyzing-source-code/scanners/sonarscanner-for-dotnet/">
+              <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner-for-dotnet/')}>
                 {translate('onboarding.analysis.msbuild.docs_link')}
-              </DocLink>
+              </Link>
             ),
           }}
         />
index 55362bb46e3b884381c64a61e01982d34f972723..7213d4c48b8bde2e5b730436112c1483a8e0923b 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 { Link, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
-import DocLink from '../../../common/DocLink';
+import { InlineSnippet } from '../../components/InlineSnippet';
 import { DotNetProps } from './DotNet';
 import DotNetExecute from './DotNetExecute';
 
 export default function DotNetFramework(props: DotNetProps) {
   const { baseUrl, component, token } = props;
 
+  const docUrl = useDocUrl();
+
   const commands = [
     `SonarScanner.MSBuild.exe begin /k:"${component.key}" /d:sonar.host.url="${baseUrl}" /d:sonar.token="${token}"`,
     'MsBuild.exe /t:Rebuild',
@@ -36,19 +40,19 @@ export default function DotNetFramework(props: DotNetProps) {
   return (
     <div>
       <div>
-        <h4 className="spacer-bottom huge-spacer-top">
+        <SubHeading className=" sw-mb-2 sw-mt-8">
           {translate('onboarding.analysis.msbuild.header')}
-        </h4>
-        <p className="markdown">
+        </SubHeading>
+        <p>
           <FormattedMessage
             defaultMessage={translate('onboarding.analysis.msbuild.text')}
             id="onboarding.analysis.msbuild.text"
             values={{
-              code: <code>%PATH%</code>,
+              code: <InlineSnippet snippet="%PATH%" />,
               link: (
-                <DocLink to="/analyzing-source-code/scanners/sonarscanner-for-dotnet/">
+                <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner-for-dotnet/')}>
                   {translate('onboarding.analysis.msbuild.docs_link')}
-                </DocLink>
+                </Link>
               ),
             }}
           />
index 99d603118434df9ab34f8b720aa218fa997c13c2..5c3afc372043113a6049faea4b76de1821edc758 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 { CodeSnippet, DownloadButton, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { translate } from '../../../../helpers/l10n';
 import { getBaseUrl } from '../../../../helpers/system';
-import CodeSnippet from '../../../common/CodeSnippet';
+import { InlineSnippet } from '../../components/InlineSnippet';
 import { OSs } from '../../types';
 
 export interface DownloadBuildWrapperProps {
@@ -39,33 +40,38 @@ const FILENAMES: { [x in OSs]: string } = {
 export default function DownloadBuildWrapper(props: DownloadBuildWrapperProps) {
   const { os, isLocal, baseUrl } = props;
   return (
-    <div className="spacer-bottom">
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.build_wrapper.header', os)}</h4>
+    <div className="sw-mb-4">
+      <SubHeading className="sw-mb-2">
+        {translate('onboarding.analysis.build_wrapper.header', os)}
+      </SubHeading>
       {isLocal ? (
         <>
-          <p className="spacer-bottom markdown">
+          <p className="sw-mb-2">
             <FormattedMessage
               defaultMessage={translate('onboarding.analysis.build_wrapper.text')}
               id="onboarding.analysis.build_wrapper.text"
               values={{
-                env_var: <code>{os === 'win' ? '%PATH%' : 'PATH'}</code>,
+                env_var: <InlineSnippet snippet={os === 'win' ? '%PATH%' : 'PATH'} />,
               }}
             />
           </p>
-          <p>
-            <a
-              className="button"
+          <p className="sw-mb-2">
+            <DownloadButton
               download={`${FILENAMES[os]}.zip`}
               href={`${getBaseUrl()}/static/cpp/${FILENAMES[os]}.zip`}
               rel="noopener noreferrer"
               target="_blank"
             >
               {translate('download_verb')}
-            </a>
+            </DownloadButton>
           </p>
         </>
       ) : (
-        <CodeSnippet snippet={getRemoteDownloadSnippet(os, baseUrl)} />
+        <CodeSnippet
+          className="sw-p-4"
+          language={os === OSs.Windows ? 'powershell' : 'bash'}
+          snippet={getRemoteDownloadSnippet(os, baseUrl)}
+        />
       )}
     </div>
   );
index 482ee6e9bced62541cde865a7a8ab5a917a05e40..93c8f98af14ef823fbec022aa3614a896b50ac04 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 {
+  ClipboardIconButton,
+  CodeSnippet,
+  Link,
+  NumberedList,
+  NumberedListItem,
+  SubHeading,
+} from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
-import CodeSnippet from '../../../common/CodeSnippet';
-import DocLink from '../../../common/DocLink';
-import { ClipboardButton } from '../../../controls/clipboard';
+import { InlineSnippet } from '../../components/InlineSnippet';
 import { OSs } from '../../types';
 
 export interface DownloadScannerProps {
@@ -34,43 +41,57 @@ export interface DownloadScannerProps {
 export default function DownloadScanner(props: DownloadScannerProps) {
   const { os, isLocal, token } = props;
 
+  const docUrl = useDocUrl();
+
   return (
-    <div>
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.sq_scanner.header', os)}</h4>
+    <div className="sw-mb-4">
+      <SubHeading className="sw-mb-2">
+        {translate('onboarding.analysis.sq_scanner.header', os)}
+      </SubHeading>
       {isLocal ? (
-        <p className="spacer-bottom markdown">
+        <p className="sw-mb-2">
           <FormattedMessage
             defaultMessage={translate('onboarding.analysis.sq_scanner.text')}
             id="onboarding.analysis.sq_scanner.text"
             values={{
-              dir: <code>bin</code>,
-              env_var: <code>{os === OSs.Windows ? '%PATH%' : 'PATH'}</code>,
+              dir: <InlineSnippet snippet="bin" />,
+              env_var: <InlineSnippet snippet={os === OSs.Windows ? '%PATH%' : 'PATH'} />,
               link: (
-                <DocLink to="/analyzing-source-code/scanners/sonarscanner/">
+                <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner/')}>
                   {translate('onboarding.analysis.sq_scanner.docs_link')}
-                </DocLink>
+                </Link>
               ),
             }}
           />
         </p>
       ) : (
         <>
-          <CodeSnippet snippet={getRemoteDownloadSnippet(os)} />
-          <h4 className="spacer-bottom big-spacer-top">
+          <CodeSnippet
+            className="sw-p-4"
+            wrap
+            language={os === OSs.Windows ? 'powershell' : 'bash'}
+            snippet={getRemoteDownloadSnippet(os)}
+            render={`<code>${getRemoteDownloadSnippet(os)}</code>`}
+          />
+          <SubHeading className="sw-mb-2 sw-mt-4">
             {translate('onboarding.analysis.sq_scanner.sonar_token_env.header')}
-          </h4>
-          <ul className="list-styled">
-            <li className="markdown">
-              {translate('onboarding.analysis.sq_scanner.sonar_token_env.var_name')}:{' '}
-              <code>SONAR_TOKEN</code>
-              <ClipboardButton className="spacer-left" copyValue="SONAR_TOKEN" />
-            </li>
-            <li className="markdown">
-              {translate('onboarding.analysis.sq_scanner.sonar_token_env.var_value')}:{' '}
-              <code>{token}</code>
-              <ClipboardButton className="spacer-left" copyValue={token} />
-            </li>
-          </ul>
+          </SubHeading>
+          <NumberedList>
+            <NumberedListItem className="sw-flex sw-items-center">
+              <span className="sw-mr-1">
+                {translate('onboarding.analysis.sq_scanner.sonar_token_env.var_name')}:
+              </span>
+              <InlineSnippet snippet="SONAR_TOKEN" />
+              <ClipboardIconButton className="sw-ml-2" copyValue="SONAR_TOKEN" />
+            </NumberedListItem>
+            <NumberedListItem className="sw-flex sw-items-center">
+              <span className="sw-mr-1">
+                {translate('onboarding.analysis.sq_scanner.sonar_token_env.var_value')}:
+              </span>
+              <InlineSnippet snippet={token} />
+              <ClipboardIconButton className="sw-ml-2" copyValue={token} />
+            </NumberedListItem>
+          </NumberedList>
         </>
       )}
     </div>
index 70d33a301af9bfd92b9511a5ce220c9846f5e1a6..4be52b108968809cb3489064ac7d6789a60c25bc 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 { CodeSnippet, Link, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
-import CodeSnippet from '../../../common/CodeSnippet';
-import DocLink from '../../../common/DocLink';
 import { OSs } from '../../types';
 
 export interface ExecBuildWrapperProps {
@@ -37,28 +37,30 @@ const executables: { [x in OSs]: string } = {
 export default function ExecBuildWrapper(props: ExecBuildWrapperProps) {
   const { os } = props;
 
+  const docUrl = useDocUrl();
+
   return (
     <>
-      <h4 className="huge-spacer-top spacer-bottom">
+      <SubHeading className="sw-mt-8 sw-mb-2">
         {translate('onboarding.analysis.build_wrapper.execute')}
-      </h4>
-      <p className="spacer-bottom markdown">
-        {translate('onboarding.analysis.build_wrapper.execute_text')}
-      </p>
+      </SubHeading>
+      <p className="sw-mb-2">{translate('onboarding.analysis.build_wrapper.execute_text')}</p>
       <CodeSnippet
+        className="sw-px-4"
+        isOneLine
         snippet={`${executables[os]} --out-dir bw-output ${translate(
           'onboarding.analysis.build_wrapper.execute_build_command'
         )}`}
       />
-      <p className="big-spacer-top markdown">
+      <p className="sw-mt-4">
         <FormattedMessage
           defaultMessage={translate('onboarding.analysis.build_wrapper.docs')}
           id="onboarding.analysis.build_wrapper.docs"
           values={{
             link: (
-              <DocLink to="/analyzing-source-code/languages/c-family/">
+              <Link to={docUrl('/analyzing-source-code/languages/c-family/')}>
                 {translate('onboarding.analysis.build_wrapper.docs_link')}
-              </DocLink>
+              </Link>
             ),
           }}
         />
index b37c57abbb90b5b8c44075ce4b5c79780f28cc9a..4303283a67c7144593d43f85c06e7e8da09094f5 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 { CodeSnippet, Link, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
 import { Component } from '../../../../types/types';
-import CodeSnippet from '../../../common/CodeSnippet';
-import DocLink from '../../../common/DocLink';
 import InstanceMessage from '../../../common/InstanceMessage';
 import { OSs } from '../../types';
 import { quote } from '../../utils';
@@ -40,6 +40,8 @@ export interface ExecScannerProps {
 export default function ExecScanner(props: ExecScannerProps) {
   const { baseUrl, os, isLocal, component, token, cfamily } = props;
 
+  const docUrl = useDocUrl();
+
   const q = quote(os);
   const command = [
     os === OSs.Windows ? 'sonar-scanner.bat' : 'sonar-scanner',
@@ -52,22 +54,22 @@ export default function ExecScanner(props: ExecScannerProps) {
 
   return (
     <div>
-      <h4 className="big-spacer-top spacer-bottom">
+      <SubHeading className="sw-mt-4 sw-mb-2">
         {translate('onboarding.analysis.sq_scanner.execute')}
-      </h4>
+      </SubHeading>
       <InstanceMessage message={translate('onboarding.analysis.sq_scanner.execute.text')}>
-        {(transformedMessage) => <p className="spacer-bottom markdown">{transformedMessage}</p>}
+        {(transformedMessage) => <p className="sw-mb-2">{transformedMessage}</p>}
       </InstanceMessage>
-      <CodeSnippet isOneLine={os === OSs.Windows} snippet={command} />
-      <p className="big-spacer-top markdown">
+      <CodeSnippet className="sw-p-4" isOneLine={os === OSs.Windows} snippet={command} />
+      <p className="sw-mt-4">
         <FormattedMessage
           defaultMessage={translate('onboarding.analysis.sq_scanner.docs')}
           id="onboarding.analysis.sq_scanner.docs"
           values={{
             link: (
-              <DocLink to="/analyzing-source-code/scanners/sonarscanner/">
+              <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner/')}>
                 {translate('onboarding.analysis.sq_scanner.docs_link')}
-              </DocLink>
+              </Link>
             ),
           }}
         />
index d4d21baa62b7a42fd7cb59cc9619db3f3f684f2b..bc21ba6098b775649c4000438a56c3314fd1e62f 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 { CodeSnippet, Link, Note, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { GRADLE_SCANNER_VERSION } from '../../../../helpers/constants';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
 import { Component } from '../../../../types/types';
-import CodeSnippet from '../../../common/CodeSnippet';
-import DocLink from '../../../common/DocLink';
 import InstanceMessage from '../../../common/InstanceMessage';
 import GradleBuildSelection from '../../components/GradleBuildSelection';
+import { InlineSnippet } from '../../components/InlineSnippet';
 import { GradleBuildDSL } from '../../types';
 import DoneNextSteps from '../DoneNextSteps';
 
@@ -36,17 +37,25 @@ export interface JavaGradleProps {
 }
 
 const config = {
-  [GradleBuildDSL.Groovy]: `plugins {
+  [GradleBuildDSL.Groovy]: {
+    lang: 'groovy',
+    snippet: `plugins {
   id "org.sonarqube" version "${GRADLE_SCANNER_VERSION}"
 }`,
-  [GradleBuildDSL.Kotlin]: `plugins {
+  },
+  [GradleBuildDSL.Kotlin]: {
+    lang: 'kts',
+    snippet: `plugins {
   id("org.sonarqube") version "${GRADLE_SCANNER_VERSION}"
 }`,
+  },
 };
 
 export default function JavaGradle(props: JavaGradleProps) {
   const { baseUrl, component, token } = props;
 
+  const docUrl = useDocUrl();
+
   const command = [
     './gradlew sonar',
     `-Dsonar.projectKey=${component.key}`,
@@ -57,53 +66,59 @@ export default function JavaGradle(props: JavaGradleProps) {
 
   return (
     <div>
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.java.gradle.header')}</h4>
+      <SubHeading className="sw-mb-2">
+        {translate('onboarding.analysis.java.gradle.header')}
+      </SubHeading>
       <InstanceMessage message={translate('onboarding.analysis.java.gradle.text.1')}>
         {(transformedMessage) => (
-          <p className="spacer-bottom markdown">
+          <p className="sw-mb-2">
             <FormattedMessage
               defaultMessage={transformedMessage}
               id="onboarding.analysis.java.gradle.text.1"
               values={{
-                plugin_code: <code>org.sonarqube</code>,
-                groovy: <code>{GradleBuildDSL.Groovy}</code>,
-                kotlin: <code>{GradleBuildDSL.Kotlin}</code>,
+                plugin_code: <InlineSnippet snippet="org.sonarqube" />,
+                groovy: <InlineSnippet snippet={GradleBuildDSL.Groovy} />,
+                kotlin: <InlineSnippet snippet={GradleBuildDSL.Kotlin} />,
               }}
             />
           </p>
         )}
       </InstanceMessage>
-      <GradleBuildSelection className="big-spacer-top big-spacer-bottom">
-        {(build) => <CodeSnippet snippet={config[build]} />}
+      <GradleBuildSelection className="sw-mt-4 sw-mb-4">
+        {(build) => (
+          <CodeSnippet
+            language={config[build].lang}
+            className="sw-p-4"
+            snippet={config[build].snippet}
+          />
+        )}
       </GradleBuildSelection>
-      <p className="big-spacer-bottom markdown">
-        <em className="small text-muted">
+      <p className="sw-mb-4">
+        <Note as="em">
           <FormattedMessage
             defaultMessage={translate('onboarding.analysis.java.gradle.latest_version')}
             id="onboarding.analysis.java.gradle.latest_version"
             values={{
               link: (
-                <DocLink to="/analyzing-source-code/scanners/sonarscanner-for-gradle/">
+                <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner-for-gradle/')}>
                   {translate('here')}
-                </DocLink>
+                </Link>
               ),
             }}
           />
-        </em>
-      </p>
-      <p className="spacer-top spacer-bottom markdown">
-        {translate('onboarding.analysis.java.gradle.text.2')}
+        </Note>
       </p>
-      <CodeSnippet snippet={command} />
-      <p className="big-spacer-top markdown">
+      <p className="sw-mt-2 sw-mb-2">{translate('onboarding.analysis.java.gradle.text.2')}</p>
+      <CodeSnippet className="sw-p-4" snippet={command} />
+      <p className="sw-mt-4">
         <FormattedMessage
           defaultMessage={translate('onboarding.analysis.docs')}
           id="onboarding.analysis.docs"
           values={{
             link: (
-              <DocLink to="/analyzing-source-code/scanners/sonarscanner-for-gradle/">
+              <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner-for-gradle/')}>
                 {translate('onboarding.analysis.java.gradle.docs_link')}
-              </DocLink>
+              </Link>
             ),
           }}
         />
index aa4ce669b68a0a589b2464d2f271b2b457b7b138..fc58bd1a242559dbdca082c6bb83677829b3a143 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 { CodeSnippet, Link, SubHeading } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { useDocUrl } from '../../../../helpers/docs';
 import { translate } from '../../../../helpers/l10n';
 import { Component } from '../../../../types/types';
-import CodeSnippet from '../../../common/CodeSnippet';
-import DocLink from '../../../common/DocLink';
 import InstanceMessage from '../../../common/InstanceMessage';
 import DoneNextSteps from '../DoneNextSteps';
 
@@ -42,22 +42,26 @@ export default function JavaMaven(props: JavaMavenProps) {
     `-Dsonar.token=${token}`,
   ];
 
+  const docUrl = useDocUrl();
+
   return (
     <div>
-      <h4 className="spacer-bottom">{translate('onboarding.analysis.java.maven.header')}</h4>
-      <p className="spacer-bottom markdown">
+      <SubHeading className="sw-mb-2">
+        {translate('onboarding.analysis.java.maven.header')}
+      </SubHeading>
+      <p className="sw-mb-2">
         <InstanceMessage message={translate('onboarding.analysis.java.maven.text')} />
       </p>
-      <CodeSnippet snippet={command} />
-      <p className="big-spacer-top markdown">
+      <CodeSnippet className="sw-p-4" snippet={command} />
+      <p className="sw-mt-4">
         <FormattedMessage
           defaultMessage={translate('onboarding.analysis.docs')}
           id="onboarding.analysis.docs"
           values={{
             link: (
-              <DocLink to="/analyzing-source-code/scanners/sonarscanner-for-maven/">
+              <Link to={docUrl('/analyzing-source-code/scanners/sonarscanner-for-maven/')}>
                 {translate('onboarding.analysis.java.maven.docs_link')}
-              </DocLink>
+              </Link>
             ),
           }}
         />
index 0ccebaa586a82ad8501fb4ccc5470aed6d7d6751..760ee7d2d95a5c987877c46394c2b189821e800d 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { screen } from '@testing-library/react';
-import { byRole, byText } from '../../helpers/testSelector';
+import { byLabelText, byRole, byText } from '../../helpers/testSelector';
 import { BuildTools, GradleBuildDSL, OSs, TutorialModes } from './types';
 
 const CI_TRANSLATE_MAP: Partial<Record<TutorialModes, string>> = {
@@ -67,30 +67,30 @@ export function getTutorialActionButtons() {
 
 export function getTutorialBuildButtons() {
   return {
-    describeBuildTitle: byRole('heading', { name: 'onboarding.build' }),
-    mavenBuildButton: byRole('button', { name: `onboarding.build.${BuildTools.Maven}` }),
-    gradleBuildButton: byRole('button', { name: `onboarding.build.${BuildTools.Gradle}` }),
-    gradleDSLButton: (name: GradleBuildDSL) => byRole('button', { name }),
-    dotnetBuildButton: byRole('button', { name: `onboarding.build.${BuildTools.DotNet}` }),
-    cFamilyBuildButton: byRole('button', { name: `onboarding.build.${BuildTools.CFamily}` }),
-    otherBuildButton: byRole('button', { name: `onboarding.build.${BuildTools.Other}` }),
-    windowsDotnetCoreButton: byRole('button', {
+    describeBuildTitle: byLabelText('onboarding.build'),
+    mavenBuildButton: byRole('radio', { name: `onboarding.build.${BuildTools.Maven}` }),
+    gradleBuildButton: byRole('radio', { name: `onboarding.build.${BuildTools.Gradle}` }),
+    gradleDSLButton: (name: GradleBuildDSL) => byRole('radio', { name }),
+    dotnetBuildButton: byRole('radio', { name: `onboarding.build.${BuildTools.DotNet}` }),
+    cFamilyBuildButton: byRole('radio', { name: `onboarding.build.${BuildTools.CFamily}` }),
+    otherBuildButton: byRole('radio', { name: `onboarding.build.${BuildTools.Other}` }),
+    windowsDotnetCoreButton: byRole('radio', {
       name: `onboarding.build.${BuildTools.DotNet}.win_core`,
     }),
-    windowsDotnetFrameworkButton: byRole('button', {
+    windowsDotnetFrameworkButton: byRole('radio', {
       name: `onboarding.build.${BuildTools.DotNet}.win_msbuild`,
     }),
-    linuxDotnetCoreButton: byRole('button', {
+    linuxDotnetCoreButton: byRole('radio', {
       name: `onboarding.build.${BuildTools.DotNet}.linux_core`,
     }),
-    dotnetCoreButton: byRole('button', {
+    dotnetCoreButton: byRole('radio', {
       name: `onboarding.build.${BuildTools.DotNet}.variant.dotnet_core`,
     }),
-    dotnetFrameworkButton: byRole('button', {
+    dotnetFrameworkButton: byRole('radio', {
       name: `onboarding.build.${BuildTools.DotNet}.variant.dotnet_framework`,
     }),
-    linuxButton: byRole('button', { name: `onboarding.build.other.os.${OSs.Linux}` }),
-    windowsButton: byRole('button', { name: `onboarding.build.other.os.${OSs.Windows}` }),
-    macosButton: byRole('button', { name: `onboarding.build.other.os.${OSs.MacOS}` }),
+    linuxButton: byRole('radio', { name: `onboarding.build.other.os.${OSs.Linux}` }),
+    windowsButton: byRole('radio', { name: `onboarding.build.other.os.${OSs.Windows}` }),
+    macosButton: byRole('radio', { name: `onboarding.build.other.os.${OSs.MacOS}` }),
   };
 }
index 1e106d55f241623aa39a24b243d528cfd0a4e58b..50bf54b6dc4f6e5ad2fc1593bb2740316138c5b9 100644 (file)
@@ -29,8 +29,14 @@ export function getUrlForDoc(url: string, version: string, to: string) {
     : `${url}/${path}`;
 }
 
-export function useDocUrl(to: string) {
+export function useDocUrl(to: string): string;
+export function useDocUrl(): (to: string) => string;
+export function useDocUrl(to?: string) {
   const { version, documentationUrl } = React.useContext(AppStateContext);
 
-  return getUrlForDoc(documentationUrl, version, to);
+  if (to) {
+    return getUrlForDoc(documentationUrl, version, to);
+  }
+
+  return (to: string) => getUrlForDoc(documentationUrl, version, to);
 }
index c9245f0e66cb4d7a660d652b006b3f71abf8b8d9..702f5af40955cc547046d9f24839ff18b267beab 100644 (file)
@@ -3817,7 +3817,7 @@ onboarding.alm.bitbucketcloud=Bitbucket Cloud
 onboarding.alm.gitlab=GitLab
 
 onboarding.project_analysis.header=Analyze your project
-onboarding.project_analysis.description=We initialized your project on {instance}, now it's up to you to launch analyses!
+onboarding.project_analysis.description=We initialized your project on SonarQube, now it's up to you to launch analyses!
 onboarding.project_analysis.guide_to_integrate_pipelines=follow the guide to integrating with Pipelines
 
 onboarding.create_project.setup_manually=Create a project
@@ -3983,7 +3983,7 @@ onboarding.analysis.auto_refresh_after_analysis.done=Is my analysis done?
 onboarding.analysis.auto_refresh_after_analysis.auto_refresh=If your analysis is successful, this page will automatically refresh in a few moments.
 onboarding.analysis.auto_refresh_after_analysis.set_up_pr_deco_and_ci.admin=You can set up Pull Request Decoration under the project settings. To set up analysis with your favorite CI tool, see the tutorials.
 onboarding.analysis.auto_refresh_after_analysis.set_up_pr_deco_and_ci=You can request from a project administrator to set up Pull Request Decoration. To set up analysis with your favorite CI tool, see the tutorials.
-onboarding.analysis.auto_refresh_after_analysis.check_these_links=Check these useful links while you wait: {link_branches}, {link_pr_analysis}.
+onboarding.analysis.auto_refresh_after_analysis.check_these_links=Check these useful links while you wait:
 onboarding.analysis.auto_refresh_after_analysis.check_these_links.pr_analysis=Pull Request Analysis
 onboarding.analysis.auto_refresh_after_analysis.check_these_links.branches=Branch Analysis