aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/projects/components
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-10-18 09:46:22 +0200
committerStas Vilchik <vilchiks@gmail.com>2016-10-21 10:24:17 +0200
commitb7129679327efeeb44f9205656b46376adfe9689 (patch)
treef01159db52a79aae8e478dcafe79a7d9d22156c6 /server/sonar-web/src/main/js/apps/projects/components
parent3d8cdcbf8558e40385f481272045056d5435a3e6 (diff)
downloadsonarqube-b7129679327efeeb44f9205656b46376adfe9689.tar.gz
sonarqube-b7129679327efeeb44f9205656b46376adfe9689.zip
SONAR-8300 Create new "Projects" page [first iter]
Diffstat (limited to 'server/sonar-web/src/main/js/apps/projects/components')
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/App.js82
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AppContainer.js27
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageHeader.js52
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js26
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js37
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js47
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js29
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js55
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js129
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js49
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js75
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js35
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js42
14 files changed, 713 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/projects/components/App.js b/server/sonar-web/src/main/js/apps/projects/components/App.js
new file mode 100644
index 00000000000..b6abec6f45f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/App.js
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import Helmet from 'react-helmet';
+import PageHeaderContainer from './PageHeaderContainer';
+import ProjectsListContainer from './ProjectsListContainer';
+import ProjectsListFooterContainer from './ProjectsListFooterContainer';
+import PageSidebar from './PageSidebar';
+import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
+import { parseUrlQuery } from '../store/utils';
+import '../styles.css';
+import { translate } from '../../../helpers/l10n';
+
+export default class App extends React.Component {
+ static propTypes = {
+ fetchProjects: React.PropTypes.func.isRequired
+ };
+
+ state = {
+ query: {}
+ };
+
+ componentDidMount () {
+ document.querySelector('html').classList.add('dashboard-page');
+ this.handleQueryChange();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.location.query !== this.props.location.query) {
+ this.handleQueryChange();
+ }
+ }
+
+ componentWillUnmount () {
+ document.querySelector('html').classList.remove('dashboard-page');
+ }
+
+ handleQueryChange () {
+ const query = parseUrlQuery(this.props.location.query);
+ this.setState({ query });
+ this.props.fetchProjects(query);
+ }
+
+ render () {
+ return (
+ <div id="projects-page" className="page page-limited">
+ <Helmet title={translate('projects.page')} titleTemplate="SonarQube - %s"/>
+
+ <PageHeaderContainer/>
+
+ <GlobalMessagesContainer/>
+
+ <div className="page-with-sidebar page-with-left-sidebar">
+ <div className="page-main">
+ <ProjectsListContainer/>
+ <ProjectsListFooterContainer query={this.state.query}/>
+ </div>
+ <aside className="page-sidebar-fixed">
+ <PageSidebar query={this.state.query}/>
+ </aside>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AppContainer.js b/server/sonar-web/src/main/js/apps/projects/components/AppContainer.js
new file mode 100644
index 00000000000..f2956e0b14a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/AppContainer.js
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { connect } from 'react-redux';
+import App from './App';
+import { fetchProjects } from '../store/actions';
+
+export default connect(
+ () => ({}),
+ { fetchProjects }
+)(App);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
new file mode 100644
index 00000000000..4043e0176f9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class PageHeader extends React.Component {
+ static propTypes = {
+ total: React.PropTypes.number,
+ loading: React.PropTypes.bool
+ };
+
+ render () {
+ const { total, loading } = this.props;
+
+ return (
+ <header className="page-header">
+ <h1 className="page-title">{translate('projects.page')}</h1>
+
+ {!!loading && (
+ <i className="spinner"/>
+ )}
+
+ <div className="page-actions">
+ {total != null && (
+ <span><strong>{total}</strong> {translate('projects._projects')}</span>
+ )}
+ </div>
+
+ <div className="page-description">
+ {translate('projects.page.description')}
+ </div>
+ </header>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js
new file mode 100644
index 00000000000..fe8c6fd6043
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js
@@ -0,0 +1,26 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { connect } from 'react-redux';
+import PageHeader from './PageHeader';
+import { getProjectsAppState } from '../../../app/store/rootReducer';
+
+export default connect(
+ state => getProjectsAppState(state)
+)(PageHeader);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
new file mode 100644
index 00000000000..c250d97c141
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import CoverageFilterContainer from '../filters/CoverageFilterContainer';
+import DuplicationsFilterContainer from '../filters/DuplicationsFilterContainer';
+import SizeFilterContainer from '../filters/SizeFilterContainer';
+import QualityGateFilterContainer from '../filters/QualityGateFilterContainer';
+
+export default class PageSidebar extends React.Component {
+ render () {
+ return (
+ <div>
+ <CoverageFilterContainer query={this.props.query}/>
+ <DuplicationsFilterContainer query={this.props.query}/>
+ <SizeFilterContainer query={this.props.query}/>
+ <QualityGateFilterContainer query={this.props.query}/>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
new file mode 100644
index 00000000000..e76fa0e975d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProjectCardMeasures from './ProjectCardMeasures';
+import { getComponentUrl } from '../../../helpers/urls';
+
+export default class ProjectCard extends React.Component {
+ static propTypes = {
+ project: React.PropTypes.object
+ };
+
+ render () {
+ const { project } = this.props;
+
+ if (project == null) {
+ return null;
+ }
+
+ return (
+ <div className="boxed-group project-card">
+ <h2 className="project-card-name">
+ <a className="link-base-color" href={getComponentUrl(project.key)}>{project.name}</a>
+ </h2>
+ <div className="boxed-group-inner">
+ <ProjectCardMeasures measures={this.props.measures}/>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js
new file mode 100644
index 00000000000..f3f9713ffb9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { connect } from 'react-redux';
+import ProjectCard from './ProjectCard';
+import { getComponent, getComponentMeasures } from '../../../app/store/rootReducer';
+
+export default connect(
+ (state, ownProps) => ({
+ project: getComponent(state, ownProps.projectKey),
+ measures: getComponentMeasures(state, ownProps.projectKey),
+ })
+)(ProjectCard);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js
new file mode 100644
index 00000000000..f91571ab8c3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import sortBy from 'lodash/sortBy';
+import { connect } from 'react-redux';
+import { getLanguages } from '../../../app/store/rootReducer';
+import { translate } from '../../../helpers/l10n';
+
+class ProjectCardLanguages extends React.Component {
+ getLanguageName (key) {
+ if (key === '<null>') {
+ return translate('unknown');
+ }
+ const language = this.props.languages[key];
+ return language != null ? language.name : key;
+ }
+
+ render () {
+ const { distribution } = this.props;
+
+ if (distribution == null) {
+ return null;
+ }
+
+ const parsedLanguages = distribution.split(';').map(item => item.split('='));
+ const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1]))
+ .slice(0, 2)
+ .map(l => this.getLanguageName(l[0]));
+
+ return <span>{finalLanguages.join(', ')}</span>;
+ }
+}
+
+const mapStateToProps = state => ({
+ languages: getLanguages(state)
+});
+
+export default connect(mapStateToProps)(ProjectCardLanguages);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js
new file mode 100644
index 00000000000..9c7cc99abfd
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProjectCardLanguages from './ProjectCardLanguages';
+import ProjectCardQualityGate from './ProjectCardQualityGate';
+import Measure from '../../component-measures/components/Measure';
+import Rating from '../../../components/ui/Rating';
+import CoverageRating from '../../../components/ui/CoverageRating';
+import DuplicationsRating from '../../../components/ui/DuplicationsRating';
+import SizeRating from '../../../components/ui/SizeRating';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProjectCardMeasures extends React.Component {
+ static propTypes = {
+ measures: React.PropTypes.object,
+ languages: React.PropTypes.array
+ };
+
+ render () {
+ const { measures } = this.props;
+
+ if (measures == null) {
+ return null;
+ }
+
+ return (
+ <div className="project-card-measures">
+ <div className="project-card-measure">
+ <div className="project-card-measure-inner">
+ <div className="project-card-measure-number">
+ <Rating value={measures['reliability_rating']}/>
+ </div>
+ <div className="project-card-measure-label">
+ {translate('metric_domain.Reliability')}
+ </div>
+ </div>
+ </div>
+
+ <div className="project-card-measure">
+ <div className="project-card-measure-inner">
+ <div className="project-card-measure-number">
+ <Rating value={measures['security_rating']}/>
+ </div>
+ <div className="project-card-measure-label">
+ {translate('metric_domain.Security')}
+ </div>
+ </div>
+ </div>
+
+ <div className="project-card-measure">
+ <div className="project-card-measure-inner">
+ <div className="project-card-measure-number">
+ <Rating value={measures['sqale_rating']}/>
+ </div>
+ <div className="project-card-measure-label">
+ {translate('metric_domain.Maintainability')}
+ </div>
+ </div>
+ </div>
+
+ <div className="project-card-measure">
+ <div className="project-card-measure-inner">
+ <div className="project-card-measure-number">
+ {measures['coverage'] != null && (
+ <span className="spacer-right">
+ <CoverageRating value={measures['coverage']}/>
+ </span>
+ )}
+ <Measure measure={{ value: measures['coverage'] }}
+ metric={{ key: 'coverage', type: 'PERCENT' }}/>
+ </div>
+ <div className="project-card-measure-label">
+ {translate('metric.coverage.name')}
+ </div>
+ </div>
+ </div>
+
+ <div className="project-card-measure">
+ <div className="project-card-measure-inner">
+ <div className="project-card-measure-number">
+ <span className="spacer-right">
+ <DuplicationsRating value={measures['duplicated_lines_density']}/>
+ </span>
+ <Measure measure={{ value: measures['duplicated_lines_density'] }}
+ metric={{ key: 'duplicated_lines_density', type: 'PERCENT' }}/>
+ </div>
+ <div className="project-card-measure-label">
+ {translate('metric.duplicated_lines_density.short_name')}
+ </div>
+ </div>
+ </div>
+
+ <div className="project-card-measure">
+ <div className="project-card-measure-inner">
+ <div className="project-card-measure-number">
+ <span className="spacer-right">
+ <SizeRating value={measures['ncloc']}/>
+ </span>
+ <Measure measure={{ value: measures['ncloc'] }}
+ metric={{ key: 'ncloc', type: 'SHORT_INT' }}/>
+ </div>
+ <div className="project-card-measure-label">
+ <ProjectCardLanguages distribution={measures['ncloc_language_distribution']}/>
+ </div>
+ </div>
+ </div>
+
+ <ProjectCardQualityGate status={measures['alert_status']}/>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js
new file mode 100644
index 00000000000..cc542cabefa
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import Level from '../../../components/ui/Level';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProjectCardQualityGate extends React.Component {
+ static propTypes = {
+ status: React.PropTypes.string
+ };
+
+ render () {
+ const { status } = this.props;
+
+ if (!status) {
+ return null;
+ }
+
+ return (
+ <div className="project-card-measure pull-right">
+ <div className="project-card-measure-inner">
+ <div>
+ <Level level={status}/>
+ </div>
+ <div className="project-card-measure-label">
+ {translate('overview.quality_gate')}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js
new file mode 100644
index 00000000000..47f4a105589
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { List, AutoSizer, WindowScroller } from 'react-virtualized';
+import ProjectCardContainer from './ProjectCardContainer';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProjectsList extends React.Component {
+ static propTypes = {
+ projects: React.PropTypes.arrayOf(React.PropTypes.string)
+ };
+
+ render () {
+ const { projects } = this.props;
+
+ if (projects == null) {
+ return null;
+ }
+
+ if (projects.length === 0) {
+ return (
+ <div className="projects-empty-list">
+ <h3>{translate('projects.no_projects.1')}</h3>
+ <p className="big-spacer-top">{translate('projects.no_projects.2')}</p>
+ </div>
+ );
+ }
+
+ const rowRenderer = ({ key, index, style }) => {
+ const projectKey = projects[index];
+ return (
+ <div key={key} style={style}>
+ <ProjectCardContainer projectKey={projectKey}/>
+ </div>
+ );
+ };
+
+ return (
+ <WindowScroller>
+ {({ height, scrollTop }) => (
+ <AutoSizer disableHeight>
+ {({ width }) => (
+ <List
+ className="projects-list"
+ autoHeight
+ width={width}
+ height={height}
+ rowCount={projects.length}
+ rowHeight={135}
+ rowRenderer={rowRenderer}
+ scrollTop={scrollTop}/>
+ )}
+ </AutoSizer>
+ )}
+ </WindowScroller>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js
new file mode 100644
index 00000000000..bdeb573a65a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { connect } from 'react-redux';
+import ProjectsList from './ProjectsList';
+import { getProjects } from '../../../app/store/rootReducer';
+
+export default connect(
+ state => ({
+ projects: getProjects(state)
+ })
+)(ProjectsList);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js
new file mode 100644
index 00000000000..d256492d68e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ListFooter from '../../../components/controls/ListFooter';
+
+export default class ProjectsListFooter extends React.Component {
+ static propTypes = {
+ total: React.PropTypes.number.isRequired,
+ };
+
+ render () {
+ if (!this.props.total) {
+ return null;
+ }
+
+ return <ListFooter {...this.props}/>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js
new file mode 100644
index 00000000000..d30f699c723
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { connect } from 'react-redux';
+import { getProjects, getProjectsAppState } from '../../../app/store/rootReducer';
+import { fetchMoreProjects } from '../store/actions';
+import ProjectsListFooter from './ProjectsListFooter';
+
+const mapStateToProps = state => {
+ const projects = getProjects(state);
+ const appState = getProjectsAppState(state);
+ return {
+ count: projects != null ? projects.length : 0,
+ total: appState.total != null ? appState.total : 0,
+ ready: !appState.loading
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ loadMore: () => dispatch(fetchMoreProjects(ownProps.query))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(ProjectsListFooter);