This is an automated email from the ASF dual-hosted git repository.

vogievetsky pushed a commit to branch segment_timeline2
in repository https://gitbox.apache.org/repos/asf/druid.git

commit 4a1286048a6b647943cdc5aae20a95c2aa2689fb
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Wed Nov 13 13:12:17 2024 -0800

    portal bubble
---
 .../{bubble.scss => portal-bubble.scss}            |  2 +-
 .../{bubble.tsx => portal-bubble.tsx}              | 20 ++++++---
 .../segment-timeline/segment-bar-chart-render.tsx  | 52 +++++++++++++++++-----
 web-console/src/utils/duration/duration.ts         | 41 +++++++++--------
 4 files changed, 77 insertions(+), 38 deletions(-)

diff --git a/web-console/src/components/segment-timeline/bubble.scss 
b/web-console/src/components/segment-timeline/portal-bubble.scss
similarity index 98%
rename from web-console/src/components/segment-timeline/bubble.scss
rename to web-console/src/components/segment-timeline/portal-bubble.scss
index 5961ae4fbe4..38663be81eb 100644
--- a/web-console/src/components/segment-timeline/bubble.scss
+++ b/web-console/src/components/segment-timeline/portal-bubble.scss
@@ -18,7 +18,7 @@
 
 @import '../../variables';
 
