diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-06-27 16:13:00 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-07-04 14:15:34 +0200 |
commit | 3a2e4d3af7939b8017379527e0213b113a6d52e1 (patch) | |
tree | 211d1695827f2b9b4fd06bfc8eb582a84ffde711 | |
parent | 3a604bda9aa574c291f764ca8791b9af91d66a67 (diff) | |
download | sonarqube-3a2e4d3af7939b8017379527e0213b113a6d52e1.tar.gz sonarqube-3a2e4d3af7939b8017379527e0213b113a6d52e1.zip |
SONAR-8550 Make the analysis version sticky in the project activity list
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; |