Browse Source

SONAR-19906 Migrating github action tutorial page to MIUI

tags/10.2.0.77647
Revanshu Paliwal 11 months ago
parent
commit
a7cd1d6bf4
28 changed files with 412 additions and 300 deletions
  1. 13
    0
      server/sonar-web/config/jest/SetupTestEnvironment.ts
  2. 2
    0
      server/sonar-web/package.json
  3. 1
    1
      server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx
  4. 10
    10
      server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx
  5. 45
    55
      server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx
  6. 6
    6
      server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx
  7. 7
    4
      server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx
  8. 8
    13
      server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx
  9. 13
    9
      server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx
  10. 11
    2
      server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx
  11. 10
    1
      server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx
  12. 2
    1
      server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx
  13. 10
    6
      server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx
  14. 7
    4
      server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx
  15. 0
    6
      server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx
  16. 33
    53
      server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx
  17. 57
    54
      server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx
  18. 23
    21
      server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx
  19. 6
    8
      server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx
  20. 9
    14
      server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx
  21. 0
    3
      server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx
  22. 9
    14
      server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx
  23. 0
    3
      server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx
  24. 7
    8
      server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx
  25. 1
    1
      server/sonar-web/src/main/js/components/tutorials/test-utils.ts
  26. 53
    0
      server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts
  27. 67
    0
      server/sonar-web/yarn.lock
  28. 2
    3
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 13
- 0
server/sonar-web/config/jest/SetupTestEnvironment.ts View 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;
});

+ 2
- 0
server/sonar-web/package.json View 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",

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-it.tsx View 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();
}

+ 10
- 10
server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/__tests__/BitbucketPipelinesTutorial-it.tsx View 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();

+ 45
- 55
server/sonar-web/src/main/js/components/tutorials/components/AllSet.tsx View File

@@ -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);

+ 6
- 6
server/sonar-web/src/main/js/components/tutorials/components/CreateYmlFile.tsx View File

@@ -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>
);
}

+ 7
- 4
server/sonar-web/src/main/js/components/tutorials/components/DefaultProjectKey.tsx View 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>
);
}

+ 8
- 13
server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx View File

@@ -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>
);
}

+ 13
- 9
server/sonar-web/src/main/js/components/tutorials/components/GradleBuild.tsx View File

@@ -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>
);
}

+ 11
- 2
server/sonar-web/src/main/js/components/tutorials/components/InlineSnippet.tsx View File

@@ -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}
/>
);
}

+ 10
- 1
server/sonar-web/src/main/js/components/tutorials/components/RenderOptions.tsx View 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,

+ 2
- 1
server/sonar-web/src/main/js/components/tutorials/components/SentenceWithFilename.tsx View 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>

+ 10
- 6
server/sonar-web/src/main/js/components/tutorials/components/TokenStepGenerator.tsx View 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} />
)}

+ 7
- 4
server/sonar-web/src/main/js/components/tutorials/components/YamlFileStep.tsx View 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>
);
}


+ 0
- 6
server/sonar-web/src/main/js/components/tutorials/github-action/AnalysisCommand.tsx View 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}
/>
);
}

+ 33
- 53
server/sonar-web/src/main/js/components/tutorials/github-action/GitHubActionTutorial.tsx View 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>
);
}

+ 57
- 54
server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx View File

@@ -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>
</>
);
}

+ 23
- 21
server/sonar-web/src/main/js/components/tutorials/github-action/__tests__/GithubActionTutorial-it.tsx View 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());

+ 6
- 8
server/sonar-web/src/main/js/components/tutorials/github-action/commands/CFamily.tsx View File

@@ -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 />
</>
)}
</>

+ 9
- 14
server/sonar-web/src/main/js/components/tutorials/github-action/commands/DotNet.tsx View 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)
)}
/>
);
}

+ 0
- 3
server/sonar-web/src/main/js/components/tutorials/github-action/commands/Gradle.tsx View 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} />
</>
);
}

+ 9
- 14
server/sonar-web/src/main/js/components/tutorials/github-action/commands/JavaMaven.tsx View 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)
)}
/>
);
}

+ 0
- 3
server/sonar-web/src/main/js/components/tutorials/github-action/commands/Others.tsx View 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} />
</>
);
}

+ 7
- 8
server/sonar-web/src/main/js/components/tutorials/jenkins/__tests__/JenkinsTutorial-it.tsx View 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();

+ 1
- 1
server/sonar-web/src/main/js/components/tutorials/test-utils.ts View 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'),
};
}


+ 53
- 0
server/sonar-web/src/main/js/hooks/useIntersectionObserver.ts View File

@@ -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;
}

+ 67
- 0
server/sonar-web/yarn.lock View 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

+ 2
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties View 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

Loading…
Cancel
Save