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 bc202c9e4923050b60f19a5ff58a5038a51389b9
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Mon Nov 18 12:49:16 2024 -0800

    fixes
---
 .../components/segment-timeline/portal-bubble.scss | 27 ++++++++-
 .../components/segment-timeline/portal-bubble.tsx  | 27 +++++++--
 .../segment-timeline/segment-bar-chart-render.scss | 10 +++-
 .../segment-timeline/segment-bar-chart-render.tsx  | 69 ++++++++++++++++------
 .../segment-timeline/segment-bar-chart.tsx         | 31 +++-------
 .../segment-timeline/segment-timeline.tsx          | 10 ++--
 6 files changed, 119 insertions(+), 55 deletions(-)

diff --git a/web-console/src/components/segment-timeline/portal-bubble.scss 
b/web-console/src/components/segment-timeline/portal-bubble.scss
index 38663be81eb..8fc2451bdb5 100644
--- a/web-console/src/components/segment-timeline/portal-bubble.scss
+++ b/web-console/src/components/segment-timeline/portal-bubble.scss
@@ -21,8 +21,6 @@
 .portal-bubble {
   position: absolute;
   @include card-like;
-  padding: 5px;
-  white-space: nowrap;
 
   .#{$bp-ns}-dark & {
     background: $dark-gray1;
@@ -39,4 +37,29 @@
   &.mute {
     pointer-events: none;
   }
+
+  & > .bubble-title-bar {
+    position: relative;
+    padding: 5px 5px 0 5px;
+    font-weight: bold;
+
+    &.with-close {
+      padding-right: 26px;
+
+      .close-button {
+        position: absolute;
+        top: 0;
+        right: 0;
+      }
+    }
+  }
+
+  & > .bubble-content {
+    padding: 5px;
+    white-space: nowrap;
+  }
+
+  .bubble-title-bar + .bubble-content {
+    padding-top: 0;
+  }
 }
diff --git a/web-console/src/components/segment-timeline/portal-bubble.tsx 
b/web-console/src/components/segment-timeline/portal-bubble.tsx
index e35c0246919..f12f6150404 100644
--- a/web-console/src/components/segment-timeline/portal-bubble.tsx
+++ b/web-console/src/components/segment-timeline/portal-bubble.tsx
@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+import { Button } from '@blueprintjs/core';
+import { IconNames } from '@blueprintjs/icons';
 import classNames from 'classnames';
 import type { ReactNode } from 'react';
 import { useRef } from 'react';
@@ -27,13 +29,14 @@ import './portal-bubble.scss';
 
 interface PortalBubbleProps {
   className?: string;
-  openOn: { x: number; y: number; text: ReactNode } | undefined;
+  openOn: { x: number; y: number; title?: string; text: ReactNode } | 
undefined;
   direction?: 'up' | 'down';
+  onClose?(): void;
   mute?: boolean;
 }
 
 export const PortalBubble = function PortalBubble(props: PortalBubbleProps) {
-  const { className, openOn, direction = 'up', mute } = props;
+  const { className, openOn, direction = 'up', onClose, mute } = props;
   const ref = useRef<HTMLDivElement | null>(null);
   if (!openOn) return null;
 
@@ -44,11 +47,27 @@ export const PortalBubble = function PortalBubble(props: 
PortalBubbleProps) {
 
   return createPortal(
     <div
-      className={classNames('portal-bubble', className, direction, { mute })}
+      className={classNames('portal-bubble', className, direction, {
+        mute: mute && !onClose,
+      })}
       ref={ref}
       style={{ left: x, top: openOn.y }}
     >
-      {openOn.text}
+      {(openOn.title || onClose) && (
+        <div className={classNames('bubble-title-bar', { 'with-close': 
Boolean(onClose) })}>
+          {openOn.title}
+          {onClose && (
+            <Button
+              className="close-button"
+              icon={IconNames.CROSS}
+              small
+              minimal
+              onClick={onClose}
+            />
+          )}
+        </div>
+      )}
+      <div className="bubble-content">{openOn.text}</div>
     </div>,
     document.body,
   );
diff --git 
a/web-console/src/components/segment-timeline/segment-bar-chart-render.scss 
b/web-console/src/components/segment-timeline/segment-bar-chart-render.scss
index 53d5f93d941..ec9f2d77446 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart-render.scss
+++ b/web-console/src/components/segment-timeline/segment-bar-chart-render.scss
@@ -89,7 +89,7 @@
     }
 
     .now-line {
-      stroke: orange;
+      stroke: $orange4;
       stroke-dasharray: 2, 2;
       opacity: 0.7;
     }
@@ -153,3 +153,11 @@
     pointer-events: none;
   }
 }
+
+.segment-bar-chart-bubble {
+  .button-bar {
+    padding-top: 5px;
+    display: flex;
+    gap: 5px;
+  }
+}
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 ac4ce6d8ed7..df4842988c3 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
@@ -16,8 +16,9 @@
  * limitations under the License.
  */
 
-import { Button } from '@blueprintjs/core';
+import { Button, Intent } from '@blueprintjs/core';
 import type { NonNullDateRange } from '@blueprintjs/datetime';
