aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-06-27 16:13:00 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-04 14:15:34 +0200
commit3a2e4d3af7939b8017379527e0213b113a6d52e1 (patch)
tree211d1695827f2b9b4fd06bfc8eb582a84ffde711
parent3a604bda9aa574c291f764ca8791b9af91d66a67 (diff)
downloadsonarqube-3a2e4d3af7939b8017379527e0213b113a6d52e1.tar.gz
sonarqube-3a2e4d3af7939b8017379527e0213b113a6d52e1.zip
SONAR-8550 Make the analysis version sticky in the project activity list
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/Events.js1
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js105
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css38
4 files changed, 114 insertions, 34 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js b/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
index 0dd5f89c054..8be0cb58165 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
@@ -20,7 +20,6 @@
// @flow
import React from 'react';
import Event from './Event';
-import './projectActivity.css';
import type { Event as EventType } from '../types';
type Props = {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
index c44ac7e4e79..d0655faec68 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
@@ -19,6 +19,7 @@
*/
// @flow
import React from 'react';
+import classNames from 'classnames';
import moment from 'moment';
import ProjectActivityAnalysis from './ProjectActivityAnalysis';
import FormattedDate from '../../../components/ui/FormattedDate';
@@ -39,28 +40,80 @@ type Props = {
loading: boolean
};
-export default function ProjectActivityAnalysesList(props: Props) {
- if (props.analyses.length === 0) {
- return (
- <div className={props.className}>
- {props.loading
- ? <div className="text-center"><i className="spinner" /></div>
- : <span className="note">{translate('no_results')}</span>}
- </div>
- );
+export default class ProjectActivityAnalysesList extends React.PureComponent {
+ scrollContainer: HTMLElement;
+ badges: HTMLCollection<HTMLElement>;
+ props: Props;
+
+ componentDidMount() {
+ this.badges = document.getElementsByClassName('project-activity-version-badge');
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.analysis !== this.props.analyses && this.scrollContainer) {
+ this.scrollContainer.scrollTop = 0;
+ for (let i = 1; i < this.badges.length; i++) {
+ this.badges[i].removeAttribute('originOffsetTop');
+ this.badges[i].classList.remove('sticky');
+ }
+ this.handleScroll();
+ }
}
- const firstAnalysisKey = props.analyses[0].key;
- const byVersionByDay = getAnalysesByVersionByDay(props.analyses);
- return (
- <div className={props.className}>
- <ul className="project-activity-versions-list">
+ handleScroll = () => {
+ if (this.scrollContainer && this.badges) {
+ const scrollTop = this.scrollContainer.scrollTop;
+ if (scrollTop != null) {
+ let newScrollTop;
+ for (let i = 1; i < this.badges.length; i++) {
+ const badge = this.badges[i];
+ let originOffsetTop = badge.getAttribute('originOffsetTop');
+ if (originOffsetTop == null) {
+ originOffsetTop = badge.offsetTop;
+ badge.setAttribute('originOffsetTop', originOffsetTop.toString());
+ }
+ if (Number(originOffsetTop) < scrollTop + 18 + i * 2) {
+ if (!badge.classList.contains('sticky')) {
+ newScrollTop = originOffsetTop;
+ }
+ badge.classList.add('sticky');
+ } else {
+ badge.classList.remove('sticky');
+ }
+ }
+ if (newScrollTop != null) {
+ this.scrollContainer.scrollTop = newScrollTop - 6;
+ }
+ }
+ }
+ };
+
+ render() {
+ if (this.props.analyses.length === 0) {
+ return (
+ <div className={this.props.className}>
+ {this.props.loading
+ ? <div className="text-center"><i className="spinner" /></div>
+ : <span className="note">{translate('no_results')}</span>}
+ </div>
+ );
+ }
+
+ const firstAnalysisKey = this.props.analyses[0].key;
+ const byVersionByDay = getAnalysesByVersionByDay(this.props.analyses);
+ return (
+ <ul
+ className={classNames('project-activity-versions-list', this.props.className)}
+ onScroll={this.handleScroll}
+ ref={element => (this.scrollContainer = element)}>
{byVersionByDay.map((version, idx) => (
<li key={idx + version.version}>
{version.version &&
- <span className="badge project-activity-version-badge spacer-top big-spacer-bottom">
- {version.version}
- </span>}
+ <div className={classNames('project-activity-version-badge', { first: idx === 0 })}>
+ <span className="badge">
+ {version.version}
+ </span>
+ </div>}
<ul className="project-activity-days-list">
{Object.keys(version.byDay).map(day => (
<li
@@ -74,13 +127,13 @@ export default function ProjectActivityAnalysesList(props: Props) {
{version.byDay[day] != null &&
version.byDay[day].map(analysis => (
<ProjectActivityAnalysis
- addCustomEvent={props.addCustomEvent}
- addVersion={props.addVersion}
+ addCustomEvent={this.props.addCustomEvent}
+ addVersion={this.props.addVersion}
analysis={analysis}
- canAdmin={props.canAdmin}
- changeEvent={props.changeEvent}
- deleteAnalysis={props.deleteAnalysis}
- deleteEvent={props.deleteEvent}
+ canAdmin={this.props.canAdmin}
+ changeEvent={this.props.changeEvent}
+ deleteAnalysis={this.props.deleteAnalysis}
+ deleteEvent={this.props.deleteEvent}
isFirst={analysis.key === firstAnalysisKey}
key={analysis.key}
version={version.version}
@@ -92,8 +145,8 @@ export default function ProjectActivityAnalysesList(props: Props) {
</ul>
</li>
))}
- {props.analysesLoading && <li className="text-center"><i className="spinner" /></li>}
+ {this.props.analysesLoading && <li className="text-center"><i className="spinner" /></li>}
</ul>
- </div>
- );
+ );
+ }
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
index 41133f90f55..5fbdb2bdf41 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
@@ -41,7 +41,7 @@ type Props = {
export default function ProjectActivityAnalysis(props: Props) {
const { date, events } = props.analysis;
const { isFirst, canAdmin } = props;
-
+ const analysisTitle = translate('project_activity.analysis');
return (
<li className="project-activity-analysis clearfix">
<div className="project-activity-time spacer-right">
@@ -49,7 +49,7 @@ export default function ProjectActivityAnalysis(props: Props) {
</div>
<div
className="project-activity-analysis-icon little-spacer-top big-spacer-right"
- title={translate('project_activity.analysis')}
+ title={analysisTitle}
/>
{canAdmin &&
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
index e27947e03aa..00e6a1a3134 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
@@ -4,9 +4,12 @@
}
.project-activity-page-side-outer {
+ position: relative;
width: 400px;
- overflow: auto;
margin-bottom: 0;
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
}
.project-activity-page-side-outer > .boxed-group-inner {
@@ -31,8 +34,13 @@
align-items: stretch;
}
-.project-activity-list {
+.project-activity-versions-list {
max-width: 400px;
+ box-sizing: border-box;
+ overflow: auto;
+ flex-grow: 1;
+ flex-shrink: 0;
+ padding-top: 52px;
}
.project-activity-graph-container {
@@ -57,7 +65,8 @@
.project-activity-days-list {}
.project-activity-day {
- margin-bottom: 32px;
+ margin-top: 8px;
+ margin-bottom: 24px;
}
.project-activity-day:last-child {
@@ -169,9 +178,28 @@
}
.project-activity-version-badge {
+ margin-left: -12px;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ background-color: white;
+}
+
+.project-activity-version-badge.sticky, .project-activity-version-badge.first {
+ position: absolute;
+ top: 0;
+ left: 12px;
+ right: 20px;
+ padding-top: 24px;
+ z-index: 100;
+}
+
+.project-activity-version-badge.sticky + .project-activity-days-list {
+ padding-top: 36px;
+}
+
+.project-activity-version-badge .badge {
vertical-align: middle;
- padding: 4px 14px 4px 16px;
- margin-left: -14px;
+ padding: 4px 14px 4px 14px;
border-radius: 2px;
font-weight: bold;
font-size: 12px;