aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-10-08 09:24:03 +0200
committersonartech <sonartech@sonarsource.com>2018-11-01 09:32:49 +0100
commit3d4819e32de3ebfb68291de0467f1e2360d0d2cf (patch)
tree43daa77cfd99b7ba9ac26b780fb3d5cfd5c5e893 /server/sonar-web/src/main/js
parent35229142c6eaf780a70fcf838280cc7aee4fe515 (diff)
downloadsonarqube-3d4819e32de3ebfb68291de0467f1e2360d0d2cf.tar.gz
sonarqube-3d4819e32de3ebfb68291de0467f1e2360d0d2cf.zip
SONARCLOUD-132 New homepage
* Create sticky menu bar * Add languages section animation * Add figures section counter animation * Animate featured projects carrousel
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/@types/react-countup.d.ts31
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/misc.css4
-rw-r--r--server/sonar-web/src/main/js/app/theme.js4
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/AsAService.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/Home.tsx347
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/SQHome.tsx86
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/SonarLintIntegration.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/VSTS.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Home-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/__snapshots__/Home-test.tsx.snap506
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.css126
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx308
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.css86
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx)3
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/Languages.tsx111
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.css48
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.css102
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.tsx91
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/Pricing.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx)0
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQPageContainer.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/SonarCloudPage.tsx)10
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQStartUsing.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/SQStartUsing.tsx)0
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQTopNav.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/SQTopNav.tsx)0
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/StartUsing.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/StartUsing.tsx)2
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.css42
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx105
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/FeaturedProjects-test.tsx107
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Footer-test.tsx (renamed from server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Footer-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/LoginButtons-test.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/FeaturedProjects-test.tsx.snap244
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/LoginButtons-test.tsx.snap39
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap43
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/new_style.css250
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/style.css160
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/utils.ts134
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap76
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.ts4
-rw-r--r--server/sonar-web/src/main/js/components/common/OrganizationAvatar.css2
-rw-r--r--server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx13
-rw-r--r--server/sonar-web/src/main/js/components/nav/NavBar.tsx5
46 files changed, 2900 insertions, 385 deletions
diff --git a/server/sonar-web/src/main/js/@types/react-countup.d.ts b/server/sonar-web/src/main/js/@types/react-countup.d.ts
new file mode 100644
index 00000000000..138db886f88
--- /dev/null
+++ b/server/sonar-web/src/main/js/@types/react-countup.d.ts
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+declare module 'react-countup' {
+ interface Props {
+ decimal?: string;
+ decimals?: number;
+ delay?: number;
+ duration?: number;
+ end: number;
+ suffix?: string;
+ }
+
+ export default function CountUp(props: Props): JSX.Element;
+}
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 b13e0a463a7..8498249f48d 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
@@ -308,6 +308,10 @@ td.big-spacer-top {
position: absolute !important;
}
+.position-relative {
+ position: relative !important;
+}
+
.rounded {
border-radius: 2px;
}
diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js
index 09c8c994264..400d4a99a9e 100644
--- a/server/sonar-web/src/main/js/app/theme.js
+++ b/server/sonar-web/src/main/js/app/theme.js
@@ -135,8 +135,8 @@ module.exports = {
popupZIndex: '5000',
// sonarcloud
- sonarcloudOrange: '#f60',
- sonarcloudOrangeDark: '#e65c00',
+ sonarcloudOrange500: '#fd6a00',
+ sonarcloudOrange700: '#db5700',
sonarcloudBlack100: '#ffffff',
sonarcloudBlack200: '#f9f9fb',
sonarcloudBlack300: '#cfd3d7',
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/AsAService.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/AsAService.tsx
index fad4823c0e1..ed1cef669bd 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/AsAService.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/AsAService.tsx
@@ -18,16 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import SonarCloudPage from './SonarCloudPage';
-import SQStartUsing from './SQStartUsing';
-import SQTopNav from './SQTopNav';
+import SQPageContainer from './components/SQPageContainer';
+import SQStartUsing from './components/SQStartUsing';
+import SQTopNav from './components/SQTopNav';
import { isLoggedIn } from '../../../app/types';
import { getBaseUrl } from '../../../helpers/urls';
import './style.css';
export default function AsAService() {
return (
- <SonarCloudPage>
+ <SQPageContainer>
{({ currentUser }) => (
<div className="page page-limited sc-page">
<SQTopNav />
@@ -85,6 +85,6 @@ export default function AsAService() {
{!isLoggedIn(currentUser) && <SQStartUsing />}
</div>
)}
- </SonarCloudPage>
+ </SQPageContainer>
);
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx
index 19c5f5dddd2..5d2695bbca2 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx
@@ -18,16 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import SonarCloudPage from './SonarCloudPage';
-import SQStartUsing from './SQStartUsing';
-import SQTopNav from './SQTopNav';
+import SQPageContainer from './components/SQPageContainer';
+import SQStartUsing from './components/SQStartUsing';
+import SQTopNav from './components/SQTopNav';
import { isLoggedIn } from '../../../app/types';
import { getBaseUrl } from '../../../helpers/urls';
import './style.css';
export default function BranchAnalysis() {
return (
- <SonarCloudPage>
+ <SQPageContainer>
{({ currentUser }) => (
<div className="page page-limited sc-page">
<SQTopNav />
@@ -115,6 +115,6 @@ export default function BranchAnalysis() {
{!isLoggedIn(currentUser) && <SQStartUsing />}
</div>
)}
- </SonarCloudPage>
+ </SQPageContainer>
);
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx
index c3b1863ea79..59d084dfe9d 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Link } from 'react-router';
import { Location } from 'history';
-import SonarCloudPage from './SonarCloudPage';
+import SQPageContainer from './components/SQPageContainer';
import Select from '../../../components/controls/Select';
import { isLoggedIn, Organization } from '../../../app/types';
import { Alert } from '../../../components/ui/Alert';
@@ -80,7 +80,7 @@ export default class Contact extends React.PureComponent<Props, State> {
render() {
return (
- <SonarCloudPage>
+ <SQPageContainer>
{({ currentUser, userOrganizations }) => (
<div className="page page-limited sc-page sc-contact-page">
<h1 className="sc-page-title">Contact us</h1>
@@ -205,7 +205,7 @@ export default class Contact extends React.PureComponent<Props, State> {
</form>
</div>
)}
- </SonarCloudPage>
+ </SQPageContainer>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Home.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/Home.tsx
index 21f62fb4f5d..ae083870881 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/Home.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/Home.tsx
@@ -18,103 +18,280 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
-import SonarCloudPage from './SonarCloudPage';
-import Pricing from './Pricing';
-import StartUsing from './StartUsing';
-import { isLoggedIn } from '../../../app/types';
-import ChevronRightIcon from '../../../components/icons-components/ChevronRightcon';
-import './style.css';
+import { FixedNavBar, TopNavBar } from './components/NavBars';
+import FeaturedProjects from './components/FeaturedProjects';
+import Footer from './components/Footer';
+import Statistics from './components/Statistics';
+import { Languages } from './components/Languages';
+import LoginButtons from './components/LoginButtons';
+import { getBaseUrl } from '../../../helpers/urls';
+import './new_style.css';
-export default function Home() {
- return (
- <SonarCloudPage>
- {({ currentUser }) => (
- <div className="page page-limited sc-page">
- <h1 className="sc-page-title">Continuous Code Quality Online</h1>
- <p className="sc-page-subtitle">
- Analyze the quality of your source code to detect bugs, vulnerabilities <br />
- and code smells throughout the development process.
- </p>
-
- <ul className="sc-features-list">
- <li className="sc-feature">
- <h2 className="sc-feature-title">Built on SonarQube</h2>
- <p className="sc-feature-description">
- The broadly used code review tool to detect bugs, code smells and vulnerability
- issues.
- </p>
- </li>
-
- <li className="sc-feature">
- <h2 className="sc-feature-title">17 languages</h2>
- <p className="sc-feature-description">
- Java, JS, C#, C/C++, Objective-C, TypeScript, Python, Go, ABAP, PL/SQL, T-SQL and
- more.
- </p>
- </li>
+// TODO Get this from an external source
+const STATISTICS = [
+ { icon: 'rules', text: 'Static analysis rules checked', value: 9675 },
+ { icon: 'locs', text: 'Lines of code analyzed', value: 20000000 },
+ { icon: 'pull-request', text: 'Pull Requests decorated', value: 100675 },
+ { icon: 'open-source', text: 'Open source projects inspected', value: 99675 }
+];
- <li className="sc-feature">
- <h2 className="sc-feature-title">Thousands of rules</h2>
- <p className="sc-feature-description">
- Track down hard-to-find bugs and quality issues thanks to powerful static code
- analyzers.
- </p>
- </li>
-
- <li className="sc-feature">
- <h2 className="sc-feature-title">Cloud CI Integrations</h2>
- <p className="sc-feature-description">
- Schedule the execution of an analysis from Cloud CI engines: Travis, VSTS, AppVeyor
- and more.
- </p>
- </li>
-
- <li className="sc-feature">
- <h2 className="sc-feature-title">Deep code analysis</h2>
- <p className="sc-feature-description">
- Explore all your source files, whether in branches or pull requests, to reach a
- green quality gate and promote the build.
- </p>
- </li>
+export default class Home extends React.PureComponent {
+ componentDidMount() {
+ document.documentElement.classList.add('white-page');
+ document.body.classList.add('white-page');
+ }
- <li className="sc-feature">
- <h2 className="sc-feature-title">Fast and Scalable</h2>
- <p className="sc-feature-description">Scale on-demand as your projects grow.</p>
- </li>
- </ul>
+ componentWillUnmount() {
+ document.documentElement.classList.remove('white-page');
+ document.body.classList.remove('white-page');
+ }
- <Pricing />
+ render() {
+ return (
+ <div className="global-container">
+ <div className="page-wrapper">
+ <div className="page-container sc-page">
+ <FixedNavBar />
+ <PageBackgroundHeader />
+ <TopNavBar />
+ <PageTitle />
+ <EnhanceWorkflow />
+ <Functionality />
+ <Languages />
+ <Stats />
+ <Projects />
+ </div>
+ </div>
+ <Footer />
+ </div>
+ );
+ }
+}
- {!isLoggedIn(currentUser) && <StartUsing />}
+function PageBackgroundHeader() {
+ return (
+ <div className="sc-header-background">
+ <div className="sc-background-start" />
+ <div className="sc-background-end" />
+ <div className="sc-background-center">
+ <img alt="" height="418px" src={`${getBaseUrl()}/images/sonarcloud/home-header.svg`} />
+ </div>
+ </div>
+ );
+}
- <div className="sc-narrow-container text-center">
- <h2 className="sc-feature-title">Explore open source projects on SonarCloud</h2>
- <p className="sc-feature-description">
- SonarCloud offers free analysis for open source projects. <br />
- It is public and open to anyone who wants to browse the service.
+function PageTitle() {
+ return (
+ <div className="sc-section sc-columns">
+ <div className="sc-column sc-column-half display-flex-center">
+ <div>
+ <h1 className="sc-title-orange">Clean Code</h1>
+ <h1 className="sc-spacer-bottom">Rockstar Status</h1>
+ <h5 className="sc-big-spacer-bottom sc-regular-weight">
+ Eliminate bugs and vulnerabilities,
+ <br />
+ champion quality code in your projects.
+ </h5>
+ <div>
+ <h6>Go ahead! Analyze your repo:</h6>
+ <LoginButtons />
+ <p className="sc-mention sc-regular-weight big-spacer-top">
+ Free for Open Source Projects
</p>
</div>
+ </div>
+ </div>
+ <div className="sc-column sc-column-half text-center">
+ <img
+ alt=""
+ src={`${getBaseUrl()}/images/sonarcloud/home-header-people.png`}
+ width="480px"
+ />
+ </div>
+ </div>
+ );
+}
+
+function EnhanceWorkflow() {
+ return (
+ <div className="sc-section sc-columns">
+ <div className="sc-column sc-column-full">
+ <h3 className="sc-big-spacer-bottom">
+ Enhance Your Workflow
+ <br />
+ with Continuous Code Quality
+ </h3>
+ <img
+ alt=""
+ className="sc-big-spacer-bottom"
+ src={`${getBaseUrl()}/images/sonarcloud/home-branch.png`}
+ srcSet={`${getBaseUrl()}/images/sonarcloud/home-branch.png 1x, ${getBaseUrl()}/images/sonarcloud/home-branch@2x.png 2x`}
+ />
+ <h5 className="spacer-bottom">Maximize your throughput, only release clean code</h5>
+ <h6 className="sc-regular-weight">
+ Sonarcloud automatically analyzes branches and decorates pull requests
+ </h6>
+ </div>
+ </div>
+ );
+}
- <div className="sc-narrow-container text-center">
- <Link className="sc-browse" to="/explore/projects">
- Browse
- </Link>
+function Functionality() {
+ return (
+ <div className="position-relative">
+ <div className="sc-functionality-background">
+ <div className="sc-background-center">
+ <img
+ alt=""
+ height="300px"
+ src={`${getBaseUrl()}/images/sonarcloud/home-grey-background.svg`}
+ />
+ </div>
+ </div>
+ <div className="sc-functionality-container">
+ <div className="sc-section">
+ <h3 className="sc-big-spacer-bottom text-center">
+ Functionality
+ <br />
+ that Fits Your Projects
+ </h3>
+ <div className="sc-columns">
+ <div className="sc-column sc-column-small">
+ <h6 className="sc-regular-weight spacer-bottom">Easy to Use</h6>
+ <p>
+ With just a few clicks you’re up and running right where your code lives. Immediate
+ access to the latest features and enhancements.
+ </p>
+ <div className="sc-separator" />
+ <span className="big-spacer-bottom sc-with-icon">
+ <img
+ alt=""
+ className="spacer-right"
+ src={`${getBaseUrl()}/images/sonarcloud/scale.svg`}
+ />{' '}
+ Scale on-demand as your projects grow.
+ </span>
+ <span className="sc-with-icon">
+ <img
+ alt=""
+ className="spacer-right"
+ src={`${getBaseUrl()}/images/sonarcloud/stop.svg`}
+ />{' '}
+ No contracts, stop/start anytime.
+ </span>
+ </div>
+ <div className="sc-column sc-column-big">
+ <img
+ alt=""
+ className="sc-rounded-img"
+ src={`${getBaseUrl()}/images/sonarcloud/home-easy-to-use.png`}
+ srcSet={`${getBaseUrl()}/images/sonarcloud/home-easy-to-use.png 1x, ${getBaseUrl()}/images/sonarcloud/home-easy-to-use@2x.png 2x`}
+ />
+ </div>
+ </div>
+ <div className="sc-columns">
+ <div className="sc-column sc-column-big">
+ <img
+ alt=""
+ className="sc-rounded-img"
+ src={`${getBaseUrl()}/images/sonarcloud/home-open-transparent.png`}
+ srcSet={`${getBaseUrl()}/images/sonarcloud/home-open-transparent.png 1x, ${getBaseUrl()}/images/sonarcloud/home-open-transparent@2x.png 2x`}
+ />
+ </div>
+ <div className="sc-column sc-column-small">
+ <div>
+ <h6 className="sc-regular-weight spacer-bottom">Open and transparent</h6>
+ <p className="big-spacer-bottom">
+ Project dashboards keep teams and stakeholders informed on code quality and
+ releasability
+ </p>
+ <p>Display project badges and show your communities you’re all about awesome</p>
+ <img
+ alt=""
+ className="big-spacer-top"
+ src={`${getBaseUrl()}/images/project_badges/sonarcloud-black.svg`}
+ width="200px"
+ />
+ </div>
+ </div>
</div>
+ <div className="sc-columns">
+ <div className="sc-column sc-column-full">
+ <div>
+ <h6 className="sc-regular-weight spacer-bottom">Effective Collaboration</h6>
- <div className="sc-narrow-container sc-news">
- <h2 className="sc-news-title">News</h2>
- <ChevronRightIcon className="big-spacer-left" fill="#cfd3d7" />
- <a
- className="sc-news-link big-spacer-left"
- href="https://blog.sonarsource.com/product/SonarCloud"
- rel="noopener noreferrer"
- target="_blank">
- See all
- </a>
+ <p className="sc-with-inline-icon">
+ Use
+ <img
+ alt="SonarCloud"
+ src={`${getBaseUrl()}/images/sonarcloud/sonarcloud-logo-text-only.svg`}
+ />
+ with your team, share best practices and have fun writing quality code!
+ </p>
+ <br />
+ <p className="sc-with-inline-icon huge-spacer-bottom">
+ Connect with
+ <img
+ alt="SonarCloud"
+ src={`${getBaseUrl()}/images/sonarcloud/sonarlint-logo.svg`}
+ />
+ and get real-time notifications in your IDE as you work.
+ </p>
+ <div>
+ <img
+ alt=""
+ className="huge-spacer-bottom"
+ src={`${getBaseUrl()}/images/sonarcloud/ide.svg`}
+ width="216px"
+ />
+ </div>
+ <img alt="" src={`${getBaseUrl()}/images/sonarcloud/collab.svg`} width="540px" />
+ </div>
+ </div>
</div>
</div>
- )}
- </SonarCloudPage>
+ </div>
+ </div>
+ );
+}
+
+function Stats() {
+ return (
+ <div className="sc-section sc-columns">
+ <div className="sc-column sc-column-full">
+ <h5 className="sc-big-spacer-bottom">
+ Over 3,000 projects
+ <br />
+ continuously analyzed
+ </h5>
+ <Statistics statistics={STATISTICS} />
+ </div>
+ </div>
+ );
+}
+
+function Projects() {
+ return (
+ <div className="sc-section sc-columns">
+ <div className="sc-column sc-column-full">
+ <h6 className="big-spacer-bottom">
+ Transparency makes sense
+ <br />
+ and that’s why the trend is growing.
+ </h6>
+ <p className="sc-big-spacer-bottom">
+ Check out these open source projects showing users
+ <br />
+ their commitment to quality.
+ </p>
+ <FeaturedProjects />
+ <h6 className="spacer-bottom">
+ Come join the fun, it’s entirely free for open source projects !
+ </h6>
+ <div className="big-spacer-bottom">
+ <LoginButtons />
+ </div>
+ </div>
+ </div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/SQHome.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/SQHome.tsx
index 1b0bfe98092..325a526dd9d 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/SQHome.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/SQHome.tsx
@@ -19,16 +19,17 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import SonarCloudPage from './SonarCloudPage';
-import Pricing from './Pricing';
-import StartUsing from './StartUsing';
+import LoginButtons from './components/LoginButtons';
+import Pricing from './components/Pricing';
+import SQPageContainer from './components/SQPageContainer';
+import StartUsing from './components/StartUsing';
import { isLoggedIn } from '../../../app/types';
import { getBaseUrl } from '../../../helpers/urls';
import './style.css';
export default function SQHome() {
return (
- <SonarCloudPage>
+ <SQPageContainer>
{({ currentUser }) => (
<div className="page sc-page sc-sq-page">
<Jumbotron />
@@ -43,7 +44,7 @@ export default function SQHome() {
<BottomNote />
</div>
)}
- </SonarCloudPage>
+ </SQPageContainer>
);
}
@@ -61,26 +62,7 @@ function Jumbotron() {
<br />
Log in or sign up with
</div>
- <div>
- <a
- className="sc-white-button sc-sq-login-button"
- href={`${getBaseUrl()}/sessions/init/github`}>
- <img alt="" height="25" src={`${getBaseUrl()}/images/sonarcloud/github.svg`} />
- GitHub
- </a>
- <a
- className="sc-white-button sc-sq-login-button"
- href={`${getBaseUrl()}/sessions/init/bitbucket`}>
- <img alt="" height="25" src={`${getBaseUrl()}/images/sonarcloud/bitbucket.svg`} />
- Bitbucket
- </a>
- <a
- className="sc-white-button sc-sq-login-button"
- href={`${getBaseUrl()}/sessions/init/microsoft`}>
- <img alt="" height="25" src={`${getBaseUrl()}/images/sonarcloud/windows.svg`} />
- VSTS
- </a>
- </div>
+ <LoginButtons />
</div>
<div className="sc-sq-jumbotron-right">
<img
@@ -162,7 +144,7 @@ function Languages() {
<img
alt="Java"
height="60"
- src={`${getBaseUrl()}/images/languages/java.svg`}
+ src={`${getBaseUrl()}/images/languages/black/java.svg`}
width="60"
/>
</li>
@@ -170,7 +152,7 @@ function Languages() {
<img
alt="JavaScript"
height="60"
- src={`${getBaseUrl()}/images/languages/js.svg`}
+ src={`${getBaseUrl()}/images/languages/black/js.svg`}
width="60"
/>
</li>
@@ -178,7 +160,7 @@ function Languages() {
<img
alt="TypeScript"
height="60"
- src={`${getBaseUrl()}/images/languages/ts.svg`}
+ src={`${getBaseUrl()}/images/languages/black/ts.svg`}
width="60"
/>
</li>
@@ -186,7 +168,7 @@ function Languages() {
<img
alt="C#"
height="60"
- src={`${getBaseUrl()}/images/languages/c-sharp.svg`}
+ src={`${getBaseUrl()}/images/languages/black/csharp.svg`}
width="60"
/>
</li>
@@ -194,34 +176,49 @@ function Languages() {
<img
alt="C++"
height="60"
- src={`${getBaseUrl()}/images/languages/c-plus.svg`}
+ src={`${getBaseUrl()}/images/languages/black/c-c-plus-plus.svg`}
width="60"
/>
</li>
<li>
- <img alt="Go" height="60" src={`${getBaseUrl()}/images/languages/go.svg`} width="60" />
+ <img
+ alt="Go"
+ height="60"
+ src={`${getBaseUrl()}/images/languages/black/go.svg`}
+ width="60"
+ />
</li>
<li>
<img
alt="Python"
height="60"
- src={`${getBaseUrl()}/images/languages/python.svg`}
+ src={`${getBaseUrl()}/images/languages/black/python.svg`}
width="60"
/>
</li>
<li>
- <img alt="PHP" height="60" src={`${getBaseUrl()}/images/languages/php.svg`} width="60" />
+ <img
+ alt="PHP"
+ height="60"
+ src={`${getBaseUrl()}/images/languages/black/php.svg`}
+ width="60"
+ />
</li>
</ul>
<ul className="sc-languages-list">
<li>
- <img alt="VB" height="60" src={`${getBaseUrl()}/images/languages/vb.svg`} width="60" />
+ <img
+ alt="VB"
+ height="60"
+ src={`${getBaseUrl()}/images/languages/black/vb.svg`}
+ width="60"
+ />
</li>
<li>
<img
alt="Flex"
height="60"
- src={`${getBaseUrl()}/images/languages/flex.png`}
+ src={`${getBaseUrl()}/images/languages/black/flex@2x.png`}
width="85"
/>
</li>
@@ -229,7 +226,7 @@ function Languages() {
<img
alt="HTML"
height="60"
- src={`${getBaseUrl()}/images/languages/html5.svg`}
+ src={`${getBaseUrl()}/images/languages/black/html5.svg`}
width="60"
/>
</li>
@@ -237,7 +234,7 @@ function Languages() {
<img
alt="Swift"
height="60"
- src={`${getBaseUrl()}/images/languages/swift.svg`}
+ src={`${getBaseUrl()}/images/languages/black/swift.svg`}
width="60"
/>
</li>
@@ -245,7 +242,7 @@ function Languages() {
<img
alt="Objective-C"
height="60"
- src={`${getBaseUrl()}/images/languages/obj-c.svg`}
+ src={`${getBaseUrl()}/images/languages/black/obj-c.svg`}
width="60"
/>
</li>
@@ -253,7 +250,7 @@ function Languages() {
<img
alt="T-SQL"
height="60"
- src={`${getBaseUrl()}/images/languages/tsql.svg`}
+ src={`${getBaseUrl()}/images/languages/black/t-sql.svg`}
width="60"
/>
</li>
@@ -261,7 +258,7 @@ function Languages() {
<img
alt="PL/SQL"
height="60"
- src={`${getBaseUrl()}/images/languages/plsql.svg`}
+ src={`${getBaseUrl()}/images/languages/black/pl-sql.svg`}
width="60"
/>
</li>
@@ -269,12 +266,17 @@ function Languages() {
<img
alt="ABAP"
height="60"
- src={`${getBaseUrl()}/images/languages/abap.svg`}
+ src={`${getBaseUrl()}/images/languages/black/abap.svg`}
width="60"
/>
</li>
<li>
- <img alt="XML" height="60" src={`${getBaseUrl()}/images/languages/xml.svg`} width="60" />
+ <img
+ alt="XML"
+ height="60"
+ src={`${getBaseUrl()}/images/languages/black/xml.svg`}
+ width="60"
+ />
</li>
</ul>
</div>
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/SonarLintIntegration.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/SonarLintIntegration.tsx
index 69a0bceabbe..d3c6eba303a 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/SonarLintIntegration.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/SonarLintIntegration.tsx
@@ -18,16 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import SonarCloudPage from './SonarCloudPage';
-import SQStartUsing from './SQStartUsing';
-import SQTopNav from './SQTopNav';
+import SQPageContainer from './components/SQPageContainer';
+import SQStartUsing from './components/SQStartUsing';
+import SQTopNav from './components/SQTopNav';
import { isLoggedIn } from '../../../app/types';
import { getBaseUrl } from '../../../helpers/urls';
import './style.css';
export default function SonarLintIntegration() {
return (
- <SonarCloudPage>
+ <SQPageContainer>
{({ currentUser }) => (
<div className="page page-limited sc-page">
<SQTopNav />
@@ -95,6 +95,6 @@ export default function SonarLintIntegration() {
{!isLoggedIn(currentUser) && <SQStartUsing />}
</div>
)}
- </SonarCloudPage>
+ </SQPageContainer>
);
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/VSTS.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/VSTS.tsx
index f641ea825bf..c5a9523bf92 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/VSTS.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/VSTS.tsx
@@ -19,14 +19,14 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import SonarCloudPage from './SonarCloudPage';
+import SQPageContainer from './components/SQPageContainer';
import { isLoggedIn } from '../../../app/types';
import { getBaseUrl } from '../../../helpers/urls';
import './style.css';
export default function VSTS() {
return (
- <SonarCloudPage>
+ <SQPageContainer>
{({ currentUser }) => (
<div className="page page-limited sc-page">
<ul className="sc-top-nav">
@@ -130,6 +130,6 @@ export default function VSTS() {
</div>
</div>
)}
- </SonarCloudPage>
+ </SQPageContainer>
);
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Home-test.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Home-test.tsx
index a4368fec7a7..5b534339198 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Home-test.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Home-test.tsx
@@ -21,8 +21,14 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import Home from '../Home';
-jest.mock('Docs/EmbedDocsSuggestions.json', () => ({}), { virtual: true });
-
it('should render', () => {
- expect(shallow(<Home />)).toBeDefined();
+ const wrapper = shallow(<Home />);
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find('PageBackgroundHeader').dive()).toMatchSnapshot();
+ expect(wrapper.find('PageTitle').dive()).toMatchSnapshot();
+ expect(wrapper.find('EnhanceWorkflow').dive()).toMatchSnapshot();
+ expect(wrapper.find('Functionality').dive()).toMatchSnapshot();
+ expect(wrapper.find('Languages').dive()).toMatchSnapshot();
+ expect(wrapper.find('Stats').dive()).toMatchSnapshot();
+ expect(wrapper.find('Projects').dive()).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/__snapshots__/Home-test.tsx.snap b/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/__snapshots__/Home-test.tsx.snap
new file mode 100644
index 00000000000..4cbc42e392c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/__snapshots__/Home-test.tsx.snap
@@ -0,0 +1,506 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="global-container"
+>
+ <div
+ className="page-wrapper"
+ >
+ <div
+ className="page-container sc-page"
+ >
+ <FixedNavBar />
+ <PageBackgroundHeader />
+ <TopNavBar />
+ <PageTitle />
+ <EnhanceWorkflow />
+ <Functionality />
+ <Languages />
+ <Stats />
+ <Projects />
+ </div>
+ </div>
+ <Footer />
+</div>
+`;
+
+exports[`should render 2`] = `
+<div
+ className="sc-header-background"
+>
+ <div
+ className="sc-background-start"
+ />
+ <div
+ className="sc-background-end"
+ />
+ <div
+ className="sc-background-center"
+ >
+ <img
+ alt=""
+ height="418px"
+ src="/images/sonarcloud/home-header.svg"
+ />
+ </div>
+</div>
+`;
+
+exports[`should render 3`] = `
+<div
+ className="sc-section sc-columns"
+>
+ <div
+ className="sc-column sc-column-half display-flex-center"
+ >
+ <div>
+ <h1
+ className="sc-title-orange"
+ >
+ Clean Code
+ </h1>
+ <h1
+ className="sc-spacer-bottom"
+ >
+ Rockstar Status
+ </h1>
+ <h5
+ className="sc-big-spacer-bottom sc-regular-weight"
+ >
+ Eliminate bugs and vulnerabilities,
+ <br />
+ champion quality code in your projects.
+ </h5>
+ <div>
+ <h6>
+ Go ahead! Analyze your repo:
+ </h6>
+ <LoginButtons />
+ <p
+ className="sc-mention sc-regular-weight big-spacer-top"
+ >
+ Free for Open Source Projects
+ </p>
+ </div>
+ </div>
+ </div>
+ <div
+ className="sc-column sc-column-half text-center"
+ >
+ <img
+ alt=""
+ src="/images/sonarcloud/home-header-people.png"
+ width="480px"
+ />
+ </div>
+</div>
+`;
+
+exports[`should render 4`] = `
+<div
+ className="sc-section sc-columns"
+>
+ <div
+ className="sc-column sc-column-full"
+ >
+ <h3
+ className="sc-big-spacer-bottom"
+ >
+ Enhance Your Workflow
+ <br />
+ with Continuous Code Quality
+ </h3>
+ <img
+ alt=""
+ className="sc-big-spacer-bottom"
+ src="/images/sonarcloud/home-branch.png"
+ srcSet="/images/sonarcloud/home-branch.png 1x, /images/sonarcloud/home-branch@2x.png 2x"
+ />
+ <h5
+ className="spacer-bottom"
+ >
+ Maximize your throughput, only release clean code
+ </h5>
+ <h6
+ className="sc-regular-weight"
+ >
+ Sonarcloud automatically analyzes branches and decorates pull requests
+ </h6>
+ </div>
+</div>
+`;
+
+exports[`should render 5`] = `
+<div
+ className="position-relative"
+>
+ <div
+ className="sc-functionality-background"
+ >
+ <div
+ className="sc-background-center"
+ >
+ <img
+ alt=""
+ height="300px"
+ src="/images/sonarcloud/home-grey-background.svg"
+ />
+ </div>
+ </div>
+ <div
+ className="sc-functionality-container"
+ >
+ <div
+ className="sc-section"
+ >
+ <h3
+ className="sc-big-spacer-bottom text-center"
+ >
+ Functionality
+ <br />
+ that Fits Your Projects
+ </h3>
+ <div
+ className="sc-columns"
+ >
+ <div
+ className="sc-column sc-column-small"
+ >
+ <h6
+ className="sc-regular-weight spacer-bottom"
+ >
+ Easy to Use
+ </h6>
+ <p>
+ With just a few clicks you’re up and running right where your code lives. Immediate access to the latest features and enhancements.
+ </p>
+ <div
+ className="sc-separator"
+ />
+ <span
+ className="big-spacer-bottom sc-with-icon"
+ >
+ <img
+ alt=""
+ className="spacer-right"
+ src="/images/sonarcloud/scale.svg"
+ />
+
+ Scale on-demand as your projects grow.
+ </span>
+ <span
+ className="sc-with-icon"
+ >
+ <img
+ alt=""
+ className="spacer-right"
+ src="/images/sonarcloud/stop.svg"
+ />
+
+ No contracts, stop/start anytime.
+ </span>
+ </div>
+ <div
+ className="sc-column sc-column-big"
+ >
+ <img
+ alt=""
+ className="sc-rounded-img"
+ src="/images/sonarcloud/home-easy-to-use.png"
+ srcSet="/images/sonarcloud/home-easy-to-use.png 1x, /images/sonarcloud/home-easy-to-use@2x.png 2x"
+ />
+ </div>
+ </div>
+ <div
+ className="sc-columns"
+ >
+ <div
+ className="sc-column sc-column-big"
+ >
+ <img
+ alt=""
+ className="sc-rounded-img"
+ src="/images/sonarcloud/home-open-transparent.png"
+ srcSet="/images/sonarcloud/home-open-transparent.png 1x, /images/sonarcloud/home-open-transparent@2x.png 2x"
+ />
+ </div>
+ <div
+ className="sc-column sc-column-small"
+ >
+ <div>
+ <h6
+ className="sc-regular-weight spacer-bottom"
+ >
+ Open and transparent
+ </h6>
+ <p
+ className="big-spacer-bottom"
+ >
+ Project dashboards keep teams and stakeholders informed on code quality and releasability
+ </p>
+ <p>
+ Display project badges and show your communities you’re all about awesome
+ </p>
+ <img
+ alt=""
+ className="big-spacer-top"
+ src="/images/project_badges/sonarcloud-black.svg"
+ width="200px"
+ />
+ </div>
+ </div>
+ </div>
+ <div
+ className="sc-columns"
+ >
+ <div
+ className="sc-column sc-column-full"
+ >
+ <div>
+ <h6
+ className="sc-regular-weight spacer-bottom"
+ >
+ Effective Collaboration
+ </h6>
+ <p
+ className="sc-with-inline-icon"
+ >
+ Use
+ <img
+ alt="SonarCloud"
+ src="/images/sonarcloud/sonarcloud-logo-text-only.svg"
+ />
+ with your team, share best practices and have fun writing quality code!
+ </p>
+ <br />
+ <p
+ className="sc-with-inline-icon huge-spacer-bottom"
+ >
+ Connect with
+ <img
+ alt="SonarCloud"
+ src="/images/sonarcloud/sonarlint-logo.svg"
+ />
+ and get real-time notifications in your IDE as you work.
+ </p>
+ <div>
+ <img
+ alt=""
+ className="huge-spacer-bottom"
+ src="/images/sonarcloud/ide.svg"
+ width="216px"
+ />
+ </div>
+ <img
+ alt=""
+ src="/images/sonarcloud/collab.svg"
+ width="540px"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render 6`] = `
+<div
+ className="position-relative"
+>
+ <div
+ className="sc-languages-container clearfix"
+ >
+ <div
+ className="sc-section sc-columns"
+ >
+ <div
+ className="sc-column-min"
+ >
+ <h3
+ className="big-spacer-bottom"
+ >
+ SonarCloud
+ <br />
+ speaks your
+ <br />
+ language
+ </h3>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ See all supported languages
+ </a>
+ </div>
+ <ul
+ className="sc-languages-list"
+ style={
+ Object {
+ "height": undefined,
+ }
+ }
+ >
+ <li
+ key="Java"
+ >
+ <img
+ alt="Java"
+ src="/images/languages/java.svg"
+ />
+ </li>
+ <li
+ key="JavaScript"
+ >
+ <img
+ alt="JavaScript"
+ src="/images/languages/js.svg"
+ />
+ </li>
+ <li
+ key="TypeScript"
+ >
+ <img
+ alt="TypeScript"
+ src="/images/languages/ts.svg"
+ />
+ </li>
+ <li
+ key="C#"
+ >
+ <img
+ alt="C#"
+ src="/images/languages/csharp.svg"
+ />
+ </li>
+ <li
+ key="Python"
+ >
+ <img
+ alt="Python"
+ src="/images/languages/python.svg"
+ />
+ </li>
+ <li
+ key="C++"
+ >
+ <img
+ alt="C++"
+ src="/images/languages/c-c-plus-plus.svg"
+ />
+ </li>
+ <li
+ key="Go"
+ >
+ <img
+ alt="Go"
+ src="/images/languages/go.svg"
+ />
+ </li>
+ <li
+ key="Kotlin"
+ >
+ <img
+ alt="Kotlin"
+ src="/images/languages/kotlin.svg"
+ />
+ </li>
+ <li
+ key="Ruby"
+ >
+ <img
+ alt="Ruby"
+ src="/images/languages/ruby.svg"
+ />
+ </li>
+ <li>
+ <h3>
+ …
+ </h3>
+ </li>
+ </ul>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render 7`] = `
+<div
+ className="sc-section sc-columns"
+>
+ <div
+ className="sc-column sc-column-full"
+ >
+ <h5
+ className="sc-big-spacer-bottom"
+ >
+ Over 3,000 projects
+ <br />
+ continuously analyzed
+ </h5>
+ <Statistics
+ statistics={
+ Array [
+ Object {
+ "icon": "rules",
+ "text": "Static analysis rules checked",
+ "value": 9675,
+ },
+ Object {
+ "icon": "locs",
+ "text": "Lines of code analyzed",
+ "value": 20000000,
+ },
+ Object {
+ "icon": "pull-request",
+ "text": "Pull Requests decorated",
+ "value": 100675,
+ },
+ Object {
+ "icon": "open-source",
+ "text": "Open source projects inspected",
+ "value": 99675,
+ },
+ ]
+ }
+ />
+ </div>
+</div>
+`;
+
+exports[`should render 8`] = `
+<div
+ className="sc-section sc-columns"
+>
+ <div
+ className="sc-column sc-column-full"
+ >
+ <h6
+ className="big-spacer-bottom"
+ >
+ Transparency makes sense
+ <br />
+ and that’s why the trend is growing.
+ </h6>
+ <p
+ className="sc-big-spacer-bottom"
+ >
+ Check out these open source projects showing users
+ <br />
+ their commitment to quality.
+ </p>
+ <FeaturedProjects />
+ <h6
+ className="spacer-bottom"
+ >
+ Come join the fun, it’s entirely free for open source projects !
+ </h6>
+ <div
+ className="big-spacer-bottom"
+ >
+ <LoginButtons />
+ </div>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.css
new file mode 100644
index 00000000000..536051276b6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.css
@@ -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.
+ */
+
+.sc-featured-projects {
+ position: relative;
+}
+
+.sc-featured-projects-container {
+ margin: 0 40px;
+ overflow: hidden;
+ padding: 10px 0 20px;
+ max-width: 985px;
+}
+
+.sc-featured-projects-inner {
+ display: flex;
+ order: 2;
+ position: relative;
+ left: -33.33333%;
+ transform: translateX(33.33333%);
+}
+
+.sc-featured-projects-inner.reversing {
+ transform: translateX(-33.33333%);
+}
+
+.sc-featured-projects-inner.ready {
+ transform: translateX(0);
+ transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1);
+}
+
+.sc-featured-projects-inner.loading {
+ left: 50%;
+}
+
+.sc-project-card-container {
+ flex-basis: 1 0 33.33333%;
+}
+
+.sc-project-card {
+ width: 255px;
+ display: inline-block;
+ margin: 0 10px;
+ padding: 25px;
+ border: 1px solid var(--sonarcloudBlack300);
+ border-radius: 5px;
+ box-shadow: 0 1px 1px rgba(7, 7, 6, 0.1);
+ transition: all 0.1s ease-in;
+ color: inherit;
+}
+
+.sc-project-card:hover,
+.sc-project-card:focus {
+ box-shadow: 0 10px 30px rgba(7, 7, 6, 0.2);
+ transform: translateY(-4px);
+ color: inherit;
+}
+
+.sc-project-card-header {
+ padding: 0 10px 30px;
+}
+
+.sc-project-card-limited {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.sc-project-card-measures li {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ padding: 6px 0 4px;
+ border-bottom: 1px solid var(--sonarcloudBlack300);
+}
+
+.sc-project-card-measures li:last-of-type {
+ border: none;
+}
+
+.sc-project-card-measures li div {
+ display: inline-flex;
+}
+
+.sc-project-button {
+ display: block;
+ position: absolute;
+ top: 50%;
+ margin: 0;
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: var(--sonarcloudBlack300);
+ text-decoration: none;
+ cursor: pointer;
+ outline: none;
+ transition: all 0.2s ease;
+}
+
+.sc-project-button:last-child {
+ right: 0;
+}
+
+.sc-project-button:hover,
+.sc-project-button:active,
+.sc-project-button:focus {
+ color: var(--sonarcloudBlack500);
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx
new file mode 100644
index 00000000000..a4851081e10
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/FeaturedProjects.tsx
@@ -0,0 +1,308 @@
+/*
+ * 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 classNames from 'classnames';
+import CountUp from 'react-countup';
+import { throttle } from 'lodash';
+import { Link } from 'react-router';
+import { Project, requestFeaturedProjects } from '../utils';
+import ChevronLeftIcon from '../../../../components/icons-components/ChevronLeftIcon';
+import ChevronRightIcon from '../../../../components/icons-components/ChevronRightcon';
+import CoverageRating from '../../../../components/ui/CoverageRating';
+import DeferredSpinner from '../../../../components/common/DeferredSpinner';
+import DuplicationsRating from '../../../../components/ui/DuplicationsRating';
+import Level from '../../../../components/ui/Level';
+import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
+import ProjectCardLanguagesContainer from '../../../projects/components/ProjectCardLanguagesContainer';
+import Rating from '../../../../components/ui/Rating';
+import { formatMeasure } from '../../../../helpers/measures';
+import { getMetricName } from '../../../overview/utils';
+import { getProjectUrl } from '../../../../helpers/urls';
+import './FeaturedProjects.css';
+
+interface State {
+ loading: boolean;
+ reversing: boolean;
+ slides: Array<{
+ order: number;
+ project: Project;
+ }>;
+ sliding: boolean;
+ translate: number;
+ viewable: boolean;
+}
+
+export default class FeaturedProjects extends React.PureComponent<{}, State> {
+ container?: HTMLElement | null;
+ mounted = false;
+
+ constructor(props: {}) {
+ super(props);
+ this.state = {
+ loading: true,
+ reversing: false,
+ slides: [],
+ sliding: false,
+ translate: 0,
+ viewable: false
+ };
+ this.fetchProjects();
+ this.handleScroll = throttle(this.handleScroll, 10);
+ }
+
+ componentDidMount() {
+ document.addEventListener('scroll', this.handleScroll, true);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('scroll', this.handleScroll, true);
+ }
+
+ handleScroll = () => {
+ if (this.container) {
+ const rect = this.container.getBoundingClientRect();
+ const windowHeight = window.innerHeight || document.documentElement.clientHeight;
+ if (rect.top <= windowHeight && rect.top + rect.height >= 0) {
+ this.setState({ viewable: true });
+ }
+ }
+ };
+
+ fetchProjects = () => {
+ requestFeaturedProjects()
+ .then(projects =>
+ // Move the last element at the begining to properly display the carousel animations
+ this.setState({
+ loading: false,
+ slides: [projects.pop(), ...projects].map((project: Project, id) => {
+ return {
+ order: id,
+ project
+ };
+ })
+ })
+ )
+ .catch(() => {
+ /* Fail silently */
+ });
+ };
+
+ handlePrevClick = () => {
+ this.setState(({ slides }) => ({
+ reversing: true,
+ sliding: true,
+ slides: slides.map(slide => {
+ slide.order = slide.order === slides.length - 1 ? 0 : slide.order + 1;
+ return slide;
+ })
+ }));
+ setTimeout(() => {
+ this.setState({ sliding: false });
+ }, 50);
+ };
+
+ handleNextClick = () => {
+ this.setState(({ slides }) => ({
+ reversing: false,
+ sliding: true,
+ slides: slides.map(slide => {
+ slide.order = slide.order === 0 ? slides.length - 1 : slide.order - 1;
+ return slide;
+ })
+ }));
+ setTimeout(() => {
+ this.setState({ sliding: false });
+ }, 50);
+ };
+
+ render() {
+ const { loading, reversing, sliding, viewable } = this.state;
+ return (
+ <div
+ className="sc-featured-projects sc-big-spacer-bottom"
+ ref={node => (this.container = node)}>
+ {!loading && (
+ <button
+ className="js-prev sc-project-button"
+ onClick={this.handlePrevClick}
+ type="button">
+ <ChevronLeftIcon className="spacer-left" size={32} />
+ </button>
+ )}
+ <div className="sc-featured-projects-container">
+ <div
+ className={classNames('sc-featured-projects-inner', {
+ reversing,
+ ready: !sliding,
+ loading
+ })}>
+ {loading && <DeferredSpinner />}
+ {!loading &&
+ this.state.slides.map(slide => (
+ <ProjectCard
+ key={slide.project.key}
+ order={slide.order}
+ project={slide.project}
+ viewable={viewable}
+ />
+ ))}
+ </div>
+ </div>
+ {!loading && (
+ <button
+ className="js-next sc-project-button"
+ onClick={this.handleNextClick}
+ type="button">
+ <ChevronRightIcon className="spacer-left" size={32} />
+ </button>
+ )}
+ </div>
+ );
+ }
+}
+
+interface ProjectCardProps {
+ order: number;
+ project: Project;
+ viewable: boolean;
+}
+
+export function ProjectCard({ project, order, viewable }: ProjectCardProps) {
+ return (
+ <div className="sc-project-card-container" style={{ order }}>
+ <Link className="sc-project-card" to={getProjectUrl(project.key)}>
+ <div className="sc-project-card-header">
+ {project.organization && (
+ <>
+ <OrganizationAvatar
+ className="no-border big-spacer-bottom"
+ organization={project.organization}
+ />
+ <p className="sc-project-card-limited" title={project.organization.name}>
+ {project.organization.name}
+ </p>
+ </>
+ )}
+ <h5 className="sc-project-card-limited spacer-bottom" title={project.name}>
+ {project.name}
+ </h5>
+ <Level level={project.measures['alert_status']} />
+ </div>
+ <ul className="sc-project-card-measures">
+ <ProjectIssues
+ measures={project.measures}
+ metric="bugs"
+ ratingMetric="reliability_rating"
+ viewable={viewable}
+ />
+ <ProjectIssues
+ measures={project.measures}
+ metric="vulnerabilities"
+ ratingMetric="security_rating"
+ viewable={viewable}
+ />
+ <ProjectIssues
+ measures={project.measures}
+ metric="code_smells"
+ ratingMetric="sqale_rating"
+ viewable={viewable}
+ />
+ <li>
+ <span>{getMetricName('coverage')}</span>
+ <div>
+ {viewable && (
+ <CountUp
+ decimal="."
+ decimals={1}
+ delay={0}
+ duration={4}
+ end={parseFloat(project.measures['coverage'])}
+ suffix="%">
+ {(data: { countUpRef?: React.RefObject<HTMLHeadingElement> }) => (
+ <h6 className="display-inline-block big-spacer-right" ref={data.countUpRef}>
+ 0
+ </h6>
+ )}
+ </CountUp>
+ )}
+ <CoverageRating value={project.measures['coverage']} />
+ </div>
+ </li>
+ <li>
+ <span>{getMetricName('duplications')}</span>
+ <div>
+ {viewable && (
+ <CountUp
+ decimal="."
+ decimals={1}
+ delay={0}
+ duration={4}
+ end={parseFloat(project.measures['duplicated_lines_density'])}
+ suffix="%">
+ {(data: { countUpRef?: React.RefObject<HTMLHeadingElement> }) => (
+ <h6 className="display-inline-block big-spacer-right" ref={data.countUpRef}>
+ 0
+ </h6>
+ )}
+ </CountUp>
+ )}
+ <DuplicationsRating value={Number(project.measures['duplicated_lines_density'])} />
+ </div>
+ </li>
+ </ul>
+ <div className="sc-mention text-left big-spacer-top">
+ {formatMeasure(project.measures['ncloc'], 'SHORT_INT')} lines of code /{' '}
+ <ProjectCardLanguagesContainer
+ className="display-inline-block"
+ distribution={project.measures['ncloc_language_distribution']}
+ />
+ </div>
+ </Link>
+ </div>
+ );
+}
+
+interface ProjectIssues {
+ measures: { [key: string]: string };
+ metric: string;
+ ratingMetric: string;
+ viewable: boolean;
+}
+
+export function ProjectIssues({ measures, metric, ratingMetric, viewable }: ProjectIssues) {
+ const value = parseFloat(formatMeasure(measures[metric], 'SHORT_INT'));
+ return (
+ <li>
+ <span>{getMetricName(metric)}</span>
+ <div>
+ {viewable && (
+ <CountUp delay={0} duration={4} end={value}>
+ {(data: { countUpRef?: React.RefObject<HTMLHeadingElement> }) => (
+ <h6 className="display-inline-block big-spacer-right" ref={data.countUpRef}>
+ 0
+ </h6>
+ )}
+ </CountUp>
+ )}
+ <Rating value={measures[ratingMetric]} />
+ </div>
+ </li>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.css
new file mode 100644
index 00000000000..94fa0d05c9d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.css
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+.sc-footer {
+ background-color: var(--sonarcloudBlack800);
+ color: var(--sonarcloudBlack300);
+ font-size: 12px;
+}
+
+.sc-footer *:focus {
+ box-shadow: 0 0 0 3px rgba(230, 92, 0, 0.25);
+}
+
+.sc-footer-limited {
+ position: relative;
+ max-width: 1280px;
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+.sc-footer-copy {
+ padding: 20px 0;
+ border-top: 1px solid var(--sonarcloudBlack700);
+ line-height: 16px;
+ font-size: 11px;
+ text-align: center;
+}
+
+.sc-footer-copy-link {
+ text-decoration: underline;
+}
+
+.sc-footer-nav {
+ display: flex;
+ padding: 30px 0;
+}
+
+.sc-footer-nav-column {
+ margin-right: 60px;
+}
+
+.sc-footer-nav-column-title {
+ line-height: 1;
+ margin-bottom: 16px;
+ color: #fff;
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.sc-footer-link {
+ border: none;
+ color: inherit;
+}
+
+.sc-footer-link:hover {
+ color: #fff;
+}
+
+.sc-footer-link:focus {
+ color: var(--sonarcloudOrange500);
+}
+
+.sc-footer-logo {
+ position: absolute;
+ top: 30px;
+ right: 20px;
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.tsx
index e5336f98577..450239845a2 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.tsx
@@ -19,7 +19,8 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import { getBaseUrl } from '../../../helpers/urls';
+import { getBaseUrl } from '../../../../helpers/urls';
+import './Footer.css';
export default function Footer() {
return (
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Languages.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Languages.tsx
new file mode 100644
index 00000000000..3eb3307d3ea
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Languages.tsx
@@ -0,0 +1,111 @@
+/*
+ * 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 { getBaseUrl } from '../../../../helpers/urls';
+
+interface State {
+ height?: number;
+ open: boolean;
+}
+
+const LANGUAGES = [
+ { name: 'Java', file: 'java.svg' },
+ { name: 'JavaScript', file: 'js.svg' },
+ { name: 'TypeScript', file: 'ts.svg' },
+ { name: 'C#', file: 'csharp.svg' },
+ { name: 'Python', file: 'python.svg' },
+ { name: 'C++', file: 'c-c-plus-plus.svg' },
+ { name: 'Go', file: 'go.svg' },
+ { name: 'Kotlin', file: 'kotlin.svg' },
+ { name: 'Ruby', file: 'ruby.svg' },
+ { name: 'ABAP', file: 'abap.svg' },
+ { name: 'Flex', file: 'flex.svg' },
+ { name: 'HTML', file: 'html5.svg' },
+ { name: 'Objective-C', file: 'obj-c.svg' },
+ { name: 'PHP', file: 'php.svg' },
+ { name: 'Swift', file: 'swift.svg' },
+ { name: 'T-SQL', file: 't-sql.svg' },
+ { name: 'PL/SQL', file: 'pl-sql.svg' },
+ { name: 'VB', file: 'vb.svg' },
+ { name: 'XML', file: 'xml.svg' }
+];
+
+export class Languages extends React.PureComponent<{}, State> {
+ container?: HTMLElement | null;
+ state: State = { open: false };
+
+ componentDidUpdate() {
+ if (this.container && this.container.clientHeight !== this.container.scrollHeight) {
+ this.setState({ height: this.container.scrollHeight });
+ }
+ }
+
+ handleOpenClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.stopPropagation();
+ this.setState({ height: this.container ? this.container.clientHeight : undefined, open: true });
+ };
+
+ render() {
+ const { open } = this.state;
+ const languages = open ? LANGUAGES : LANGUAGES.slice(0, 9);
+
+ return (
+ <div className="position-relative">
+ <div className="sc-languages-container clearfix">
+ <div className="sc-section sc-columns">
+ <div className="sc-column-min">
+ <h3 className="big-spacer-bottom">
+ SonarCloud
+ <br />
+ speaks your
+ <br />
+ language
+ </h3>
+ {!open && (
+ <a href="#" onClick={this.handleOpenClick}>
+ See all supported languages
+ </a>
+ )}
+ </div>
+ <ul
+ className="sc-languages-list"
+ ref={node => (this.container = node)}
+ style={{ height: this.state.height }}>
+ {languages.map(language => (
+ <li key={language.name}>
+ <img
+ alt={language.name}
+ src={`${getBaseUrl()}/images/languages/${language.file}`}
+ />
+ </li>
+ ))}
+ {!open && (
+ <li>
+ <h3>…</h3>
+ </li>
+ )}
+ </ul>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.css
new file mode 100644
index 00000000000..818e074cfe4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.css
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+.sc-login-button {
+ display: inline-block;
+ background-color: var(--sonarcloudBlack100);
+ border: 1px solid var(--sonarcloudBlack300);
+ color: var(--sonarcloudBlack800);
+ margin: 10px 12px auto auto;
+ font-size: 18px;
+ font-weight: 700;
+ padding: 0 15px;
+ border-radius: 4px;
+ height: 44px;
+ line-height: 44px;
+ transition: all 0.2s ease;
+ box-shadow: 0 1px 2px rgba(7, 7, 6, 0.1);
+}
+
+.sc-login-button > img {
+ height: 25px;
+ padding-top: 10px;
+ margin-right: 12px;
+ margin-bottom: 1px;
+}
+
+.sc-login-button:hover {
+ box-shadow: 0 10px 20px rgba(7, 7, 6, 0.2);
+ transform: translate(0, -2px);
+ color: var(--sonarcloudBlack800);
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.tsx
new file mode 100644
index 00000000000..404ec582067
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/LoginButtons.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { getBaseUrl } from '../../../../helpers/urls';
+import './LoginButtons.css';
+
+export default function LoginButtons() {
+ return (
+ <div>
+ <a className="sc-login-button" href={`${getBaseUrl()}/sessions/init/github`}>
+ <img alt="" height="25" src={`${getBaseUrl()}/images/sonarcloud/github.svg`} />
+ GitHub
+ </a>
+ <a className="sc-login-button" href={`${getBaseUrl()}/sessions/init/bitbucket`}>
+ <img alt="" height="25" src={`${getBaseUrl()}/images/sonarcloud/bitbucket.svg`} />
+ Bitbucket
+ </a>
+ <a className="sc-login-button" href={`${getBaseUrl()}/sessions/init/microsoft`}>
+ <img alt="" height="25" src={`${getBaseUrl()}/images/sonarcloud/azure.svg`} />
+ Azure DevOps
+ </a>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.css
new file mode 100644
index 00000000000..2c615b2b72f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.css
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+.navbar {
+ position: fixed;
+ background-color: #fff;
+ width: 100%;
+ display: block;
+ transition: top 0.3s;
+ height: 60px;
+ line-height: 60px;
+ z-index: 10;
+ border-bottom: 1px solid var(--sonarcloudBlack300);
+ top: -100px;
+}
+
+.top-navbar {
+ height: 60px;
+ line-height: 60px;
+}
+
+.top-navbar a,
+.navbar a {
+ border-bottom: none;
+}
+
+.top-navbar img,
+.navbar img {
+ height: 50px;
+ vertical-align: middle;
+}
+
+.top-navbar ul,
+.navbar ul {
+ float: right;
+}
+
+.top-navbar ul li,
+.navbar ul li {
+ display: inline-block;
+ margin: 0 0 0 30px;
+ line-height: 30px;
+}
+
+.top-navbar ul li a,
+.navbar ul li a {
+ font-family: var(--sonarcloudFontFamily);
+ color: var(--sonarcloudBlack800);
+ font-weight: 700;
+}
+
+.navbar ul li:hover {
+ border-bottom: 2px solid var(--sonarcloudOrange500);
+}
+
+.top-navbar ul li:hover {
+ border-bottom: 2px solid #fff;
+}
+
+.navbar ul li a:hover {
+ color: var(--sonarcloudOrange500);
+}
+
+.top-navbar ul li.outline:hover,
+.navbar ul li.outline:hover {
+ border-bottom: none;
+}
+
+.navbar ul li.outline:hover a {
+ border-color: var(--sonarcloudOrange500);
+}
+
+.top-navbar ul li.outline a,
+.navbar ul li.outline a {
+ border: 1px solid var(--sonarcloudBlack800);
+ padding: 6px 10px;
+ border-radius: 4px;
+}
+
+.top-navbar ul li a {
+ color: #fff;
+}
+
+.top-navbar ul li.outline a {
+ border: 1px solid #fff;
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.tsx
new file mode 100644
index 00000000000..961954d3307
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/NavBars.tsx
@@ -0,0 +1,91 @@
+/*
+ * 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 { throttle } from 'lodash';
+import { getBaseUrl } from '../../../../helpers/urls';
+import NavBar from '../../../../components/nav/NavBar';
+import './NavBars.css';
+
+interface State {
+ top: number;
+}
+
+export class FixedNavBar extends React.PureComponent<{}, State> {
+ constructor(props: {}) {
+ super(props);
+ this.state = { top: -100 };
+ this.handleScroll = throttle(this.handleScroll, 10);
+ }
+
+ componentDidMount() {
+ document.addEventListener('scroll', this.handleScroll, true);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('scroll', this.handleScroll, true);
+ }
+
+ handleScroll = () => {
+ if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) {
+ this.setState({ top: 0 });
+ } else {
+ this.setState({ top: -100 });
+ }
+ };
+
+ render() {
+ return (
+ <NavBar height={60} top={this.state.top}>
+ <NavBarLinks />
+ </NavBar>
+ );
+ }
+}
+
+export function TopNavBar() {
+ return (
+ <div className="top-navbar">
+ <div className="navbar-limited">
+ <NavBarLinks />
+ </div>
+ </div>
+ );
+}
+
+function NavBarLinks() {
+ return (
+ <>
+ <a href={`${getBaseUrl()}/`}>
+ <img alt="SonarCloud" src={`${getBaseUrl()}/images/sonarcloud-logo-black.svg`} />
+ </a>
+ <ul>
+ <li>
+ <a href="/">Pricing</a>
+ </li>
+ <li>
+ <a href={`${getBaseUrl()}/explore/projects`}>Explore</a>
+ </li>
+ <li className="outline">
+ <a href={`${getBaseUrl()}/sessions/new`}>Log in</a>
+ </li>
+ </ul>
+ </>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Pricing.tsx
index 89d4308b71f..89d4308b71f 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/Pricing.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Pricing.tsx
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/SonarCloudPage.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQPageContainer.tsx
index 3608dbd79eb..97030a220ae 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/SonarCloudPage.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQPageContainer.tsx
@@ -21,9 +21,9 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { withRouter, WithRouterProps } from 'react-router';
import Footer from './Footer';
-import { getCurrentUser, getMyOrganizations, Store } from '../../../store/rootReducer';
-import { CurrentUser, Organization } from '../../../app/types';
-import GlobalContainer from '../../../app/components/GlobalContainer';
+import { getCurrentUser, getMyOrganizations, Store } from '../../../../store/rootReducer';
+import { CurrentUser, Organization } from '../../../../app/types';
+import GlobalContainer from '../../../../app/components/GlobalContainer';
interface StateProps {
currentUser: CurrentUser;
@@ -36,7 +36,7 @@ interface OwnProps {
type Props = StateProps & WithRouterProps & OwnProps;
-class SonarCloudPage extends React.Component<Props> {
+class SQPageContainer extends React.Component<Props> {
componentDidMount() {
if (document.documentElement) {
document.documentElement.classList.add('white-page');
@@ -66,4 +66,4 @@ const mapStateToProps = (state: Store) => ({
userOrganizations: getMyOrganizations(state)
});
-export default withRouter<OwnProps>(connect(mapStateToProps)(SonarCloudPage));
+export default withRouter<OwnProps>(connect(mapStateToProps)(SQPageContainer));
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/SQStartUsing.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQStartUsing.tsx
index f7d6693df92..f7d6693df92 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/SQStartUsing.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQStartUsing.tsx
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/SQTopNav.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQTopNav.tsx
index 7c0a17b8b79..7c0a17b8b79 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/SQTopNav.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/SQTopNav.tsx
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/StartUsing.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/StartUsing.tsx
index c640474aa76..b70e15a69fc 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/StartUsing.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/StartUsing.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import ChevronRightIcon from '../../../components/icons-components/ChevronRightcon';
+import ChevronRightIcon from '../../../../components/icons-components/ChevronRightcon';
export default function StartUsing() {
return (
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.css
new file mode 100644
index 00000000000..1b84bce642f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.css
@@ -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.
+ */
+
+.sc-stats {
+ display: flex;
+ justify-content: space-around;
+}
+
+.sc-stat-card {
+ flex: 0 0 240px;
+ display: flex;
+ align-items: center;
+ border: 1px solid var(--sonarcloudBlack300);
+ border-radius: 3px;
+}
+
+.sc-stat-icon {
+ background-color: var(--sonarcloudBlack200);
+ padding: 32px 16px;
+}
+
+.sc-stat-content {
+ padding: 0 16px;
+ text-align: left;
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx
new file mode 100644
index 00000000000..2570e8a7b11
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx
@@ -0,0 +1,105 @@
+/*
+ * 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 { throttle } from 'lodash';
+import CountUp from 'react-countup';
+import { formatMeasure } from '../../../../helpers/measures';
+import { getBaseUrl } from '../../../../helpers/urls';
+import './Statistics.css';
+
+interface Statistic {
+ icon: string;
+ text: string;
+ value: number;
+}
+
+interface Props {
+ statistics: Statistic[];
+}
+
+export default function Statistics({ statistics }: Props) {
+ return (
+ <div className="sc-stats">
+ {statistics.map(stat => (
+ <StatisticCard key={stat.icon} statistic={stat} />
+ ))}
+ </div>
+ );
+}
+
+interface StatisticCardProps {
+ statistic: Statistic;
+}
+
+interface StatisticCardState {
+ viewable: boolean;
+}
+
+export class StatisticCard extends React.PureComponent<StatisticCardProps, StatisticCardState> {
+ container?: HTMLElement | null;
+
+ constructor(props: StatisticCardProps) {
+ super(props);
+ this.state = { viewable: false };
+ this.handleScroll = throttle(this.handleScroll, 10);
+ }
+
+ componentDidMount() {
+ document.addEventListener('scroll', this.handleScroll, true);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('scroll', this.handleScroll, true);
+ }
+
+ handleScroll = () => {
+ if (this.container) {
+ const rect = this.container.getBoundingClientRect();
+ const windowHeight = window.innerHeight || document.documentElement.clientHeight;
+ if (rect.top <= windowHeight && rect.top + rect.height >= 0) {
+ this.setState({ viewable: true });
+ }
+ }
+ };
+
+ render() {
+ const { statistic } = this.props;
+ const formattedString = formatMeasure(statistic.value, 'SHORT_INT');
+ const value = parseFloat(formattedString.slice(0, -1));
+ const suffix = formattedString.substr(-1);
+ return (
+ <div className="sc-stat-card" ref={node => (this.container = node)}>
+ <div className="sc-stat-icon">
+ <img alt="" height={32} src={`${getBaseUrl()}/images/sonarcloud/${statistic.icon}.svg`} />
+ </div>
+ <div className="sc-stat-content">
+ {this.state.viewable && (
+ <CountUp delay={0} duration={4} end={value} suffix={suffix}>
+ {(data: { countUpRef?: React.RefObject<HTMLHeadingElement> }) => (
+ <h5 ref={data.countUpRef}>0</h5>
+ )}
+ </CountUp>
+ )}
+ <span className="sc-medium-weight">{statistic.text}</span>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/FeaturedProjects-test.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/FeaturedProjects-test.tsx
new file mode 100644
index 00000000000..eee9d02ca01
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/FeaturedProjects-test.tsx
@@ -0,0 +1,107 @@
+/*
+ * 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 FeaturedProjects, { ProjectCard, ProjectIssues } from '../FeaturedProjects';
+import { requestFeaturedProjects } from '../../utils';
+import { click, waitAndUpdate } from '../../../../../helpers/testUtils';
+
+jest.mock('../../utils', () => ({
+ requestFeaturedProjects: jest.fn(() =>
+ Promise.resolve([
+ {
+ key: 'foo',
+ measures: { coverage: '20', foo: '15' },
+ name: 'Foo',
+ organization: { name: 'Foo', avatar: '' }
+ },
+ {
+ key: 'bar',
+ measures: { bar: '20', foo: '15' },
+ name: 'Bar',
+ organization: { name: 'Bar', avatar: '' }
+ },
+ {
+ key: 'baz',
+ measures: { bar: '20', foo: '15' },
+ name: 'Baz',
+ organization: { name: 'Baz', avatar: '' }
+ },
+ {
+ key: 'foobar',
+ measures: { bar: '20', foo: '15' },
+ name: 'Foobar',
+ organization: { name: 'Foobar', avatar: '' }
+ }
+ ])
+ )
+}));
+
+const PROJECT = {
+ key: 'foo',
+ measures: { bar: '20', foo: '15' },
+ name: 'Foo',
+ organization: { name: 'Foo', avatar: '' }
+};
+
+beforeEach(() => {
+ (requestFeaturedProjects as jest.Mock<any>).mockClear();
+});
+
+it('should render ProjectIssues correctly', () => {
+ expect(
+ shallow(
+ <ProjectIssues
+ measures={{ bar: '20', foo: '15' }}
+ metric="foo"
+ ratingMetric="bar"
+ viewable={false}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should render ProjectCard correctly', () => {
+ expect(shallow(<ProjectCard order={1} project={PROJECT} viewable={false} />)).toMatchSnapshot();
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallow(<FeaturedProjects />);
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should cycle through projects', async () => {
+ const wrapper = shallow(<FeaturedProjects />);
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().slides.map((slide: any) => slide.order)).toEqual([0, 1, 2, 3]);
+
+ click(wrapper.find('.js-next'));
+ expect(wrapper.state().slides.map((slide: any) => slide.order)).toEqual([3, 0, 1, 2]);
+
+ click(wrapper.find('.js-next'));
+ click(wrapper.find('.js-next'));
+ expect(wrapper.state().slides.map((slide: any) => slide.order)).toEqual([1, 2, 3, 0]);
+
+ click(wrapper.find('.js-prev'));
+ click(wrapper.find('.js-prev'));
+ expect(wrapper.state().slides.map((slide: any) => slide.order)).toEqual([3, 0, 1, 2]);
+});
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Footer-test.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Footer-test.tsx
index 0d1eb668efa..0d1eb668efa 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/__tests__/Footer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Footer-test.tsx
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/LoginButtons-test.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/LoginButtons-test.tsx
new file mode 100644
index 00000000000..fcbda53c751
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/LoginButtons-test.tsx
@@ -0,0 +1,26 @@
+/*
+ * 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 LoginButtons from '../LoginButtons';
+
+it('should render', () => {
+ expect(shallow(<LoginButtons />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx
new file mode 100644
index 00000000000..21ef75b6d22
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx
@@ -0,0 +1,36 @@
+/*
+ * 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 Statistics, { StatisticCard } from '../Statistics';
+
+const STATISTICS = {
+ icon: 'stat-icon',
+ text: 'my stat',
+ value: 26666
+};
+
+it('should render', () => {
+ expect(shallow(<Statistics statistics={[STATISTICS]} />)).toMatchSnapshot();
+});
+
+it('should render StatisticCard', () => {
+ expect(shallow(<StatisticCard statistic={STATISTICS} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/FeaturedProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/FeaturedProjects-test.tsx.snap
new file mode 100644
index 00000000000..dd38ee80c5c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/FeaturedProjects-test.tsx.snap
@@ -0,0 +1,244 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render ProjectCard correctly 1`] = `
+<div
+ className="sc-project-card-container"
+ style={
+ Object {
+ "order": 1,
+ }
+ }
+>
+ <Link
+ className="sc-project-card"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": undefined,
+ "id": "foo",
+ },
+ }
+ }
+ >
+ <div
+ className="sc-project-card-header"
+ >
+ <React.Fragment>
+ <OrganizationAvatar
+ className="no-border big-spacer-bottom"
+ organization={
+ Object {
+ "avatar": "",
+ "name": "Foo",
+ }
+ }
+ />
+ <p
+ className="sc-project-card-limited"
+ title="Foo"
+ >
+ Foo
+ </p>
+ </React.Fragment>
+ <h5
+ className="sc-project-card-limited spacer-bottom"
+ title="Foo"
+ >
+ Foo
+ </h5>
+ <Level />
+ </div>
+ <ul
+ className="sc-project-card-measures"
+ >
+ <ProjectIssues
+ measures={
+ Object {
+ "bar": "20",
+ "foo": "15",
+ }
+ }
+ metric="bugs"
+ ratingMetric="reliability_rating"
+ viewable={false}
+ />
+ <ProjectIssues
+ measures={
+ Object {
+ "bar": "20",
+ "foo": "15",
+ }
+ }
+ metric="vulnerabilities"
+ ratingMetric="security_rating"
+ viewable={false}
+ />
+ <ProjectIssues
+ measures={
+ Object {
+ "bar": "20",
+ "foo": "15",
+ }
+ }
+ metric="code_smells"
+ ratingMetric="sqale_rating"
+ viewable={false}
+ />
+ <li>
+ <span>
+ overview.metric.coverage
+ </span>
+ <div>
+ <CoverageRating />
+ </div>
+ </li>
+ <li>
+ <span>
+ overview.metric.duplications
+ </span>
+ <div>
+ <DuplicationsRating
+ value={NaN}
+ />
+ </div>
+ </li>
+ </ul>
+ <div
+ className="sc-mention text-left big-spacer-top"
+ >
+ lines of code /
+
+ <Connect(ProjectCardLanguages)
+ className="display-inline-block"
+ />
+ </div>
+ </Link>
+</div>
+`;
+
+exports[`should render ProjectIssues correctly 1`] = `
+<li>
+ <span>
+ overview.metric.foo
+ </span>
+ <div>
+ <Rating
+ value="20"
+ />
+ </div>
+</li>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+ className="sc-featured-projects sc-big-spacer-bottom"
+>
+ <button
+ className="js-prev sc-project-button"
+ onClick={[Function]}
+ type="button"
+ >
+ <ChevronLeftIcon
+ className="spacer-left"
+ size={32}
+ />
+ </button>
+ <div
+ className="sc-featured-projects-container"
+ >
+ <div
+ className="sc-featured-projects-inner ready"
+ >
+ <ProjectCard
+ key="foobar"
+ order={0}
+ project={
+ Object {
+ "key": "foobar",
+ "measures": Object {
+ "bar": "20",
+ "foo": "15",
+ },
+ "name": "Foobar",
+ "organization": Object {
+ "avatar": "",
+ "name": "Foobar",
+ },
+ }
+ }
+ viewable={false}
+ />
+ <ProjectCard
+ key="foo"
+ order={1}
+ project={
+ Object {
+ "key": "foo",
+ "measures": Object {
+ "coverage": "20",
+ "foo": "15",
+ },
+ "name": "Foo",
+ "organization": Object {
+ "avatar": "",
+ "name": "Foo",
+ },
+ }
+ }
+ viewable={false}
+ />
+ <ProjectCard
+ key="bar"
+ order={2}
+ project={
+ Object {
+ "key": "bar",
+ "measures": Object {
+ "bar": "20",
+ "foo": "15",
+ },
+ "name": "Bar",
+ "organization": Object {
+ "avatar": "",
+ "name": "Bar",
+ },
+ }
+ }
+ viewable={false}
+ />
+ <ProjectCard
+ key="baz"
+ order={3}
+ project={
+ Object {
+ "key": "baz",
+ "measures": Object {
+ "bar": "20",
+ "foo": "15",
+ },
+ "name": "Baz",
+ "organization": Object {
+ "avatar": "",
+ "name": "Baz",
+ },
+ }
+ }
+ viewable={false}
+ />
+ </div>
+ </div>
+ <button
+ className="js-next sc-project-button"
+ onClick={[Function]}
+ type="button"
+ >
+ <ChevronRightIcon
+ className="spacer-left"
+ size={32}
+ />
+ </button>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/LoginButtons-test.tsx.snap b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/LoginButtons-test.tsx.snap
new file mode 100644
index 00000000000..4101f390579
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/LoginButtons-test.tsx.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div>
+ <a
+ className="sc-login-button"
+ href="/sessions/init/github"
+ >
+ <img
+ alt=""
+ height="25"
+ src="/images/sonarcloud/github.svg"
+ />
+ GitHub
+ </a>
+ <a
+ className="sc-login-button"
+ href="/sessions/init/bitbucket"
+ >
+ <img
+ alt=""
+ height="25"
+ src="/images/sonarcloud/bitbucket.svg"
+ />
+ Bitbucket
+ </a>
+ <a
+ className="sc-login-button"
+ href="/sessions/init/microsoft"
+ >
+ <img
+ alt=""
+ height="25"
+ src="/images/sonarcloud/azure.svg"
+ />
+ Azure DevOps
+ </a>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap
new file mode 100644
index 00000000000..9c31c201c67
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="sc-stats"
+>
+ <StatisticCard
+ key="stat-icon"
+ statistic={
+ Object {
+ "icon": "stat-icon",
+ "text": "my stat",
+ "value": 26666,
+ }
+ }
+ />
+</div>
+`;
+
+exports[`should render StatisticCard 1`] = `
+<div
+ className="sc-stat-card"
+>
+ <div
+ className="sc-stat-icon"
+ >
+ <img
+ alt=""
+ height={32}
+ src="/images/sonarcloud/stat-icon.svg"
+ />
+ </div>
+ <div
+ className="sc-stat-content"
+ >
+ <span
+ className="sc-medium-weight"
+ >
+ my stat
+ </span>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/new_style.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/new_style.css
new file mode 100644
index 00000000000..84848f99f88
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/new_style.css
@@ -0,0 +1,250 @@
+/*
+ * 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 url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700');
+
+.sc-page,
+.sc-page span,
+.sc-page li,
+.sc-page p {
+ font-family: var(--baseFontFamily);
+ font-size: 15px;
+}
+
+.sc-page h1,
+.sc-page h2,
+.sc-page h3,
+.sc-page h4,
+.sc-page h5 {
+ font-family: var(--sonarcloudFontFamily);
+ color: var(--sonarcloudBlack800);
+ font-weight: 700;
+}
+
+.sc-page h1 {
+ line-height: 62px;
+ font-size: 60px;
+}
+
+.sc-page h2 {
+ line-height: 50px;
+ font-size: 44px;
+}
+
+.sc-page h3 {
+ line-height: 42px;
+ font-size: 36px;
+}
+
+.sc-page h4 {
+ line-height: 38px;
+ font-size: 32px;
+}
+
+.sc-page h5 {
+ line-height: 32px;
+ font-size: 26px;
+}
+
+.sc-page h6 {
+ line-height: 26px;
+ font-size: 21px;
+}
+
+.sc-section {
+ position: relative;
+ max-width: 1040px;
+ padding: 0px 20px;
+ margin-left: auto;
+ margin-right: auto;
+ overflow: hidden;
+}
+
+.sc-mention,
+.sc-mention span {
+ font-size: 12px;
+ font-style: italic;
+}
+
+.sc-light-weight {
+ font-weight: 300 !important;
+}
+
+.sc-regular-weight {
+ font-weight: 400 !important;
+}
+
+.sc-medium-weight {
+ font-weight: 500 !important;
+}
+
+.sc-bold-weight {
+ font-weight: 700 !important;
+}
+
+.sc-columns {
+ display: flex;
+ justify-content: space-around;
+ margin: 0 auto 80px;
+}
+
+.sc-column-half {
+ width: 50%;
+}
+
+.sc-column-min {
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.sc-column-full {
+ flex-grow: 1;
+ text-align: center;
+}
+
+.sc-column-small {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ max-width: 38%;
+}
+
+.sc-column-big {
+ max-width: 52%;
+}
+
+.sc-spacer-bottom {
+ margin-bottom: 20px;
+}
+
+.sc-big-spacer-bottom {
+ margin-bottom: 50px;
+}
+
+.sc-title-orange {
+ color: var(--sonarcloudOrange500) !important;
+}
+
+.sc-separator {
+ margin: 20px 0;
+ width: 42px;
+ border-top: 1px solid var(--sonarcloudBlack300);
+}
+
+.sc-rounded-img {
+ border-radius: 3px;
+ box-shadow: 0 3px 40px rgba(0, 0, 0, 0.05);
+}
+
+.sc-header-background {
+ position: absolute;
+ width: 100%;
+ min-width: 1080px;
+ z-index: 0;
+}
+
+.sc-functionality-background {
+ position: relative;
+ width: 100%;
+ min-width: 1080px;
+ margin-top: -150px;
+ z-index: 0;
+}
+
+.sc-background-start {
+ position: absolute;
+ background-color: #fdc300;
+ height: 5px;
+ width: 50%;
+ left: 0;
+ z-index: 0;
+}
+
+.sc-background-end {
+ position: absolute;
+ background-color: var(--sonarcloudOrange500);
+ height: 5px;
+ width: 50%;
+ right: 0;
+ z-index: 0;
+}
+
+.sc-background-center {
+ display: flex;
+ justify-content: center;
+ overflow: hidden;
+}
+
+.sc-background-center img {
+ flex-grow: 0;
+ flex-shrink: 0;
+ z-index: 10;
+}
+
+.sc-with-icon,
+.sc-with-inline-icon {
+ display: inline-flex;
+ align-items: center;
+ white-space: nowrap;
+}
+
+.sc-with-icon img {
+ height: 14px;
+}
+
+.sc-with-inline-icon img {
+ max-height: 18px;
+ padding: 0 4px;
+}
+
+.sc-functionality-container {
+ background-color: var(--sonarcloudBlack200);
+}
+
+.sc-languages-container {
+ padding: 90px 0 0;
+ background-color: var(--sonarcloudBlack800);
+ margin-bottom: 90px;
+}
+
+.sc-languages-list {
+ display: flex;
+ flex-wrap: wrap;
+ margin-left: 40px;
+ margin-bottom: -40px;
+ transition: height 0.3s ease;
+}
+
+.sc-languages-list > li {
+ display: inline-block;
+ text-align: center;
+ margin: 0 0 45px 80px;
+ height: 60px;
+ width: 60px;
+}
+
+.sc-languages-list > li img {
+ height: 100%;
+ width: 100%;
+}
+
+.sc-languages-container h3,
+.sc-languages-container a {
+ color: #fff;
+}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css
index 7b5ad4ec559..6a5737a9102 100644
--- a/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css
@@ -27,40 +27,10 @@
font-family: var(--systemFontFamily);
}
-.sc-page *:focus,
-.sc-footer *:focus {
+.sc-page *:focus {
box-shadow: 0 0 0 3px rgba(230, 92, 0, 0.25);
}
-.sc-white-button {
- display: inline-block;
- background-color: var(--sonarcloudBlack100);
- border: 1px solid var(--sonarcloudBlack300);
- margin: 10px auto;
- font-size: 18px;
- font-weight: 500;
- padding-right: 15px;
- color: var(--sonarcloudBlack800);
- border-radius: 4px;
- height: 50px;
- line-height: 50px;
- padding-left: 15px;
- transition: all 0.1s ease-in;
- box-shadow: 0 1px 2px rgba(7, 7, 6, 0.1);
-}
-
-.sc-white-button img {
- height: 25px;
- padding-top: 12px;
- margin-bottom: 5px;
-}
-
-.sc-white-button:hover {
- box-shadow: 0 10px 20px rgba(7, 7, 6, 0.2);
- transform: translate(0, -2px);
- color: var(--sonarcloudBlack800);
-}
-
.sc-black-button,
.sc-orange-button {
display: inline-flex;
@@ -86,12 +56,12 @@
}
.sc-orange-button {
- background-color: var(--sonarcloudOrange);
+ background-color: var(--sonarcloudOrange500);
}
.sc-orange-button:hover,
.sc-orange-button:focus {
- background-color: var(--sonarcloudOrangeDark);
+ background-color: var(--sonarcloudOrange700);
color: #fff;
}
@@ -149,14 +119,14 @@
.sc-feature-link {
margin-top: 16px;
- color: var(--sonarcloudOrange) !important;
+ color: var(--sonarcloudOrange500) !important;
font-size: 16px;
font-weight: 400;
}
.sc-feature-link:hover,
.sc-feature-link:focus {
- color: var(--sonarcloudOrangeDark) !important;
+ color: var(--sonarcloudOrange700) !important;
}
.sc-narrow-container {
@@ -185,7 +155,7 @@
.sc-pricing-title {
line-height: 34px;
margin-bottom: 20px;
- color: var(--sonarcloudOrange);
+ color: var(--sonarcloudOrange500);
font-size: 24px;
font-weight: 400;
}
@@ -220,7 +190,7 @@
}
.sc-arrow-link:focus {
- color: var(--sonarcloudOrange);
+ color: var(--sonarcloudOrange500);
}
.sc-arrow-link:hover::after {
@@ -236,7 +206,7 @@
.sc-start:hover,
.sc-start:focus {
- background-color: var(--sonarcloudOrangeDark);
+ background-color: var(--sonarcloudOrange700);
color: #fff;
}
@@ -247,9 +217,9 @@
line-height: 33px;
padding: 0 16px;
border-radius: 5px;
- border: 1px solid var(--sonarcloudOrange);
+ border: 1px solid var(--sonarcloudOrange500);
box-sizing: border-box;
- color: var(--sonarcloudOrange);
+ color: var(--sonarcloudOrange500);
font-size: 16px;
font-weight: 700;
transition: box-shadow 0.3s ease;
@@ -257,7 +227,7 @@
.sc-browse:hover,
.sc-browse:focus {
- background-color: var(--sonarcloudOrange);
+ background-color: var(--sonarcloudOrange500);
color: #fff;
}
@@ -282,72 +252,8 @@
.sc-news-link:hover,
.sc-news-link:focus {
- border-bottom-color: var(--sonarcloudOrange);
- color: var(--sonarcloudOrange);
-}
-
-.sc-footer {
- background-color: var(--sonarcloudBlack800);
- color: var(--sonarcloudBlack300);
- font-size: 12px;
-}
-
-.sc-footer-limited {
- position: relative;
- max-width: 1280px;
- margin-left: auto;
- margin-right: auto;
- padding-left: 20px;
- padding-right: 20px;
-}
-
-.sc-footer-copy {
- padding: 20px 0;
- border-top: 1px solid var(--sonarcloudBlack700);
- line-height: 16px;
- font-size: 11px;
- text-align: center;
-}
-
-.sc-footer-copy-link {
- text-decoration: underline;
-}
-
-.sc-footer-nav {
- display: flex;
- padding: 30px 0;
-}
-
-.sc-footer-nav-column {
- margin-right: 60px;
-}
-
-.sc-footer-nav-column-title {
- line-height: 1;
- margin-bottom: 16px;
- color: #fff;
- font-size: 12px;
- font-weight: 700;
- text-transform: uppercase;
-}
-
-.sc-footer-link {
- border: none;
- color: inherit;
-}
-
-.sc-footer-link:hover {
- color: #fff;
-}
-
-.sc-footer-link:focus {
- color: var(--sonarcloudOrange);
-}
-
-.sc-footer-logo {
- position: absolute;
- top: 30px;
- right: 20px;
+ border-bottom-color: var(--sonarcloudOrange500);
+ color: var(--sonarcloudOrange500);
}
.sc-sq-jumbotron {
@@ -359,7 +265,7 @@
}
.sc-sq-jumbotron-left {
- min-width: 500px;
+ min-width: 540px;
}
.sc-sq-jumbotron-right {
@@ -382,7 +288,7 @@
}
.sc-sq-jumbotron-title-orange {
- color: var(--sonarcloudOrange);
+ color: var(--sonarcloudOrange500);
}
.sc-sq-jumbotron-login {
@@ -390,16 +296,6 @@
font-size: 18px;
}
-.sc-sq-login-button {
- margin-top: 16px;
- margin-right: 16px;
-}
-
-.sc-sq-login-button > img {
- margin-right: 12px;
- margin-bottom: 1px;
-}
-
.sc-sq-header2 {
margin: 80px 0 40px;
line-height: 45px;
@@ -463,8 +359,8 @@
.sc-bottom-note-link:hover,
.sc-bottom-note-link:focus {
- border-bottom-color: var(--sonarcloudOrange);
- color: var(--sonarcloudOrange);
+ border-bottom-color: var(--sonarcloudOrange500);
+ color: var(--sonarcloudOrange500);
}
.sc-sq-page {
@@ -493,8 +389,8 @@
a.sc-top-nav-link:hover,
a.sc-top-nav-link:focus {
- border-bottom-color: var(--sonarcloudOrange);
- color: var(--sonarcloudOrange);
+ border-bottom-color: var(--sonarcloudOrange500);
+ color: var(--sonarcloudOrange500);
}
.sc-top-nav-active {
@@ -524,13 +420,13 @@ a.sc-top-nav-link:focus {
.sc-child-lead-link {
border-bottom-color: rgba(255, 102, 0, 0.2);
- color: var(--sonarcloudOrange);
+ color: var(--sonarcloudOrange500);
}
.sc-child-lead-link:hover,
.sc-child-lead-link:focus {
- border-bottom-color: var(--sonarcloudOrangeDark);
- color: var(--sonarcloudOrangeDark);
+ border-bottom-color: var(--sonarcloudOrange700);
+ color: var(--sonarcloudOrange700);
}
.sc-child-feature {
@@ -712,8 +608,8 @@ a.sc-top-nav-link:focus {
.sc-contact-page button {
padding: 6px 12px;
background: none;
- color: var(--sonarcloudOrange);
- border: 1px solid var(--sonarcloudOrange);
+ color: var(--sonarcloudOrange500);
+ border: 1px solid var(--sonarcloudOrange500);
border-radius: 5px;
font-size: 12px;
font-weight: 400;
@@ -722,18 +618,18 @@ a.sc-top-nav-link:focus {
.sc-contact-page button:hover {
color: white;
- background-color: var(--sonarcloudOrange);
+ background-color: var(--sonarcloudOrange500);
}
.sc-contact-page button:focus {
color: white;
- background-color: var(--sonarcloudOrange);
+ background-color: var(--sonarcloudOrange500);
outline: none;
}
.sc-contact-page button:active {
color: white;
- background-color: var(--sonarcloudOrange);
+ background-color: var(--sonarcloudOrange500);
}
.sc-contact-page .form-group {
@@ -750,7 +646,7 @@ a.sc-top-nav-link:focus {
}
.sc-contact-page a {
- color: var(--sonarcloudOrange) !important;
+ color: var(--sonarcloudOrange500) !important;
border: none;
}
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/utils.ts b/server/sonar-web/src/main/js/apps/about/sonarcloud/utils.ts
new file mode 100644
index 00000000000..ef08c461e78
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/utils.ts
@@ -0,0 +1,134 @@
+/*
+ * 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 Project {
+ key: string;
+ measures: { [key: string]: string };
+ name: string;
+ organization?: { avatar: string; name: string };
+}
+
+// TODO Get this from an external source
+const PROJECTS = [
+ {
+ key: 'org.hibernate.search.v6poc:hibernate-search-parent',
+ name: 'Hibernate Search 6 POC Parent POM',
+ measures: {
+ bugs: '39',
+ reliability_rating: '1',
+ vulnerabilities: '5',
+ security_rating: '2',
+ code_smells: '1',
+ sqale_rating: '1.0',
+ coverage: '89.9',
+ duplicated_lines_density: '1.1',
+ ncloc: '336726',
+ ncloc_language_distribution: 'java=175123;js=26382'
+ },
+ organization: {
+ avatar: '',
+ name: 'Hibernate'
+ }
+ },
+ {
+ key: 'com.github.sdorra:web-resources',
+ name: 'Web Resources',
+ measures: {
+ bugs: '39',
+ reliability_rating: '1',
+ vulnerabilities: '5',
+ security_rating: '2',
+ code_smells: '1',
+ sqale_rating: '1.0',
+ coverage: '89.9',
+ duplicated_lines_density: '1.1',
+ ncloc: '336726',
+ ncloc_language_distribution: 'java=175123;js=26382'
+ },
+ organization: {
+ avatar: '',
+ name: 'sdorra-github'
+ }
+ },
+ {
+ key: 'vyos:vyos-1x',
+ name: 'vyos-1x',
+ measures: {
+ bugs: '39',
+ reliability_rating: '1',
+ vulnerabilities: '5',
+ security_rating: '2',
+ code_smells: '1',
+ sqale_rating: '1.0',
+ coverage: '89.9',
+ duplicated_lines_density: '1.1',
+ ncloc: '336726',
+ ncloc_language_distribution: 'java=175123;js=26382'
+ },
+ organization: {
+ avatar: '',
+ name: 'vyos'
+ }
+ },
+ {
+ key: 'sonarlint-visualstudio',
+ name: 'SonarLint for Visual Studio',
+ measures: {
+ bugs: '39',
+ reliability_rating: '1',
+ vulnerabilities: '5',
+ security_rating: '2',
+ code_smells: '1',
+ sqale_rating: '1.0',
+ coverage: '89.9',
+ duplicated_lines_density: '1.1',
+ ncloc: '336726',
+ ncloc_language_distribution: 'java=175123;js=26382'
+ },
+ organization: {
+ avatar: '',
+ name: 'sonarsource'
+ }
+ },
+ {
+ key: 'sample-1',
+ name: 'Dummy project',
+ measures: {
+ bugs: '39',
+ reliability_rating: '1',
+ vulnerabilities: '5',
+ security_rating: '2',
+ code_smells: '1',
+ sqale_rating: '1.0',
+ coverage: '89.9',
+ duplicated_lines_density: '1.1',
+ ncloc: '336726',
+ ncloc_language_distribution: 'java=175123;js=26382'
+ },
+ organization: {
+ avatar: '',
+ name: 'sonarsource'
+ }
+ }
+];
+
+export function requestFeaturedProjects(): Promise<Project[]> {
+ return Promise.resolve(PROJECTS);
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx
index 507c75ef39c..790fd8ec979 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx
@@ -24,11 +24,12 @@ import { translate } from '../../../helpers/l10n';
import { Languages } from '../../../app/types';
interface Props {
+ className?: string;
distribution?: string;
languages: Languages;
}
-export default function ProjectCardLanguages({ distribution, languages }: Props) {
+export default function ProjectCardLanguages({ className, distribution, languages }: Props) {
if (distribution === undefined) {
return null;
}
@@ -38,22 +39,25 @@ export default function ProjectCardLanguages({ distribution, languages }: Props)
getLanguageName(languages, l[0])
);
- const tooltip = (
- <span>
- {finalLanguages.map(language => (
- <span key={language}>
- {language}
- <br />
- </span>
- ))}
- </span>
- );
-
const languagesText =
finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : '');
+ let tooltip;
+ if (finalLanguages.length > 2) {
+ tooltip = (
+ <span>
+ {finalLanguages.map(language => (
+ <span key={language}>
+ {language}
+ <br />
+ </span>
+ ))}
+ </span>
+ );
+ }
+
return (
- <div className="project-card-languages">
+ <div className={className}>
<Tooltip overlay={tooltip}>
<span>{languagesText}</span>
</Tooltip>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
index fce96f6e707..88fbee8de55 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
@@ -144,6 +144,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
</div>
<div className="project-card-measure-label">
<ProjectCardLanguagesContainer
+ className="project-card-languages"
distribution={measures['ncloc_language_distribution']}
/>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
index a9392317623..30fdc18ecc1 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
@@ -1,23 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`handles unknown languages 1`] = `
-<div
- className="project-card-languages"
->
- <Tooltip
- overlay={
- <span>
- <span>
- cpp
- <br />
- </span>
- <span>
- Java
- <br />
- </span>
- </span>
- }
- >
+<div>
+ <Tooltip>
<span>
cpp, Java
</span>
@@ -26,23 +11,8 @@ exports[`handles unknown languages 1`] = `
`;
exports[`handles unknown languages 2`] = `
-<div
- className="project-card-languages"
->
- <Tooltip
- overlay={
- <span>
- <span>
- unknown
- <br />
- </span>
- <span>
- Java
- <br />
- </span>
- </span>
- }
- >
+<div>
+ <Tooltip>
<span>
unknown, Java
</span>
@@ -51,23 +21,8 @@ exports[`handles unknown languages 2`] = `
`;
exports[`renders 1`] = `
-<div
- className="project-card-languages"
->
- <Tooltip
- overlay={
- <span>
- <span>
- Java
- <br />
- </span>
- <span>
- JavaScript
- <br />
- </span>
- </span>
- }
- >
+<div>
+ <Tooltip>
<span>
Java, JavaScript
</span>
@@ -76,23 +31,8 @@ exports[`renders 1`] = `
`;
exports[`sorts languages 1`] = `
-<div
- className="project-card-languages"
->
- <Tooltip
- overlay={
- <span>
- <span>
- JavaScript
- <br />
- </span>
- <span>
- Java
- <br />
- </span>
- </span>
- }
- >
+<div>
+ <Tooltip>
<span>
JavaScript, Java
</span>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
index cc78296268a..e7f739d048a 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
@@ -230,7 +230,9 @@ exports[`should render correctly with all data 1`] = `
<div
className="project-card-measure-label"
>
- <Connect(ProjectCardLanguages) />
+ <Connect(ProjectCardLanguages)
+ className="project-card-languages"
+ />
</div>
</div>
</div>
@@ -272,7 +274,9 @@ exports[`should render ncloc correctly 1`] = `
<div
className="project-card-measure-label"
>
- <Connect(ProjectCardLanguages) />
+ <Connect(ProjectCardLanguages)
+ className="project-card-languages"
+ />
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts
index 6c515d4a1df..b1ebb722c26 100644
--- a/server/sonar-web/src/main/js/apps/projects/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projects/utils.ts
@@ -248,7 +248,7 @@ function convertToQueryData(
return data;
}
-function fetchProjectMeasures(projects: Array<{ key: string }>, query: Query) {
+export function fetchProjectMeasures(projects: Array<{ key: string }>, query: Query) {
if (!projects.length) {
return Promise.resolve([]);
}
@@ -258,7 +258,7 @@ function fetchProjectMeasures(projects: Array<{ key: string }>, query: Query) {
return getMeasuresForProjects(projectKeys, metrics);
}
-function fetchProjectOrganizations(
+export function fetchProjectOrganizations(
projects: Array<{ organization: string }>,
organization: Organization | undefined
) {
diff --git a/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css
index f3b78ef71c5..ae52179a1a7 100644
--- a/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css
+++ b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.css
@@ -27,7 +27,7 @@
border: 1px solid var(--barBorderColor);
}
-.navbar-context-avatar.is-empty {
+.navbar-context-avatar.no-border {
border: none;
}
diff --git a/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx
index f9f5b25d8e3..c4ad360e729 100644
--- a/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx
+++ b/server/sonar-web/src/main/js/components/common/OrganizationAvatar.tsx
@@ -23,6 +23,7 @@ import GenericAvatar from '../ui/GenericAvatar';
import './OrganizationAvatar.css';
interface Props {
+ className?: string;
organization: {
avatar?: string;
name: string;
@@ -30,13 +31,15 @@ interface Props {
small?: boolean;
}
-export default function OrganizationAvatar({ organization, small }: Props) {
+export default function OrganizationAvatar({ className, organization, small }: Props) {
return (
<div
- className={classNames('navbar-context-avatar', 'rounded', {
- 'is-empty': !organization.avatar,
- 'is-small': small
- })}>
+ className={classNames(
+ 'navbar-context-avatar',
+ 'rounded',
+ { 'no-border': !organization.avatar, 'is-small': small },
+ className
+ )}>
{organization.avatar ? (
<img alt={organization.name} className="rounded" src={organization.avatar} />
) : (
diff --git a/server/sonar-web/src/main/js/components/nav/NavBar.tsx b/server/sonar-web/src/main/js/components/nav/NavBar.tsx
index d18ba17b7d2..8370ff1ed9b 100644
--- a/server/sonar-web/src/main/js/components/nav/NavBar.tsx
+++ b/server/sonar-web/src/main/js/components/nav/NavBar.tsx
@@ -26,6 +26,7 @@ interface Props {
children?: any;
className?: string;
height: number;
+ top?: number;
notif?: React.ReactNode;
[prop: string]: any;
}
@@ -58,9 +59,9 @@ export default class NavBar extends React.PureComponent<Props, State> {
};
render() {
- const { children, className, height, notif, ...other } = this.props;
+ const { children, className, height, top, notif, ...other } = this.props;
return (
- <nav {...other} className={classNames('navbar', className)} style={{ height }}>
+ <nav {...other} className={classNames('navbar', className)} style={{ height, top }}>
<div
className={classNames('navbar-inner', { 'navbar-inner-with-notif': notif != null })}
style={{ height, left: this.state.left }}>