]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9375 Infinite loading of a project on the projects page
authorStas Vilchik <stas.vilchik@sonarsource.com>
Thu, 31 Aug 2017 10:03:24 +0000 (12:03 +0200)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Wed, 13 Sep 2017 11:53:58 +0000 (13:53 +0200)
14 files changed:
server/sonar-web/src/main/js/app/styles/boxed-group.css
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx
server/sonar-web/src/main/js/components/measure/Measure.tsx
server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx
server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap
server/sonar-web/src/main/js/components/ui/Rating.tsx
server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js [deleted file]
server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap [new file with mode: 0644]

index 56c69e066ec793de12944b8877329004d39d8102..14c5f9753b2702237cf69fe44f395cab8fc653a0 100644 (file)
   margin-right: -20px;
   padding: 8px 20px;
 }
-
-.boxed-group-loading {
-  position: relative;
-  transition: border-color 0.25s;
-}
-
-.boxed-group-loading:before,
-.boxed-group-loading:after {
-  position: absolute;
-  z-index: 1;
-  border: 2px solid transparent;
-  box-sizing: border-box;
-  content: '';
-}
-
-.boxed-group-loading:before {
-  width: 100%;
-  height: 100%;
-  top: 0;
-  left: 0;
-  border-top-color: #4b9fd5;
-  border-right-color: #4b9fd5;
-  animation: 3s top-left-border 0s infinite;
-}
-
-.boxed-group-loading:after {
-  width: 0;
-  height: 0;
-  bottom: 0;
-  right: 0;
-  border-bottom-color: #4b9fd5;
-  border-left-color: #4b9fd5;
-  animation: 3s border-bottom-border 0s infinite;
-}
-
-@keyframes top-left-border {
-  0% {
-    width: 0;
-    height: 0;
-  }
-
-  25% {
-    width: 100%;
-    height: 0;
-  }
-
-  50%,
-  100% {
-    width: 100%;
-    height: 100%;
-  }
-}
-
-@keyframes border-bottom-border {
-  0%,
-  50% {
-    width: 0;
-    height: 0;
-    border-width: 0;
-  }
-
-  51% {
-    border-width: 2px;
-  }
-
-  75% {
-    width: 100%;
-    height: 0;
-  }
-
-  100% {
-    width: 100%;
-    height: 100%;
-  }
-}
index f0e8fca7d5437390edbd21d6afb109808ee0ceb7..89e6dd61c7892b8d7d0a48271c708997bec5d82f 100644 (file)
@@ -18,7 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as classNames from 'classnames';
 import { Link } from 'react-router';
 import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