+import { IconNames } from '@blueprintjs/icons';
 import IntervalTree from '@flatten-js/interval-tree';
 import classNames from 'classnames';
 import { max, sort, sum } from 'd3-array';
@@ -205,12 +206,13 @@ export interface DatasourceRules {
 }
 
 export interface SegmentBarChartRenderProps {
+  intervalRows: IntervalRow[];
+  datasourceRules: DatasourceRules | undefined;
+
   stage: Stage;
   dateRange: NonNullDateRange;
   changeDateRange(dateRange: NonNullDateRange): void;
   shownIntervalStat: IntervalStat;
-  intervalRows: IntervalRow[];
-  datasourceRules: DatasourceRules | undefined;
   shownDatasource: string | undefined;
   changeShownDatasource(datasource: string | undefined): void;
 }
@@ -497,25 +499,27 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
 
   console.log('Bar chart render');
 
-  let hoveredOpenOn: { x: number; y: number; text: ReactNode } | undefined;
+  let hoveredOpenOn: { x: number; y: number; title?: string; text: ReactNode } 
| undefined;
   if (svgRef.current) {
     const rect = svgRef.current.getBoundingClientRect();
 
     if (bubbleInfo) {
       const hoveredIntervalBars = bubbleInfo.intervalBars;
 
+      let title: string | undefined;
       let text: ReactNode;
       if (hoveredIntervalBars.length === 0) {
-        text = bubbleInfo.timeLabel;
+        title = bubbleInfo.timeLabel;
+        text = '';
       } else if (hoveredIntervalBars.length === 1) {
         const hoveredIntervalBar = hoveredIntervalBars[0];
+        title = `${formatStartDuration(
+          hoveredIntervalBar.start,
+          hoveredIntervalBar.originalTimeSpan,
+        )}${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`;
         text = (
           <>
-            <div>{`${formatStartDuration(
-              hoveredIntervalBar.start,
-              hoveredIntervalBar.originalTimeSpan,
-            )}${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`}</div>
-            <div>{`Datasource: ${hoveredIntervalBar.datasource}`}</div>
+            {!shownDatasource && <div>{`Datasource: 
${hoveredIntervalBar.datasource}`}</div>}
             <div>{`Size: ${
               hoveredIntervalBar.realtime
                 ? 'estimated for realtime'
@@ -528,10 +532,12 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
       } else {
         const datasources = uniq(hoveredIntervalBars.map(b => b.datasource));
         const agg = aggregateSegmentStats(hoveredIntervalBars);
+        title = bubbleInfo.timeLabel;
         text = (
           <>
-            <div>{bubbleInfo.timeLabel}</div>
-            <div>{`Totals for ${pluralIfNeeded(datasources.length, 
'datasource')}:`}</div>
+            {!shownDatasource && (
+              <div>{`Totals for ${pluralIfNeeded(datasources.length, 
'datasource')}:`}</div>
+            )}
             <div>{`Size: ${formatIntervalStat('size', agg.size)}`}</div>
             <div>{`Rows: ${formatIntervalStat('rows', agg.rows)}`}</div>
             <div>{`Segments: ${formatIntervalStat('segments', 
agg.segments)}`}</div>
@@ -545,31 +551,50 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           CHART_MARGIN.left +
           timeScale(new Date((bubbleInfo.start.valueOf() + 
bubbleInfo.end.valueOf()) / 2)),
         y: rect.y + CHART_MARGIN.top - 10,
+        title,
         text,
       };
     } else if (selection) {
+      const selectedBars = intervalTree.search([
+        selection.start.valueOf() + 1,
+        selection.end.valueOf() - 1,
+      ]) as IntervalBar[];
+      const datasources = uniq(selectedBars.map(b => b.datasource));
+      const agg = aggregateSegmentStats(selectedBars);
       hoveredOpenOn = {
         x:
           rect.x +
           CHART_MARGIN.left +
           timeScale(new Date((selection.start.valueOf() + 
selection.end.valueOf()) / 2)),
         y: rect.y + CHART_MARGIN.top - 10,
+        title: `${formatIsoDateOnly(selection.start)} → 
${formatIsoDateOnly(selection.end)}`,
         text: (
           <>
-            <div>{`${formatIsoDateOnly(selection.start)} → ${formatIsoDateOnly(
-              selection.end,
-            )}`}</div>
+            {!shownDatasource && (
+              <div>{`Totals for ${pluralIfNeeded(datasources.length, 
'datasource')}:`}</div>
+            )}
+            <div>{`Size: ${formatIntervalStat('size', agg.size)}`}</div>
+            <div>{`Rows: ${formatIntervalStat('rows', agg.rows)}`}</div>
+            <div>{`Segments: ${formatIntervalStat('segments', 
agg.segments)}`}</div>
             {selection.done && (
-              <div>
+              <div className="button-bar">
                 <Button
+                  icon={IconNames.ZOOM_IN}
                   text="Zoom in"
+                  intent={Intent.PRIMARY}
+                  small
                   onClick={() => {
                     if (!selection) return;
                     setSelection(undefined);
                     changeDateRange([selection.start, selection.end]);
                   }}
                 />
-                <Button text="Cancel" onClick={() => setSelection(undefined)} 
/>
+                <Button
+                  text="Open in segments view"
+                  small
+                  rightIcon={IconNames.ARROW_TOP_RIGHT}
+                  onClick={() => null}
+                />
               </div>
             )}
           </>
@@ -660,7 +685,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
                 />
               );
             })}
-            {bubbleInfo &&
+            {bubbleInfo?.intervalBars.length === 1 &&
               bubbleInfo.intervalBars.map((intervalBar, i) => (
                 <rect key={i} className="hovered-bar" 
{...segmentBarToRect(intervalBar)} />
               ))}
@@ -699,7 +724,13 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           <div className="no-data-text">There are no segments in the selected 
range</div>
         </div>
       )}
-      <PortalBubble openOn={hoveredOpenOn} mute={!selection?.done} 
direction="up" />
+      <PortalBubble
+        className="segment-bar-chart-bubble"
+        openOn={hoveredOpenOn}
+        onClose={selection?.done ? () => setSelection(undefined) : undefined}
+        mute
+        direction="up"
+      />
     </div>
   );
 };
diff --git a/web-console/src/components/segment-timeline/segment-bar-chart.tsx 
b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
index 5a8c162f0c2..20b1dec7045 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-import type { NonNullDateRange } from '@blueprintjs/datetime';
 import { C, F, L, N, sql, SqlExpression, SqlQuery } from 
'@druid-toolkit/query';
 import { useMemo } from 'react';
 
@@ -25,34 +24,21 @@ import type { Capabilities } from '../../helpers';
 import { useQueryManager } from '../../hooks';
 import { Api } from '../../singletons';
 import { Duration, filterMap, getApiArray, queryDruidSql, TZ_UTC } from 
'../../utils';
-import type { Stage } from '../../utils/stage';
 import { Loader } from '../loader/loader';
 
-import type { IntervalRow, IntervalStat } from './common';
+import type { IntervalRow } from './common';
+import type { SegmentBarChartRenderProps } from './segment-bar-chart-render';
 import { SegmentBarChartRender } from './segment-bar-chart-render';
 
 import './segment-bar-chart.scss';
 
-interface SegmentBarChartProps {
+interface SegmentBarChartProps
+  extends Omit<SegmentBarChartRenderProps, 'intervalRows' | 'datasourceRules'> 
{
   capabilities: Capabilities;
-  stage: Stage;
-  dateRange: NonNullDateRange;
-  changeDateRange(newDateRange: NonNullDateRange): void;
-  shownIntervalStat: IntervalStat;
-  shownDatasource: string | undefined;
-  changeShownDatasource(datasource: string | undefined): void;
 }
 
 export const SegmentBarChart = function SegmentBarChart(props: 
SegmentBarChartProps) {
-  const {
-    capabilities,
-    dateRange,
-    changeDateRange,
-    stage,
-    shownIntervalStat,
-    shownDatasource,
-    changeShownDatasource,
-  } = props;
+  const { capabilities, dateRange, shownDatasource, ...otherProps } = props;
 
   const intervalsQuery = useMemo(
     () => ({ capabilities, dateRange, shownDatasource: shownDatasource }),
@@ -158,14 +144,11 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
     <>
       {intervalRows && (
         <SegmentBarChartRender
-          stage={stage}
-          dateRange={dateRange}
-          changeDateRange={changeDateRange}
-          shownIntervalStat={shownIntervalStat}
           intervalRows={intervalRows}
           datasourceRules={datasourceRules}
+          dateRange={dateRange}
           shownDatasource={shownDatasource}
-          changeShownDatasource={changeShownDatasource}
+          {...otherProps}
         />
       )}
       {intervalRowsState.loading && <Loader />}
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx 
b/web-console/src/components/segment-timeline/segment-timeline.tsx
index ab06abf52c3..72be20a3d83 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -56,11 +56,6 @@ import { SegmentBarChart } from './segment-bar-chart';
 
 import './segment-timeline.scss';
 
-interface SegmentTimelineProps {
-  capabilities: Capabilities;
-  datasource: string | undefined;
-}
-
 const DEFAULT_SHOWN_DURATION = new Duration('P1Y');
 const SHOWN_DURATION_OPTIONS: Duration[] = [
   new Duration('P1D'),
@@ -85,6 +80,11 @@ function dateRangesEqual(dr1: NonNullDateRange, dr2: 
NonNullDateRange): boolean
   return dr1[0].valueOf() === dr2[0].valueOf() && dr2[1].valueOf() === 
dr2[1].valueOf();
 }
 
+interface SegmentTimelineProps {
+  capabilities: Capabilities;
+  datasource: string | undefined;
+}
+
 export const SegmentTimeline = function SegmentTimeline(props: 
SegmentTimelineProps) {
   const { capabilities, datasource } = props;
   const [stage, setStage] = useState<Stage | undefined>();


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

Reply via email to