diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-10-08 09:24:03 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2018-11-01 09:32:49 +0100 |
commit | 3d4819e32de3ebfb68291de0467f1e2360d0d2cf (patch) | |
tree | 43daa77cfd99b7ba9ac26b780fb3d5cfd5c5e893 /server/sonar-web/src/main/js | |
parent | 35229142c6eaf780a70fcf838280cc7aee4fe515 (diff) | |
download | sonarqube-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')
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 }}> |