diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-07-26 11:44:57 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-08-10 20:21:30 +0200 |
commit | 4f5f81d6c146d6cc873239258343141e9631c9b6 (patch) | |
tree | cbfc4686ececf2c9308ad3f5ece645c5fc0945cd /server/sonar-web/src/main | |
parent | 75f56b1e0ce0d59f9bdb039b41563c1994488775 (diff) | |
download | sonarqube-4f5f81d6c146d6cc873239258343141e9631c9b6.tar.gz sonarqube-4f5f81d6c146d6cc873239258343141e9631c9b6.zip |
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
Diffstat (limited to 'server/sonar-web/src/main')
111 files changed, 1889 insertions, 624 deletions
diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 82d4b81197e..176a21d06ac 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/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), diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index 8a7e7e2f444..b85885bbcd0 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/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} diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index 83a4afb2822..e67564559f6 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/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' } }}> diff --git a/server/sonar-web/src/main/js/app/styles/components/alerts.css b/server/sonar-web/src/main/js/app/styles/components/alerts.css index b965dced2d6..97457fe8dd6 100644 --- a/server/sonar-web/src/main/js/app/styles/components/alerts.css +++ b/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 { diff --git a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css index e77b22430da..5a79c7f70e8 100644 --- a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css +++ b/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; diff --git a/server/sonar-web/src/main/js/app/styles/components/page.css b/server/sonar-web/src/main/js/app/styles/components/page.css index c493b82d22c..a18b282cc45 100644 --- a/server/sonar-web/src/main/js/app/styles/components/page.css +++ b/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; diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index c0f7bd23abb..11bbc4bb9f7 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/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; diff --git a/server/sonar-web/src/main/js/app/styles/sonarcloud.css b/server/sonar-web/src/main/js/app/styles/sonarcloud.css index 66a4f2f0ba8..aa5ab408659 100644 --- a/server/sonar-web/src/main/js/app/styles/sonarcloud.css +++ b/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; } diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index ea3be59fc5c..4b55b604c22 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/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; diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx index 9eb8edb79ed..2540d76707c 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx +++ b/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> + </> ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js index 4b8edc255c3..183875dc425 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js +++ b/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} diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap index 2a3aab950cb..f526bbb3a16 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap +++ b/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" diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx index d888dbbb507..2953934dd4a 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx +++ b/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> + </> ); } } diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.tsx b/server/sonar-web/src/main/js/apps/overview/components/App.tsx index d54ea53f10b..68c8238c23c 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/App.tsx +++ b/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} + /> + )} + </> ); } } diff --git a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx b/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx index 9beed6e65b5..69c3d81ae27 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx +++ b/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> diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx index 402725bdc71..dcf365a6615 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx +++ b/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"> diff --git a/server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx b/server/sonar-web/src/main/js/apps/overview/components/SonarCloudEmptyOverview.tsx new file mode 100644 index 00000000000..53dace6236e --- /dev/null +++ b/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); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx index ac84c986293..73eb99d9350 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx +++ b/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', () => { diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx index c2468df3638..6fd204472ab 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/EmptyOverview-test.tsx +++ b/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(); }); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarCloudEmptyOverview-test.tsx new file mode 100644 index 00000000000..278fca079d1 --- /dev/null +++ b/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(); +}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js b/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js deleted file mode 100644 index 0d9a0fdc23b..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/Timeline-test.js +++ /dev/null @@ -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]); -}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap index 4d8cdf63248..cede657480c 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/EmptyOverview-test.tsx.snap +++ b/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>, diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarCloudEmptyOverview-test.tsx.snap new file mode 100644 index 00000000000..1dd9c747b5a --- /dev/null +++ b/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> +`; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap deleted file mode 100644 index 3c01c85264d..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/Timeline-test.js.snap +++ /dev/null @@ -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, - ] - } -/> -`; diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx index 3e7749343a5..64a0a9e4ea6 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx +++ b/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} diff --git a/server/sonar-web/src/main/js/apps/overview/styles.css b/server/sonar-web/src/main/js/apps/overview/styles.css index 7f14ac0420a..92ed309cda0 100644 --- a/server/sonar-web/src/main/js/apps/overview/styles.css +++ b/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 { diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AlmRepositoryItem.tsx b/server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx index 8e21b7111ae..8e21b7111ae 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AlmRepositoryItem.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/AlmRepositoryItem.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx b/server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx index 078a54d13de..078a54d13de 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/AutoProjectCreate.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/projects/create/CreateProjectPage.tsx index 17bae7689e7..63224ebacec 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx +++ b/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 ); diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/projects/create/ManualProjectCreate.tsx index 59d615583c7..041b09bdd4d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx +++ b/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 }; } diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx index 72b25cb2815..72b25cb2815 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/AlmRepositoryItem-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx index bfe95430578..bfe95430578 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/AutoProjectCreate-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx index f7cfa3ce8da..6f995afd252 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx +++ b/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} /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx index b79b4e4ae35..b79b4e4ae35 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap index ad71bfc6e60..ad71bfc6e60 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap index 6d6c0398864..6d6c0398864 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap index 1920eb22a54..1880a16ba4c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap +++ b/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", diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap index fafb751c9bb..fafb751c9bb 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projects/create/utils.ts b/server/sonar-web/src/main/js/apps/projects/create/utils.ts new file mode 100644 index 00000000000..f3528d0a633 --- /dev/null +++ b/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) + }) +); diff --git a/server/sonar-web/src/main/js/apps/projects/routes.ts b/server/sonar-web/src/main/js/apps/projects/routes.ts index 873d933ce07..061627c206a 100644 --- a/server/sonar-web/src/main/js/apps/projects/routes.ts +++ b/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; diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx new file mode 100644 index 00000000000..7ed7513f6f4 --- /dev/null +++ b/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} + /> + </> + )} + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx new file mode 100644 index 00000000000..a93c73b94c8 --- /dev/null +++ b/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; +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx new file mode 100644 index 00000000000..39f0233081f --- /dev/null +++ b/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} />); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx new file mode 100644 index 00000000000..bc4a6d7b55b --- /dev/null +++ b/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(); +}); diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap new file mode 100644 index 00000000000..af09b977a36 --- /dev/null +++ b/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> +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorialSuggestion-test.tsx.snap new file mode 100644 index 00000000000..b682fbd8ed0 --- /dev/null +++ b/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> +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/LanguageForm.tsx index 25b7544ddcc..8762501b301 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/LanguageStep.tsx +++ b/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()} + </> ); } } diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx index 5244898544d..5244898544d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewOrganizationForm.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/NewOrganizationForm.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx index f09b4d152bf..f09b4d152bf 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/NewProjectForm.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/NewProjectForm.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/OrganizationStep.tsx index 934965c7559..934965c7559 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/OrganizationStep.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/OrganizationStep.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/ProjectAnalysisStep.tsx new file mode 100644 index 00000000000..3a924f2415e --- /dev/null +++ b/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')} + /> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx index 7fda3877528..7fda3877528 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/Step.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/Step.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx index e0cd2c5aacc..e0cd2c5aacc 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/TokenStep.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/TokenStep.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/LanguageForm-test.tsx index 629044f2c4c..f767450c0ac 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/LanguageStep-test.tsx +++ b/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(); diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewOrganizationForm-test.tsx index bb16f901167..bb16f901167 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewOrganizationForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewOrganizationForm-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx index 2c8c18cace4..2c8c18cace4 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/NewProjectForm-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/NewProjectForm-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/OrganizationStep-test.tsx index b2192a0bce7..b2192a0bce7 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/OrganizationStep-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/OrganizationStep-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/Step-test.tsx index 8a9664f71c8..8a9664f71c8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/Step-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/Step-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx index 25b5f0c7462..25b5f0c7462 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/TokenStep-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TokenStep-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/LanguageForm-test.tsx.snap index 8e82d384b3f..c653180e332 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/LanguageStep-test.tsx.snap +++ b/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> `; diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap index eae8fcdb833..eae8fcdb833 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewOrganizationForm-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap index f6722a21bf6..f6722a21bf6 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/NewProjectForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/NewProjectForm-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap index 8ece74c899f..8ece74c899f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/OrganizationStep-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/OrganizationStep-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/Step-test.tsx.snap index 4667ca7ae8a..4667ca7ae8a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/Step-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/Step-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TokenStep-test.tsx.snap index 54847972e8c..54847972e8c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/TokenStep-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/__snapshots__/TokenStep-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/AnalysisCommand.tsx new file mode 100644 index 00000000000..3bcdb9bdbdb --- /dev/null +++ b/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(); + } + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/BuildWrapper.tsx index ed03adbf911..ed03adbf911 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/BuildWrapper.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/BuildWrapper.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/ClangGCC.tsx index 6ebe42a3ca9..0afc9783def 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/ClangGCC.tsx +++ b/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" diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/DotNet.tsx index 6dc321dbb42..12de244aaf8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/DotNet.tsx +++ b/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') }} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaGradle.tsx index 02da9fd1a2a..fa95c27bf5e 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaGradle.tsx +++ b/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> diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/JavaMaven.tsx index 5663b1f2911..8ade1b432bd 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/JavaMaven.tsx +++ b/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> diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/MSBuildScanner.tsx index 65c4005af86..65c4005af86 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/MSBuildScanner.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/MSBuildScanner.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/Msvc.tsx index cf6120a3b31..838da876e26 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Msvc.tsx +++ b/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') }} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx index 3d0b3b2a718..3d0b3b2a718 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/Other.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/Other.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/SQScanner.tsx index 78b90f851bc..78b90f851bc 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/SQScanner.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/SQScanner.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/AnalysisCommand-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/AnalysisCommand-test.tsx new file mode 100644 index 00000000000..3dc16151384 --- /dev/null +++ b/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} />); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/BuildWrapper-test.tsx index b19e334793b..b19e334793b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/BuildWrapper-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/BuildWrapper-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/ClangGCC-test.tsx index a6f7c4f550a..8ffa75c7217 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/ClangGCC-test.tsx +++ b/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" /> ) diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/DotNet-test.tsx index 91ecab828b2..e95302cb59a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/DotNet-test.tsx +++ b/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(); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaGradle-test.tsx index f0d130a248d..f0d130a248d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaGradle-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaGradle-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaMaven-test.tsx index dbda16ca13e..dbda16ca13e 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/JavaMaven-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/JavaMaven-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/MSBuildScanner-test.tsx index e5411341d10..e5411341d10 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/MSBuildScanner-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/MSBuildScanner-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Msvc-test.tsx index 78470bbd2e6..68ee47b0de3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Msvc-test.tsx +++ b/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(); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Other-test.tsx index 16cc44ae69f..16cc44ae69f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/Other-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/Other-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/SQScanner-test.tsx index 2be4e546bb3..2be4e546bb3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/SQScanner-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/SQScanner-test.tsx diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/AnalysisCommand-test.tsx.snap new file mode 100644 index 00000000000..f6a09725d51 --- /dev/null +++ b/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" +/> +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap index 07e8eb45f0a..07e8eb45f0a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/BuildWrapper-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap index cdffd4c2061..642935280e5 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/ClangGCC-test.tsx.snap +++ b/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 diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/DotNet-test.tsx.snap index e072eef9d83..7f0e70d7242 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/DotNet-test.tsx.snap +++ b/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 diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap index 401137a9234..c5ff1eb813c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaGradle-test.tsx.snap +++ b/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", } } /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap index d4462b957c6..7993a392397 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/JavaMaven-test.tsx.snap +++ b/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", } } /> diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap index 740e26ed12b..740e26ed12b 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/MSBuildScanner-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Msvc-test.tsx.snap index 2240a13995c..accb581024c 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Msvc-test.tsx.snap +++ b/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", diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Other-test.tsx.snap index 73aba357dca..73aba357dca 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/Other-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/Other-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap index cf6a8de4ce3..cf6a8de4ce3 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/components/commands/__tests__/__snapshots__/SQScanner-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.tsx index 6f19ade590d..b8006ff3758 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx +++ b/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); diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingPage.tsx new file mode 100644 index 00000000000..d69e98ae561 --- /dev/null +++ b/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); diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OnboardingModal-test.tsx index 9bab36759a5..76c801faaf8 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx +++ b/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} diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap index 18b2f16f846..18b2f16f846 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/__snapshots__/OnboardingModal-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx deleted file mode 100644 index a91ff541119..00000000000 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/AnalysisStep.tsx +++ /dev/null @@ -1,201 +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 * as React from 'react'; -import Step from './Step'; -import LanguageStep, { Result } from './LanguageStep'; -import JavaMaven from './commands/JavaMaven'; -import JavaGradle from './commands/JavaGradle'; -import DotNet from './commands/DotNet'; -import Msvc from './commands/Msvc'; -import ClangGCC from './commands/ClangGCC'; -import Other from './commands/Other'; -import { translate } from '../../../helpers/l10n'; -import { getHostUrl } from '../../../helpers/urls'; - -interface Props { - onFinish: (projectKey?: string) => void; - onReset: () => void; - open: boolean; - organization?: string; - stepNumber: number; - token?: string; -} - -interface State { - result?: Result; -} - -export default class AnalysisStep extends React.PureComponent<Props, State> { - state: State = {}; - - handleLanguageSelect = (result?: Result) => { - this.setState({ result }); - const projectKey = result && result.language !== 'java' ? result.projectKey : undefined; - this.props.onFinish(projectKey); - }; - - handleLanguageReset = () => { - this.setState({ result: undefined }); - this.props.onReset(); - }; - - renderForm = () => { - return ( - <div className="boxed-group-inner"> - <div className="flex-columns"> - <div className="flex-column flex-column-half bordered-right"> - <LanguageStep - onDone={this.handleLanguageSelect} - onReset={this.handleLanguageReset} - organization={this.props.organization} - /> - </div> - <div className="flex-column flex-column-half">{this.renderCommand()}</div> - </div> - </div> - ); - }; - - renderFormattedCommand = (...lines: Array<string>) => ( - // keep this "useless" concatentation for the readability reason - // eslint-disable-next-line no-useless-concat - <pre>{lines.join(' ' + '\\' + '\n' + ' ')}</pre> - ); - - renderCommand = () => { - const { result } = this.state; - - if (!result) { - return null; - } - - if (result.language === 'java') { - return result.javaBuild === 'maven' - ? this.renderCommandForMaven() - : this.renderCommandForGradle(); - } else if (result.language === 'dotnet') { - return this.renderCommandForDotNet(); - } else if (result.language === 'c-family') { - return result.cFamilyCompiler === 'msvc' - ? this.renderCommandForMSVC() - : this.renderCommandForClangGCC(); - } else { - return this.renderCommandForOther(); - } - }; - - 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 { token } = this.props; - const { result } = this.state; - if (!result || !result.projectKey || !token) { - return null; - } - return ( - <DotNet - host={getHostUrl()} - organization={this.props.organization} - projectKey={result.projectKey} - token={token} - /> - ); - }; - - renderCommandForMSVC = () => { - const { token } = this.props; - const { result } = this.state; - if (!result || !result.projectKey || !token) { - return null; - } - return ( - <Msvc - host={getHostUrl()} - organization={this.props.organization} - projectKey={result.projectKey} - token={token} - /> - ); - }; - - renderCommandForClangGCC = () => { - const { token } = this.props; - const { result } = this.state; - if (!result || !result.projectKey || !result.os || !token) { - return null; - } - return ( - <ClangGCC - host={getHostUrl()} - organization={this.props.organization} - os={result.os} - projectKey={result.projectKey} - token={token} - /> - ); - }; - - renderCommandForOther = () => { - const { token } = this.props; - const { result } = this.state; - if (!result || !result.projectKey || !result.os || !token) { - return null; - } - return ( - <Other - host={getHostUrl()} - organization={this.props.organization} - os={result.os} - projectKey={result.projectKey} - token={token} - /> - ); - }; - - 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')} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx index 384e1726e39..e3511631aa4 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/ProjectOnboarding.tsx @@ -21,10 +21,10 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import Helmet from 'react-helmet'; import { connect } from 'react-redux'; -import TokenStep from './TokenStep'; -import OrganizationStep from './OrganizationStep'; -import AnalysisStep from './AnalysisStep'; import ProjectWatcher from './ProjectWatcher'; +import ProjectAnalysisStep from '../components/ProjectAnalysisStep'; +import OrganizationStep from '../components/OrganizationStep'; +import TokenStep from '../components/TokenStep'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; import { CurrentUser, isLoggedIn } from '../../../app/types'; @@ -161,7 +161,7 @@ export class ProjectOnboarding extends React.PureComponent<Props, State> { stepNumber={stepNumber++} /> - <AnalysisStep + <ProjectAnalysisStep onFinish={this.handleFinish} onReset={this.handleReset} open={step === 'analysis'} diff --git a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap index 0c5b93e108a..655ed26af50 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/projectOnboarding/__tests__/__snapshots__/ProjectOnboarding-test.tsx.snap @@ -36,7 +36,7 @@ exports[`guides for on-premise 1`] = ` open={true} stepNumber={1} /> - <AnalysisStep + <ProjectAnalysisStep onFinish={[Function]} onReset={[Function]} open={false} @@ -97,7 +97,7 @@ exports[`guides for on-premise 2`] = ` open={false} stepNumber={1} /> - <AnalysisStep + <ProjectAnalysisStep onFinish={[Function]} onReset={[Function]} open={true} @@ -172,7 +172,7 @@ exports[`guides for sonarcloud 1`] = ` open={false} stepNumber={2} /> - <AnalysisStep + <ProjectAnalysisStep onFinish={[Function]} onReset={[Function]} open={false} @@ -246,7 +246,7 @@ exports[`guides for sonarcloud 2`] = ` open={true} stepNumber={2} /> - <AnalysisStep + <ProjectAnalysisStep onFinish={[Function]} onReset={[Function]} open={false} @@ -321,7 +321,7 @@ exports[`guides for sonarcloud 3`] = ` open={false} stepNumber={2} /> - <AnalysisStep + <ProjectAnalysisStep onFinish={[Function]} onReset={[Function]} open={true} diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.ts b/server/sonar-web/src/main/js/apps/tutorials/routes.ts index 5c8f9c88641..94c25460e0a 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/routes.ts +++ b/server/sonar-web/src/main/js/apps/tutorials/routes.ts @@ -26,7 +26,7 @@ const routes = [ component: lazyLoad( () => isSonarCloud() - ? import('./createProjectOnboarding/CreateProjectOnboarding') + ? import('./onboarding/OnboardingPage') : import('./projectOnboarding/ProjectOnboardingPage') ) } diff --git a/server/sonar-web/src/main/js/apps/tutorials/styles.css b/server/sonar-web/src/main/js/apps/tutorials/styles.css index 798fce21bcc..da2571a369d 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/styles.css +++ b/server/sonar-web/src/main/js/apps/tutorials/styles.css @@ -58,8 +58,7 @@ .onboarding-choices { display: flex; justify-content: space-around; - padding-top: 44px; - padding-bottom: 44px; + padding: 44px 0; background-color: var(--barBackgroundColor); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/utils.ts b/server/sonar-web/src/main/js/apps/tutorials/utils.ts new file mode 100644 index 00000000000..f84785ef61d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/utils.ts @@ -0,0 +1,40 @@ +/* + * 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. + */ +export interface LanguageConfig { + language?: string; + javaBuild?: string; + cFamilyCompiler?: string; + os?: string; + projectKey?: string; +} + +export function isLanguageConfigured(config?: LanguageConfig) { + if (!config) { + return false; + } + const { language, javaBuild, cFamilyCompiler, os, projectKey } = config; + 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; + + return isJavaConfigured || isDotNetConfigured || isCFamilyConfigured || isOtherConfigured; +} diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx index 3592d9b0be0..7542f09a3aa 100644 --- a/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx @@ -26,7 +26,7 @@ export default function OnboardingPrivateIcon({ size }: IconProps) { return ( - <Icon className={className} size={size || 64} viewBox=""> + <Icon className={className} size={size || 64} viewBox="0 0 64 64"> <g fill="none" stroke={fill} strokeWidth="2"> <path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" /> <path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" /> diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx index d2090eed867..d97972db6e7 100644 --- a/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx @@ -26,7 +26,7 @@ export default function OnboardingProjectIcon({ size }: IconProps) { return ( - <Icon className={className} size={size || 64} viewBox=""> + <Icon className={className} size={size || 64} viewBox="0 0 64 64"> <g fill="none" fillRule="evenodd" stroke={fill} strokeWidth="2"> <path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" /> <path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" /> diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx index 6ce74838a0d..023067e74a8 100644 --- a/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx @@ -22,7 +22,7 @@ import Icon, { IconProps } from './Icon'; export default function OnboardingTeamIcon({ className, fill = 'currentColor', size }: IconProps) { return ( - <Icon className={className} size={size || 64} viewBox=""> + <Icon className={className} size={size || 64} viewBox="0 0 64 64"> <g fill="none" fillRule="evenodd" stroke={fill} strokeWidth="2"> <path d="M32 9v5M11.5195 43.0898l7.48-4.091m33.481-18.0994l-7.48 4.1m-33.481-4.1l7.48 4.1M45 38.999l7.48 4.101M32 50v5m15-23c0 8.284-6.715 15-15 15s-15-6.716-15-15c0-8.285 6.715-15 15-15s15 6.715 15 15z" /> <path d="M40 38c0 1.656-3.58 2-8 2s-8-.344-8-2m16 0v-3l-5-3-1-1m-10 7v-3l5-3 1-1m6-4c0 2.2-1.8 4-4 4s-4-1.8-4-4v-1c0-2.2 1.8-4 4-4s4 1.8 4 4v1zm-.0098-21.71c7.18 1.069 13.439 4.96 17.609 10.51m-17.609 42.91c7.18-1.07 13.439-4.96 17.609-10.51M6.6299 41.25c-1.06-2.88-1.63-6-1.63-9.25s.57-6.37 1.63-9.25m3.7705-6.9502c4.17-5.55 10.43-9.44 17.609-10.51m-17.609 42.9104c4.17 5.55 10.43 9.439 17.609 10.51M57.3701 22.75c1.06 2.88 1.63 6 1.63 9.25s-.57 6.37-1.63 9.25" /> diff --git a/server/sonar-web/src/main/js/components/ui/buttons.css b/server/sonar-web/src/main/js/components/ui/buttons.css index cc24ba79baf..032c0255117 100644 --- a/server/sonar-web/src/main/js/components/ui/buttons.css +++ b/server/sonar-web/src/main/js/components/ui/buttons.css @@ -130,6 +130,10 @@ transition: all 0.2s ease; } +.dropdown .button-link { + border-bottom: none; +} + .button-link:hover { background: transparent; color: var(--blue); diff --git a/server/sonar-web/src/main/js/helpers/markdown.js b/server/sonar-web/src/main/js/helpers/markdown.js index 7f1c21325df..633c3450289 100644 --- a/server/sonar-web/src/main/js/helpers/markdown.js +++ b/server/sonar-web/src/main/js/helpers/markdown.js @@ -72,7 +72,7 @@ function parseFrontMatter(lines) { * @returns {string} */ function filterContent(content) { - const { isSonarCloud } = require('../helpers/system'); + const { isSonarCloud } = require('./system'); const contentWithoutStatic = cutConditionalContent(content, 'static'); return isSonarCloud() ? cutConditionalContent(contentWithoutStatic, 'sonarqube') diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 4d17bde0b09..9d3959260bf 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -49,10 +49,6 @@ export function getPathUrlAsString(path: Location): string { return `${getBaseUrl()}${path.pathname}?${stringify(omitBy(path.query, isNil))}`; } -export function getSonarCloudUrlAsString(location: Location) { - return 'https://sonarcloud.io' + getPathUrlAsString(location); -} - export function getProjectUrl(project: string, branch?: string): Location { return { pathname: '/dashboard', query: { id: project, branch } }; } |