]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19906 Migrating github action tutorial page to MIUI
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Fri, 14 Jul 2023 14:50:17 +0000 (16:50 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 21 Jul 2023 20:03:16 +0000 (20:03 +0000)
28 files changed:
server/sonar-web/config/jest/SetupTestEnvironment.ts
server/sonar-web/package.json
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx
server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx
server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx
server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx
server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx
server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx
server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx
server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx
server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx
server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx
server/sonar-web/src/main/js/components/tutorials/test-utils.ts
server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts [new file with mode: 0644]
server/sonar-web/yarn.lock
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 543508551066199adddf53784ed762579eeafeb4..a10eb5dd7e379258bde9fc406a85a41f3b534026 100644 (file)
@@ -21,6 +21,12 @@ import React from 'react';
 
 (window as any).React = React;
 
+const MockObserver = {
+  observe: jest.fn(),
+  unobserve: jest.fn(),
+  disconnect: jest.fn(),
+};
+
 const content = document.createElement('div');
 content.id = 'content';
 document.documentElement.appendChild(content);
@@ -37,3 +43,10 @@ jest.mock('../../src/main/js/helpers/l10n', () => ({
   translateWithParameters: (messageKey: string, ...parameters: Array<string | number>) =>
     [messageKey, ...parameters].join('.'),
 }));
+
+const MockIntersectionObserverEntries = [{ isIntersecting: true }];
+
+(window as any).IntersectionObserver = jest.fn().mockImplementation((callback) => {
+  callback(MockIntersectionObserverEntries, MockObserver);
+  return MockObserver;
+});
index 6d42f3de7bb4657246ec11bc8696a96e8d4efc20..dfdd3cf70929883696a5b7ed641b3e0e0a4e354c 100644 (file)
@@ -11,6 +11,8 @@
     "@emotion/react": "11.11.1",
     "@emotion/styled": "11.11.0",
     "@primer/octicons-react": "19.3.0",
+    "@react-spring/rafz": "9.7.3",
+    "@react-spring/web": "9.7.3",
     "@tanstack/react-query": "4.29.14",
     "classnames": "2.3.2",
     "clipboard": "2.0.11",
index 6080a73c7be649702208d8c59ff278f3219306d4..541a20afae0d609d2f4d4f4c541dc4d2cd9d379e 100644 (file)
@@ -216,7 +216,7 @@ function assertOtherStepIsCorrectlyRendered() {
 function assertFinishStepIsCorrectlyRendered() {
   expect(
     screen.getByRole('heading', {
-      name: 'onboarding.tutorial.ci_outro.all_set.title',
+      name: 'onboarding.tutorial.ci_outro.done',
     })
   ).toBeInTheDocument();
 }
index 0a1285123a27bc8c90e67758ca54c9afaf5ea164..07691d586b8821ca0e12f8508335a48eb56c3e8b 100644 (file)
@@ -28,7 +28,7 @@ import {
 } from '../../../../helpers/mocks/alm-settings';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
-import { renderApp, RenderContext } from '../../../../helpers/testReactTestingUtils';
+import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils';
 import { AlmKeys } from '../../../../types/alm-settings';
 import { Feature } from '../../../../types/features';
 import {
@@ -75,28 +75,28 @@ it('should follow and complete all steps', async () => {
   // Create/update configuration file step
   // Maven
   await user.click(ui.mavenBuildButton.get());
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('Maven: bitbucket-pipelines.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Maven: bitbucket-pipelines.yml');
 
   // Gradle
   await user.click(ui.gradleBuildButton.get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('Groovy: build.gradle');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Groovy: build.gradle');
   await user.click(ui.gradleDSLButton(GradleBuildDSL.Kotlin).get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('Kotlin: build.gradle.kts');
-  expect(getCopyToClipboardValue(4)).toMatchSnapshot('Gradle: bitbucket-pipelines.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Kotlin: build.gradle.kts');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Gradle: bitbucket-pipelines.yml');
 
   // .NET
   await user.click(ui.dotnetBuildButton.get());
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('.NET: bitbucket-pipelines.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('.NET: bitbucket-pipelines.yml');
 
   // CFamily
   await user.click(ui.cFamilyBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('CFamily: sonar-project.properties');
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('CFamily: bitbucket-pipelines.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('CFamily: sonar-project.properties');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('CFamily: bitbucket-pipelines.yml');
 
   // Other
   await user.click(ui.otherBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('Other: sonar-project.properties');
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('Other: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Other: sonar-project.properties');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Other: .github/workflows/build.yml');
 
   await user.click(ui.finishTutorialButton.get());
   expect(ui.allSetSentence.get()).toBeInTheDocument();
index afe2f20d884801b3f495b4b327c2ff1cf894c104..b3a6b0e2ece4edf33e4ba8c9e00ed7fcb771e888 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 styled from '@emotion/styled';
+import { animated, config, useSpring } from '@react-spring/web';
+import { CheckIcon, FlagVisual, SubTitle } from 'design-system';
 import * as React from 'react';
 import withAvailableFeatures, {
   WithAvailableFeaturesProps,
 } from '../../../app/components/available-features/withAvailableFeatures';
 import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
+import useIntersectionObserver from '../../../hooks/useIntersectionObserver';
 import { AlmKeys } from '../../../types/alm-settings';
 import { Feature } from '../../../types/features';
-import SentenceWithHighlights from './SentenceWithHighlights';
 
 export interface AllSetProps extends WithAvailableFeaturesProps {
   alm: AlmKeys;
@@ -33,65 +35,53 @@ export interface AllSetProps extends WithAvailableFeaturesProps {
 }
 
 export function AllSet(props: AllSetProps) {
+  const outroRef = React.useRef<HTMLDivElement>(null);
   const { alm, willRefreshAutomatically } = props;
   const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
 
+  const intersectionEntry = useIntersectionObserver(outroRef, { freezeOnceVisible: true });
+
+  const outroAnimation = useSpring({
+    from: { top: '200px' },
+    to: intersectionEntry?.isIntersecting ? { top: '0px' } : { top: '200px' },
+    config: config.wobbly,
+  });
+
   return (
-    <>
-      <div className="abs-width-600">
-        <p className="big-spacer-bottom">
-          <SentenceWithHighlights
-            highlightKeys={['all_set']}
-            translationKey="onboarding.tutorial.ci_outro.all_set"
-          />
+    <animated.div
+      className="sw-flex sw-flex-col sw-items-center sw-relative"
+      ref={outroRef}
+      style={outroAnimation}
+    >
+      <FlagVisual />
+      <SubTitle className="sw-mt-3 sw-mb-12">
+        {translate('onboarding.tutorial.ci_outro.done')}
+      </SubTitle>
+      <MessageContainer>
+        <p className="sw-body-sm sw-mb-4">
+          {translate('onboarding.tutorial.ci_outro.refresh_text')}
         </p>
-        <div className="display-flex-row big-spacer-bottom">
-          <div>
-            <img
-              alt="" // Should be ignored by screen readers
-              className="big-spacer-right"
-              width={30}
-              src={`${getBaseUrl()}/images/tutorials/commit.svg`}
-            />
-          </div>
-          <div>
-            <p className="little-spacer-bottom">
-              <strong>{translate('onboarding.tutorial.ci_outro.commit')}</strong>
-            </p>
-            <p>
-              {branchSupportEnabled
-                ? translate('onboarding.tutorial.ci_outro.commit.why', alm)
-                : translate('onboarding.tutorial.ci_outro.commit.why.no_branches')}
-            </p>
-          </div>
-        </div>
-        {willRefreshAutomatically && (
-          <div className="display-flex-row">
-            <div>
-              <img
-                alt="" // Should be ignored by screen readers
-                className="big-spacer-right"
-                width={30}
-                src={`${getBaseUrl()}/images/tutorials/refresh.svg`}
-              />
-            </div>
-            <div>
-              <p className="little-spacer-bottom">
-                <strong>{translate('onboarding.tutorial.ci_outro.refresh')}</strong>
-              </p>
-              <p>{translate('onboarding.tutorial.ci_outro.refresh.why')}</p>
-            </div>
-          </div>
-        )}
-      </div>
-      {willRefreshAutomatically && (
-        <div className="huge-spacer-bottom huge-spacer-top big-padded-top text-muted display-flex-center display-flex-justify-center">
-          <i className="spinner spacer-right" />
-          {translate('onboarding.tutorial.ci_outro.waiting_for_fist_analysis')}
-        </div>
-      )}
-    </>
+        <ul className="sw-mb-6">
+          <li className="sw-mb-4 sw-flex sw-items-center">
+            <CheckIcon className="sw-mr-2" />
+            {branchSupportEnabled
+              ? translate('onboarding.tutorial.ci_outro.commit.why', alm)
+              : translate('onboarding.tutorial.ci_outro.commit.why.no_branches')}
+          </li>
+          {willRefreshAutomatically && (
+            <li className="sw-mb-4 sw-flex sw-items-center">
+              <CheckIcon className="sw-mr-2" />
+              {translate('onboarding.tutorial.ci_outro.refresh.why')}
+            </li>
+          )}
+        </ul>
+      </MessageContainer>
+    </animated.div>
   );
 }
 
+const MessageContainer = styled.div`
+  width: 840px;
+`;
+
 export default withAvailableFeatures(AllSet);
index 26a1368bac7b40074493ada08524f5116a7e6ba9..cad8d25b570329083fd8a7a14e2520c46eb0aa09 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, NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { ClipboardIconButton } from '../../../components/controls/clipboard';
 import { translate } from '../../../helpers/l10n';
-import CodeSnippet from '../../common/CodeSnippet';
+import { InlineSnippet } from './InlineSnippet';
 
 export interface CreateYmlFileProps {
   yamlFileName: string;
@@ -31,20 +31,20 @@ export interface CreateYmlFileProps {
 export default function CreateYmlFile(props: CreateYmlFileProps) {
   const { yamlTemplate, yamlFileName } = props;
   return (
-    <li className="abs-width-800">
+    <NumberedListItem>
       <FormattedMessage
         defaultMessage={translate('onboarding.tutorial.with.github_action.yaml.create_yml')}
         id="onboarding.tutorial.with.github_action.yaml.create_yml"
         values={{
           file: (
             <>
-              <code className="rule">{yamlFileName}</code>
+              <InlineSnippet snippet={yamlFileName} />
               <ClipboardIconButton copyValue={yamlFileName} />
             </>
           ),
         }}
       />
-      <CodeSnippet snippet={yamlTemplate} />
-    </li>
+      <CodeSnippet className="sw-p-6 sw-overflow-auto" snippet={yamlTemplate} language="yml" />
+    </NumberedListItem>
   );
 }
index 5112acc8cede2e06b4117761f60e048c01f1a88e..5833fdfd374cd00efcc1471bcaec097ef5eda02c 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 { CodeSnippet, NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { Component } from '../../../types/types';
-import CodeSnippet from '../../common/CodeSnippet';
 import SentenceWithFilename from './SentenceWithFilename';
 
 export interface DefaultProjectKeyProps {
@@ -31,12 +31,15 @@ const sonarProjectSnippet = (key: string) => `sonar.projectKey=${key}`;
 export default function DefaultProjectKey(props: DefaultProjectKeyProps) {
   const { component } = props;
   return (
-    <li className="abs-width-600">
+    <NumberedListItem>
       <SentenceWithFilename
         filename="sonar-project.properties"
         translationKey="onboarding.tutorial.other.project_key"
       />
-      <CodeSnippet snippet={sonarProjectSnippet(component.key)} />
-    </li>
+      <CodeSnippet
+        snippet={sonarProjectSnippet(component.key)}
+        className="sw-p-8 sw-overflow-auto"
+      />
+    </NumberedListItem>
   );
 }
index 99d924eb5a36b6b3d650f3bd861864d37ccf8c86..332ba0ff603c16e46b5acb7d688d39a9ccb4ec6d 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import classNames from 'classnames';
+import { Card, LightLabel, StandoutLink } from 'design-system';
 import React from 'react';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/system';
-import Link from '../../common/Link';
 import { OSs, TutorialModes } from '../types';
 import './GithubCFamilyExampleRepositories.css';
 
@@ -57,26 +57,21 @@ export default function GithubCFamilyExampleRepositories(
   const link = `https://github.com/orgs/sonarsource-cfamily-examples/repositories?q=${queryParams}`;
 
   return (
-    <div
-      className={classNames(
-        'github-cfamily-example-repositories-box big-padded boxed-group',
-        className
-      )}
-    >
-      <div className="display-flex-center">
+    <Card className={classNames('sw-p-4', className)}>
+      <div>
         <img
           alt="" // Should be ignored by screen readers
           className="spacer-right"
           height={20}
           src={`${getBaseUrl()}/images/alm/github.svg`}
         />
-        <Link className="spacer-left big" target="_blank" to={link}>
+        <StandoutLink target="_blank" to={link}>
           sonarsource-cfamily-examples
-        </Link>
+        </StandoutLink>
       </div>
-      <p className="spacer-top">
+      <LightLabel as="p" className="sw-mt-4">
         {translate('onboarding.tutorial.cfamily.examples_repositories_description')}
-      </p>
-    </div>
+      </LightLabel>
+    </Card>
   );
 }
index 895aadb09b1de0c0fb27b770256f2b6351573969..8087bdb5b04c5afbcbdbce03870a80f28170deba 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, NumberedListItem } from 'design-system/lib';
 import React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { translate } from '../../../helpers/l10n';
 import { Component } from '../../../types/types';
-import CodeSnippet from '../../common/CodeSnippet';
-import { ClipboardIconButton } from '../../controls/clipboard';
 import { GradleBuildDSL } from '../types';
 import { buildGradleSnippet } from '../utils';
 import GradleBuildSelection from './GradleBuildSelection';
+import { InlineSnippet } from './InlineSnippet';
 
 interface Props {
   component: Component;
@@ -33,31 +33,35 @@ interface Props {
 
 export default function GradleBuild({ component }: Props) {
   return (
-    <li className="abs-width-600">
+    <NumberedListItem>
       <FormattedMessage
         defaultMessage={translate('onboarding.tutorial.with.yaml.gradle')}
         id="onboarding.tutorial.with.yaml.gradle"
         values={{
           groovy: (
             <>
-              <code className="rule">{GradleBuildDSL.Groovy}</code>
+              <InlineSnippet snippet={GradleBuildDSL.Groovy} />
               <ClipboardIconButton copyValue={GradleBuildDSL.Groovy} />
             </>
           ),
           kotlin: (
             <>
-              <code className="rule">{GradleBuildDSL.Kotlin}</code>
+              <InlineSnippet snippet={GradleBuildDSL.Kotlin} />
               <ClipboardIconButton copyValue={GradleBuildDSL.Kotlin} />
             </>
           ),
-          sq: <code className="rule">org.sonarqube</code>,
+          sq: <InlineSnippet snippet="org.sonarqube" />,
         }}
       />
-      <GradleBuildSelection className="big-spacer-top big-spacer-bottom">
+      <GradleBuildSelection className="sw-my-4">
         {(build) => (
-          <CodeSnippet snippet={buildGradleSnippet(component.key, component.name, build)} />
+          <CodeSnippet
+            language="gradle"
+            className="sw-p-6"
+            snippet={buildGradleSnippet(component.key, component.name, build)}
+          />
         )}
       </GradleBuildSelection>
-    </li>
+    </NumberedListItem>
   );
 }
index d84392f84cca8c5fe0017b86f296d7908883d7fb..18f56f690d8d6e6cfd51db6d90f7b9c5bbd7744a 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 { CodeSnippet } from 'design-system';
 import * as React from 'react';
 import { FCProps } from '../../../types/misc';
 
-export function InlineSnippet({ snippet }: Pick<FCProps<typeof CodeSnippet>, 'snippet'>) {
+export function InlineSnippet({
+  snippet,
+  className,
+}: Pick<FCProps<typeof CodeSnippet>, 'snippet'> & { className?: string }) {
   return (
-    <CodeSnippet className="sw-code sw-inline-block sw-px-1" noCopy isOneLine snippet={snippet} />
+    <CodeSnippet
+      className={classNames('sw-code sw-inline-block sw-px-1', className)}
+      noCopy
+      isOneLine
+      snippet={snippet}
+    />
   );
 }
index 301008e4cbbc9f8edcf3dc262293f36185677344..4a138015375d481ee3e5fb06d703574810fa5aae 100644 (file)
@@ -28,23 +28,32 @@ export interface RenderOptionsProps {
   optionLabelKey: string;
   options: string[];
   titleLabelKey?: string;
+  setDone?: (doneStatus: boolean) => void;
 }
 
 export default function RenderOptions({
   checked,
   label,
   onCheck,
+  setDone,
   optionLabelKey,
   options,
   titleLabelKey,
 }: RenderOptionsProps) {
+  const onChange = (checked: string) => {
+    if (setDone) {
+      setDone(true);
+    }
+    onCheck(checked);
+  };
+
   return (
     <div className="sw-mt-4">
       {titleLabelKey && <label className="sw-block sw-mb-1">{translate(titleLabelKey)}</label>}
 
       <ToggleButton
         label={label}
-        onChange={onCheck}
+        onChange={onChange}
         options={options.map((build) => ({
           label: translate(optionLabelKey, build),
           value: build,
index eacfc511ef2632cdebcec6c33a5245c00b7fa73d..94aa6f25a077a35d08052753fbdaed6a95a40849 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { translate } from '../../../helpers/l10n';
+import { InlineSnippet } from './InlineSnippet';
 
 export interface SentenceWithFilenameProps {
   filename: string;
@@ -36,7 +37,7 @@ export default function SentenceWithFilename({
         defaultMessage={translate(translationKey, 'sentence')}
         id={`${translationKey}.sentence`}
         values={{
-          file: <code>{filename}</code>,
+          file: <InlineSnippet snippet={filename} />,
         }}
       />
     </span>
index d44606562cd8c631ef68ac5d763ee157c22f1603..e43cb39b1c9c2b575f559279fba6f32aa69140e9 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 { ButtonSecondary, NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { Button } from '../../../components/controls/buttons';
 import { translate } from '../../../helpers/l10n';
 import { Component } from '../../../types/types';
 import { LoggedInUser } from '../../../types/users';
@@ -39,21 +39,25 @@ export default function TokenStepGenerator(props: TokenStepGeneratorProps) {
 
   return (
     <>
-      <li className="big-spacer-bottom">
+      <NumberedListItem>
         <FormattedMessage
           defaultMessage={translate('onboarding.tutorial.env_variables')}
           id="onboarding.tutorial.env_variables"
           values={{
             extra: (
-              <Button className="spacer-left" onClick={toggleTokenModal}>
+              <ButtonSecondary className="sw-ml-2" onClick={toggleTokenModal}>
                 {translate('onboarding.token.generate.long')}
-              </Button>
+              </ButtonSecondary>
+            ),
+            field: (
+              <span className="sw-body-sm-highlight">
+                {translate('onboarding.tutorial.env_variables.field')}
+              </span>
             ),
-            field: <strong>{translate('onboarding.tutorial.env_variables.field')}</strong>,
             value: translate('onboarding.tutorial.env_variables.token_generator.value'),
           }}
         />
-      </li>
+      </NumberedListItem>
       {isModalVisible && (
         <EditTokenModal component={component} currentUser={currentUser} onClose={closeTokenModal} />
       )}
index e451af414915a3ca56fc1b3239aeee8e7e539ca1..b968dd2d3f6914ab77a3320065c235ea7e939c01 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 { NumberedList, NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 import { withCLanguageFeature } from '../../hoc/withCLanguageFeature';
@@ -26,6 +27,7 @@ import { BuildTools } from '../types';
 export interface YamlFileStepProps {
   children?: (buildTool: BuildTools) => React.ReactElement<{}>;
   hasCLanguageFeature: boolean;
+  setDone?: (doneStatus: boolean) => void;
 }
 
 export function YamlFileStep(props: YamlFileStepProps) {
@@ -40,8 +42,8 @@ export function YamlFileStep(props: YamlFileStepProps) {
   const [buildToolSelected, setBuildToolSelected] = React.useState<BuildTools>();
 
   return (
-    <ol className="list-styled big-spacer-top big-spacer-bottom">
-      <li className="abs-width-600">
+    <NumberedList>
+      <NumberedListItem>
         {translate('onboarding.build')}
         <RenderOptions
           label={translate('onboarding.build')}
@@ -49,10 +51,11 @@ export function YamlFileStep(props: YamlFileStepProps) {
           onCheck={(value) => setBuildToolSelected(value as BuildTools)}
           options={buildTools}
           optionLabelKey="onboarding.build"
+          setDone={props.setDone}
         />
-      </li>
+      </NumberedListItem>
       {children && buildToolSelected && children(buildToolSelected)}
-    </ol>
+    </NumberedList>
   );
 }
 
index 6204a994d682295f37d01be2e8e4991a0d099b7f..89ac0a07d6cfdb61d3ad0223e6b254a9519d115c 100644 (file)
@@ -34,7 +34,6 @@ export interface AnalysisCommandProps extends WithAvailableFeaturesProps {
   buildTool: BuildTools;
   mainBranchName: string;
   component: Component;
-  onDone: () => void;
 }
 
 export function AnalysisCommand(props: AnalysisCommandProps) {
@@ -48,7 +47,6 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
           branchesEnabled={branchSupportEnabled}
           mainBranchName={mainBranchName}
           component={component}
-          onDone={props.onDone}
         />
       );
     case BuildTools.Gradle:
@@ -57,7 +55,6 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
           branchesEnabled={branchSupportEnabled}
           mainBranchName={mainBranchName}
           component={component}
-          onDone={props.onDone}
         />
       );
     case BuildTools.DotNet:
@@ -66,7 +63,6 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
           branchesEnabled={branchSupportEnabled}
           mainBranchName={mainBranchName}
           component={component}
-          onDone={props.onDone}
         />
       );
     case BuildTools.CFamily:
@@ -75,7 +71,6 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
           branchesEnabled={branchSupportEnabled}
           mainBranchName={mainBranchName}
           component={component}
-          onDone={props.onDone}
         />
       );
     case BuildTools.Other:
@@ -84,7 +79,6 @@ export function AnalysisCommand(props: AnalysisCommandProps) {
           branchesEnabled={branchSupportEnabled}
           mainBranchName={mainBranchName}
           component={component}
-          onDone={props.onDone}
         />
       );
   }
index 1315f0627298968e466ecab368ad04677b44f882..0f0b134674ec31347ac3ff19a20a2cf09b232e9c 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 { BasicSeparator, TutorialStep, TutorialStepList } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 import {
@@ -26,18 +27,11 @@ import {
 } from '../../../types/alm-settings';
 import { Component } from '../../../types/types';
 import { LoggedInUser } from '../../../types/users';
-import AllSetStep from '../components/AllSetStep';
-import Step from '../components/Step';
+import AllSet from '../components/AllSet';
 import YamlFileStep from '../components/YamlFileStep';
 import AnalysisCommand from './AnalysisCommand';
 import SecretStep from './SecretStep';
 
-export enum Steps {
-  CREATE_SECRET = 1,
-  YAML = 2,
-  ALL_SET = 3,
-}
-
 export interface GitHubActionTutorialProps {
   almBinding?: AlmSettingsInstance;
   baseUrl: string;
@@ -49,6 +43,7 @@ export interface GitHubActionTutorialProps {
 }
 
 export default function GitHubActionTutorial(props: GitHubActionTutorialProps) {
+  const [done, setDone] = React.useState<boolean>(false);
   const {
     almBinding,
     baseUrl,
@@ -58,52 +53,37 @@ export default function GitHubActionTutorial(props: GitHubActionTutorialProps) {
     mainBranchName,
     willRefreshAutomatically,
   } = props;
-
-  const [step, setStep] = React.useState<Steps>(Steps.CREATE_SECRET);
   return (
-    <>
-      <Step
-        finished={step > Steps.CREATE_SECRET}
-        onOpen={() => setStep(Steps.CREATE_SECRET)}
-        open={step === Steps.CREATE_SECRET}
-        renderForm={() => (
-          <SecretStep
-            almBinding={almBinding}
-            baseUrl={baseUrl}
-            component={component}
-            currentUser={currentUser}
-            projectBinding={projectBinding}
-            onDone={() => setStep(Steps.YAML)}
+    <TutorialStepList className="sw-mb-8">
+      <TutorialStep title={translate('onboarding.tutorial.with.github_action.create_secret.title')}>
+        <SecretStep
+          almBinding={almBinding}
+          baseUrl={baseUrl}
+          component={component}
+          currentUser={currentUser}
+          projectBinding={projectBinding}
+        />
+      </TutorialStep>
+      <TutorialStep title={translate('onboarding.tutorial.with.github_action.yaml.title')}>
+        <YamlFileStep setDone={setDone}>
+          {(buildTool) => (
+            <AnalysisCommand
+              buildTool={buildTool}
+              mainBranchName={mainBranchName}
+              component={component}
+            />
+          )}
+        </YamlFileStep>
+      </TutorialStep>
+      {done && (
+        <>
+          <BasicSeparator className="sw-my-10" />
+          <AllSet
+            alm={almBinding?.alm || AlmKeys.GitHub}
+            willRefreshAutomatically={willRefreshAutomatically}
           />
-        )}
-        stepNumber={Steps.CREATE_SECRET}
-        stepTitle={translate('onboarding.tutorial.with.github_action.create_secret.title')}
-      />
-      <Step
-        finished={step > Steps.YAML}
-        onOpen={() => setStep(Steps.YAML)}
-        open={step === Steps.YAML}
-        renderForm={() => (
-          <YamlFileStep>
-            {(buildTool) => (
-              <AnalysisCommand
-                buildTool={buildTool}
-                mainBranchName={mainBranchName}
-                component={component}
-                onDone={() => setStep(Steps.ALL_SET)}
-              />
-            )}
-          </YamlFileStep>
-        )}
-        stepNumber={Steps.YAML}
-        stepTitle={translate('onboarding.tutorial.with.github_action.yaml.title')}
-      />
-      <AllSetStep
-        alm={almBinding?.alm || AlmKeys.GitHub}
-        open={step === Steps.ALL_SET}
-        stepNumber={Steps.ALL_SET}
-        willRefreshAutomatically={willRefreshAutomatically}
-      />
-    </>
+        </>
+      )}
+    </TutorialStepList>
   );
 }
index 2e137e2353b30728cb88b56b61199bd5062b5064..a86d562f45ed7b50f893428ae3da146df4bad4f6 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,
+  ClipboardIconButton,
+  NumberedList,
+  NumberedListItem,
+  StandoutLink,
+} from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { Button } from '../../../components/controls/buttons';
-import { ClipboardIconButton } from '../../../components/controls/clipboard';
 import { translate } from '../../../helpers/l10n';
 import { AlmSettingsInstance, ProjectAlmBindingResponse } from '../../../types/alm-settings';
 import { Component } from '../../../types/types';
 import { LoggedInUser } from '../../../types/users';
+import { InlineSnippet } from '../components/InlineSnippet';
 import SentenceWithHighlights from '../components/SentenceWithHighlights';
 import TokenStepGenerator from '../components/TokenStepGenerator';
 import { buildGithubLink } from '../utils';
@@ -35,97 +41,94 @@ export interface SecretStepProps {
   component: Component;
   currentUser: LoggedInUser;
   projectBinding?: ProjectAlmBindingResponse;
-  onDone: () => void;
 }
 
 export default function SecretStep(props: SecretStepProps) {
   const { almBinding, baseUrl, component, currentUser, projectBinding } = props;
 
   return (
-    <div className="boxed-group-inner">
-      <p className="big-spacer-bottom">
-        <FormattedMessage
-          defaultMessage={translate('onboarding.tutorial.with.github_action.secret.intro')}
-          id="onboarding.tutorial.with.github_action.secret.intro"
-          values={{
-            settings_secret:
-              almBinding && projectBinding ? (
-                <a
-                  href={`${buildGithubLink(almBinding, projectBinding)}/settings/secrets`}
-                  target="_blank"
-                  rel="noopener noreferrer"
-                >
-                  {translate('onboarding.tutorial.with.github_action.secret.intro.link')}
-                </a>
-              ) : (
-                <strong>
-                  {translate('onboarding.tutorial.with.github_action.secret.intro.link')}
-                </strong>
-              ),
-          }}
-        />
-      </p>
-      <ol className="list-styled">
-        <li>
+    <>
+      <FormattedMessage
+        defaultMessage={translate('onboarding.tutorial.with.github_action.secret.intro')}
+        id="onboarding.tutorial.with.github_action.secret.intro"
+        values={{
+          settings_secret:
+            almBinding && projectBinding ? (
+              <StandoutLink
+                to={`${buildGithubLink(almBinding, projectBinding)}/settings/secrets`}
+                target="_blank"
+                rel="noopener noreferrer"
+              >
+                {translate('onboarding.tutorial.with.github_action.secret.intro.link')}
+              </StandoutLink>
+            ) : (
+              <span className="sw-body-sm-highlight">
+                {translate('onboarding.tutorial.with.github_action.secret.intro.link')}
+              </span>
+            ),
+        }}
+      />
+      <NumberedList>
+        <NumberedListItem>
           <SentenceWithHighlights
             translationKey="onboarding.tutorial.with.github_action.secret.new"
             highlightKeys={['new_secret']}
           />
-        </li>
-        <li>
+        </NumberedListItem>
+        <NumberedListItem>
           <SentenceWithHighlights
             translationKey="onboarding.tutorial.with.github_action.secret.name"
             highlightKeys={['name']}
           />
-          <code className="rule little-spacer-left">SONAR_TOKEN</code>
+          <InlineSnippet snippet="SONAR_TOKEN" className="sw-ml-1" />
           <ClipboardIconButton copyValue="SONAR_TOKEN" />
-        </li>
+        </NumberedListItem>
         <TokenStepGenerator component={component} currentUser={currentUser} />
-        <li>
+        <NumberedListItem>
           <SentenceWithHighlights
             translationKey="onboarding.tutorial.with.github_action.secret.add"
             highlightKeys={['add_secret']}
           />
-        </li>
-      </ol>
-
-      <hr className="no-horizontal-margins" />
-
-      <ol className="list-styled big-spacer-top big-spacer-bottom">
-        <li>
+        </NumberedListItem>
+      </NumberedList>
+      <BasicSeparator className="sw-my-6" />
+      <NumberedList>
+        <NumberedListItem>
           <SentenceWithHighlights
             translationKey="onboarding.tutorial.with.github_action.secret.new"
             highlightKeys={['new_secret']}
           />
-        </li>
-        <li>
+        </NumberedListItem>
+        <NumberedListItem>
           <SentenceWithHighlights
             translationKey="onboarding.tutorial.with.github_action.secret.name"
             highlightKeys={['name']}
           />
-
-          <code className="rule little-spacer-left">SONAR_HOST_URL</code>
+          <InlineSnippet snippet="SONAR_HOST_URL" className="sw-ml-1" />
           <ClipboardIconButton copyValue="SONAR_HOST_URL" />
-        </li>
-        <li className="big-spacer-bottom">
+        </NumberedListItem>
+        <NumberedListItem>
           <FormattedMessage
             defaultMessage={translate('onboarding.tutorial.env_variables')}
             id="onboarding.tutorial.env_variables"
             values={{
               extra: <ClipboardIconButton copyValue={baseUrl} />,
-              field: <strong>{translate('onboarding.tutorial.env_variables.field')}</strong>,
-              value: <code className="rule">{baseUrl}</code>,
+              field: (
+                <span className="sw-body-sm-highlight">
+                  {translate('onboarding.tutorial.env_variables.field')}
+                </span>
+              ),
+              value: <InlineSnippet snippet={baseUrl} className="sw-ml-1" />,
             }}
           />
-        </li>
-        <li>
+        </NumberedListItem>
+        <NumberedListItem>
           <SentenceWithHighlights
             translationKey="onboarding.tutorial.with.github_action.secret.add"
             highlightKeys={['add_secret']}
           />
-        </li>
-      </ol>
-      <Button onClick={props.onDone}>{translate('continue')}</Button>
-    </div>
+        </NumberedListItem>
+      </NumberedList>
+    </>
   );
 }
index 06ab3f48ec55b6c075b9a962913dcbef15a0bf8a..16c3e2e9c87e2b92bfcd3d7b78ce1445588d4c24 100644 (file)
@@ -28,7 +28,7 @@ import {
 } from '../../../../helpers/mocks/alm-settings';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import { mockLanguage, mockLoggedInUser } from '../../../../helpers/testMocks';
-import { renderApp, RenderContext } from '../../../../helpers/testReactTestingUtils';
+import { RenderContext, renderApp } from '../../../../helpers/testReactTestingUtils';
 import { AlmKeys } from '../../../../types/alm-settings';
 import { Feature } from '../../../../types/features';
 import {
@@ -65,45 +65,49 @@ it('should follow and complete all steps', async () => {
   expect(await ui.secretsStepTitle.find()).toBeInTheDocument();
 
   // Env variables step
-  expect(getCopyToClipboardValue()).toMatchSnapshot('sonar token key');
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('sonarqube host url key');
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('sonarqube host url value');
-  await user.click(ui.continueButton.get());
+  expect(getCopyToClipboardValue(0, 'Copy to clipboard')).toMatchSnapshot('sonar token key');
+  expect(getCopyToClipboardValue(1, 'Copy to clipboard')).toMatchSnapshot('sonarqube host url key');
+  expect(getCopyToClipboardValue(2, 'Copy to clipboard')).toMatchSnapshot(
+    'sonarqube host url value'
+  );
 
   // Create/update configuration file step
   // Maven
   await user.click(ui.mavenBuildButton.get());
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('Maven: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Maven: .github/workflows/build.yml');
 
   // Gradle
   await user.click(ui.gradleBuildButton.get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('Groovy: build.gradle');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Groovy: build.gradle');
   await user.click(ui.gradleDSLButton(GradleBuildDSL.Kotlin).get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('Kotlin: build.gradle.kts');
-  expect(getCopyToClipboardValue(4)).toMatchSnapshot('Gradle: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Kotlin: build.gradle.kts');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Gradle: .github/workflows/build.yml');
 
   // .NET
   await user.click(ui.dotnetBuildButton.get());
-  expect(getCopyToClipboardValue(1)).toMatchSnapshot('.NET: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('.NET: .github/workflows/build.yml');
 
   // CFamily
   await user.click(ui.cFamilyBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('CFamily: sonar-project.properties');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('CFamily: sonar-project.properties');
   await user.click(ui.linuxButton.get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('CFamily Linux: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot(
+    'CFamily Linux: .github/workflows/build.yml'
+  );
   await user.click(ui.windowsButton.get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot(
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot(
     'CFamily Windows: .github/workflows/build.yml'
   );
   await user.click(ui.macosButton.get());
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('CFamily MacOS: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot(
+    'CFamily MacOS: .github/workflows/build.yml'
+  );
 
   // Other
   await user.click(ui.otherBuildButton.get());
-  expect(getCopyToClipboardValue()).toMatchSnapshot('Other: sonar-project.properties');
-  expect(getCopyToClipboardValue(2)).toMatchSnapshot('Other: .github/workflows/build.yml');
+  expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot('Other: sonar-project.properties');
+  expect(getCopyToClipboardValue(1, 'Copy')).toMatchSnapshot('Other: .github/workflows/build.yml');
 
-  await user.click(ui.finishTutorialButton.get());
   expect(ui.allSetSentence.get()).toBeInTheDocument();
 });
 
@@ -116,7 +120,7 @@ it('should generate/delete a new token or use existing one', async () => {
   // Generate token
   await user.click(ui.genTokenDialogButton.get());
   await user.click(ui.generateTokenButton.get());
-  expect(getCopyToClipboardValue(3)).toEqual('generatedtoken2');
+  expect(getCopyToClipboardValue()).toEqual('generatedtoken2');
 
   // Revoke current token and create new one
   await user.click(ui.deleteTokenButton.get());
@@ -124,7 +128,7 @@ it('should generate/delete a new token or use existing one', async () => {
   await selectEvent.select(ui.expiresInSelect.get(), 'users.tokens.expiration.365');
   await user.click(ui.generateTokenButton.get());
   expect(ui.tokenValue.get()).toBeInTheDocument();
-  await user.click(ui.continueButton.getAll()[1]);
+  await user.click(ui.continueButton.getAll()[0]);
   expect(ui.tokenValue.query()).not.toBeInTheDocument();
 });
 
@@ -141,9 +145,7 @@ it('navigates between steps', async () => {
   // If project is bound, link to repo is visible
   expect(await ui.linkToRepo.find()).toBeInTheDocument();
 
-  await user.click(await ui.continueButton.find());
   await user.click(ui.mavenBuildButton.get());
-  await user.click(ui.finishTutorialButton.get());
   expect(ui.allSetSentence.get()).toBeInTheDocument();
 
   await user.click(ui.ymlFileStepTitle.get());
index 34c99c81052be03093c3bd340e7ad010b36f32d3..7aeaf12410aa1e101ad9eb4272e11e394d7c56c2 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 { NumberedListItem } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../../helpers/l10n';
 import { Component } from '../../../../types/types';
 import { CompilationInfo } from '../../components/CompilationInfo';
 import CreateYmlFile from '../../components/CreateYmlFile';
 import DefaultProjectKey from '../../components/DefaultProjectKey';
-import FinishButton from '../../components/FinishButton';
 import GithubCFamilyExampleRepositories from '../../components/GithubCFamilyExampleRepositories';
 import RenderOptions from '../../components/RenderOptions';
 import { OSs, TutorialModes } from '../../types';
@@ -33,7 +33,6 @@ export interface CFamilyProps {
   branchesEnabled?: boolean;
   mainBranchName: string;
   component: Component;
-  onDone: () => void;
 }
 
 const STEPS = {
@@ -118,12 +117,12 @@ const STEPS = {
 
 export default function CFamily(props: CFamilyProps) {
   const { component, branchesEnabled, mainBranchName } = props;
-  const [os, setOs] = React.useState<undefined | OSs>();
+  const [os, setOs] = React.useState<undefined | OSs>(OSs.Linux);
 
   return (
     <>
       <DefaultProjectKey component={component} />
-      <li className="abs-width-600">
+      <NumberedListItem>
         <span>{translate('onboarding.build.other.os')}</span>
         <RenderOptions
           label={translate('onboarding.build.other.os')}
@@ -134,12 +133,12 @@ export default function CFamily(props: CFamilyProps) {
         />
         {os && (
           <GithubCFamilyExampleRepositories
-            className="big-spacer-top"
+            className="sw-mt-4"
             os={os}
             ci={TutorialModes.GitHubActions}
           />
         )}
-      </li>
+      </NumberedListItem>
       {os && (
         <>
           <CreateYmlFile
@@ -151,8 +150,7 @@ export default function CFamily(props: CFamilyProps) {
               STEPS[os]
             )}
           />
-          <CompilationInfo className="abs-width-800" />
-          <FinishButton onClick={props.onDone} />
+          <CompilationInfo />
         </>
       )}
     </>
index 26580bbfc348dbe56380c9d918695de439e3aa6c..ad306727393fbf90ba66862a7f81e2193292c0b0 100644 (file)
@@ -20,7 +20,6 @@
 import * as React from 'react';
 import { Component } from '../../../../types/types';
 import CreateYmlFile from '../../components/CreateYmlFile';
-import FinishButton from '../../components/FinishButton';
 import { GITHUB_ACTIONS_RUNS_ON_WINDOWS } from '../constants';
 import { generateGitHubActionsYaml } from '../utils';
 
@@ -28,7 +27,6 @@ export interface DotNetProps {
   branchesEnabled?: boolean;
   mainBranchName: string;
   component: Component;
-  onDone: () => void;
 }
 
 function dotnetYamlSteps(projectKey: string) {
@@ -69,17 +67,14 @@ function dotnetYamlSteps(projectKey: string) {
 export default function DotNet(props: DotNetProps) {
   const { component, branchesEnabled, mainBranchName } = props;
   return (
-    <>
-      <CreateYmlFile
-        yamlFileName=".github/workflows/build.yml"
-        yamlTemplate={generateGitHubActionsYaml(
-          mainBranchName,
-          !!branchesEnabled,
-          GITHUB_ACTIONS_RUNS_ON_WINDOWS,
-          dotnetYamlSteps(component.key)
-        )}
-      />
-      <FinishButton onClick={props.onDone} />
-    </>
+    <CreateYmlFile
+      yamlFileName=".github/workflows/build.yml"
+      yamlTemplate={generateGitHubActionsYaml(
+        mainBranchName,
+        !!branchesEnabled,
+        GITHUB_ACTIONS_RUNS_ON_WINDOWS,
+        dotnetYamlSteps(component.key)
+      )}
+    />
   );
 }
index c0e84c06cd8d87b99d10a90b85b579a947903ed3..f5ab48b5b1dacbe8543ae81fd4e97e56b4a85780 100644 (file)
@@ -20,7 +20,6 @@
 import * as React from 'react';
 import { Component } from '../../../../types/types';
 import CreateYmlFile from '../../components/CreateYmlFile';
-import FinishButton from '../../components/FinishButton';
 import GradleBuild from '../../components/GradleBuild';
 import { GITHUB_ACTIONS_RUNS_ON_LINUX } from '../constants';
 import { generateGitHubActionsYaml } from '../utils';
@@ -29,7 +28,6 @@ export interface GradleProps {
   branchesEnabled?: boolean;
   mainBranchName: string;
   component: Component;
-  onDone: () => void;
 }
 
 const GRADLE_YAML_STEPS = `
@@ -71,7 +69,6 @@ export default function Gradle(props: GradleProps) {
           GRADLE_YAML_STEPS
         )}
       />
-      <FinishButton onClick={props.onDone} />
     </>
   );
 }
index c9b253dd3e840b848dd1bf900125f6e5fa497956..a619a5eeb8776aa6b80c1e7b8a8ec37c4f8d299a 100644 (file)
@@ -20,7 +20,6 @@
 import * as React from 'react';
 import { Component } from '../../../../types/types';
 import CreateYmlFile from '../../components/CreateYmlFile';
-import FinishButton from '../../components/FinishButton';
 import { GITHUB_ACTIONS_RUNS_ON_LINUX } from '../constants';
 import { generateGitHubActionsYaml } from '../utils';
 
@@ -28,7 +27,6 @@ export interface JavaMavenProps {
   branchesEnabled?: boolean;
   mainBranchName: string;
   component: Component;
-  onDone: () => void;
 }
 
 function mavenYamlSteps(projectKey: string, projectName: string) {
@@ -60,17 +58,14 @@ function mavenYamlSteps(projectKey: string, projectName: string) {
 export default function JavaMaven(props: JavaMavenProps) {
   const { component, branchesEnabled, mainBranchName } = props;
   return (
-    <>
-      <CreateYmlFile
-        yamlFileName=".github/workflows/build.yml"
-        yamlTemplate={generateGitHubActionsYaml(
-          mainBranchName,
-          !!branchesEnabled,
-          GITHUB_ACTIONS_RUNS_ON_LINUX,
-          mavenYamlSteps(component.key, component.name)
-        )}
-      />
-      <FinishButton onClick={props.onDone} />
-    </>
+    <CreateYmlFile
+      yamlFileName=".github/workflows/build.yml"
+      yamlTemplate={generateGitHubActionsYaml(
+        mainBranchName,
+        !!branchesEnabled,
+        GITHUB_ACTIONS_RUNS_ON_LINUX,
+        mavenYamlSteps(component.key, component.name)
+      )}
+    />
   );
 }
index b2a067f0b7cdf17a12bb437df3abd92957949074..d3633fce33cd9af3d5112253a3676fe0d644cd05 100644 (file)
@@ -21,7 +21,6 @@ import * as React from 'react';
 import { Component } from '../../../../types/types';
 import CreateYmlFile from '../../components/CreateYmlFile';
 import DefaultProjectKey from '../../components/DefaultProjectKey';
-import FinishButton from '../../components/FinishButton';
 import { GITHUB_ACTIONS_RUNS_ON_LINUX } from '../constants';
 import { generateGitHubActionsYaml } from '../utils';
 
@@ -29,7 +28,6 @@ export interface OthersProps {
   branchesEnabled?: boolean;
   mainBranchName: string;
   component: Component;
-  onDone: () => void;
 }
 
 function otherYamlSteps(branchesEnabled: boolean) {
@@ -70,7 +68,6 @@ export default function Others(props: OthersProps) {
           otherYamlSteps(!!branchesEnabled)
         )}
       />
-      <FinishButton onClick={props.onDone} />
     </>
   );
 }
index 0031a56f1ddec3d5524345f2b5e41ddd8ca54cab..fd1378270cfebc7736311b19a0ddac4b86e52946 100644 (file)
@@ -17,7 +17,6 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-
 import userEvent from '@testing-library/user-event';
 import React from 'react';
 import UserTokensMock from '../../../../api/mocks/UserTokensMock';
@@ -80,7 +79,7 @@ const ui = {
   jenkinsStepTitle: byRole('heading', {
     name: 'onboarding.tutorial.with.jenkins.jenkinsfile.title',
   }),
-  allSetSentence: byText('onboarding.tutorial.ci_outro.all_set.sentence'),
+  allSetSentence: byText('onboarding.tutorial.ci_outro.done'),
   ...getTutorialActionButtons(),
   ...getTutorialBuildButtons(),
 };
@@ -139,23 +138,23 @@ it.each([AlmKeys.BitbucketCloud, AlmKeys.BitbucketServer, AlmKeys.GitHub, AlmKey
 
     // CFamilly
     await user.click(ui.cFamilyBuildButton.get());
-    expect(getCopyToClipboardValue()).toMatchSnapshot(`sonar-project.properties code`);
+    expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(`sonar-project.properties code`);
 
     await user.click(ui.linuxButton.get());
-    expect(getCopyToClipboardValue(1)).toMatchSnapshot(`cfamily linux jenkinsfile`);
+    expect(getCopyToClipboardValue()).toMatchSnapshot(`cfamily linux jenkinsfile`);
 
     await user.click(ui.windowsButton.get());
-    expect(getCopyToClipboardValue(1)).toMatchSnapshot(`cfamily windows jenkinsfile`);
+    expect(getCopyToClipboardValue()).toMatchSnapshot(`cfamily windows jenkinsfile`);
 
     await user.click(ui.macosButton.get());
-    expect(getCopyToClipboardValue(1)).toMatchSnapshot(`cfamily macos jenkinsfile`);
+    expect(getCopyToClipboardValue()).toMatchSnapshot(`cfamily macos jenkinsfile`);
 
     // Other
     await user.click(ui.otherBuildButton.get());
-    expect(getCopyToClipboardValue()).toMatchSnapshot(
+    expect(getCopyToClipboardValue(0, 'Copy')).toMatchSnapshot(
       `other build tools sonar-project.properties code`
     );
-    expect(getCopyToClipboardValue(1)).toMatchSnapshot(`other build tools jenkinsfile`);
+    expect(getCopyToClipboardValue()).toMatchSnapshot(`other build tools jenkinsfile`);
 
     await user.click(ui.finishTutorialButton.get());
     expect(ui.allSetSentence.get()).toBeInTheDocument();
index 760ee7d2d95a5c987877c46394c2b189821e800d..58be0817983f6aa02ac48b21dab8fd8534efaec1 100644 (file)
@@ -52,7 +52,7 @@ export function getCommonNodes(ci: TutorialModes) {
         ci === TutorialModes.GitHubActions ? 'secret' : 'variables'
       }.intro.link`,
     }),
-    allSetSentence: byText('onboarding.tutorial.ci_outro.all_set.sentence'),
+    allSetSentence: byText('onboarding.tutorial.ci_outro.done'),
   };
 }
 
diff --git a/server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts b/server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts
new file mode 100644 (file)
index 0000000..6da44bf
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 { RefObject, useEffect, useState } from 'react';
+import { isDefined } from '../helpers/types';
+
+interface Options extends IntersectionObserverInit {
+  freezeOnceVisible?: boolean;
+}
+
+export default function useIntersectionObserver<T extends Element>(
+  ref: RefObject<T>,
+  options: Options = {}
+) {
+  const { root = null, rootMargin = '0px', threshold = 0, freezeOnceVisible = false } = options;
+  const [entry, setEntry] = useState<IntersectionObserverEntry>();
+
+  const frozen = (entry?.isIntersecting || false) && freezeOnceVisible;
+
+  useEffect(() => {
+    if (!isDefined(IntersectionObserver) || !isDefined(ref.current) || frozen) {
+      return;
+    }
+
+    const observer = new IntersectionObserver(
+      ([entry]) => setEntry(entry),
+
+      { root, rootMargin, threshold }
+    );
+
+    observer.observe(ref.current);
+    return () => observer.disconnect();
+  }, [ref, frozen, root, rootMargin, threshold]);
+
+  return entry;
+}
index 17a4c72095dc18ebe1320437f177d1b83e4bcdd7..909ed7d284fb64446ed1e93f5a89ff01b85fef15 100644 (file)
@@ -3606,6 +3606,71 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@react-spring/animated@npm:~9.7.3":
+  version: 9.7.3
+  resolution: "@react-spring/animated@npm:9.7.3"
+  dependencies:
+    "@react-spring/shared": ~9.7.3
+    "@react-spring/types": ~9.7.3
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 468942ca3a11c02c3e56def26b2da9dd10ddbed548004245c4ac309cce00b58d971e781abed67db0d652f72737eaa73766ea9a43b8ef3b08a7ed2eddc04d4c39
+  languageName: node
+  linkType: hard
+
+"@react-spring/core@npm:~9.7.3":
+  version: 9.7.3
+  resolution: "@react-spring/core@npm:9.7.3"
+  dependencies:
+    "@react-spring/animated": ~9.7.3
+    "@react-spring/shared": ~9.7.3
+    "@react-spring/types": ~9.7.3
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 8a80a07276458fd14099320eda824e58a11ce3a9b03a5c9cd3f4252adb4d26da04ee5caf5cbc961199f55c2d58a99638d5ea292cdb6aa029208dbab741b5c531
+  languageName: node
+  linkType: hard
+
+"@react-spring/rafz@npm:9.7.3":
+  version: 9.7.3
+  resolution: "@react-spring/rafz@npm:9.7.3"
+  checksum: c88b3c6306eab4a93a9f4ea38ad2e0ea3287a8b79a9a092cb1fd934a97ce092e41c32373b2b2e3de4b63027de7ae7414e0d442936dd8d94b51c2ff7d51e8f643
+  languageName: node
+  linkType: hard
+
+"@react-spring/shared@npm:~9.7.3":
+  version: 9.7.3
+  resolution: "@react-spring/shared@npm:9.7.3"
+  dependencies:
+    "@react-spring/types": ~9.7.3
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 912b5e567eb5345c9a6c8e8c0c2d69b1f411af72a0685b95831809c267c89846a31341ca071f284ace98b3cb5de647054dc76f6ace81d6379513eaf96b52f195
+  languageName: node
+  linkType: hard
+
+"@react-spring/types@npm:~9.7.3":
+  version: 9.7.3
+  resolution: "@react-spring/types@npm:9.7.3"
+  checksum: f47b81fe556464aa54a78603311cb584d6a0f03088522229afb058265bbe2ade2095a55ec7f4e960c3b9cceaa5d47865bc41fc6643c0f5f4bd3d8650203d8389
+  languageName: node
+  linkType: hard
+
+"@react-spring/web@npm:9.7.3":
+  version: 9.7.3
+  resolution: "@react-spring/web@npm:9.7.3"
+  dependencies:
+    "@react-spring/animated": ~9.7.3
+    "@react-spring/core": ~9.7.3
+    "@react-spring/shared": ~9.7.3
+    "@react-spring/types": ~9.7.3
+  peerDependencies:
+    react: ^16.8.0 || ^17.0.0 || ^18.0.0
+    react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+  checksum: 7f5cd05b2314b7f2f715e1926abcf9aa0a539399b222ab34e989144f48350adfcd2edab65d41425570f72c57f602fc6994d6730fbeed902171ac527b630a8a9b
+  languageName: node
+  linkType: hard
+
 "@remix-run/router@npm:1.6.2":
   version: 1.6.2
   resolution: "@remix-run/router@npm:1.6.2"
@@ -4844,6 +4909,8 @@ __metadata:
     "@emotion/react": 11.11.1
     "@emotion/styled": 11.11.0
     "@primer/octicons-react": 19.3.0
+    "@react-spring/rafz": 9.7.3
+    "@react-spring/web": 9.7.3
     "@swc/core": 1.3.65
     "@swc/jest": 0.2.26
     "@tanstack/react-query": 4.29.14
index 702f5af40955cc547046d9f24839ff18b267beab..ab12969f8633ee3f458a004ad8b0209c031ef3f2 100644 (file)
@@ -4058,8 +4058,8 @@ onboarding.analysis.dotnetcore.global.text.path=Make sure dotnet tools folder is
 
 onboarding.tutorial.return_to_list=Choose another option
 onboarding.tutorial.ci_outro.all_set.title=You're all set!
-onboarding.tutorial.ci_outro.all_set.sentence={all_set} and ready to improve the quality and security of your code!
-onboarding.tutorial.ci_outro.all_set.sentence.all_set=You're all set
+onboarding.tutorial.ci_outro.done=And you are done!
+onboarding.tutorial.ci_outro.refresh_text=If everything is running successfully, once the analysis is complete you'll be redirected to the Overview page of your project where the new analysis results will be displayed. This can take a few minutes.
 onboarding.tutorial.ci_outro.commit=Commit and push your code to start the analysis.
 onboarding.tutorial.ci_outro.commit.why.gitlab=Each new push you make on your branches or merge requests will trigger a new analysis in SonarQube. We will decorate merge requests directly on GitLab for you.
 onboarding.tutorial.ci_outro.commit.why.github=Each new push you make on your branches or pull requests will trigger a new analysis in SonarQube. We will decorate pull requests directly on GitHub for you.
@@ -4069,7 +4069,6 @@ onboarding.tutorial.ci_outro.commit.why.azure=Each new push you make on your bra
 onboarding.tutorial.ci_outro.commit.why.no_branches=Each new push you make on your main branch will trigger a new analysis in SonarQube.
 onboarding.tutorial.ci_outro.refresh=This page will then refresh with your analysis results.
 onboarding.tutorial.ci_outro.refresh.why=If the page doesn't refresh after a while, please double-check the analysis configuration, and check your logs.
-onboarding.tutorial.ci_outro.waiting_for_fist_analysis=Waiting for the first analysis to come in...
 onboarding.tutorial.other.project_key.sentence=Create a {file} file in your repository and paste the following code:
 onboarding.tutorial.cfamilly.compilation_database_info=If you have trouble using the build wrapper, you can try using a {link}.
 onboarding.tutorial.cfamilly.compilation_database_info.link=compilation database