瀏覽代碼

SONAR-11029 Move the tutorial inside provisioned projects dashboard

* Move/Rename files of tutorials folder
* SONAR-11049 Update tutorial UI and move it inside the project dashboard
* SONAR-11050 Update tutorial to skip now useless steps
* Remove unused style
* SONAR-11030 Make dashboard tutorial work with already known project key
* Better manage error messages when no analysis and analyzed branches
* SONAR-11052 Refresh project status as long as there is no analysis
* SONAR-11051 Add infos suggestions depending on the ALM of the project
* Do no display tutorial when there is analyses in the pipe
tags/7.5
Grégoire Aubert 5 年之前
父節點
當前提交
4f5f81d6c1
共有 100 個檔案被更改,包括 1831 行新增404 行删除
  1. 1
    1
      server/sonar-docs/src/templates/page.css
  2. 6
    1
      server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
  3. 3
    3
      server/sonar-web/src/main/js/app/components/StartupModal.tsx
  4. 16
    24
      server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
  5. 1
    1
      server/sonar-web/src/main/js/app/styles/components/alerts.css
  6. 1
    1
      server/sonar-web/src/main/js/app/styles/components/boxed-group.css
  7. 0
    12
      server/sonar-web/src/main/js/app/styles/components/page.css
  8. 5
    0
      server/sonar-web/src/main/js/app/styles/init/misc.css
  9. 6
    3
      server/sonar-web/src/main/js/app/styles/sonarcloud.css
  10. 2
    0
      server/sonar-web/src/main/js/app/types.ts
  11. 2
    2
      server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
  12. 1
    1
      server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
  13. 1
    3
      server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap
  14. 2
    2
      server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
  15. 48
    17
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  16. 32
    17
      server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
  17. 1
    13
      server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
  18. 126
    0
      server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx
  19. 27
    5
      server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
  20. 27
    3
      server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx
  21. 117
    0
      server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx
  22. 0
    51
      server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js
  23. 60
    15
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap
  24. 229
    0
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap
  25. 0
    71
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap
  26. 17
    12
      server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
  27. 1
    0
      server/sonar-web/src/main/js/apps/overview/styles.css
  28. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx
  29. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx
  30. 31
    32
      server/sonar-web/src/main/js/apps/projects/create/CreateProjectPage.tsx
  31. 1
    1
      server/sonar-web/src/main/js/apps/projects/create/ManualProjectCreate.tsx
  32. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx
  33. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx
  34. 12
    4
      server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx
  35. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx
  36. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap
  37. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap
  38. 1
    1
      server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
  39. 0
    0
      server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap
  40. 42
    0
      server/sonar-web/src/main/js/apps/projects/create/utils.ts
  41. 8
    2
      server/sonar-web/src/main/js/apps/projects/routes.ts
  42. 94
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
  43. 88
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
  44. 47
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx
  45. 38
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx
  46. 54
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap
  47. 78
    0
      server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap
  48. 44
    47
      server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx
  49. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx
  50. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx
  51. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/OrganizationStep.tsx
  52. 120
    0
      server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx
  53. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx
  54. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx
  55. 5
    5
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx
  56. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewOrganizationForm-test.tsx
  57. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx
  58. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/OrganizationStep-test.tsx
  59. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/Step-test.tsx
  60. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx
  61. 20
    20
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap
  62. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap
  63. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap
  64. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap
  65. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/Step-test.tsx.snap
  66. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TokenStep-test.tsx.snap
  67. 146
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx
  68. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/BuildWrapper.tsx
  69. 2
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx
  70. 3
    2
      server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx
  71. 3
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx
  72. 3
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx
  73. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/MSBuildScanner.tsx
  74. 3
    2
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Msvc.tsx
  75. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx
  76. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/SQScanner.tsx
  77. 71
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/AnalysisCommand-test.tsx
  78. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/BuildWrapper-test.tsx
  79. 1
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/ClangGCC-test.tsx
  80. 7
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/DotNet-test.tsx
  81. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaGradle-test.tsx
  82. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaMaven-test.tsx
  83. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/MSBuildScanner-test.tsx
  84. 9
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Msvc-test.tsx
  85. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Other-test.tsx
  86. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/SQScanner-test.tsx
  87. 49
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap
  88. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap
  89. 0
    2
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap
  90. 2
    3
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/DotNet-test.tsx.snap
  91. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap
  92. 1
    1
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap
  93. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap
  94. 0
    2
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Msvc-test.tsx.snap
  95. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Other-test.tsx.snap
  96. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap
  97. 12
    12
      server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx
  98. 99
    0
      server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx
  99. 4
    4
      server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx
  100. 0
    0
      server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap

+ 1
- 1
server/sonar-docs/src/templates/page.css 查看文件

@@ -24,7 +24,7 @@
.alert-info {
border-color: #bce8f1;
background-color: #d9edf7;
color: #31708f;
color: #666666;
}

.alert-success {

+ 6
- 1
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx 查看文件

@@ -176,7 +176,12 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
differenceBy(newTasksInProgress, tasksInProgress, 'id').length > 0);

