diff options
author | Stas Vilchik <stas-vilchik@users.noreply.github.com> | 2017-03-09 17:32:32 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-09 17:32:32 +0100 |
commit | 82a3b919083f95998fa7cc274c1dd0f10ddcb470 (patch) | |
tree | 12cd78721784fdf15501c8bb5f6bc70f2179ecb8 /server/sonar-web/src/main/js/apps | |
parent | 64256992734c3e8184db370f42797e34ddc6339d (diff) | |
download | sonarqube-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')
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; } |