summaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorStas Vilchik <stas-vilchik@users.noreply.github.com>2017-03-09 17:32:32 +0100
committerGitHub <noreply@github.com>2017-03-09 17:32:32 +0100
commit82a3b919083f95998fa7cc274c1dd0f10ddcb470 (patch)
tree12cd78721784fdf15501c8bb5f6bc70f2179ecb8 /server/sonar-web/src/main/js/apps
parent64256992734c3e8184db370f42797e34ddc6339d (diff)
downloadsonarqube-82a3b919083f95998fa7cc274c1dd0f10ddcb470.tar.gz
sonarqube-82a3b919083f95998fa7cc274c1dd0f10ddcb470.zip
SONAR-8913 display analysis date on projects page (#1765)
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js92
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js46
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap38
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/actions.js32
-rw-r--r--server/sonar-web/src/main/js/apps/projects/styles.css10
5 files changed, 170 insertions, 48 deletions
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
index b835eeadae5..a9fa6a95109 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
@@ -17,19 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+// @flow
import React from 'react';
import classNames from 'classnames';
+import moment from 'moment';
import { Link } from 'react-router';
import ProjectCardQualityGate from './ProjectCardQualityGate';
import ProjectCardMeasures from './ProjectCardMeasures';
import FavoriteContainer from '../../../components/controls/FavoriteContainer';
-import { translate } from '../../../helpers/l10n';
import Organization from '../../../components/shared/Organization';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
export default class ProjectCard extends React.Component {
- static propTypes = {
- project: React.PropTypes.object,
- organization: React.PropTypes.object
+ props: {
+ measures: { [string]: string },
+ organization?: {},
+ project?: {
+ analysisDate?: string,
+ key: string,
+ name: string
+ }
};
render () {
@@ -40,46 +47,53 @@ export default class ProjectCard extends React.Component {
}
const areProjectMeasuresLoaded = this.props.measures != null;
- const isProjectAnalyzed = areProjectMeasuresLoaded &&
- (this.props.measures['ncloc'] != null || this.props.measures['sqale_rating'] != null);
-
- const className = classNames('boxed-group', 'project-card', { 'boxed-group-loading': !areProjectMeasuresLoaded });
+ const isProjectAnalyzed = project.analysisDate != null;
+ const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
+ const className = classNames('boxed-group', 'project-card', {
+ 'boxed-group-loading': !areProjectMeasuresLoaded
+ });
return (
- <div data-key={project.key} className={className}>
- {isProjectAnalyzed && (
- <div className="boxed-group-actions">
- <ProjectCardQualityGate status={this.props.measures['alert_status']}/>
+ <div data-key={project.key} className={className}>
+ {displayQualityGate &&
+ <div className="boxed-group-actions">
+ <ProjectCardQualityGate status={this.props.measures['alert_status']}/>
+ </div>}
+
+ <div className="boxed-group-header">
+ {project.isFavorite != null &&
+ <FavoriteContainer className="spacer-right" componentKey={project.key}/>}
+ <h2 className="project-card-name">
+ {this.props.organization == null &&
+ project.organization != null &&
+ <span className="text-normal">
+ <Organization organizationKey={project.organization}/>
+ </span>}
+ <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>
+ {project.name}
+ </Link>
+ </h2>
+ </div>
+
+ {isProjectAnalyzed
+ ? <div className="boxed-group-inner">
+ {areProjectMeasuresLoaded && <ProjectCardMeasures measures={this.props.measures}/>}
+ </div>
+ : <div className="boxed-group-inner">
+ <div className="note project-card-not-analyzed">
+ {translate('projects.not_analyzed')}
</div>
- )}
- <div className="boxed-group-header">
- {project.isFavorite != null && (
- <FavoriteContainer className="spacer-right" componentKey={project.key}/>
+ </div>}
+
+ {isProjectAnalyzed &&
+ <div className="project-card-analysis-date note">
+ {translateWithParameters(
+ 'overview.last_analysis_on_x',
+ moment(project.analysisDate).format('LLL')
)}
- <h2 className="project-card-name">
- {this.props.organization == null && project.organization != null && (
- <span className="text-normal">
- <Organization organizationKey={project.organization}/>
- </span>
- )}
- <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>
- {project.name}
- </Link>
- </h2>
- </div>
- {isProjectAnalyzed ? (
- <div className="boxed-group-inner">
- <ProjectCardMeasures measures={this.props.measures}/>
- </div>
- ) : (
- <div className="boxed-group-inner">
- <div className="note project-card-not-analyzed">
- {translate('projects.not_analyzed')}
- </div>
- </div>
- )}
- </div>
+ </div>}
+ </div>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js
new file mode 100644
index 00000000000..1ada9d2f0db
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import ProjectCard from '../ProjectCard';
+
+const PROJECT = { analysisDate: '2017-01-01', key: 'foo', name: 'Foo' };
+const MEASURES = {};
+
+it('should display analysis date', () => {
+ expect(
+ shallow(<ProjectCard measures={MEASURES} project={PROJECT}/>).find(
+ '.project-card-analysis-date'
+ )
+ ).toMatchSnapshot();
+});
+
+it('should NOT display analysis date', () => {
+ const project = { ...PROJECT, analysisDate: undefined };
+ expect(
+ shallow(<ProjectCard measures={MEASURES} project={project}/>)
+ .find('.project-card-analysis-date')
+ .exists()
+ ).toBeFalsy();
+});
+
+it('should display loading', () => {
+ expect(shallow(<ProjectCard project={PROJECT}/>)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap
new file mode 100644
index 00000000000..43c9b8fa589
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap
@@ -0,0 +1,38 @@
+exports[`test should display analysis date 1`] = `
+<div
+ className="project-card-analysis-date note">
+ overview.last_analysis_on_x.January 1, 2017 12:00 AM
+</div>
+`;
+
+exports[`test should display loading 1`] = `
+<div
+ className="boxed-group project-card boxed-group-loading"
+ data-key="foo">
+ <div
+ className="boxed-group-header">
+ <h2
+ className="project-card-name">
+ <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }>
+ Foo
+ </Link>
+ </h2>
+ </div>
+ <div
+ className="boxed-group-inner" />
+ <div
+ className="project-card-analysis-date note">
+ overview.last_analysis_on_x.January 1, 2017 12:00 AM
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projects/store/actions.js b/server/sonar-web/src/main/js/apps/projects/store/actions.js
index 7cca5a9ec47..ad6cb7f0377 100644
--- a/server/sonar-web/src/main/js/apps/projects/store/actions.js
+++ b/server/sonar-web/src/main/js/apps/projects/store/actions.js
@@ -92,7 +92,10 @@ const fetchProjectMeasures = projects => dispatch => {
}
const projectKeys = projects.map(project => project.key);
- return getMeasuresForProjects(projectKeys, METRICS).then(onReceiveMeasures(dispatch, projectKeys), onFail(dispatch));
+ return getMeasuresForProjects(projectKeys, METRICS).then(
+ onReceiveMeasures(dispatch, projectKeys),
+ onFail(dispatch)
+ );
};
const fetchProjectOrganizations = projects => dispatch => {
@@ -101,7 +104,10 @@ const fetchProjectOrganizations = projects => dispatch => {
}
const organizationKeys = uniq(projects.map(project => project.organization));
- return getOrganizations(organizationKeys).then(onReceiveOrganizations(dispatch), onFail(dispatch));
+ return getOrganizations(organizationKeys).then(
+ onReceiveOrganizations(dispatch),
+ onFail(dispatch)
+ );
};
const handleFavorites = (dispatch, projects) => {
@@ -122,10 +128,12 @@ const onReceiveProjects = dispatch => response => {
]).then(() => {
dispatch(updateState({ loading: false }));
});
- dispatch(updateState({
- total: response.paging.total,
- pageIndex: response.paging.pageIndex
- }));
+ dispatch(
+ updateState({
+ total: response.paging.total,
+ pageIndex: response.paging.pageIndex
+ })
+ );
};
const onReceiveMoreProjects = dispatch => response => {
@@ -143,7 +151,11 @@ const onReceiveMoreProjects = dispatch => response => {
export const fetchProjects = (query, isFavorite, organization) => dispatch => {
dispatch(updateState({ loading: true }));
- const data = convertToQueryData(query, isFavorite, organization, { ps: PAGE_SIZE, facets: FACETS.join() });
+ const data = convertToQueryData(query, isFavorite, organization, {
+ ps: PAGE_SIZE,
+ facets: FACETS.join(),
+ f: 'analysisDate'
+ });
return searchProjects(data).then(onReceiveProjects(dispatch), onFail(dispatch));
};
@@ -151,6 +163,10 @@ export const fetchMoreProjects = (query, isFavorite, organization) => (dispatch,
dispatch(updateState({ loading: true }));
const state = getState();
const { pageIndex } = getProjectsAppState(state);
- const data = convertToQueryData(query, isFavorite, organization, { ps: PAGE_SIZE, p: pageIndex + 1 });
+ const data = convertToQueryData(query, isFavorite, organization, {
+ ps: PAGE_SIZE,
+ p: pageIndex + 1,
+ f: 'analysisDate'
+ });
return searchProjects(data).then(onReceiveMoreProjects(dispatch), onFail(dispatch));
};
diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css
index 3b2ced4253a..be23d753a3d 100644
--- a/server/sonar-web/src/main/js/apps/projects/styles.css
+++ b/server/sonar-web/src/main/js/apps/projects/styles.css
@@ -19,7 +19,8 @@
}
.project-card {
- height: 115px;
+ position: relative;
+ min-height: 121px;
box-sizing: border-box;
}
@@ -96,6 +97,13 @@
line-height: 24px;
}
+.project-card-analysis-date {
+ margin-top: -15px;
+ padding-bottom: 5px;
+ padding-right: 20px;
+ text-align: right;
+}
+
.project-card-not-analyzed {
padding: 14px 0;
}