]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9245 Add the leak period start date
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 24 May 2017 13:06:17 +0000 (15:06 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 9 Jun 2017 06:26:48 +0000 (08:26 +0200)
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap
server/sonar-web/src/main/js/apps/projects/store/actions.js
server/sonar-web/src/main/js/apps/projects/styles.css
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 18b485bdcc8b164848fa2d91c2c7902109e47034..2150c92ba7d004a4d093046da4072812fb1e7f1f 100644 (file)
@@ -37,10 +37,12 @@ type Props = {
   project?: {
     analysisDate?: string,
     key: string,
+    leakPeriodDate?: string,
     name: string,
     tags: Array<string>,
     isFavorite?: boolean,
-    organization?: string
+    organization?: string,
+    visibility?: boolean
   },
   type?: string
 };
@@ -51,6 +53,9 @@ export default function ProjectCard({ measures, organization, project, type }: P
   }
 
   const isProjectAnalyzed = project.analysisDate != null;
+  const isPrivate = project.visibility === 'private';
+  const hasLeakPeriodStart = project.leakPeriodDate != null;
+  const hasTags = project.tags.length > 0;
   const isLeakView = type === 'leak';
 
   let areProjectMeasuresLoaded;
@@ -72,22 +77,7 @@ export default function ProjectCard({ measures, organization, project, type }: P
 
   return (
     <div data-key={project.key} className={className}>
-
-      <div className="boxed-group-actions text-right">
-        {project.visibility === 'private' &&
-          <PrivateBadge className="spacer-left" tooltipPlacement="left" />}
-        {project.tags.length > 0 && <TagsList tags={project.tags} customClass="spacer-left" />}
-        {isLeakView &&
-          isProjectAnalyzed &&
-          <div className="little-spacer-top spacer-left note">
-            {translateWithParameters(
-              'overview.last_analysis_on_x',
-              moment(project.analysisDate).format('LLL')
-            )}
-          </div>}
-      </div>
-
-      <div className="boxed-group-header">
+      <div className="boxed-group-header clearfix">
         {project.isFavorite != null &&
           <FavoriteContainer className="spacer-right" componentKey={project.key} />}
         <h2 className="project-card-name">
@@ -99,6 +89,31 @@ export default function ProjectCard({ measures, organization, project, type }: P
           <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
         </h2>
         {displayQualityGate && <ProjectCardQualityGate status={measures['alert_status']} />}
+        <div className="pull-right text-right">
+          {isPrivate && <PrivateBadge className="spacer-left" tooltipPlacement="left" />}
+          {hasTags && <TagsList tags={project.tags} customClass="spacer-left" />}
+        </div>
+        {isLeakView &&
+          isProjectAnalyzed &&
+          <div
+            className={classNames('project-card-dates note text-right pull-right', {
+              'width-100': isPrivate || hasTags
+            })}>
+            {hasLeakPeriodStart &&
+              <span>
+                {translateWithParameters(
+                  'projects.leak_period_x',
+                  moment(project.leakPeriodDate).fromNow()
+                )}
+              </span>}
+            {isProjectAnalyzed &&
+              <span className="big-spacer-left">
+                {translateWithParameters(
+                  'projects.last_analysis_on_x',
+                  moment(project.analysisDate).format('LLL')
+                )}
+              </span>}
+          </div>}
       </div>
 
       {isProjectAnalyzed
index 87ad40ee285a912b93a2bf23a1531ea8801db69a..219a6650903b74432b672e74f274279f8c921567 100644 (file)
@@ -31,7 +31,7 @@ type Props = {
   measures?: { [string]: string }
 };
 
-export default function ProjectCardMeasures({ measures }: Props) {
+export default function ProjectCardOverallMeasures({ measures }: Props) {
   if (measures == null) {
     return null;
   }
index bb5b0ecef820d22008202bf0c71bdf431c6065ac..186b45b2737dda0471e1dadfa0b75b2c28c1e259 100644 (file)
@@ -21,31 +21,116 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectCard from '../ProjectCard';
 
-const PROJECT = { analysisDate: '2017-01-01', key: 'foo', name: 'Foo', tags: [] };
-const MEASURES = {};
-
-it('should not display analysis date', () => {
-  expect(
-    shallow(<ProjectCard measures={MEASURES} project={PROJECT} />).find(
-      '.project-card-analysis-date'
-    )
-  ).toMatchSnapshot();
-});
+const PROJECT = {
+  analysisDate: '2017-01-01',
+  leakPeriodDate: '2016-12-01',
+  key: 'foo',
+  name: 'Foo',
+  tags: []
+};
+const MEASURES = {
+  alert_status: 'OK',
+  reliability_rating: '1.0',
+  sqale_rating: '1.0',
+  new_bugs: 12
+};
 
-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();
-});
+jest.mock('moment', () => () => ({
+  format: () => 'March 1, 2017 9:36 AM',
+  fromNow: () => 'a month ago'
+}));
+
+describe('overall status project card', () => {
+  it('should never display analysis date', () => {
+    expect(
+      shallow(<ProjectCard measures={{}} project={PROJECT} />).find('.project-card-dates').exists()
+    ).toBeFalsy();
+  });
+
+  it('should display loading', () => {
+    const measures = { ...MEASURES, sqale_rating: undefined };
+    expect(
+      shallow(<ProjectCard project={PROJECT} />)
+        .find('.boxed-group')
+        .hasClass('boxed-group-loading')
+    ).toBeTruthy();
+    expect(
+      shallow(<ProjectCard measures={measures} project={PROJECT} />)
+        .find('.boxed-group')
+        .hasClass('boxed-group-loading')
+    ).toBeTruthy();
+  });
+
+  it('should not display the quality gate', () => {
+    const project = { ...PROJECT, analysisDate: undefined };
+    expect(
+      shallow(<ProjectCard measures={MEASURES} project={project} />)
+        .find('ProjectCardQualityGate')
+        .exists()
+    ).toBeFalsy();
+  });
+
+  it('should display tags', () => {
+    const project = { ...PROJECT, tags: ['foo', 'bar'] };
+    expect(shallow(<ProjectCard project={project} />).find('TagsList').exists()).toBeTruthy();
+  });
 
-it('should display loading', () => {
-  expect(shallow(<ProjectCard project={PROJECT} />)).toMatchSnapshot();
+  it('should private badge', () => {
+    const project = { ...PROJECT, visibility: 'private' };
+    expect(
+      shallow(<ProjectCard type="overall" project={project} />).find('PrivateBadge').exists()
+    ).toBeTruthy();
+  });
+
+  it('should display the overall measures and quality gate', () => {
+    expect(shallow(<ProjectCard measures={MEASURES} project={PROJECT} />)).toMatchSnapshot();
+  });
 });
 
-it('should display tags', () => {
-  const project = { ...PROJECT, tags: ['foo', 'bar'] };
-  expect(shallow(<ProjectCard project={project} />)).toMatchSnapshot();
+describe('leak project card', () => {
+  it('should display analysis date and leak start date', () => {
+    const project = { ...PROJECT, leakPeriodDate: undefined, visibility: 'private' };
+    const card = shallow(<ProjectCard type="leak" measures={MEASURES} project={PROJECT} />);
+    const card2 = shallow(<ProjectCard type="leak" measures={MEASURES} project={project} />);
+    expect(card.find('.project-card-dates').exists()).toBeTruthy();
+    expect(card.find('.project-card-dates').find('span').getNodes()).toHaveLength(2);
+    expect(card.find('.project-card-dates').hasClass('width-100')).toBeFalsy();
+    expect(card2.find('.project-card-dates').find('span').getNodes()).toHaveLength(1);
+    expect(card2.find('.project-card-dates').hasClass('width-100')).toBeTruthy();
+  });
+
+  it('should not display analysis date or leak start date', () => {
+    const project = { ...PROJECT, analysisDate: undefined };
+    const card = shallow(<ProjectCard type="leak" measures={MEASURES} project={project} />);
+    expect(card.find('.project-card-dates').exists()).toBeFalsy();
+  });
+
+  it('should display loading', () => {
+    const measures = { ...MEASURES, new_bugs: undefined };
+    expect(
+      shallow(<ProjectCard type="leak" measures={measures} project={PROJECT} />)
+        .find('.boxed-group')
+        .hasClass('boxed-group-loading')
+    ).toBeTruthy();
+  });
+
+  it('should display tags', () => {
+    const project = { ...PROJECT, tags: ['foo', 'bar'] };
+    expect(
+      shallow(<ProjectCard type="leak" project={project} />).find('TagsList').exists()
+    ).toBeTruthy();
+  });
+
+  it('should private badge', () => {
+    const project = { ...PROJECT, visibility: 'private' };
+    expect(
+      shallow(<ProjectCard type="leak" project={project} />).find('PrivateBadge').exists()
+    ).toBeTruthy();
+  });
+
+  it('should display the leak measures and quality gate', () => {
+    expect(
+      shallow(<ProjectCard type="leak" measures={MEASURES} project={PROJECT} />)
+    ).toMatchSnapshot();
+  });
 });
index ff77d4e7b33dfdfb93a698a02e365b4abd3123a7..19805eb328a78eb9245fba42ee8c29230bb5049c 100644 (file)
@@ -1,15 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should display loading 1`] = `
+exports[`leak project card should display the leak measures and quality gate 1`] = `
 <div
-  className="boxed-group project-card boxed-group-loading"
+  className="boxed-group project-card"
   data-key="foo"
 >
   <div
-    className="boxed-group-actions text-right"
-  />
-  <div
-    className="boxed-group-header"
+    className="boxed-group-header clearfix"
   >
     <h2
       className="project-card-name"
@@ -29,36 +26,49 @@ exports[`should display loading 1`] = `
         Foo
       </Link>
     </h2>
+    <ProjectCardQualityGate
+      status="OK"
+    />
+    <div
+      className="pull-right text-right"
+    />
+    <div
+      className="project-card-dates note text-right pull-right"
+    >
+      <span>
+        projects.leak_period_x.a month ago
+      </span>
+      <span
+        className="big-spacer-left"
+      >
+        projects.last_analysis_on_x.March 1, 2017 9:36 AM
+      </span>
+    </div>
   </div>
   <div
     className="boxed-group-inner"
   >
-    <ProjectCardMeasures />
+    <ProjectCardLeakMeasures
+      measures={
+        Object {
+          "alert_status": "OK",
+          "new_bugs": 12,
+          "reliability_rating": "1.0",
+          "sqale_rating": "1.0",
+        }
+      }
+    />
   </div>
 </div>
 `;
 
-exports[`should display tags 1`] = `
+exports[`overall status project card should display the overall measures and quality gate 1`] = `
 <div
-  className="boxed-group project-card boxed-group-loading"
+  className="boxed-group project-card"
   data-key="foo"
 >
   <div
-    className="boxed-group-actions text-right"
-  >
-    <TagsList
-      allowUpdate={false}
-      customClass="spacer-left"
-      tags={
-        Array [
-          "foo",
-          "bar",
-        ]
-      }
-    />
-  </div>
-  <div
-    className="boxed-group-header"
+    className="boxed-group-header clearfix"
   >
     <h2
       className="project-card-name"
@@ -78,13 +88,26 @@ exports[`should display tags 1`] = `
         Foo
       </Link>
     </h2>
+    <ProjectCardQualityGate
+      status="OK"
+    />
+    <div
+      className="pull-right text-right"
+    />
   </div>
   <div
     className="boxed-group-inner"
   >
-    <ProjectCardMeasures />
+    <ProjectCardOverallMeasures
+      measures={
+        Object {
+          "alert_status": "OK",
+          "new_bugs": 12,
+          "reliability_rating": "1.0",
+          "sqale_rating": "1.0",
+        }
+      }
+    />
   </div>
 </div>
 `;
-
-exports[`should not display analysis date 1`] = `undefined`;
index 7b5d11e2e4710ca3e7b98422e5d39d3b91fb1d40..38a2d21cacaf8ca5713836cffcb707c644eaa9be 100644 (file)
@@ -194,7 +194,7 @@ export const fetchProjects = (query, isFavorite, organization) => dispatch => {
   const data = convertToQueryData(query, isFavorite, organization, {
     ps,
     facets: FACETS.join(),
-    f: 'analysisDate'
+    f: 'analysisDate,leakPeriodDate'
   });
   return searchProjects(data).then(onReceiveProjects(dispatch, query), onFail(dispatch));
 };
@@ -206,7 +206,7 @@ export const fetchMoreProjects = (query, isFavorite, organization) => (dispatch,
   const data = convertToQueryData(query, isFavorite, organization, {
     ps: PAGE_SIZE,
     p: pageIndex + 1,
-    f: 'analysisDate'
+    f: 'analysisDate,leakPeriodDate'
   });
   return searchProjects(data).then(onReceiveMoreProjects(dispatch, query), onFail(dispatch));
 };
index a5c32750f3cfb6647e906549efe25aac88565c72..734c6bd7e4e7c06ad678a6bc4a543029130dbc38 100644 (file)
@@ -73,7 +73,7 @@
 
 .project-card {
   position: relative;
-  min-height: 121px;
+  min-height: 114px;
   box-sizing: border-box;
 }
 
   font-weight: 600;
 }
 
+.project-card-dates {
+  margin-top: 4px;
+  margin-bottom: -16px;
+}
+
 .project-card-measures {
   margin: 0 -15px;
 }
index 7a9644cf45c9e3487c96bc2c6e88c1d49e089799..047896dc5dd6c1c81b38711ff48ec043429a030d 100644 (file)
@@ -860,6 +860,8 @@ projects.no_favorite_projects=You don't have any favorite projects yet.
 projects.no_favorite_projects.engagement=Discover and mark as favorites projects you are interested in to have a quick access to them.
 projects.explore_projects=Explore Projects
 projects.not_analyzed=Project is not analyzed yet.
+projects.leak_period_x=Leak Period started {0}
+projects.last_analysis_on_x=Last analysis on {0}
 projects.search=Search by project name or key
 projects.sort_list=Sort list by
 projects.perspective=Perspective