]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9415 Click on the graph or on the list to see the matching analysis on project...
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 5 Jul 2017 13:55:14 +0000 (15:55 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 13 Jul 2017 12:34:17 +0000 (14:34 +0200)
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
server/sonar-web/src/main/js/apps/projectActivity/types.js
server/sonar-web/src/main/js/apps/projectActivity/utils.js
server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js

index a2aa8084c66600f385257522dcdf73f9ed2409bd..9c98102a95e93f1de07b50e060ca82f88dae65dc 100644 (file)
@@ -25,7 +25,12 @@ import { throttle } from 'lodash';
 import ProjectActivityAnalysis from './ProjectActivityAnalysis';
 import FormattedDate from '../../../components/ui/FormattedDate';
 import { translate } from '../../../helpers/l10n';
-import { activityQueryChanged, getAnalysesByVersionByDay } from '../utils';
+import {
+  activityQueryChanged,
+  getAnalysesByVersionByDay,
+  selectedDateQueryChanged
+} from '../utils';
+import type { RawQuery } from '../../../helpers/query';
 import type { Analysis, Query } from '../types';
 
 type Props = {
@@ -39,13 +44,15 @@ type Props = {
   deleteAnalysis: (analysis: string) => Promise<*>,
   deleteEvent: (analysis: string, event: string) => Promise<*>,
   loading: boolean,
-  query: Query
+  query: Query,
+  updateQuery: RawQuery => void
 };
 
 export default class ProjectActivityAnalysesList extends React.PureComponent {
-  scrollContainer: HTMLElement;
+  analyses: HTMLCollection<HTMLElement>;
   badges: HTMLCollection<HTMLElement>;
   props: Props;
+  scrollContainer: HTMLElement;
 
   constructor(props: Props) {
     super(props);
@@ -54,22 +61,36 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
 
   componentDidMount() {
     this.badges = document.getElementsByClassName('project-activity-version-badge');
+    this.analyses = document.getElementsByClassName('project-activity-analysis');
   }
 
   componentDidUpdate(prevProps: Props) {
-    if (prevProps.analysis !== this.props.analyses && this.scrollContainer) {
-      if (activityQueryChanged(prevProps.query, this.props.query)) {
-        this.scrollContainer.scrollTop = 0;
-      }
-      for (let i = 1; i < this.badges.length; i++) {
-        this.badges[i].removeAttribute('originOffsetTop');
-        this.badges[i].classList.remove('sticky');
+    if (this.scrollContainer) {
+      const selectedDateChanged = selectedDateQueryChanged(prevProps.query, this.props.query);
+      if (selectedDateChanged || prevProps.analysis !== this.props.analyses) {
+        if (selectedDateChanged && this.props.query.selectedDate) {
+          const selectedDate = this.props.query.selectedDate.valueOf();
+          for (let i = 1; i < this.analyses.length; i++) {
+            if (Number(this.analyses[i].getAttribute('data-date')) === selectedDate) {
+              const containerHeight = this.scrollContainer.offsetHeight - 100;
+              const scrollDiff = Math.abs(
+                this.scrollContainer.scrollTop - this.analyses[i].offsetTop
+              );
+              // Center only the extremities and the ones outside of the container
+              if (scrollDiff > containerHeight || scrollDiff < 100) {
+                this.resetScrollTop(this.analyses[i].offsetTop - containerHeight / 2);
+              }
+              break;
+            }
+          }
+        } else if (activityQueryChanged(prevProps.query, this.props.query)) {
+          this.resetScrollTop(0, true);
+        }
       }
-      this.handleScroll();
     }
   }
 
-  handleScroll = () => {
+  updateStickyBadges = (forceBadgeAlignement?: boolean) => {
     if (this.scrollContainer && this.badges) {
       const scrollTop = this.scrollContainer.scrollTop;
       if (scrollTop != null) {
@@ -78,11 +99,12 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
           const badge = this.badges[i];
           let originOffsetTop = badge.getAttribute('originOffsetTop');
           if (originOffsetTop == null) {
+            // Set the originOffsetTop attribute, to avoid using getBoundingClientRect
             originOffsetTop = badge.offsetTop;
             badge.setAttribute('originOffsetTop', originOffsetTop.toString());
           }
           if (Number(originOffsetTop) < scrollTop + 18 + i * 2) {
-            if (!badge.classList.contains('sticky')) {
+            if (forceBadgeAlignement && !badge.classList.contains('sticky')) {
               newScrollTop = originOffsetTop;
             }
             badge.classList.add('sticky');
@@ -90,12 +112,26 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
             badge.classList.remove('sticky');
           }
         }
-        if (newScrollTop != null) {
+        if (forceBadgeAlignement && newScrollTop != null) {
           this.scrollContainer.scrollTop = newScrollTop - 6;
         }
       }
     }
   };
+  handleScroll = () => this.updateStickyBadges(true);
+
+  resetScrollTop = (newScrollTop: number, forceBadgeAlignement?: boolean) => {
+    this.scrollContainer.scrollTop = newScrollTop;
+    for (let i = 1; i < this.badges.length; i++) {
+      this.badges[i].removeAttribute('originOffsetTop');
+      this.badges[i].classList.remove('sticky');
+    }
+    this.updateStickyBadges(forceBadgeAlignement);
+  };
+
+  updateSelectedDate = (date: Date) => {
+    this.props.updateQuery({ selectedDate: date });
+  };
 
   render() {
     if (this.props.analyses.length === 0) {
@@ -110,6 +146,9 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
 
     const firstAnalysisKey = this.props.analyses[0].key;
     const byVersionByDay = getAnalysesByVersionByDay(this.props.analyses);
+    const selectedDate = this.props.query.selectedDate
+      ? this.props.query.selectedDate.valueOf()
+      : null;
     return (
       <ul
         className={classNames('project-activity-versions-list', this.props.className)}
@@ -145,6 +184,8 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
                           deleteEvent={this.props.deleteEvent}
                           isFirst={analysis.key === firstAnalysisKey}
                           key={analysis.key}
+                          selected={analysis.date.valueOf() === selectedDate}
+                          updateSelectedDate={this.updateSelectedDate}
                         />
                       ))}
                   </ul>
index 1cd17b22bb9b6ed77f99c3ee3731fcfcc3603293..4ccb7cb971830d2892be9e1f174d1c4058149d26 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import classNames from 'classnames';
 import Events from './Events';
 import AddEventForm from './forms/AddEventForm';
 import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
@@ -34,72 +35,87 @@ type Props = {
   changeEvent: (event: string, name: string) => Promise<*>,
   deleteAnalysis: (analysis: string) => Promise<*>,
   deleteEvent: (analysis: string, event: string) => Promise<*>,
-  isFirst: boolean
+  isFirst: boolean,
+  selected: boolean,
+  updateSelectedDate: Date => void
 };
 
-export default function ProjectActivityAnalysis(props: Props) {
-  const { date, events } = props.analysis;
-  const { isFirst, canAdmin } = props;
-  const analysisTitle = translate('project_activity.analysis');
-  const hasVersion = events.find(event => event.category === 'VERSION') != null;
-  return (
-    <li className="project-activity-analysis clearfix">
-      <div className="project-activity-time spacer-right">
-        <FormattedDate date={date} format="LT" tooltipFormat="LTS" />
-      </div>
-      <div
-        className="project-activity-analysis-icon little-spacer-top big-spacer-right"
-        title={analysisTitle}
-      />
+export default class ProjectActivityAnalysis extends React.PureComponent {
+  props: Props;
 
-      {canAdmin &&
-        <div className="project-activity-analysis-actions spacer-left">
-          <div className="dropdown display-inline-block">
-            <button
-              className="js-analysis-actions button-small button-compact dropdown-toggle"
-              data-toggle="dropdown">
-              <i className="icon-settings" />
-              {' '}
-              <i className="icon-dropdown" />
-            </button>
-            <ul className="dropdown-menu dropdown-menu-right">
-              {!hasVersion &&
+  handleClick = () => this.props.updateSelectedDate(this.props.analysis.date);
+
+  render() {
+    const { analysis, isFirst, canAdmin } = this.props;
+    const { date, events } = analysis;
+    const analysisTitle = translate('project_activity.analysis');
+    const hasVersion = events.find(event => event.category === 'VERSION') != null;
+    return (
+      <li
+        className={classNames('project-activity-analysis clearfix', {
+          selected: this.props.selected
+        })}
+        data-date={date.valueOf()}
+        onClick={this.handleClick}
+        role="listitem"
+        tabIndex="0">
+        <div className="project-activity-time spacer-right">
+          <FormattedDate date={date} format="LT" tooltipFormat="LTS" />
+        </div>
+        <div
+          className="project-activity-analysis-icon little-spacer-top big-spacer-right"
+          title={analysisTitle}
+        />
+
+        {canAdmin &&
+          <div className="project-activity-analysis-actions spacer-left">
+            <div className="dropdown display-inline-block">
+              <button
+                className="js-analysis-actions button-small button-compact dropdown-toggle"
+                data-toggle="dropdown">
+                <i className="icon-settings" />
+                {' '}
+                <i className="icon-dropdown" />
+              </button>
+              <ul className="dropdown-menu dropdown-menu-right">
+                {!hasVersion &&
+                  <li>
+                    <AddEventForm
+                      addEvent={this.props.addVersion}
+                      analysis={analysis}
+                      addEventButtonText="project_activity.add_version"
+                    />
+                  </li>}
                 <li>
                   <AddEventForm
-                    addEvent={props.addVersion}
-                    analysis={props.analysis}
-                    addEventButtonText="project_activity.add_version"
-                  />
-                </li>}
-              <li>
-                <AddEventForm
-                  addEvent={props.addCustomEvent}
-                  analysis={props.analysis}
-                  addEventButtonText="project_activity.add_custom_event"
-                />
-              </li>
-              {!isFirst && <li role="separator" className="divider" />}
-              {!isFirst &&
-                <li>
-                  <RemoveAnalysisForm
-                    analysis={props.analysis}
-                    deleteAnalysis={props.deleteAnalysis}
+                    addEvent={this.props.addCustomEvent}
+                    analysis={analysis}
+                    addEventButtonText="project_activity.add_custom_event"
                   />
-                </li>}
-            </ul>
-          </div>
-        </div>}
+                </li>
+                {!isFirst && <li role="separator" className="divider" />}
+                {!isFirst &&
+                  <li>
+                    <RemoveAnalysisForm
+                      analysis={analysis}
+                      deleteAnalysis={this.props.deleteAnalysis}
+                    />
+                  </li>}
+              </ul>
+            </div>
+          </div>}
 
-      {events.length > 0 &&
-        <Events
-          analysis={props.analysis.key}
-          canAdmin={canAdmin}
-          changeEvent={props.changeEvent}
-          deleteEvent={props.deleteEvent}
-          events={events}
-          isFirst={props.isFirst}
-        />}
+        {events.length > 0 &&
+          <Events
+            analysis={analysis.key}
+            canAdmin={canAdmin}
+            changeEvent={this.props.changeEvent}
+            deleteEvent={this.props.deleteEvent}
+            events={events}
+            isFirst={this.props.isFirst}
+          />}
 
-    </li>
-  );
+      </li>
+    );
+  }
 }
index dc7d213f30f38bec959e13c3e133abb7b76bd877..46b6dd4fe53c3cc22f53028baf6e4ddbd2601d7c 100644 (file)
@@ -112,6 +112,7 @@ export default class ProjectActivityApp extends React.PureComponent {
               deleteEvent={this.props.deleteEvent}
               loading={this.props.loading}
               query={this.props.query}
+              updateQuery={this.props.updateQuery}
             />
           </div>
           <div className="project-activity-layout-page-main">
index 4baa9a82dbaf0314a6c0755ef25401652221b910..03605c8114c5ab14d7b7403104d14797e696bd9c 100644 (file)
@@ -40,7 +40,6 @@ type Props = {
 };
 
 type State = {
-  selectedDate?: ?Date,
   graphStartDate: ?Date,
   graphEndDate: ?Date,
   series: Array<Serie>
@@ -97,7 +96,7 @@ export default class ProjectActivityGraphs extends React.PureComponent {
     }
   };
 
-  updateSelectedDate = (selectedDate: ?Date) => this.setState({ selectedDate });
+  updateSelectedDate = (selectedDate: ?Date) => this.props.updateQuery({ selectedDate });
 
   updateGraphZoom = (graphStartDate: ?Date, graphEndDate: ?Date) => {
     if (graphEndDate != null && graphStartDate != null) {
@@ -138,7 +137,7 @@ export default class ProjectActivityGraphs extends React.PureComponent {
           measuresHistory={this.props.measuresHistory}
           metricsType={metricsType}
           project={this.props.project}
-          selectedDate={this.state.selectedDate}
+          selectedDate={this.props.query.selectedDate}
           series={series}
           updateGraphZoom={this.updateGraphZoom}
           updateSelectedDate={this.updateSelectedDate}
index fbea7a4d2c85890d183b4aba0a61e85121b8fa26..d07737c8d02c30e3e4183d26d49456d063aba746 100644 (file)
@@ -47,6 +47,7 @@ type Props = {
 };
 
 type State = {
+  selectedDate?: ?Date,
   tooltipIdx: ?number,
   tooltipXPos: ?number
 };
@@ -86,8 +87,8 @@ export default class StaticGraphs extends React.PureComponent {
 
   hasSeriesData = () => some(this.props.series, serie => serie.data && serie.data.length > 2);
 
-  updateTooltipPos = (tooltipXPos: ?number, tooltipIdx: ?number) =>
-    this.setState({ tooltipXPos, tooltipIdx });
+  updateTooltip = (selectedDate: ?Date, tooltipXPos: ?number, tooltipIdx: ?number) =>
+    this.setState({ selectedDate, tooltipXPos, tooltipIdx });
 
   render() {
     const { loading } = this.props;
@@ -111,8 +112,8 @@ export default class StaticGraphs extends React.PureComponent {
         </div>
       );
     }
-
-    const { graph, selectedDate, series } = this.props;
+    const { selectedDate, tooltipIdx, tooltipXPos } = this.state;
+    const { graph, series } = this.props;
     return (
       <div className="project-activity-graph-container">
         <StaticGraphsLegend series={series} />
@@ -129,16 +130,16 @@ export default class StaticGraphs extends React.PureComponent {
                   formatYTick={this.formatValue}
                   leakPeriodDate={this.props.leakPeriodDate}
                   metricType={this.props.metricsType}
-                  selectedDate={selectedDate}
+                  selectedDate={this.props.selectedDate}
                   series={series}
                   showAreas={['coverage', 'duplications'].includes(graph)}
                   startDate={this.props.graphStartDate}
                   updateSelectedDate={this.props.updateSelectedDate}
-                  updateTooltipPos={this.updateTooltipPos}
+                  updateTooltip={this.updateTooltip}
                   updateZoom={this.props.updateGraphZoom}
                 />
                 {selectedDate != null &&
-                  this.state.tooltipXPos != null &&
+                  tooltipXPos != null &&
                   <GraphsTooltips
                     formatValue={this.formatValue}
                     graph={graph}
@@ -146,8 +147,8 @@ export default class StaticGraphs extends React.PureComponent {
                     measuresHistory={this.props.measuresHistory}
                     selectedDate={selectedDate}
                     series={series}
-                    tooltipIdx={this.state.tooltipIdx}
-                    tooltipPos={this.state.tooltipXPos}
+                    tooltipIdx={tooltipIdx}
+                    tooltipPos={tooltipXPos}
                   />}
               </div>
             )}
index b97c9aa90541772fc5c549c2d33ccf51b04fbbfe..91f1dcdc0e4d43f2091dd68bbc8acb06d369f42a 100644 (file)
@@ -117,6 +117,7 @@ exports[`should render correctly 1`] = `
             "project": "org.sonarsource.sonarqube:sonarqube",
           }
         }
+        updateQuery={[Function]}
       />
     </div>
     <div
index 1702b791a096c0c0040ac39a20776da27b7e2d58..903bd9ff4f950d00a0647f86c9caf7893df5da1b 100644 (file)
   cursor: pointer;
 }
 
+.project-activity-analysis.selected {
+  background-color: #ecf6fe;
+}
+
+.project-activity-analysis:focus {
+  outline: none;
+}
+
 .project-activity-analysis:hover {
   background-color: #ecf6fe;
 }
index 106fb632866459c3914c63d20678aa10582e6248..f3c75fa853581fd60adb805e0e0ed7512f7ebb82 100644 (file)
@@ -53,5 +53,6 @@ export type Query = {
   from?: Date,
   graph: string,
   project: string,
-  to?: Date
+  to?: Date,
+  selectedDate?: Date
 };
index 50b3ab32929acce0b36bcca7b5c1a8acb7e32eea..5e04dcc54360d16132b1d2bfd76b5508356e72b5 100644 (file)
@@ -49,16 +49,25 @@ export const GRAPHS_METRICS = {
 };
 
 export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
-  prevQuery.category !== nextQuery.category ||
-  prevQuery.from !== nextQuery.from ||
-  prevQuery.to !== nextQuery.to;
+  prevQuery.category !== nextQuery.category || datesQueryChanged(prevQuery, nextQuery);
 
-export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
-  prevQuery.from !== nextQuery.from || prevQuery.to !== nextQuery.to;
+export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => {
+  const nextFrom = nextQuery.from ? nextQuery.from.valueOf() : null;
+  const previousFrom = prevQuery.from ? prevQuery.from.valueOf() : null;
+  const nextTo = nextQuery.to ? nextQuery.to.valueOf() : null;
+  const previousTo = prevQuery.to ? prevQuery.to.valueOf() : null;
+  return previousFrom !== nextFrom || previousTo !== nextTo;
+};
 
 export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
   prevQuery.graph !== nextQuery.graph;
 
+export const selectedDateQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => {
+  const nextSelectedDate = nextQuery.selectedDate ? nextQuery.selectedDate.valueOf() : null;
+  const previousSelectedDate = prevQuery.selectedDate ? prevQuery.selectedDate.valueOf() : null;
+  return nextSelectedDate !== previousSelectedDate;
+};
+
 export const generateCoveredLinesMetric = (
   uncoveredLines: MeasureHistory,
   measuresHistory: Array<MeasureHistory>,
@@ -143,7 +152,8 @@ export const parseQuery = (urlQuery: RawQuery): Query => ({
   from: parseAsDate(urlQuery['from']),
   graph: parseGraph(urlQuery['graph']),
   project: parseAsString(urlQuery['id']),
-  to: parseAsDate(urlQuery['to'])
+  to: parseAsDate(urlQuery['to']),
+  selectedDate: parseAsDate(urlQuery['selected_date'])
 });
 
 export const serializeQuery = (query: Query): RawQuery =>
@@ -160,6 +170,7 @@ export const serializeUrlQuery = (query: Query): RawQuery => {
     from: serializeDate(query.from),
     graph: serializeGraph(query.graph),
     id: serializeString(query.project),
-    to: serializeDate(query.to)
+    to: serializeDate(query.to),
+    selected_date: serializeDate(query.selectedDate)
   });
 };
index fc2123d1fe430c76c8f27fda3dca1e41056274c2..413a53a78d337d439ac50b0e74a3be6bd0fdffc6 100644 (file)
@@ -50,7 +50,7 @@ type Props = {
   showEventMarkers?: boolean,
   startDate: ?Date,
   updateSelectedDate?: (selectedDate: ?Date) => void,
-  updateTooltipPos?: (tooltipXPos: ?number, tooltipIdx: ?number) => void,
+  updateTooltip?: (selectedDate: ?Date, tooltipXPos: ?number, tooltipIdx: ?number) => void,
   updateZoom?: (start: ?Date, endDate: ?Date) => void,
   zoomSpeed: number
 };
@@ -59,6 +59,7 @@ type State = {
   maxXRange: Array<number>,
   mouseOver?: boolean,
   mouseOverlayPos?: { [string]: number },
+  selectedDate: ?Date,
   selectedDateXPos: ?number,
   selectedDateIdx: ?number,
   yScale: Scale,
@@ -78,8 +79,9 @@ export default class AdvancedTimeline extends React.PureComponent {
   constructor(props: Props) {
     super(props);
     const scales = this.getScales(props);
-    this.state = { ...scales, ...this.getSelectedDatePos(scales.xScale, props.selectedDate) };
-    this.updateSelectedDate = throttle(this.updateSelectedDate, 40);
+    const selectedDatePos = this.getSelectedDatePos(scales.xScale, props.selectedDate);
+    this.state = { ...scales, ...selectedDatePos };
+    this.updateTooltipPos = throttle(this.updateTooltipPos, 40);
   }
 
   componentWillReceiveProps(nextProps: Props) {
@@ -100,8 +102,9 @@ export default class AdvancedTimeline extends React.PureComponent {
       const xScale = scales ? scales.xScale : this.state.xScale;
       const selectedDatePos = this.getSelectedDatePos(xScale, nextProps.selectedDate);
       this.setState({ ...scales, ...selectedDatePos });
-      if (nextProps.updateTooltipPos) {
-        nextProps.updateTooltipPos(
+      if (nextProps.updateTooltip) {
+        nextProps.updateTooltip(
+          selectedDatePos.selectedDate,
           selectedDatePos.selectedDateXPos,
           selectedDatePos.selectedDateIdx
         );
@@ -158,12 +161,13 @@ export default class AdvancedTimeline extends React.PureComponent {
         this.props.series.some(serie => serie.data[idx].y || serie.data[idx].y === 0)
       ) {
         return {
+          selectedDate,
           selectedDateXPos: xScale(selectedDate),
           selectedDateIdx: idx
         };
       }
     }
-    return { selectedDateXPos: null, selectedDateIdx: null };
+    return { selectedDate: null, selectedDateXPos: null, selectedDateIdx: null };
   };
 
   getEventMarker = (size: number) => {
@@ -197,31 +201,43 @@ export default class AdvancedTimeline extends React.PureComponent {
 
   handleMouseMove = (evt: MouseEvent & { target: HTMLElement }) => {
     const parentBbox = this.getMouseOverlayPos(evt.target);
-    this.updateSelectedDate(evt.pageX - parentBbox.left);
+    this.updateTooltipPos(evt.pageX - parentBbox.left);
   };
 
   handleMouseEnter = () => this.setState({ mouseOver: true });
 
   handleMouseOut = (evt: Event & { relatedTarget: HTMLElement }) => {
-    const { updateSelectedDate } = this.props;
+    const { updateTooltip } = this.props;
     const targetClass = evt.relatedTarget && typeof evt.relatedTarget.className === 'string'
       ? evt.relatedTarget.className
       : '';
     if (
-      !updateSelectedDate ||
+      !updateTooltip ||
       targetClass.includes('bubble-popup') ||
       targetClass.includes('graph-tooltip')
     ) {
       return;
     }
-    this.setState({ mouseOver: false });
-    updateSelectedDate(null);
+    this.setState({
+      mouseOver: false,
+      selectedDate: null,
+      selectedDateXPos: null,
+      selectedDateIdx: null
+    });
+    updateTooltip(null, null, null);
   };
 
-  updateSelectedDate = (xPos: number) => {
+  handleClick = () => {
     const { updateSelectedDate } = this.props;
+    if (updateSelectedDate) {
+      updateSelectedDate(this.state.selectedDate);
+    }
+  };
+
+  updateTooltipPos = (xPos: number) => {
     const firstSerie = this.props.series[0];
-    if (this.state.mouseOver && firstSerie && updateSelectedDate) {
+    if (this.state.mouseOver && firstSerie) {
+      const { updateTooltip } = this.props;
       const date = this.state.xScale.invert(xPos);
       const bisectX = bisector(d => d.x).right;
       let idx = bisectX(firstSerie.data, date);
@@ -231,7 +247,12 @@ export default class AdvancedTimeline extends React.PureComponent {
         if (!nextPoint || (previousPoint && date - previousPoint.x <= nextPoint.x - date)) {
           idx--;
         }
-        updateSelectedDate(firstSerie.data[idx].x);
+        const selectedDate = firstSerie.data[idx].x;
+        const xPos = this.state.xScale(selectedDate);
+        this.setState({ selectedDate, selectedDateXPos: xPos, selectedDateIdx: idx });
+        if (updateTooltip) {
+          updateTooltip(selectedDate, xPos, idx);
+        }
       }
     }
   };
@@ -428,11 +449,14 @@ export default class AdvancedTimeline extends React.PureComponent {
     if (zoomEnabled) {
       mouseEvents.onWheel = this.handleWheel;
     }
-    if (this.props.updateSelectedDate) {
+    if (this.props.updateTooltip) {
       mouseEvents.onMouseEnter = this.handleMouseEnter;
       mouseEvents.onMouseMove = this.handleMouseMove;
       mouseEvents.onMouseOut = this.handleMouseOut;
     }
+    if (this.props.updateSelectedDate) {
+      mouseEvents.onClick = this.handleClick;
+    }
     return (
       <rect
         className="chart-mouse-events-overlay"