aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2023-07-14 16:50:17 +0200
committersonartech <sonartech@sonarsource.com>2023-07-21 20:03:16 +0000
commita7cd1d6bf47e1e2966e3029bd28badf74c2af46b (patch)
tree5fbb424fbced3458ef540b1545a0b5cd91929dc0 /server
parent106e5141332a4d11dd055941394d4a8b4bc83f10 (diff)
downloadsonarqube-a7cd1d6bf47e1e2966e3029bd28badf74c2af46b.tar.gz
sonarqube-a7cd1d6bf47e1e2966e3029bd28badf74c2af46b.zip
SONAR-19906 Migrating github action tutorial page to MIUI
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/config/jest/SetupTestEnvironment.ts13
-rw-r--r--server/sonar-web/package.json2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx20
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx100
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx12
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx21
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx22
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx16
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx86
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx111
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx44
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx14
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx23
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx23
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx15
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/test-utils.ts2
-rw-r--r--server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts53
-rw-r--r--server/sonar-web/yarn.lock67
27 files changed, 410 insertions, 297 deletions
diff --git a/server/sonar-web/config/jest/SetupTestEnvironment.ts b/server/sonar-web/config/jest/SetupTestEnvironment.ts
index 54350855106..a10eb5dd7e3 100644
--- a/server/sonar-web/config/jest/SetupTestEnvironment.ts
+++ b/server/sonar-web/config/jest/SetupTestEnvironment.ts
@@ -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;
+});
diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json
index 6d42f3de7bb..dfdd3cf7092 100644
--- a/server/sonar-web/package.json
+++ b/server/sonar-web/package.json
@@ -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",
diff --git a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
index 6080a73c7be..541a20afae0 100644
--- a/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
@@ -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();
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx
index 0a1285123a2..07691d586b8 100644
--- a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx
@@ -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();
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx b/server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
index afe2f20d884..b3a6b0e2ece 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
@@ -17,15 +17,17 @@
* 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);
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx b/server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx
index 26a1368bac7..cad8d25b570 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx
@@ -17,11 +17,11 @@
* 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>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx b/server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx
index 5112acc8ced..5833fdfd374 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx b/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx
index 99d924eb5a3..332ba0ff603 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx
@@ -18,10 +18,10 @@
* 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>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx b/server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx
index 895aadb09b1..8087bdb5b04 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx
@@ -17,15 +17,15 @@
* 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>
);
}
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
index d84392f84cc..18f56f690d8 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx
@@ -17,12 +17,21 @@
* 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}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx b/server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx
index 301008e4cbb..4a138015375 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx
@@ -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,
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx b/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx
index eacfc511ef2..94aa6f25a07 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx
@@ -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>
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx b/server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx
index d44606562cd..e43cb39b1c9 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx
@@ -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} />
)}
diff --git a/server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx b/server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx
index e451af41491..b968dd2d3f6 100644
--- a/server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
index 6204a994d68..89ac0a07d6c 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
@@ -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}
/>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx
index 1315f062729..0f0b134674e 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx
index 2e137e2353b..a86d562f45e 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx
@@ -17,14 +17,20 @@
* 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>
+ </>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx
index 06ab3f48ec5..16c3e2e9c87 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx
@@ -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());
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx
index 34c99c81052..7aeaf12410a 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx
@@ -17,13 +17,13 @@
* 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 />
</>
)}
</>
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx
index 26580bbfc34..ad306727393 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx
@@ -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)
+ )}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx
index c0e84c06cd8..f5ab48b5b1d 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx
@@ -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} />
</>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx
index c9b253dd3e8..a619a5eeb87 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx
@@ -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)
+ )}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx
index b2a067f0b7c..d3633fce33c 100644
--- a/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx
@@ -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} />
</>
);
}
diff --git a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx
index 0031a56f1dd..fd1378270cf 100644
--- a/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx
@@ -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();
diff --git a/server/sonar-web/src/main/js/components/tutorials/test-utils.ts b/server/sonar-web/src/main/js/components/tutorials/test-utils.ts
index 760ee7d2d95..58be0817983 100644
--- a/server/sonar-web/src/main/js/components/tutorials/test-utils.ts
+++ b/server/sonar-web/src/main/js/components/tutorials/test-utils.ts
@@ -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
index 00000000000..6da44bf380f
--- /dev/null
+++ b/server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts
@@ -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;
+}
diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock
index 17a4c72095d..909ed7d284f 100644
--- a/server/sonar-web/yarn.lock
+++ b/server/sonar-web/yarn.lock
@@ -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