-.bubble {
+.portal-bubble {
   position: absolute;
   @include card-like;
   padding: 5px;
diff --git a/web-console/src/components/segment-timeline/bubble.tsx 
b/web-console/src/components/segment-timeline/portal-bubble.tsx
similarity index 68%
rename from web-console/src/components/segment-timeline/bubble.tsx
rename to web-console/src/components/segment-timeline/portal-bubble.tsx
index efb72431379..e35c0246919 100644
--- a/web-console/src/components/segment-timeline/bubble.tsx
+++ b/web-console/src/components/segment-timeline/portal-bubble.tsx
@@ -18,25 +18,35 @@
 
 import classNames from 'classnames';
 import type { ReactNode } from 'react';
+import { useRef } from 'react';
 import { createPortal } from 'react-dom';
 
-import './bubble.scss';
+import { clamp } from '../../utils';
 
-interface BubbleProps {
+import './portal-bubble.scss';
+
+interface PortalBubbleProps {
   className?: string;
   openOn: { x: number; y: number; text: ReactNode } | undefined;
   direction?: 'up' | 'down';
   mute?: boolean;
 }
 
-export const Bubble = function Bubble(props: BubbleProps) {
+export const PortalBubble = function PortalBubble(props: PortalBubbleProps) {
   const { className, openOn, direction = 'up', mute } = props;
+  const ref = useRef<HTMLDivElement | null>(null);
   if (!openOn) return null;
 
+  const div: HTMLDivElement | null = ref.current;
+  const myWidth = div ? div.offsetWidth : 200;
+
+  const x = clamp(openOn.x, myWidth / 2, window.innerWidth - myWidth / 2);
+
   return createPortal(
     <div
-      className={classNames('bubble', className, direction, { mute })}
-      style={{ left: openOn.x, top: openOn.y }}
+      className={classNames('portal-bubble', className, direction, { mute })}
+      ref={ref}
+      style={{ left: x, top: openOn.y }}
     >
       {openOn.text}
     </div>,
diff --git 
a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx 
b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
index 6dcadc9b629..dfbb50fc628 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
@@ -38,15 +38,14 @@ import {
   groupByAsMap,
   minute,
   month,
-  prettyFormatIsoDate,
   TZ_UTC,
 } from '../../utils';
 import type { Margin, Stage } from '../../utils/stage';
 
-import { Bubble } from './bubble';
 import { ChartAxis } from './chart-axis';
 import type { IntervalBar, IntervalRow, IntervalStat, TrimmedIntervalRow } 
from './common';
 import { aggregateSegmentStats, formatIntervalStat } from './common';
+import { PortalBubble } from './portal-bubble';
 
 import './segment-bar-chart-render.scss';
 
@@ -63,6 +62,38 @@ const POSSIBLE_GRANULARITIES = [
 
 const EXTEND_X_SCALE_DOMAIN_BY = 1;
 
+function formatStartDuration(start: Date, duration: Duration): string {
+  let sliceLength;
+  const { singleSpan } = duration;
+  switch (singleSpan) {
+    case 'year':
+      sliceLength = 4;
+      break;
+
+    case 'month':
+      sliceLength = 7;
+      break;
+
+    case 'day':
+      sliceLength = 10;
+      break;
+
+    case 'hour':
+      sliceLength = 13;
+      break;
+
+    case 'minute':
+      sliceLength = 16;
+      break;
+
+    default:
+      sliceLength = 19;
+      break;
+  }
+
+  return `${start.toISOString().slice(0, sliceLength)}/${duration}`;
+}
+
 // ---------------------------------------
 // Load rule stuff
 
@@ -413,11 +444,10 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
       y: rect.y + CHART_MARGIN.top - 10,
       text: (
         <>
-          <div>{`${prettyFormatIsoDate(hoveredIntervalBar.start)
-            .replace(/:00:00$/, '')
-            .replace(/ 00$/, '')}/${hoveredIntervalBar.originalTimeSpan}${
-            hoveredIntervalBar.realtime ? ' (realtime)' : ''
-          }`}</div>
+          <div>{`${formatStartDuration(
+            hoveredIntervalBar.start,
+            hoveredIntervalBar.originalTimeSpan,
+          )}${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`}</div>
           <div>Datasource: {hoveredIntervalBar.datasource}</div>
           <div>{`Size: ${
             hoveredIntervalBar.realtime
@@ -469,7 +499,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
             className="gridline-x"
             transform="translate(0,0)"
             axis={axisLeft(statScale)
-              .ticks(3)
+              .tickValues(statScale.ticks(3).filter(v => v !== 0))
               .tickSize(-innerStage.width)
               .tickFormat(() => '')
               .tickSizeOuter(0)}
@@ -491,7 +521,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           <ChartAxis
             className="axis-y"
             axis={axisLeft(statScale)
-              .ticks(5)
+              .ticks(3)
               .tickFormat(e => formatTickRate(e.valueOf()))}
           />
           <g className="bar-group">
@@ -567,8 +597,8 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           <div className="no-data-text">There are no segments in the selected 
range</div>
         </div>
       )}
-      <Bubble openOn={hoveredOpenOn} mute direction="up" />
-      <Bubble openOn={bubbleOpenOn} mute direction="down" />
+      <PortalBubble openOn={hoveredOpenOn} mute direction="up" />
+      <PortalBubble openOn={bubbleOpenOn} mute direction="down" />
     </div>
   );
 };
diff --git a/web-console/src/utils/duration/duration.ts 
b/web-console/src/utils/duration/duration.ts
index e582556b3f1..bc7236821f1 100755
--- a/web-console/src/utils/duration/duration.ts
+++ b/web-console/src/utils/duration/duration.ts
@@ -21,23 +21,22 @@ import { capitalizeFirst, pluralIfNeeded } from 
'../general';
 
 export const TZ_UTC = 'Etc/UTC';
 
-const SPANS_WITH_WEEK = ['year', 'month', 'week', 'day', 'hour', 'minute', 
'second'];
-const SPANS_WITHOUT_WEEK = ['year', 'month', 'day', 'hour', 'minute', 
'second'];
-const SPANS_WITHOUT_WEEK_OR_MONTH = ['year', 'day', 'hour', 'minute', 
'second'];
-const SPANS_UP_TO_DAY = ['day', 'hour', 'minute', 'second'];
-
-export interface DurationValue {
-  year?: number;
-  month?: number;
-  week?: number;
-  day?: number;
-  hour?: number;
-  minute?: number;
-  second?: number;
-
-  // Indexable
-  [span: string]: number | undefined;
-}
+export type DurationSpan = 'year' | 'month' | 'week' | 'day' | 'hour' | 
'minute' | 'second';
+
+const SPANS_WITH_WEEK: DurationSpan[] = [
+  'year',
+  'month',
+  'week',
+  'day',
+  'hour',
+  'minute',
+  'second',
+];
+const SPANS_WITHOUT_WEEK: DurationSpan[] = ['year', 'month', 'day', 'hour', 
'minute', 'second'];
+const SPANS_WITHOUT_WEEK_OR_MONTH: DurationSpan[] = ['year', 'day', 'hour', 
'minute', 'second'];
+const SPANS_UP_TO_DAY: DurationSpan[] = ['day', 'hour', 'minute', 'second'];
+
+export type DurationValue = Partial<Record<DurationSpan, number>>;
 
 const periodWeekRegExp = /^P(\d+)W$/;
 const periodRegExp = 
/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
@@ -116,8 +115,8 @@ function removeZeros(spans: DurationValue): DurationValue {
   return newSpans;
 }
 
-function fitIntoSpans(length: number, spansToCheck: string[]): Record<string, 
number> {
-  const spans: Record<string, number> = {};
+function fitIntoSpans(length: number, spansToCheck: DurationSpan[]): 
DurationValue {
+  const spans: DurationValue = {};
 
   let lengthLeft = length;
   for (let i = 0; i < spansToCheck.length; i++) {
@@ -138,7 +137,7 @@ function fitIntoSpans(length: number, spansToCheck: 
string[]): Record<string, nu
  * Represents an ISO duration like P1DT3H
  */
 export class Duration {
-  public readonly singleSpan?: string;
+  public readonly singleSpan?: DurationSpan;
   public readonly spans: Readonly<DurationValue>;
 
   static fromCanonicalLength(length: number, skipMonths = false): Duration {
@@ -180,7 +179,7 @@ export class Duration {
     const effectiveSpans: DurationValue =
       typeof spans === 'string' ? getSpansFromString(spans) : 
removeZeros(spans);
 
-    const usedSpans = Object.keys(effectiveSpans);
+    const usedSpans = Object.keys(effectiveSpans) as DurationSpan[];
     if (!usedSpans.length) throw new Error('Duration can not be empty');
     if (usedSpans.length === 1) {
       this.singleSpan = usedSpans[0];


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to