@@ -39,22 +38,11 @@ interface Props {
 export default function ProjectCardLeak({ organization, project }: Props) {
   const { measures } = project;
 
-  const isProjectAnalyzed = project.analysisDate != null;
   const isPrivate = project.visibility === 'private';
-  const hasLeakPeriodStart = project.leakPeriodDate != undefined;
   const hasTags = project.tags.length > 0;
 
-  // check for particular measures because only some measures can be loaded
-  // if coming from visualizations tab
-  const areProjectMeasuresLoaded = measures != undefined && measures['new_bugs'];
-
-  const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
-  const className = classNames('boxed-group', 'project-card', {
-    'boxed-group-loading': isProjectAnalyzed && hasLeakPeriodStart && !areProjectMeasuresLoaded
-  });
-
   return (
-    <div data-key={project.key} className={className}>
+    <div data-key={project.key} className="boxed-group project-card">
       <div className="boxed-group-header clearfix">
         {project.isFavorite != null && (
           <Favorite
@@ -67,44 +55,38 @@ export default function ProjectCardLeak({ organization, project }: Props) {
           {!organization && <ProjectCardOrganization organization={project.organization} />}
           <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
         </h2>
-        {displayQualityGate && <ProjectCardQualityGate status={measures!['alert_status']} />}
+        {project.analysisDate && <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>
-        {isProjectAnalyzed &&
-        hasLeakPeriodStart && (
+        {project.analysisDate &&
+        project.leakPeriodDate && (
           <div className="project-card-dates note text-right pull-right">
-            {hasLeakPeriodStart && (
-              <DateFromNow date={project.leakPeriodDate!}>
-                {fromNow => (
-                  <span className="project-card-leak-date pull-right">
-                    {translateWithParameters('projects.leak_period_x', fromNow)}
-                  </span>
-                )}
-              </DateFromNow>
-            )}
-            {isProjectAnalyzed && (
-              <DateTimeFormatter date={project.analysisDate!}>
-                {formattedDate => (
-                  <span>
-                    {translateWithParameters('projects.last_analysis_on_x', formattedDate)}
-                  </span>
-                )}
-              </DateTimeFormatter>
-            )}
+            <DateFromNow date={project.leakPeriodDate!}>
+              {fromNow => (
+                <span className="project-card-leak-date pull-right">
+                  {translateWithParameters('projects.leak_period_x', fromNow)}
+                </span>
+              )}
+            </DateFromNow>
+            <DateTimeFormatter date={project.analysisDate!}>
+              {formattedDate => (
+                <span>{translateWithParameters('projects.last_analysis_on_x', formattedDate)}</span>
+              )}
+            </DateTimeFormatter>
           </div>
         )}
       </div>
 
-      {isProjectAnalyzed && hasLeakPeriodStart ? (
+      {project.analysisDate && project.leakPeriodDate ? (
         <div className="boxed-group-inner">
-          {areProjectMeasuresLoaded && <ProjectCardLeakMeasures measures={measures} />}
+          <ProjectCardLeakMeasures measures={measures} />
         </div>
       ) : (
         <div className="boxed-group-inner">
           <div className="note project-card-not-analyzed">
-            {isProjectAnalyzed ? (
+            {project.analysisDate ? (
               translate('projects.no_leak_period')
             ) : (
               translate('projects.not_analyzed')
index 353a0d6ccc908443de5e5cc29873ed2b831e4a16..570ddbe075f0cb396232b598a6d5eb779184fa06 100644 (file)
@@ -26,14 +26,10 @@ import VulnerabilityIcon from '../../../components/icons-components/Vulnerabilit
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
-  measures?: { [key: string]: string };
+  measures: { [key: string]: string };
 }
 
 export default function ProjectCardLeakMeasures({ measures }: Props) {
-  if (measures == undefined) {
-    return null;
-  }
-
   return (
     <div className="project-card-leak-measures">
       <div className="project-card-measure smaller-card" data-key="new_reliability_rating">
index 75f0f8375d04f798b276763829ad92f67e65452f..135f71c3e35b63a138030f64d612d12a7d4ef59a 100644 (file)
@@ -18,7 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import * as classNames from 'classnames';
 import { Link } from 'react-router';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import ProjectCardQualityGate from './ProjectCardQualityGate';
@@ -38,24 +37,11 @@ interface Props {
 export default function ProjectCardOverall({ organization, project }: Props) {
   const { measures } = project;
 
-  const isProjectAnalyzed = project.analysisDate != undefined;
   const isPrivate = project.visibility === 'private';
   const hasTags = project.tags.length > 0;
 
-  // check for particular measures because only some measures can be loaded
-  // if coming from visualizations tab
-  const areProjectMeasuresLoaded =
-    measures != undefined &&
-    measures['reliability_rating'] != undefined &&
-    measures['sqale_rating'] != undefined;
-
-  const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
-  const className = classNames('boxed-group', 'project-card', {
-    'boxed-group-loading': isProjectAnalyzed && !areProjectMeasuresLoaded
-  });
-
   return (
-    <div data-key={project.key} className={className}>
+    <div data-key={project.key} className="boxed-group project-card">
       <div className="boxed-group-header clearfix">
         {project.isFavorite != undefined && (
           <Favorite
@@ -68,7 +54,7 @@ export default function ProjectCardOverall({ organization, project }: Props) {
           {!organization && <ProjectCardOrganization organization={project.organization} />}
           <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
         </h2>
-        {displayQualityGate && <ProjectCardQualityGate status={measures!['alert_status']} />}
+        {project.analysisDate && <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" />}
@@ -86,9 +72,9 @@ export default function ProjectCardOverall({ organization, project }: Props) {
         )}
       </div>
 
-      {isProjectAnalyzed ? (
+      {project.analysisDate ? (
         <div className="boxed-group-inner">
-          {areProjectMeasuresLoaded && <ProjectCardOverallMeasures measures={measures} />}
+          {<ProjectCardOverallMeasures measures={measures} />}
         </div>
       ) : (
         <div className="boxed-group-inner">
index 15c2565cc31a9bbad9406e216e17db8f70476aec..29b763580346b07827ff7ab5f918fb319477abff 100644 (file)
@@ -27,7 +27,7 @@ import SizeRating from '../../../components/ui/SizeRating';
 import { translate } from '../../../helpers/l10n';
 
 interface Props {
-  measures?: { [key: string]: string };
+  measures: { [key: string]: string | undefined };
 }
 
 export default function ProjectCardOverallMeasures({ measures }: Props) {
index 8a9a75cf1cd6a07cb2fc1503aa2f0d1a11c0051e..12899cfb047c140bd49f6f6102c5b5c51f377e88 100644 (file)
@@ -52,15 +52,6 @@ it('should not display analysis date or leak start date', () => {
   expect(card.find('.project-card-dates').exists()).toBeFalsy();
 });
 
-it('should display loading', () => {
-  const measures = { alert_status: 'OK', reliability_rating: '1.0', sqale_rating: '1.0' };
-  expect(
-    shallow(<ProjectCardLeak project={{ ...PROJECT, measures }} />)
-      .find('.boxed-group')
-      .hasClass('boxed-group-loading')
-  ).toBeTruthy();
-});
-
 it('should display tags', () => {
   const project = { ...PROJECT, tags: ['foo', 'bar'] };
   expect(
index 7a3e3bb3baf8511cc61d303fc3b45d796ac779bb..7410fcb371152d373cf94b1a55107b99234e47bc 100644 (file)
@@ -51,19 +51,6 @@ it('should display analysis date (and not leak period) when defined', () => {
   ).toBeFalsy();
 });
 
-it('should display loading', () => {
-  expect(
-    shallow(<ProjectCardOverall project={{ ...PROJECT, measures: {} }} />)
-      .find('.boxed-group')
-      .hasClass('boxed-group-loading')
-  ).toBeTruthy();
-  expect(
-    shallow(<ProjectCardOverall project={{ ...PROJECT, measures: { sqale_rating: '1.0' } }} />)
-      .find('.boxed-group')
-      .hasClass('boxed-group-loading')
-  ).toBeTruthy();
-});
-
 it('should not display the quality gate', () => {
   const project = { ...PROJECT, analysisDate: undefined };
   expect(
index 35177b49b95c2227a0ce776faad8ad0fd68a32cf..d345aff6cb5c91f4019ea6415537f888a2bab7f9 100644 (file)
@@ -35,7 +35,7 @@ export default function Measure({ className, decimals, measure }: Props) {
   const value = isDiffMetric(metric.key) ? measure.leak : measure.value;
 
   if (value == undefined) {
-    return null;
+    return <span>{'–'}</span>;
   }
 
   if (metric.type === 'LEVEL') {
index 8929a8bc1c5a252557b085508cede0700b74aa75..5eb0a6efa3efc4bfc3a1a7842f2c4b5e868e5376 100644 (file)
@@ -63,3 +63,8 @@ it('renders unknown RATING', () => {
   };
   expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
 });
+
+it('renders undefined measure', () => {
+  const measure = { metric: { key: 'foo', name: 'Foo', type: 'PERCENT' } };
+  expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+});
index 2988d9210e84439cab474cb963911dc149f946d5..c9ca3a03a7ac6f7e78312aea79509b5929e13cfd 100644 (file)
@@ -31,6 +31,12 @@ exports[`renders trivial measure 1`] = `
 </span>
 `;
 
+exports[`renders undefined measure 1`] = `
+<span>
+  –
+</span>
+`;
+
 exports[`renders unknown RATING 1`] = `
 <Rating
   value="4"
index 67abfe61782a44a4a6ab9aa86c1168b96a7e8e59..183994c7eae12154a3d30e0e6cbe561791d84969 100644 (file)
@@ -26,10 +26,13 @@ interface Props {
   className?: string;
   muted?: boolean;
   small?: boolean;
-  value: string | number;
+  value: string | number | undefined;
 }
 
 export default function Rating({ className, muted = false, small = false, value }: Props) {
+  if (value == undefined) {
+    return <span>{'–'}</span>;
+  }
   const formatted = formatMeasure(value, 'RATING');
   return (
     <span
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.js
deleted file mode 100644 (file)
index 05e24ce..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 { shallow } from 'enzyme';
-import React from 'react';
-import Rating from '../Rating';
-
-it('should render with numeric value', () => {
-  const rating = shallow(<Rating value={2} />);
-  expect(rating.is('.rating-B')).toBe(true);
-});
-
-it('should render with string value', () => {
-  const rating = shallow(<Rating value="2.0" />);
-  expect(rating.is('.rating-B')).toBe(true);
-});
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/Rating-test.tsx
new file mode 100644 (file)
index 0000000..0512177
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import Rating from '../Rating';
+
+it('renders numeric value', () => {
+  expect(shallow(<Rating value={2} />)).toMatchSnapshot();
+});
+
+it('renders string value', () => {
+  expect(shallow(<Rating value="2.0" />)).toMatchSnapshot();
+});
+
+it('renders undefined value', () => {
+  expect(shallow(<Rating value={undefined} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Rating-test.tsx.snap
new file mode 100644 (file)
index 0000000..94922c0
--- /dev/null
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders numeric value 1`] = `
+<span
+  className="rating rating-B"
+>
+  B
+</span>
+`;
+
+exports[`renders string value 1`] = `
+<span
+  className="rating rating-B"
+>
+  B
+</span>
+`;
+
+exports[`renders undefined value 1`] = `
+<span>
+  –
+</span>
+`;