shouldFetchComponent = Boolean(currentTaskChanged || progressChanged);
if (!shouldFetchComponent && component && newTasksInProgress.length > 0) {
if (
!shouldFetchComponent &&
component &&
(newTasksInProgress.length > 0 || !component.analysisDate)
) {
// Refresh the status as long as there is tasks in progress or no analysis
window.clearTimeout(this.watchStatusTimer);
this.watchStatusTimer = window.setTimeout(
() => this.fetchStatus(component),

+ 3
- 3
server/sonar-web/src/main/js/app/components/StartupModal.tsx 查看文件

@@ -35,7 +35,7 @@ import { lazyLoad } from '../../components/lazyLoad';
const CreateOrganizationForm = lazyLoad(() =>
import('../../apps/account/organizations/CreateOrganizationForm')
);
const Onboarding = lazyLoad(() => import('../../apps/tutorials/Onboarding'));
const OnboardingModal = lazyLoad(() => import('../../apps/tutorials/onboarding/OnboardingModal'));
const LicensePromptModal = lazyLoad(
() => import('../../apps/marketplace/components/LicensePromptModal'),
'LicensePromptModal'
@@ -135,7 +135,7 @@ export class StartupModal extends React.PureComponent<Props, State> {
openProjectOnboarding = () => {
if (isSonarCloud()) {
this.setState({ automatic: false, modal: undefined });
this.context.router.push(`/onboarding`);
this.context.router.push(`/projects/create`);
} else {
this.setState({ modal: ModalKey.projectOnboarding });
}
@@ -189,7 +189,7 @@ export class StartupModal extends React.PureComponent<Props, State> {
{this.props.children}
{modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />}
{modal === ModalKey.onboarding && (
<Onboarding
<OnboardingModal
onClose={this.closeOnboarding}
onOpenOrganizationOnboarding={this.openOrganizationOnboarding}
onOpenProjectOnboarding={this.openProjectOnboarding}

+ 16
- 24
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx 查看文件

@@ -36,25 +36,23 @@ import { STATUSES } from '../../../apps/background-tasks/constants';
import { waitAndUpdate } from '../../../helpers/testUtils';

jest.mock('../../../api/branches', () => ({
getBranches: jest.fn(() => Promise.resolve([])),
getPullRequests: jest.fn(() => Promise.resolve([]))
getBranches: jest.fn().mockResolvedValue([]),
getPullRequests: jest.fn().mockResolvedValue([])
}));

jest.mock('../../../api/ce', () => ({
getTasksForComponent: jest.fn(() => Promise.resolve({ queue: [] }))
getTasksForComponent: jest.fn().mockResolvedValue({ queue: [] })
}));

jest.mock('../../../api/components', () => ({
getComponentData: jest.fn(() => Promise.resolve({}))
getComponentData: jest.fn().mockResolvedValue({ analysisDate: '2018-07-30' })
}));

jest.mock('../../../api/nav', () => ({
getComponentNavigation: jest.fn(() =>
Promise.resolve({
breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
key: 'portfolioKey'
})
)
getComponentNavigation: jest.fn().mockResolvedValue({
breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }],
key: 'portfolioKey'
})
}));

// mock this, because some of its children are using redux store
@@ -90,14 +88,12 @@ it('changes component', () => {
});

it("loads branches for module's project", async () => {
(getComponentNavigation as jest.Mock<any>).mockImplementationOnce(() =>
Promise.resolve({
breadcrumbs: [
{ key: 'projectKey', name: 'project', qualifier: 'TRK' },
{ key: 'moduleKey', name: 'module', qualifier: 'BRC' }
]
})
);
(getComponentNavigation as jest.Mock<any>).mockResolvedValueOnce({
breadcrumbs: [
{ key: 'projectKey', name: 'project', qualifier: 'TRK' },
{ key: 'moduleKey', name: 'module', qualifier: 'BRC' }
]
});

mount(
<ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'moduleKey' } }}>
@@ -149,9 +145,7 @@ it('updates branches on change', () => {
});

it('loads organization', async () => {
(getComponentData as jest.Mock<any>).mockImplementationOnce(() =>
Promise.resolve({ organization: 'org' })
);
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });

const fetchOrganizations = jest.fn();
mount(
@@ -166,9 +160,7 @@ it('loads organization', async () => {
});

it('fetches status', async () => {
(getComponentData as jest.Mock<any>).mockImplementationOnce(() =>
Promise.resolve({ organization: 'org' })
);
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });

mount(
<ComponentContainer fetchOrganizations={jest.fn()} location={{ query: { id: 'foo' } }}>

+ 1
- 1
server/sonar-web/src/main/js/app/styles/components/alerts.css 查看文件

@@ -51,7 +51,7 @@
.alert-info {
border-color: #bce8f1;
background-color: #d9edf7;
color: #31708f;
color: #666666;
}

.alert-success {

+ 1
- 1
server/sonar-web/src/main/js/app/styles/components/boxed-group.css 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.boxed-group {
margin-bottom: 20px;
margin-bottom: calc(2.5 * var(--gridSize));
border: 1px solid var(--barBorderColor);
border-radius: 2px;
background-color: #fff;

+ 0
- 12
server/sonar-web/src/main/js/app/styles/components/page.css 查看文件

@@ -187,7 +187,6 @@
}

.page-sidebar-fixed {
width: 30%;
min-width: 300px;
flex-shrink: 0;
padding-left: 40px;
@@ -228,17 +227,6 @@
}
}

.page-sidebar-sticky .page-sidebar-sticky-inner .search-navigator-facets-list {
width: 260px;
margin-left: calc(50vw - 640px + 290px - 260px - 37px);
}

@media (max-width: 1335px) {
.page-sidebar-sticky .page-sidebar-sticky-inner .search-navigator-facets-list {
margin-left: 20px;
}
}

.layout-page {
display: flex;
align-items: stretch;

+ 5
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css 查看文件

@@ -293,6 +293,11 @@ td.big-spacer-top {
flex-direction: row;
}

.display-flex-column {
display: flex !important;
flex-direction: column;
}

.display-flex-center {
display: flex !important;
align-items: center;

+ 6
- 3
server/sonar-web/src/main/js/app/styles/sonarcloud.css 查看文件

@@ -37,19 +37,22 @@
display: flex;
clear: left;
margin-bottom: calc(3 * var(--gridSize));
box-shadow: 0 1px 0 var(--barBorderColor);
border-bottom: 1px solid var(--barBorderColor);
font-size: var(--mediumFontSize);
}

.sonarcloud .flex-tabs > li > a {
position: relative;
display: block;
top: 1px;
height: 100%;
width: 100%;
box-sizing: border-box;
color: var(--secondFontColor);
font-weight: 600;
cursor: pointer;
padding-bottom: var(--gridSize);
border-bottom: 2px solid transparent;
padding-bottom: calc(1.5 * var(--gridSize));
border-bottom: 3px solid transparent;
transition: color 0.2s ease;
}


+ 2
- 0
server/sonar-web/src/main/js/app/types.ts 查看文件

@@ -61,6 +61,8 @@ export interface Breadcrumb {
}

export interface Component extends LightComponent {
almId?: string;
almRepoUrl?: string;
analysisDate?: string;
breadcrumbs: Breadcrumb[];
configuration?: ComponentConfiguration;

+ 2
- 2
server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx 查看文件

@@ -57,7 +57,7 @@ export default function FacetsList(props: Props) {
props.selectedProfile === undefined ||
!props.query.activation;
return (
<div className="search-navigator-facets-list">
<>
<LanguageFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
@@ -145,6 +145,6 @@ export default function FacetsList(props: Props) {
/>
</>
)}
</div>
</>
);
}

+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js 查看文件

@@ -77,7 +77,7 @@ export default class Sidebar extends React.PureComponent {

render() {
return (
<div className="search-navigator-facets-list">
<div>
<ProjectOverviewFacet
onChange={this.changeMetric}
selected={this.props.selectedMetric}

+ 1
- 3
server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap 查看文件

@@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display two facets 1`] = `
<div
className="search-navigator-facets-list"
>
<div>
<ProjectOverviewFacet
onChange={[Function]}
selected="duplicated_lines_density"

+ 2
- 2
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx 查看文件

@@ -77,7 +77,7 @@ export default class Sidebar extends React.PureComponent<Props> {
(this.props.organization && this.props.organization.key);

return (
<div className="search-navigator-facets-list">
<>
<TypeFacet
fetching={this.props.loadingFacets.types === true}
loading={this.props.loading}
@@ -256,7 +256,7 @@ export default class Sidebar extends React.PureComponent<Props> {
stats={facets.authors}
/>
)}
</div>
</>
);
}
}

+ 48
- 17
server/sonar-web/src/main/js/apps/overview/components/App.tsx 查看文件

@@ -19,11 +19,21 @@
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
import OverviewApp from './OverviewApp';
import { Helmet } from 'react-helmet';
import EmptyOverview from './EmptyOverview';
import OverviewApp from './OverviewApp';
import SonarCloudEmptyOverview from './SonarCloudEmptyOverview';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { Component, BranchLike } from '../../../app/types';
import { isShortLivingBranch } from '../../../helpers/branches';
import { getShortLivingBranchUrl, getCodeUrl } from '../../../helpers/urls';
import {
getShortLivingBranchUrl,
getCodeUrl,
getProjectUrl,
getBaseUrl,
getPathUrlAsString
} from '../../../helpers/urls';
import { isSonarCloud } from '../../../helpers/system';

interface Props {
branchLike?: BranchLike;
@@ -67,22 +77,43 @@ export default class App extends React.PureComponent<Props> {
return null;
}

if (!component.analysisDate) {
return (
<EmptyOverview
component={component.key}
hasBranches={branchLikes.length > 1}
showWarning={!this.props.isPending && !this.props.isInProgress}
/>
);
}

return (
<OverviewApp
branchLike={branchLike}
component={component}
onComponentChange={this.props.onComponentChange}
/>
<>
{isSonarCloud() && (
<Helmet>
<link
href={getBaseUrl() + getPathUrlAsString(getProjectUrl(component.key))}
rel="canonical"
/>
</Helmet>
)}
<Suggestions suggestions="overview" />

{!component.analysisDate &&
(isSonarCloud() ? (
<SonarCloudEmptyOverview
branchLike={branchLike}
branchLikes={branchLikes}
component={component}
hasAnalyses={this.props.isPending || this.props.isInProgress}
onComponentChange={this.props.onComponentChange}
/>
) : (
<EmptyOverview
branchLike={branchLike}
branchLikes={branchLikes}
component={component.key}
showWarning={!this.props.isPending && !this.props.isInProgress}
/>
))}
{component.analysisDate && (
<OverviewApp
branchLike={branchLike}
component={component}
onComponentChange={this.props.onComponentChange}
/>
)}
</>
);
}
}

+ 32
- 17
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx 查看文件

@@ -21,30 +21,39 @@ import * as React from 'react';
import { Link } from 'react-router';
import { FormattedMessage } from 'react-intl';
import { translate } from '../../../helpers/l10n';
import { BranchLike } from '../../../app/types';
import { isBranch, isLongLivingBranch } from '../../../helpers/branches';

interface Props {
branchLike?: BranchLike;
branchLikes: BranchLike[];
component: string;
hasBranches?: boolean;
showWarning?: boolean;
}

export default function EmptyOverview({ component, hasBranches, showWarning }: Props) {
const rawMessage = translate('provisioning.no_analysis.delete');
const head = rawMessage.substr(0, rawMessage.indexOf('{0}'));
const tail = rawMessage.substr(rawMessage.indexOf('{0}') + 3);
export default function EmptyOverview({ branchLike, branchLikes, component, showWarning }: Props) {
const hasBranches = branchLikes.length > 1;
const hasBadConfig =
branchLikes.length > 2 ||
(branchLikes.length === 2 && branchLikes.some(branch => isLongLivingBranch(branch)));

const branchWarnMsg = hasBadConfig
? translate('provisioning.no_analysis_on_main_branch.bad_configuration')
: translate('provisioning.no_analysis_on_main_branch');

return (
<div className="page page-limited">
{showWarning && (
<div className="big-spacer-bottom">
<div className="alert alert-warning">
{hasBranches ? (
{hasBranches && isBranch(branchLike) ? (
<FormattedMessage
defaultMessage={translate('provisioning.no_analysis_on_main_branch')}
id="provisioning.no_analysis_on_main_branch"
defaultMessage={branchWarnMsg}
id={branchWarnMsg}
values={{
branch: (
<div className="outline-badge text-baseline little-spacer-right">
branchName: branchLike.name,
branchType: (
<div className="outline-badge text-baseline">
{translate('branches.main_branch')}
</div>
)
@@ -57,13 +66,19 @@ export default function EmptyOverview({ component, hasBranches, showWarning }: P

{!hasBranches && (
<div className="big-spacer-top">
{head}
<Link
className="text-danger"
to={{ pathname: '/project/deletion', query: { id: component } }}>
{translate('provisioning.no_analysis.delete_project')}
</Link>
{tail}
<FormattedMessage
defaultMessage={translate('provisioning.no_analysis.delete')}
id={'provisioning.no_analysis.delete'}
values={{
link: (
<Link
className="text-danger"
to={{ pathname: '/project/deletion', query: { id: component } }}>
{translate('provisioning.no_analysis.delete_project')}
</Link>
)
}}
/>
</div>
)}
</div>

+ 1
- 13
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx 查看文件

@@ -20,16 +20,14 @@
import * as React from 'react';
import { uniq } from 'lodash';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import QualityGate from '../qualityGate/QualityGate';
import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate';
import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
import CodeSmells from '../main/CodeSmells';
import Coverage from '../main/Coverage';
import Duplications from '../main/Duplications';
import MetaContainer from '../meta/MetaContainer';
import QualityGate from '../qualityGate/QualityGate';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { getMeasuresAndMeta } from '../../../api/measures';
import { getAllTimeMachineData, History } from '../../../api/time-machine';
import { parseDate } from '../../../helpers/dates';
@@ -52,8 +50,6 @@ import { fetchMetrics } from '../../../store/rootActions';
import { getMetrics } from '../../../store/rootReducer';
import { BranchLike, Component, Metric } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl, getSonarCloudUrlAsString } from '../../../helpers/urls';
import { isSonarCloud } from '../../../helpers/system';
import '../styles.css';

interface OwnProps {
@@ -246,14 +242,6 @@ export class OverviewApp extends React.PureComponent<Props, State> {
return (
<div className="page page-limited">
<div className="overview page-with-sidebar">
<Suggestions suggestions="overview" />

{isSonarCloud() && (
<Helmet>
<link href={getSonarCloudUrlAsString(getProjectUrl(component.key))} rel="canonical" />
</Helmet>
)}

{this.renderMain()}

<div className="overview-sidebar page-sidebar-fixed">

+ 126
- 0
server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx 查看文件

@@ -0,0 +1,126 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import AnalyzeTutorial from '../../tutorials/analyzeProject/AnalyzeTutorial';
import MetaContainer from '../meta/MetaContainer';
import { BranchLike, Component, CurrentUser, isLoggedIn } from '../../../app/types';
import { isLongLivingBranch, isBranch, isMainBranch } from '../../../helpers/branches';
import { translate } from '../../../helpers/l10n';
import { getCurrentUser } from '../../../store/rootReducer';
import '../../../app/styles/sonarcloud.css';

interface OwnProps {
branchLike?: BranchLike;
branchLikes: BranchLike[];
component: Component;
hasAnalyses?: boolean;
onComponentChange: (changes: {}) => void;
}

interface StateProps {
currentUser: CurrentUser;
}

type Props = OwnProps & StateProps;

export function SonarCloudEmptyOverview({
branchLike,
branchLikes,
component,
currentUser,
hasAnalyses,
onComponentChange
}: Props) {
const hasBranches = branchLikes.length > 1;
const hasBadBranchConfig =
branchLikes.length > 2 ||
(branchLikes.length === 2 && branchLikes.some(branch => isLongLivingBranch(branch)));
return (
<div className="page page-limited">
<div className="overview page-with-sidebar">
<div className="overview-main page-main sonarcloud">
{isLoggedIn(currentUser) && isMainBranch(branchLike) ? (
<>
{hasBranches && (
<WarningMessage
branchLike={branchLike}
message={
hasBadBranchConfig
? translate('provisioning.no_analysis_on_main_branch.bad_configuration')
: translate('provisioning.no_analysis_on_main_branch')
}
/>
)}
{!hasBranches &&
!hasAnalyses && <AnalyzeTutorial component={component} currentUser={currentUser} />}
</>
) : (
<WarningMessage
branchLike={branchLike}
message={translate('provisioning.no_analysis_on_main_branch')}
/>
)}
</div>

<div className="overview-sidebar page-sidebar-fixed">
<MetaContainer
branchLike={branchLike}
component={component}
onComponentChange={onComponentChange}
/>
</div>
</div>
</div>
);
}

export function WarningMessage({
branchLike,
message
}: {
branchLike?: BranchLike;
message: string;
}) {
if (!isBranch(branchLike)) {
return null;
}
return (
<div className="alert alert-warning">
<FormattedMessage
defaultMessage={message}
id={message}
values={{
branchName: branchLike.name,
branchType: (
<div className="outline-badge text-baseline">{translate('branches.main_branch')}</div>
)
}}
/>
</div>
);
}

const mapStateToProps = (state: any) => ({
currentUser: getCurrentUser(state)
});

export default connect<StateProps, {}, OwnProps>(mapStateToProps)(SonarCloudEmptyOverview);

+ 27
- 5
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx 查看文件

@@ -20,9 +20,10 @@
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import App from '../App';
import OverviewApp from '../OverviewApp';
import EmptyOverview from '../EmptyOverview';
import { BranchType, LongLivingBranch } from '../../../../app/types';
import { isSonarCloud } from '../../../../helpers/system';

jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));

const component = {
key: 'foo',
@@ -34,13 +35,34 @@ const component = {
version: '0.0.1'
};

beforeEach(() => {
(isSonarCloud as jest.Mock<any>).mockClear();
(isSonarCloud as jest.Mock<any>).mockReturnValue(false);
});

it('should render OverviewApp', () => {
expect(getWrapper().type()).toBe(OverviewApp);
expect(
getWrapper()
.find('Connect(OverviewApp)')
.exists()
).toBeTruthy();
});

it('should render EmptyOverview', () => {
const output = getWrapper({ component: { key: 'foo' } });
expect(output.type()).toBe(EmptyOverview);
expect(
getWrapper({ component: { key: 'foo' } })
.find('EmptyOverview')
.exists()
).toBeTruthy();
});

it('should render SonarCloudEmptyOverview', () => {
(isSonarCloud as jest.Mock<any>).mockReturnValue(true);
expect(
getWrapper({ component: { key: 'foo' } })
.find('Connect(SonarCloudEmptyOverview)')
.exists()
).toBeTruthy();
});

it('redirects on Code page for files', () => {

+ 27
- 3
server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx 查看文件

@@ -20,17 +20,41 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import EmptyOverview from '../EmptyOverview';
import { BranchType } from '../../../../app/types';

const branch = { isMain: true, name: 'b', type: BranchType.LONG };

it('renders', () => {
expect(shallow(<EmptyOverview component="abcd" showWarning={true} />)).toMatchSnapshot();
expect(
shallow(<EmptyOverview branchLikes={[]} component="abcd" showWarning={true} />)
).toMatchSnapshot();
});

it('does not render warning', () => {
expect(shallow(<EmptyOverview component="abcd" showWarning={false} />)).toMatchSnapshot();
expect(
shallow(<EmptyOverview branchLikes={[]} component="abcd" showWarning={false} />)
).toMatchSnapshot();
});

it('should render another message when there are branches', () => {
expect(
shallow(<EmptyOverview component="abcd" hasBranches={true} showWarning={true} />)
shallow(
<EmptyOverview
branchLike={branch}
branchLikes={[branch, branch]}
component="abcd"
showWarning={true}
/>
)
).toMatchSnapshot();
expect(
shallow(
<EmptyOverview
branchLike={branch}
branchLikes={[branch, branch, branch]}
component="abcd"
showWarning={true}
/>
)
).toMatchSnapshot();
});

+ 117
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx 查看文件

@@ -0,0 +1,117 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { shallow } from 'enzyme';
import { SonarCloudEmptyOverview, WarningMessage } from '../SonarCloudEmptyOverview';
import { BranchType } from '../../../../app/types';

const branch = { isMain: true, name: 'b', type: BranchType.LONG };

const component = {
key: 'foo',
analysisDate: '2016-01-01',
breadcrumbs: [],
name: 'Foo',
organization: 'org',
qualifier: 'TRK',
version: '0.0.1'
};

const LoggedInUser = {
isLoggedIn: true,
login: 'luke',
name: 'Skywalker'
};

it('renders correctly', () => {
expect(
shallow(
<SonarCloudEmptyOverview
branchLike={branch}
branchLikes={[branch]}
component={component}
currentUser={LoggedInUser}
onComponentChange={jest.fn()}
/>
)
).toMatchSnapshot();
});

it('should render another message when there are branches', () => {
expect(
shallow(
<SonarCloudEmptyOverview
branchLike={branch}
branchLikes={[branch, branch]}
component={component}
currentUser={LoggedInUser}
onComponentChange={jest.fn()}
/>
)
).toMatchSnapshot();
expect(
shallow(
<SonarCloudEmptyOverview
branchLike={branch}
branchLikes={[branch, branch, branch]}
component={component}
currentUser={LoggedInUser}
onComponentChange={jest.fn()}
/>
)
).toMatchSnapshot();
});

it('should not render the tutorial', () => {
expect(
shallow(
<SonarCloudEmptyOverview
branchLike={branch}
branchLikes={[branch]}
component={component}
currentUser={LoggedInUser}
hasAnalyses={true}
onComponentChange={jest.fn()}
/>
)
).toMatchSnapshot();
});

it('should render warning message', () => {
expect(shallow(<WarningMessage branchLike={branch} message="foo" />)).toMatchSnapshot();
});

it('should not render warning message', () => {
expect(
shallow(
<WarningMessage
branchLike={{
base: 'foo',
branch: 'bar',
key: '1',
title: 'PR bar'
}}
message="foo"
/>
)
.find('FormattedMessage')
.exists()
).toBeFalsy();
});

+ 0
- 51
server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js 查看文件

@@ -1,51 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 React from 'react';
import { shallow } from 'enzyme';
import Timeline from '../Timeline';
import { parseDate } from '../../../../helpers/dates';

const range = parseDate('2017-05-01T00:00:00.000Z');
const history = [
{ date: parseDate('2017-04-08T00:00:00.000Z'), value: '29.6' },
{ date: parseDate('2017-04-09T00:00:00.000Z'), value: '170.8' },
{ date: parseDate('2017-05-08T00:00:00.000Z'), value: '360' },
{ date: parseDate('2017-05-09T00:00:00.000Z'), value: '39' }
];

it('should render correctly with an "after" range', () => {
expect(shallow(<Timeline after={range} history={history} />)).toMatchSnapshot();
});

it('should render correctly with a "before" range', () => {
expect(shallow(<Timeline before={range} history={history} />)).toMatchSnapshot();
});

it('should have a correct domain with strings or numbers', () => {
const date = parseDate('2017-05-08T00:00:00.000Z');
const wrapper = shallow(<Timeline after={range} history={history} />);
expect(wrapper.find('LineChart').props().domain).toEqual([0, 360]);

wrapper.setProps({ history: [{ date, value: '360.33' }, { date, value: '39.54' }] });
expect(wrapper.find('LineChart').props().domain).toEqual([0, 360.33]);

wrapper.setProps({ history: [{ date, value: 360 }, { date, value: 39 }] });
expect(wrapper.find('LineChart').props().domain).toEqual([0, 360]);
});

+ 60
- 15
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap 查看文件

@@ -30,22 +30,29 @@ exports[`renders 1`] = `
<div
className="big-spacer-top"
>
<Link
className="text-danger"
onlyActiveOnIndex={false}
style={Object {}}
to={
<FormattedMessage
defaultMessage="provisioning.no_analysis.delete"
id="provisioning.no_analysis.delete"
values={
Object {
"pathname": "/project/deletion",
"query": Object {
"id": "abcd",
},
"link": <Link
className="text-danger"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/deletion",
"query": Object {
"id": "abcd",
},
}
}
>
provisioning.no_analysis.delete_project
</Link>,
}
}
>
provisioning.no_analysis.delete_project
</Link>
ovisioning.no_analysis.delete
/>
</div>
</div>
<div>
@@ -74,8 +81,46 @@ exports[`should render another message when there are branches 1`] = `
id="provisioning.no_analysis_on_main_branch"
values={
Object {
"branch": <div
className="outline-badge text-baseline little-spacer-right"
"branchName": "b",
"branchType": <div
className="outline-badge text-baseline"
>
branches.main_branch
</div>,
}
}
/>
</div>
</div>
<div>
<h4>
key
</h4>
<code>
abcd
</code>
</div>
</div>
`;

exports[`should render another message when there are branches 2`] = `
<div
className="page page-limited"
>
<div
className="big-spacer-bottom"
>
<div
className="alert alert-warning"
>
<FormattedMessage
defaultMessage="provisioning.no_analysis_on_main_branch.bad_configuration"
id="provisioning.no_analysis_on_main_branch.bad_configuration"
values={
Object {
"branchName": "b",
"branchType": <div
className="outline-badge text-baseline"
>
branches.main_branch
</div>,

+ 229
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap 查看文件

@@ -0,0 +1,229 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders correctly 1`] = `
<div
className="page page-limited"
>
<div
className="overview page-with-sidebar"
>
<div
className="overview-main page-main sonarcloud"
>
<React.Fragment>
<AnalyzeTutorial
component={
Object {
"analysisDate": "2016-01-01",
"breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
"organization": "org",
"qualifier": "TRK",
"version": "0.0.1",
}
}
currentUser={
Object {
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
}
}
/>
</React.Fragment>
</div>
<div
className="overview-sidebar page-sidebar-fixed"
>
<Connect(Meta)
branchLike={
Object {
"isMain": true,
"name": "b",
"type": "LONG",
}
}
component={
Object {
"analysisDate": "2016-01-01",
"breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
"organization": "org",
"qualifier": "TRK",
"version": "0.0.1",
}
}
onComponentChange={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should not render the tutorial 1`] = `
<div
className="page page-limited"
>
<div
className="overview page-with-sidebar"
>
<div
className="overview-main page-main sonarcloud"
>
<React.Fragment />
</div>
<div
className="overview-sidebar page-sidebar-fixed"
>
<Connect(Meta)
branchLike={
Object {
"isMain": true,
"name": "b",
"type": "LONG",
}
}
component={
Object {
"analysisDate": "2016-01-01",
"breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
"organization": "org",
"qualifier": "TRK",
"version": "0.0.1",
}
}
onComponentChange={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should render another message when there are branches 1`] = `
<div
className="page page-limited"
>
<div
className="overview page-with-sidebar"
>
<div
className="overview-main page-main sonarcloud"
>
<React.Fragment>
<WarningMessage
branchLike={
Object {
"isMain": true,
"name": "b",
"type": "LONG",
}
}
message="provisioning.no_analysis_on_main_branch"
/>
</React.Fragment>
</div>
<div
className="overview-sidebar page-sidebar-fixed"
>
<Connect(Meta)
branchLike={
Object {
"isMain": true,
"name": "b",
"type": "LONG",
}
}
component={
Object {
"analysisDate": "2016-01-01",
"breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
"organization": "org",
"qualifier": "TRK",
"version": "0.0.1",
}
}
onComponentChange={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should render another message when there are branches 2`] = `
<div
className="page page-limited"
>
<div
className="overview page-with-sidebar"
>
<div
className="overview-main page-main sonarcloud"
>
<React.Fragment>
<WarningMessage
branchLike={
Object {
"isMain": true,
"name": "b",
"type": "LONG",
}
}
message="provisioning.no_analysis_on_main_branch.bad_configuration"
/>
</React.Fragment>
</div>
<div
className="overview-sidebar page-sidebar-fixed"
>
<Connect(Meta)
branchLike={
Object {
"isMain": true,
"name": "b",
"type": "LONG",
}
}
component={
Object {
"analysisDate": "2016-01-01",
"breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
"organization": "org",
"qualifier": "TRK",
"version": "0.0.1",
}
}
onComponentChange={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should render warning message 1`] = `
<div
className="alert alert-warning"
>
<FormattedMessage
defaultMessage="foo"
id="foo"
values={
Object {
"branchName": "b",
"branchType": <div
className="outline-badge text-baseline"
>
branches.main_branch
</div>,
}
}
/>
</div>
`;

+ 0
- 71
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap 查看文件

@@ -1,71 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly with a "before" range 1`] = `
<LineChart
data={
Array [
Object {
"x": 0,
"y": 29.6,
},
Object {
"x": 1,
"y": 170.8,
},
]
}
displayBackdrop={true}
displayPoints={false}
displayVerticalGrid={false}
domain={
Array [
0,
360,
]
}
height={80}
padding={
Array [
0,
0,
0,
0,
]
}
/>
`;

exports[`should render correctly with an "after" range 1`] = `
<LineChart
data={
Array [
Object {
"x": 0,
"y": 360,
},
Object {
"x": 1,
"y": 39,
},
]
}
displayBackdrop={true}
displayPoints={false}
displayVerticalGrid={false}
domain={
Array [
0,
360,
]
}
height={80}
padding={
Array [
0,
0,
0,
0,
]
}
/>
`;

+ 17
- 12
server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx 查看文件

@@ -58,8 +58,8 @@ interface OwnProps {
branchLike?: BranchLike;
component: Component;
history?: History;
measures: MeasureEnhanced[];
metrics: { [key: string]: Metric };
measures?: MeasureEnhanced[];
metrics?: { [key: string]: Metric };
onComponentChange: (changes: {}) => void;
}

@@ -106,7 +106,7 @@ export class Meta extends React.PureComponent<Props> {

render() {
const { organizationsEnabled } = this.context;
const { branchLike, component, metrics, organization } = this.props;
const { branchLike, component, measures, metrics, organization } = this.props;
const { qualifier, description, visibility } = component;

const isProject = qualifier === 'TRK';
@@ -131,16 +131,20 @@ export class Meta extends React.PureComponent<Props> {
{isProject && (
<MetaTags component={component} onComponentChange={this.props.onComponentChange} />
)}
<MetaSize branchLike={branchLike} component={component} measures={this.props.measures} />
{measures && (
<MetaSize branchLike={branchLike} component={component} measures={measures} />
)}
</div>

<AnalysesList
branchLike={branchLike}
component={component}
history={this.props.history}
metrics={metrics}
qualifier={component.qualifier}
/>
{metrics && (
<AnalysesList
branchLike={branchLike}
component={component}
history={this.props.history}
metrics={metrics}
qualifier={component.qualifier}
/>
)}

{this.renderQualityInfos()}

@@ -152,7 +156,8 @@ export class Meta extends React.PureComponent<Props> {
</div>

{!isPrivate &&
(isProject || isApp) && (
(isProject || isApp) &&
metrics && (
<BadgesModal
branchLike={branchLike}
metrics={metrics}

+ 1
- 0
server/sonar-web/src/main/js/apps/overview/styles.css 查看文件

@@ -24,6 +24,7 @@
.overview-main {
background-color: var(--barBackgroundColor);
transition: transform 0.5s ease, opacity 0.5s ease;
width: calc(100% - 300px);
}

.overview-sidebar {

server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AlmRepositoryItem.tsx → server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx → server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx → server/sonar-web/src/main/js/apps/projects/create/CreateProjectPage.tsx 查看文件

@@ -19,11 +19,13 @@
*/
import * as React from 'react';
import * as classNames from 'classnames';
import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { InjectedRouter } from 'react-router';
import { Location } from 'history';
import Helmet from 'react-helmet';
import AutoProjectCreate from './AutoProjectCreate';
import ManualProjectCreate from './ManualProjectCreate';
import { serializeQuery, Query, parseQuery } from './utils';
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
import { getCurrentUser } from '../../../store/rootReducer';
import { skipOnboarding } from '../../../store/users/actions';
@@ -32,10 +34,11 @@ import { translate } from '../../../helpers/l10n';
import { ProjectBase } from '../../../api/components';
import { getProjectUrl, getOrganizationUrl } from '../../../helpers/urls';
import '../../../app/styles/sonarcloud.css';
import '../styles.css';

interface OwnProps {
location: Location;
onFinishOnboarding: () => void;
router: Pick<InjectedRouter, 'push' | 'replace'>;
}

interface StateProps {
@@ -46,26 +49,16 @@ interface DispatchProps {
skipOnboarding: () => void;
}

enum Tabs {
AUTO,
MANUAL
}

type Props = OwnProps & StateProps & DispatchProps;

interface State {
activeTab: Tabs;
}

export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
export class CreateProjectPage extends React.PureComponent<Props> {
mounted = false;
static contextTypes = {
router: PropTypes.object
};

constructor(props: Props) {
super(props);
this.state = { activeTab: this.shouldDisplayTabs(props) ? Tabs.AUTO : Tabs.MANUAL };
if (!this.canAutoCreate(props)) {
this.updateQuery({ manual: true });
}
}

componentDidMount() {
@@ -85,13 +78,13 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {

handleProjectCreate = (projects: Pick<ProjectBase, 'key'>[], organization?: string) => {
if (projects.length > 1 && organization) {
this.context.router.push(getOrganizationUrl(organization) + '/projects');
this.props.router.push(getOrganizationUrl(organization) + '/projects');
} else if (projects.length === 1) {
this.context.router.push(getProjectUrl(projects[0].key));
this.props.router.push(getProjectUrl(projects[0].key));
}
};

shouldDisplayTabs = ({ currentUser } = this.props) => {
canAutoCreate = ({ currentUser } = this.props) => {
return (
isLoggedIn(currentUser) &&
['bitbucket', 'github'].includes(currentUser.externalProvider || '')
@@ -100,12 +93,19 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {

showAuto = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
this.setState({ activeTab: Tabs.AUTO });
this.updateQuery({ manual: false });
};

showManual = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
this.setState({ activeTab: Tabs.MANUAL });
this.updateQuery({ manual: true });
};

updateQuery = (changes: Partial<Query>) => {
this.props.router.replace({
pathname: this.props.location.pathname,
query: serializeQuery({ ...parseQuery(this.props.location.query), ...changes })
});
};

render() {
@@ -113,8 +113,7 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
if (!isLoggedIn(currentUser)) {
return null;
}

const { activeTab } = this.state;
const displayManual = parseQuery(this.props.location.query).manual;
const header = translate('onboarding.create_project.header');
return (
<>
@@ -124,11 +123,11 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
<h1 className="page-title">{header}</h1>
</div>

{this.shouldDisplayTabs() && (
{this.canAutoCreate() && (
<ul className="flex-tabs">
<li>
<a
className={classNames('js-auto', { selected: activeTab === Tabs.AUTO })}
className={classNames('js-auto', { selected: !displayManual })}
href="#"
onClick={this.showAuto}>
{translate('onboarding.create_project.select_repositories')}
@@ -136,8 +135,8 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
className={classNames(
'rounded alert alert-small spacer-left display-inline-block',
{
'alert-info': activeTab === Tabs.AUTO,
'alert-muted': activeTab !== Tabs.AUTO
'alert-info': !displayManual,
'alert-muted': displayManual
}
)}>
{translate('beta')}
@@ -146,7 +145,7 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
</li>
<li>
<a
className={classNames('js-manual', { selected: activeTab === Tabs.MANUAL })}
className={classNames('js-manual', { selected: displayManual })}
href="#"
onClick={this.showManual}>
{translate('onboarding.create_project.create_manually')}
@@ -155,13 +154,13 @@ export class CreateProjectOnboarding extends React.PureComponent<Props, State> {
</ul>
)}

{activeTab === Tabs.AUTO ? (
<AutoProjectCreate
{displayManual || !this.canAutoCreate() ? (
<ManualProjectCreate
currentUser={currentUser}
onProjectCreate={this.handleProjectCreate}
/>
) : (
<ManualProjectCreate
<AutoProjectCreate
currentUser={currentUser}
onProjectCreate={this.handleProjectCreate}
/>
@@ -181,5 +180,5 @@ const mapStateToProps = (state: any): StateProps => {
const mapDispatchToProps: DispatchProps = { skipOnboarding };

export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
CreateProjectOnboarding
CreateProjectPage
);

server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx → server/sonar-web/src/main/js/apps/projects/create/ManualProjectCreate.tsx 查看文件

@@ -63,7 +63,7 @@ export class ManualProjectCreate extends React.PureComponent<Props, State> {
projectName: '',
projectKey: '',
selectedOrganization:
props.userOrganizations.length <= 1 ? props.userOrganizations[0].key : '',
props.userOrganizations.length === 1 ? props.userOrganizations[0].key : '',
submitting: false
};
}

server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx → server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx → server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx → server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx 查看文件

@@ -19,7 +19,8 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import { CreateProjectOnboarding } from '../CreateProjectOnboarding';
import { Location } from 'history';
import { CreateProjectPage } from '../CreateProjectPage';
import { LoggedInUser } from '../../../../app/types';
import { click } from '../../../../helpers/testUtils';

@@ -35,11 +36,16 @@ it('should render correctly', () => {
});

it('should render with Manual creation only', () => {
expect(getWrapper({ currentUser: { ...user, externalProvider: 'vsts' } })).toMatchSnapshot();
expect(getWrapper({ currentUser: { ...user, externalProvider: 'microsoft' } })).toMatchSnapshot();
});

it('should switch tabs', () => {
const wrapper = getWrapper();
const replace = jest.fn();
const wrapper = getWrapper({ router: { replace } });
replace.mockImplementation(location => {
wrapper.setProps({ location }).update();
});

click(wrapper.find('.js-manual'));
expect(wrapper.find('Connect(ManualProjectCreate)').exists()).toBeTruthy();
click(wrapper.find('.js-auto'));
@@ -48,9 +54,11 @@ it('should switch tabs', () => {

function getWrapper(props = {}) {
return shallow(
<CreateProjectOnboarding
<CreateProjectPage
currentUser={user}
location={{ pathname: 'foo', query: { manual: 'false' } } as Location}
onFinishOnboarding={jest.fn()}
router={{ push: jest.fn(), replace: jest.fn() }}
skipOnboarding={jest.fn()}
{...props}
/>

server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx → server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap → server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap → server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap → server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap 查看文件

@@ -85,7 +85,7 @@ exports[`should render with Manual creation only 1`] = `
<Connect(ManualProjectCreate)
currentUser={
Object {
"externalProvider": "vsts",
"externalProvider": "microsoft",
"isLoggedIn": true,
"login": "foo",
"name": "Foo",

server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap → server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap 查看文件


+ 42
- 0
server/sonar-web/src/main/js/apps/projects/create/utils.ts 查看文件

@@ -0,0 +1,42 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 { memoize } from 'lodash';
import {
cleanQuery,
RawQuery,
parseAsBoolean,
serializeOptionalBoolean
} from '../../../helpers/query';

export interface Query {
manual: boolean;
}

export const parseQuery = memoize((urlQuery: RawQuery): Query => {
return {
manual: parseAsBoolean(urlQuery['manual'], false)
};
});

export const serializeQuery = memoize((query: Query): RawQuery =>
cleanQuery({
manual: serializeOptionalBoolean(query.manual || undefined)
})
);

+ 8
- 2
server/sonar-web/src/main/js/apps/projects/routes.ts 查看文件

@@ -22,6 +22,8 @@ import DefaultPageSelectorContainer from './components/DefaultPageSelectorContai
import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
import { PROJECTS_DEFAULT_FILTER, PROJECTS_ALL } from './utils';
import { save } from '../../helpers/storage';
import { isSonarCloud } from '../../helpers/system';
import { lazyLoad } from '../../components/lazyLoad';

const routes = [
{ indexRoute: { component: DefaultPageSelectorContainer } },
@@ -32,7 +34,11 @@ const routes = [
replace('/projects');
}
},
{ path: 'favorite', component: FavoriteProjectsContainer }
];
{ path: 'favorite', component: FavoriteProjectsContainer },
isSonarCloud() && {
path: 'create',
component: lazyLoad(() => import('./create/CreateProjectPage'))
}
].filter(Boolean);

export default routes;

+ 94
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx 查看文件

@@ -0,0 +1,94 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import AnalyzeTutorialSuggestion from './AnalyzeTutorialSuggestion';
import ProjectAnalysisStep from '../components/ProjectAnalysisStep';
import TokenStep from '../components/TokenStep';
import { Component, LoggedInUser } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import '../styles.css';

enum Steps {
ANALYSIS,
TOKEN
}

interface Props {
component: Component;
currentUser: LoggedInUser;
}

interface State {
step: Steps;
token?: string;
}

export default class AnalyzeTutorial extends React.PureComponent<Props, State> {
state: State = { step: Steps.TOKEN };

handleTokenDone = (token: string) => {
this.setState({ step: Steps.ANALYSIS, token });
};

handleTokenOpen = () => {
this.setState({ step: Steps.TOKEN });
};

render() {
const { component, currentUser } = this.props;
const { step, token } = this.state;
let stepNumber = 1;

const almId = component.almId || currentUser.externalProvider;
const showTutorial = almId !== 'microsoft';
return (
<>
<div className="page-header big-spacer-bottom">
<h1 className="page-title">{translate('onboarding.project_analysis.header')}</h1>
<p className="page-description">{translate('onboarding.project_analysis.description')}</p>
</div>

<AnalyzeTutorialSuggestion almId={almId} />

{showTutorial && (
<>
<TokenStep
currentUser={currentUser}
finished={Boolean(this.state.token)}
onContinue={this.handleTokenDone}
onOpen={this.handleTokenOpen}
open={step === Steps.TOKEN}
stepNumber={stepNumber++}
/>

<ProjectAnalysisStep
component={component}
displayRowLayout={true}
open={step === Steps.ANALYSIS}
organization={component.organization}
stepNumber={stepNumber++}
token={token}
/>
</>
)}
</>
);
}
}

+ 88
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx 查看文件

@@ -0,0 +1,88 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';

export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string }) {
if (almId && almId.startsWith('bitbucket')) {
return (
<div className="alert alert-info big-spacer-bottom">
<p>{translate('onboarding.project_analysis.commands_for_analysis')}</p>
<p>{translate('onboarding.project_analysis.suggestions.bitbucket')}</p>
<FormattedMessage
defaultMessage={translate('onboarding.project_analysis.simply_link')}
id={'onboarding.project_analysis.simply_link'}
values={{
link: (
<a
href={
getBaseUrl() +
'/documentation/integrations/bitbucketcloud#analyzing-with-pipelines'
}
target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_piplines')}
</a>
)
}}
/>
</div>
);
} else if (almId === 'github') {
return (
<div className="alert alert-info big-spacer-bottom">
<p>{translate('onboarding.project_analysis.commands_for_analysis')} </p>
<p>{translate('onboarding.project_analysis.suggestions.github')}</p>
<FormattedMessage
defaultMessage={translate('onboarding.project_analysis.simply_link')}
id={'onboarding.project_analysis.simply_link'}
values={{
link: (
<a
href="https://docs.travis-ci.com/user/sonarcloud/"
rel="noopener noreferrer"
target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_travis')}
</a>
)
}}
/>
</div>
);
} else if (almId === 'microsoft') {
return (
<p className="alert alert-info big-spacer-bottom">
<FormattedMessage
defaultMessage={translate('onboarding.project_analysis.simply_link')}
id={'onboarding.project_analysis.simply_link'}
values={{
link: (
<a href={getBaseUrl() + '/documentation/integrations/vsts'} target="_blank">
{translate('onboarding.project_analysis.guide_to_integrate_vsts')}
</a>
)
}}
/>
</p>
);
}
return null;
}

+ 47
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx 查看文件

@@ -0,0 +1,47 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { shallow } from 'enzyme';
import AnalyzeTutorial from '../AnalyzeTutorial';
import { LoggedInUser } from '../../../../app/types';

const component = {
key: 'foo',
analysisDate: '2016-01-01',
breadcrumbs: [],
name: 'Foo',
organization: 'org',
qualifier: 'TRK',
version: '0.0.1'
};

const loggedInUser: LoggedInUser = {
isLoggedIn: true,
login: 'luke',
name: 'Skywalker'
};

it('renders correctly', () => {
expect(getWrapper()).toMatchSnapshot();
});

function getWrapper(props = {}) {
return shallow(<AnalyzeTutorial component={component} currentUser={loggedInUser} {...props} />);
}

+ 38
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx 查看文件

@@ -0,0 +1,38 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { shallow } from 'enzyme';
import AnalyzeTutorialSuggestion from '../AnalyzeTutorialSuggestion';

it('should not render', () => {
expect(shallow(<AnalyzeTutorialSuggestion almId={undefined} />).type()).toBeNull();
});

it('renders bitbucket suggestions correctly', () => {
expect(shallow(<AnalyzeTutorialSuggestion almId="bitbucket" />)).toMatchSnapshot();
});

it('renders github suggestions correctly', () => {
expect(shallow(<AnalyzeTutorialSuggestion almId="github" />)).toMatchSnapshot();
});

it('renders vsts suggestions correctly', () => {
expect(shallow(<AnalyzeTutorialSuggestion almId="microsoft" />)).toMatchSnapshot();
});

+ 54
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap 查看文件

@@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders correctly 1`] = `
<React.Fragment>
<div
className="page-header big-spacer-bottom"
>
<h1
className="page-title"
>
onboarding.project_analysis.header
</h1>
<p
className="page-description"
>
onboarding.project_analysis.description
</p>
</div>
<AnalyzeTutorialSuggestion />
<React.Fragment>
<TokenStep
currentUser={
Object {
"isLoggedIn": true,
"login": "luke",
"name": "Skywalker",
}
}
finished={false}
onContinue={[Function]}
onOpen={[Function]}
open={true}
stepNumber={1}
/>
<ProjectAnalysisStep
component={
Object {
"analysisDate": "2016-01-01",
"breadcrumbs": Array [],
"key": "foo",
"name": "Foo",
"organization": "org",
"qualifier": "TRK",
"version": "0.0.1",
}
}
displayRowLayout={true}
open={false}
organization="org"
stepNumber={2}
/>
</React.Fragment>
</React.Fragment>
`;

+ 78
- 0
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap 查看文件

@@ -0,0 +1,78 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders bitbucket suggestions correctly 1`] = `
<div
className="alert alert-info big-spacer-bottom"
>
<p>
onboarding.project_analysis.commands_for_analysis
</p>
<p>
onboarding.project_analysis.suggestions.bitbucket
</p>
<FormattedMessage
defaultMessage="onboarding.project_analysis.simply_link"
id="onboarding.project_analysis.simply_link"
values={
Object {
"link": <a
href="/documentation/integrations/bitbucketcloud#analyzing-with-pipelines"
target="_blank"
>
onboarding.project_analysis.guide_to_integrate_piplines
</a>,
}
}
/>
</div>
`;

exports[`renders github suggestions correctly 1`] = `
<div
className="alert alert-info big-spacer-bottom"
>
<p>
onboarding.project_analysis.commands_for_analysis
</p>
<p>
onboarding.project_analysis.suggestions.github
</p>
<FormattedMessage
defaultMessage="onboarding.project_analysis.simply_link"
id="onboarding.project_analysis.simply_link"
values={
Object {
"link": <a
href="https://docs.travis-ci.com/user/sonarcloud/"
rel="noopener noreferrer"
target="_blank"
>
onboarding.project_analysis.guide_to_integrate_travis
</a>,
}
}
/>
</div>
`;

exports[`renders vsts suggestions correctly 1`] = `
<p
className="alert alert-info big-spacer-bottom"
>
<FormattedMessage
defaultMessage="onboarding.project_analysis.simply_link"
id="onboarding.project_analysis.simply_link"
values={
Object {
"link": <a
href="/documentation/integrations/vsts"
target="_blank"
>
onboarding.project_analysis.guide_to_integrate_vsts
</a>,
}
}
/>
</p>
`;

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx → server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx 查看文件

@@ -23,39 +23,30 @@ import NewProjectForm from './NewProjectForm';
import RadioToggle from '../../../components/controls/RadioToggle';
import { translate } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';

export interface Result {
language?: string;
javaBuild?: string;
cFamilyCompiler?: string;
os?: string;
projectKey?: string;
}
import { Component } from '../../../app/types';
import { isLanguageConfigured, LanguageConfig } from '../utils';

interface Props {
onDone: (result: Result) => void;
component?: Component;
config?: LanguageConfig;
onDone: (config: LanguageConfig) => void;
onReset: () => void;
organization?: string;
}

type State = Result;

export default class LanguageStep extends React.PureComponent<Props, State> {
state: State = {};

isConfigured = () => {
const { language, javaBuild, cFamilyCompiler, os, projectKey } = this.state;
const isJavaConfigured = language === 'java' && javaBuild != null;
const isDotNetConfigured = language === 'dotnet' && projectKey != null;
const isCFamilyConfigured =
language === 'c-family' && (cFamilyCompiler === 'msvc' || os != null) && projectKey != null;
const isOtherConfigured = language === 'other' && projectKey != null;
type State = LanguageConfig;

return isJavaConfigured || isDotNetConfigured || isCFamilyConfigured || isOtherConfigured;
};
export default class LanguageForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
...(this.props.config || {}),
projectKey: props.component ? props.component.key : undefined
};
}

handleChange = () => {
if (this.isConfigured()) {
if (isLanguageConfigured(this.state)) {
this.props.onDone(this.state);
} else {
this.props.onReset();
@@ -131,29 +122,36 @@ export default class LanguageStep extends React.PureComponent<Props, State> {
</div>
);

renderProjectKey = () => (
<NewProjectForm
onDelete={this.handleProjectKeyDelete}
onDone={this.handleProjectKeyDone}
organization={this.props.organization}
projectKey={this.state.projectKey}
/>
);
renderProjectKey = () => {
const { cFamilyCompiler, language, os } = this.state;
const needProjectKey =
language === 'dotnet' ||
(language === 'c-family' &&
(cFamilyCompiler === 'msvc' || (cFamilyCompiler === 'clang-gcc' && os !== undefined))) ||
(language === 'other' && os !== undefined);

render() {
const shouldAskProjectKey =
this.state.language === 'dotnet' ||
(this.state.language === 'c-family' &&
(this.state.cFamilyCompiler === 'msvc' ||
(this.state.cFamilyCompiler === 'clang-gcc' && this.state.os != null))) ||
(this.state.language === 'other' && this.state.os !== undefined);
if (!needProjectKey || this.props.component) {
return null;
}

return (
<NewProjectForm
onDelete={this.handleProjectKeyDelete}
onDone={this.handleProjectKeyDone}
organization={this.props.organization}
projectKey={this.state.projectKey}
/>
);
};

render() {
const { cFamilyCompiler, language } = this.state;
const languages = isSonarCloud()
? ['java', 'dotnet', 'c-family', 'other']
: ['java', 'dotnet', 'other'];

return (
<div>
<>
<div>
<h4 className="spacer-bottom">{translate('onboarding.language')}</h4>
<RadioToggle
@@ -163,16 +161,15 @@ export default class LanguageStep extends React.PureComponent<Props, State> {
label: translate('onboarding.language', language),
value: language
}))}
value={this.state.language}
value={language}
/>
</div>
{this.state.language === 'java' && this.renderJavaBuild()}
{this.state.language === 'c-family' && this.renderCFamilyCompiler()}
{((this.state.language === 'c-family' && this.state.cFamilyCompiler === 'clang-gcc') ||
this.state.language === 'other') &&
{language === 'java' && this.renderJavaBuild()}
{language === 'c-family' && this.renderCFamilyCompiler()}
{((language === 'c-family' && cFamilyCompiler === 'clang-gcc') || language === 'other') &&
this.renderOS()}
{shouldAskProjectKey && this.renderProjectKey()}
</div>
{this.renderProjectKey()}
</>
);
}
}

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx → server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx → server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx → server/sonar-web/src/main/js/apps/tutorials/components/OrganizationStep.tsx 查看文件


+ 120
- 0
server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx 查看文件

@@ -0,0 +1,120 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import Step from './Step';
import LanguageForm from './LanguageForm';
import AnalysisCommand from './commands/AnalysisCommand';
import { translate } from '../../../helpers/l10n';
import { Component } from '../../../app/types';
import { LanguageConfig } from '../utils';

interface Props {
component?: Component;
displayRowLayout?: boolean;
onFinish?: (projectKey?: string) => void;
onReset?: () => void;
open: boolean;
organization?: string;
stepNumber: number;
token?: string;
}

interface State {
config?: LanguageConfig;
}

export default class ProjectAnalysisStep extends React.PureComponent<Props, State> {
state: State = {};

getProjectKey = ({ config } = this.state, { component } = this.props) => {
return (component && component.key) || (config && config.projectKey);
};

handleLanguageSelect = (config: LanguageConfig) => {
this.setState({ config });
if (this.props.onFinish) {
const projectKey = config.language !== 'java' ? this.getProjectKey({ config }) : undefined;
this.props.onFinish(projectKey);
}
};

handleLanguageReset = () => {
this.setState({ config: undefined });
if (this.props.onReset) {
this.props.onReset();
}
};

renderForm = () => {
const languageComponent = (
<LanguageForm
component={this.props.component}
onDone={this.handleLanguageSelect}
onReset={this.handleLanguageReset}
organization={this.props.organization}
/>
);
const analysisComponent = this.state.config && (
<AnalysisCommand
component={this.props.component}
languageConfig={this.state.config}
organization={this.props.organization}
small={true}
token={this.props.token}
/>
);

if (this.props.displayRowLayout) {
return (
<div className="boxed-group-inner">
<div className="display-flex-column">
{languageComponent}
{analysisComponent && <div className="huge-spacer-top">{analysisComponent}</div>}
</div>
</div>
);
}

return (
<div className="boxed-group-inner">
<div className="flex-columns">
<div className="flex-column flex-column-half bordered-right">{languageComponent}</div>
<div className="flex-column flex-column-half">{analysisComponent}</div>
</div>
</div>
);
};

renderResult = () => null;

render() {
return (
<Step
finished={false}
onOpen={() => {}}
open={this.props.open}
renderForm={this.renderForm}
renderResult={this.renderResult}
stepNumber={this.props.stepNumber}
stepTitle={translate('onboarding.analysis.header')}
/>
);
}
}

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx → server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx → server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx 查看文件

@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import LanguageStep from '../LanguageStep';
import LanguageForm from '../LanguageForm';
import { isSonarCloud } from '../../../../helpers/system';

jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
@@ -30,7 +30,7 @@ beforeEach(() => {

it('selects java', () => {
const onDone = jest.fn();
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
const wrapper = shallow(<LanguageForm onDone={onDone} onReset={jest.fn()} />);

(wrapper.find('RadioToggle').prop('onCheck') as Function)('java');
wrapper.update();
@@ -55,7 +55,7 @@ it('selects java', () => {

it('selects c#', () => {
const onDone = jest.fn();
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
const wrapper = shallow(<LanguageForm onDone={onDone} onReset={jest.fn()} />);

(wrapper.find('RadioToggle').prop('onCheck') as Function)('dotnet');
wrapper.update();
@@ -68,7 +68,7 @@ it('selects c#', () => {
it('selects c-family', () => {
(isSonarCloud as jest.Mock<any>).mockImplementation(() => true);
const onDone = jest.fn();
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
const wrapper = shallow(<LanguageForm onDone={onDone} onReset={jest.fn()} />);

(wrapper.find('RadioToggle').prop('onCheck') as Function)('c-family');
wrapper.update();
@@ -113,7 +113,7 @@ it('selects c-family', () => {

it('selects other', () => {
const onDone = jest.fn();
const wrapper = shallow(<LanguageStep onDone={onDone} onReset={jest.fn()} />);
const wrapper = shallow(<LanguageForm onDone={onDone} onReset={jest.fn()} />);

(wrapper.find('RadioToggle').prop('onCheck') as Function)('other');
wrapper.update();

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewOrganizationForm-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/OrganizationStep-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/Step-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap 查看文件

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`selects c# 1`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -35,11 +35,11 @@ exports[`selects c# 1`] = `
onDelete={[Function]}
onDone={[Function]}
/>
</div>
</React.Fragment>
`;

exports[`selects c-family 1`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -100,11 +100,11 @@ exports[`selects c-family 1`] = `
value={null}
/>
</div>
</div>
</React.Fragment>
`;

exports[`selects c-family 2`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -169,11 +169,11 @@ exports[`selects c-family 2`] = `
onDelete={[Function]}
onDone={[Function]}
/>
</div>
</React.Fragment>
`;

exports[`selects c-family 3`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -265,11 +265,11 @@ exports[`selects c-family 3`] = `
value={null}
/>
</div>
</div>
</React.Fragment>
`;

exports[`selects c-family 4`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -366,11 +366,11 @@ exports[`selects c-family 4`] = `
onDone={[Function]}
projectKey="project-foo"
/>
</div>
</React.Fragment>
`;

exports[`selects java 1`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -427,11 +427,11 @@ exports[`selects java 1`] = `
value={null}
/>
</div>
</div>
</React.Fragment>
`;

exports[`selects java 2`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -488,11 +488,11 @@ exports[`selects java 2`] = `
value="maven"
/>
</div>
</div>
</React.Fragment>
`;

exports[`selects java 3`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -549,11 +549,11 @@ exports[`selects java 3`] = `
value="gradle"
/>
</div>
</div>
</React.Fragment>
`;

exports[`selects other 1`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -614,11 +614,11 @@ exports[`selects other 1`] = `
value={null}
/>
</div>
</div>
</React.Fragment>
`;

exports[`selects other 2`] = `
<div>
<React.Fragment>
<div>
<h4
className="spacer-bottom"
@@ -683,5 +683,5 @@ exports[`selects other 2`] = `
onDelete={[Function]}
onDone={[Function]}
/>
</div>
</React.Fragment>
`;

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/Step-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TokenStep-test.tsx.snap 查看文件


+ 146
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx 查看文件

@@ -0,0 +1,146 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import JavaMaven from './JavaMaven';
import JavaGradle from './JavaGradle';
import DotNet from './DotNet';
import Msvc from './Msvc';
import ClangGCC from './ClangGCC';
import Other from './Other';
import { getHostUrl } from '../../../../helpers/urls';
import { Component } from '../../../../app/types';
import { LanguageConfig } from '../../utils';

interface Props {
component?: Component;
organization?: string;
languageConfig: LanguageConfig;
small?: boolean;
token?: string;
}

export default class AnalysisCommand extends React.PureComponent<Props> {
getProjectKey = ({ component, languageConfig } = this.props) => {
return (component && component.key) || languageConfig.projectKey;
};

renderCommandForMaven = () => {
const { token } = this.props;
if (!token) {
return null;
}
return <JavaMaven host={getHostUrl()} organization={this.props.organization} token={token} />;
};

renderCommandForGradle = () => {
const { token } = this.props;
if (!token) {
return null;
}
return <JavaGradle host={getHostUrl()} organization={this.props.organization} token={token} />;
};

renderCommandForDotNet = () => {
const { small, token } = this.props;
const projectKey = this.getProjectKey();
if (!projectKey || !token) {
return null;
}
return (
<DotNet
host={getHostUrl()}
organization={this.props.organization}
projectKey={projectKey}
small={small}
token={token}
/>
);
};

renderCommandForMSVC = () => {
const { small, token } = this.props;
const projectKey = this.getProjectKey();
if (!projectKey || !token) {
return null;
}
return (
<Msvc
host={getHostUrl()}
organization={this.props.organization}
projectKey={projectKey}
small={small}
token={token}
/>
);
};

renderCommandForClangGCC = () => {
const { languageConfig, small, token } = this.props;
const projectKey = this.getProjectKey();
if (!languageConfig || !projectKey || !languageConfig.os || !token) {
return null;
}
return (
<ClangGCC
host={getHostUrl()}
organization={this.props.organization}
os={languageConfig.os}
projectKey={projectKey}
small={small}
token={token}
/>
);
};

renderCommandForOther = () => {
const { languageConfig, token } = this.props;
const projectKey = this.getProjectKey();
if (!languageConfig || !projectKey || !languageConfig.os || !token) {
return null;
}
return (
<Other
host={getHostUrl()}
organization={this.props.organization}
os={languageConfig.os}
projectKey={projectKey}
token={token}
/>
);
};

render() {
const { languageConfig } = this.props;

if (languageConfig.language === 'java') {
return languageConfig.javaBuild === 'maven'
? this.renderCommandForMaven()
: this.renderCommandForGradle();
} else if (languageConfig.language === 'dotnet') {
return this.renderCommandForDotNet();
} else if (languageConfig.language === 'c-family') {
return languageConfig.cFamilyCompiler === 'msvc'
? this.renderCommandForMSVC()
: this.renderCommandForClangGCC();
} else {
return this.renderCommandForOther();
}
}
}

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/BuildWrapper.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx 查看文件

@@ -29,6 +29,7 @@ interface Props {
os: string;
organization?: string;
projectKey: string;
small?: boolean;
token: string;
}

@@ -67,7 +68,7 @@ export default function ClangGCC(props: Props) {
/>
)}
</InstanceMessage>
<CodeSnippet isOneLine={true} snippet={command1} />
<CodeSnippet isOneLine={props.small} snippet={command1} />
<CodeSnippet isOneLine={props.os === 'win'} snippet={command2} />
<p
className="big-spacer-top markdown"

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx 查看文件

@@ -27,6 +27,7 @@ interface Props {
host: string;
organization?: string;
projectKey: string;
small?: boolean;
token: string;
}

@@ -59,8 +60,8 @@ export default function DotNet(props: Props) {
)}
</InstanceMessage>
<CodeSnippet isOneLine={true} snippet={command1} />
<CodeSnippet isOneLine={true} snippet={command2} />
<CodeSnippet isOneLine={true} snippet={command3} />
<CodeSnippet isOneLine={false} snippet={command2} />
<CodeSnippet isOneLine={props.small} snippet={command3} />
<p
className="big-spacer-top markdown"
dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.docs') }}

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx 查看文件

@@ -61,7 +61,9 @@ export default function JavaGradle(props: Props) {
<p
className="big-spacer-top markdown"
dangerouslySetInnerHTML={{
__html: translate('onboarding.analysis.browse_url_after_analysis')
__html: props.projectKey
? translate('onboarding.analysis.auto_refresh_after_analysis')
: translate('onboarding.analysis.browse_url_after_analysis')
}}
/>
</div>

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx 查看文件

@@ -50,7 +50,9 @@ export default function JavaMaven(props: Props) {
<p
className="big-spacer-top markdown"
dangerouslySetInnerHTML={{
__html: translate('onboarding.analysis.browse_url_after_analysis')
__html: props.projectKey
? translate('onboarding.analysis.auto_refresh_after_analysis')
: translate('onboarding.analysis.browse_url_after_analysis')
}}
/>
</div>

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/MSBuildScanner.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/Msvc.tsx 查看文件

@@ -28,6 +28,7 @@ interface Props {
host: string;
organization?: string;
projectKey: string;
small?: boolean;
token: string;
}

@@ -62,8 +63,8 @@ export default function Msvc(props: Props) {
)}
</InstanceMessage>
<CodeSnippet isOneLine={true} snippet={command1} />
<CodeSnippet isOneLine={true} snippet={command2} />
<CodeSnippet isOneLine={true} snippet={command3} />
<CodeSnippet isOneLine={props.small} snippet={command2} />
<CodeSnippet isOneLine={props.small} snippet={command3} />
<p
className="big-spacer-top markdown"
dangerouslySetInnerHTML={{ __html: translate('onboarding.analysis.msbuild.docs') }}

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/SQScanner.tsx 查看文件


+ 71
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/AnalysisCommand-test.tsx 查看文件

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import { shallow } from 'enzyme';
import AnalysisCommand from '../AnalysisCommand';

jest.mock('../../../../../helpers/system', () => ({
isSonarCloud: jest.fn().mockReturnValue(true)
}));

it('display java command', () => {
expect(
getWrapper({ languageConfig: { language: 'java', javaBuild: 'gradle' } })
).toMatchSnapshot();
expect(
getWrapper({ languageConfig: { language: 'java', javaBuild: 'maven' } })
).toMatchSnapshot();
});

it('display c# command', () => {
expect(
getWrapper({ languageConfig: { language: 'dotnet', projectKey: 'project-foo' } })
).toMatchSnapshot();
});

it('display c-family command', () => {
expect(
getWrapper({
languageConfig: { language: 'c-family', cFamilyCompiler: 'msvc', projectKey: 'project-foo' }
})
).toMatchSnapshot();
expect(
getWrapper({
languageConfig: {
language: 'c-family',
cFamilyCompiler: 'clang-gcc',
os: 'linux',
projectKey: 'project-foo'
}
})
).toMatchSnapshot();
});

it('display others command', () => {
expect(
getWrapper({
languageConfig: { language: 'other', os: 'window', projectKey: 'project-foo' }
})
).toMatchSnapshot();
});

function getWrapper(props = {}) {
return shallow(<AnalysisCommand languageConfig={{}} token="myToken" {...props} />);
}

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/BuildWrapper-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/ClangGCC-test.tsx 查看文件

@@ -38,6 +38,7 @@ it('renders correctly', () => {
organization="organization"
os="linux"
projectKey="projectKey"
small={true}
token="token"
/>
)

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/DotNet-test.tsx 查看文件

@@ -26,7 +26,13 @@ it('renders correctly', () => {
expect(shallow(<DotNet host="host" projectKey="projectKey" token="token" />)).toMatchSnapshot();
expect(
shallow(
<DotNet host="host" organization="organization" projectKey="projectKey" token="token" />
<DotNet
host="host"
organization="organization"
projectKey="projectKey"
small={true}
token="token"
/>
)
).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaGradle-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaMaven-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/MSBuildScanner-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Msvc-test.tsx 查看文件

@@ -25,6 +25,14 @@ import Msvc from '../Msvc';
it('renders correctly', () => {
expect(shallow(<Msvc host="host" projectKey="projectKey" token="token" />)).toMatchSnapshot();
expect(
shallow(<Msvc host="host" organization="organization" projectKey="projectKey" token="token" />)
shallow(
<Msvc
host="host"
organization="organization"
projectKey="projectKey"
small={true}
token="token"
/>
)
).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Other-test.tsx 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/SQScanner-test.tsx 查看文件


+ 49
- 0
server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap 查看文件

@@ -0,0 +1,49 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`display c# command 1`] = `
<DotNet
host="null"
projectKey="project-foo"
token="myToken"
/>
`;

exports[`display c-family command 1`] = `
<Msvc
host="null"
projectKey="project-foo"
token="myToken"
/>
`;

exports[`display c-family command 2`] = `
<ClangGCC
host="null"
os="linux"
projectKey="project-foo"
token="myToken"
/>
`;

exports[`display java command 1`] = `
<JavaGradle
host="null"
token="myToken"
/>
`;

exports[`display java command 2`] = `
<JavaMaven
host="null"
token="myToken"
/>
`;

exports[`display others command 1`] = `
<Other
host="null"
os="window"
projectKey="project-foo"
token="myToken"
/>
`;

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap 查看文件

@@ -18,7 +18,6 @@ exports[`renders correctly 1`] = `
message="onboarding.analysis.sq_scanner.execute.text"
/>
<CodeSnippet
isOneLine={true}
snippet="build-wrapper-win-x86-64.exe --out-dir bw-output make clean all"
/>
<CodeSnippet
@@ -64,7 +63,6 @@ exports[`renders correctly 2`] = `
message="onboarding.analysis.sq_scanner.execute.text"
/>
<CodeSnippet
isOneLine={true}
snippet="build-wrapper-linux-x86-64 --out-dir bw-output make clean all"
/>
<CodeSnippet

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/DotNet-test.tsx.snap 查看文件

@@ -24,11 +24,10 @@ exports[`renders correctly 1`] = `
}
/>
<CodeSnippet
isOneLine={true}
isOneLine={false}
snippet="MsBuild.exe /t:Rebuild"
/>
<CodeSnippet
isOneLine={true}
snippet={
Array [
"SonarScanner.MSBuild.exe end",
@@ -71,7 +70,7 @@ exports[`renders correctly 2`] = `
}
/>
<CodeSnippet
isOneLine={true}
isOneLine={false}
snippet="MsBuild.exe /t:Rebuild"
/>
<CodeSnippet

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap 查看文件

@@ -91,7 +91,7 @@ exports[`renders correctly 2`] = `
className="big-spacer-top markdown"
dangerouslySetInnerHTML={
Object {
"__html": "onboarding.analysis.browse_url_after_analysis",
"__html": "onboarding.analysis.auto_refresh_after_analysis",
}
}
/>

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap 查看文件

@@ -79,7 +79,7 @@ exports[`renders correctly 2`] = `
className="big-spacer-top markdown"
dangerouslySetInnerHTML={
Object {
"__html": "onboarding.analysis.browse_url_after_analysis",
"__html": "onboarding.analysis.auto_refresh_after_analysis",
}
}
/>

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Msvc-test.tsx.snap 查看文件

@@ -29,11 +29,9 @@ exports[`renders correctly 1`] = `
}
/>
<CodeSnippet
isOneLine={true}
snippet="build-wrapper-win-x86-64.exe --out-dir bw-output MsBuild.exe /t:Rebuild"
/>
<CodeSnippet
isOneLine={true}
snippet={
Array [
"SonarQube.Scanner.MSBuild.exe end",

server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Other-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx → server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx 查看文件

@@ -19,16 +19,16 @@
*/
import * as React from 'react';
import { connect } from 'react-redux';
import handleRequiredAuthentication from '../../app/utils/handleRequiredAuthentication';
import Modal from '../../components/controls/Modal';
import OnboardingPrivateIcon from '../../components/icons-components/OnboardingPrivateIcon';
import OnboardingProjectIcon from '../../components/icons-components/OnboardingProjectIcon';
import OnboardingTeamIcon from '../../components/icons-components/OnboardingTeamIcon';
import { Button, ResetButtonLink } from '../../components/ui/buttons';
import { translate } from '../../helpers/l10n';
import { CurrentUser, isLoggedIn } from '../../app/types';
import { getCurrentUser } from '../../store/rootReducer';
import './styles.css';
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
import Modal from '../../../components/controls/Modal';
import OnboardingPrivateIcon from '../../../components/icons-components/OnboardingPrivateIcon';
import OnboardingProjectIcon from '../../../components/icons-components/OnboardingProjectIcon';
import OnboardingTeamIcon from '../../../components/icons-components/OnboardingTeamIcon';
import { Button, ResetButtonLink } from '../../../components/ui/buttons';
import { translate } from '../../../helpers/l10n';
import { CurrentUser, isLoggedIn } from '../../../app/types';
import { getCurrentUser } from '../../../store/rootReducer';
import '../styles.css';

interface OwnProps {
onClose: () => void;
@@ -43,7 +43,7 @@ interface StateProps {

type Props = OwnProps & StateProps;

export class Onboarding extends React.PureComponent<Props> {
export class OnboardingModal extends React.PureComponent<Props> {
componentDidMount() {
if (!isLoggedIn(this.props.currentUser)) {
handleRequiredAuthentication();
@@ -96,4 +96,4 @@ export class Onboarding extends React.PureComponent<Props> {

const mapStateToProps = (state: any): StateProps => ({ currentUser: getCurrentUser(state) });

export default connect<StateProps, {}, OwnProps>(mapStateToProps)(Onboarding);
export default connect<StateProps, {}, OwnProps>(mapStateToProps)(OnboardingModal);

+ 99
- 0
server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx 查看文件

@@ -0,0 +1,99 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as React from 'react';
import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import OnboardingModal from './OnboardingModal';
import { skipOnboarding } from '../../../api/users';
import { skipOnboarding as skipOnboardingAction } from '../../../store/users/actions';
import CreateOrganizationForm from '../../account/organizations/CreateOrganizationForm';
import TeamOnboardingModal from '../teamOnboarding/TeamOnboardingModal';
import { Organization } from '../../../app/types';

interface DispatchProps {
skipOnboardingAction: () => void;
}

enum ModalKey {
onboarding,
organizationOnboarding,
teamOnboarding
}

interface State {
modal?: ModalKey;
}

export class OnboardingPage extends React.PureComponent<DispatchProps, State> {
static contextTypes = {
openProjectOnboarding: PropTypes.func.isRequired,
router: PropTypes.object.isRequired
};

state: State = { modal: ModalKey.onboarding };

closeOnboarding = () => {
skipOnboarding();
this.props.skipOnboardingAction();
this.context.router.replace('/');
};

closeOrganizationOnboarding = ({ key }: Pick<Organization, 'key'>) => {
this.closeOnboarding();
this.context.router.push(`/organizations/${key}`);
};

openOrganizationOnboarding = () => {
this.setState({ modal: ModalKey.organizationOnboarding });
};

openTeamOnboarding = () => {
this.setState({ modal: ModalKey.teamOnboarding });
};

render() {
const { modal } = this.state;
return (
<>
{modal === ModalKey.onboarding && (
<OnboardingModal
onClose={this.closeOnboarding}
onOpenOrganizationOnboarding={this.openOrganizationOnboarding}
onOpenProjectOnboarding={this.context.openProjectOnboarding}
onOpenTeamOnboarding={this.openTeamOnboarding}
/>
)}
{modal === ModalKey.organizationOnboarding && (
<CreateOrganizationForm
onClose={this.closeOnboarding}
onCreate={this.closeOrganizationOnboarding}
/>
)}
{modal === ModalKey.teamOnboarding && (
<TeamOnboardingModal onFinish={this.closeOnboarding} />
)}
</>
);
}
}

const mapDispatchToProps: DispatchProps = { skipOnboardingAction };

export default connect<{}, DispatchProps>(null, mapDispatchToProps)(OnboardingPage);

server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx → server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx 查看文件

@@ -19,13 +19,13 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import { Onboarding } from '../Onboarding';
import { click } from '../../../helpers/testUtils';
import { OnboardingModal } from '../OnboardingModal';
import { click } from '../../../../helpers/testUtils';

it('renders correctly', () => {
expect(
shallow(
<Onboarding
<OnboardingModal
currentUser={{ isLoggedIn: true }}
onClose={jest.fn()}
onOpenOrganizationOnboarding={jest.fn()}
@@ -43,7 +43,7 @@ it('should correctly open the different tutorials', () => {
const onOpenTeamOnboarding = jest.fn();
const push = jest.fn();
const wrapper = shallow(
<Onboarding
<OnboardingModal
currentUser={{ isLoggedIn: true }}
onClose={onClose}
onOpenOrganizationOnboarding={onOpenOrganizationOnboarding}

server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap → server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap 查看文件


部分文件因文件數量過多而無法顯示

Loading…
取消
儲存