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 ad0517a3cb3b01f2febc338882cea3cad85c897d
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Fri Nov 15 22:20:31 2024 -0800

    progress
---
 .../src/components/segment-timeline/common.ts      |   4 +
 .../segment-timeline/segment-bar-chart-render.scss |   5 +
 .../segment-timeline/segment-bar-chart-render.tsx  | 193 +++++++++++++--------
 .../segment-timeline/segment-bar-chart.tsx         |  34 ++--
 .../segment-timeline/segment-timeline.tsx          |   4 +-
 5 files changed, 145 insertions(+), 95 deletions(-)

diff --git a/web-console/src/components/segment-timeline/common.ts 
b/web-console/src/components/segment-timeline/common.ts
index b4cea01062b..48aa5ce48d4 100644
--- a/web-console/src/components/segment-timeline/common.ts
+++ b/web-console/src/components/segment-timeline/common.ts
@@ -81,3 +81,7 @@ export interface TrimmedIntervalRow extends IntervalRow {
 export interface IntervalBar extends TrimmedIntervalRow {
   offset: Record<IntervalStat, number>;
 }
+
+export function formatIsoDateOnly(date: Date): string {
+  return date.toISOString().slice(0, 10);
+}
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 576cf54e146..53d5f93d941 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
@@ -53,6 +53,11 @@
       fill: transparent;
       stroke: #ffffff;
       stroke-width: 1px;
+      opacity: 0.8;
+
+      &.done {
+        opacity: 1;
+      }
     }
 
     .shifter {
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 bc73d3db7a4..ac4ce6d8ed7 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,6 +16,7 @@
  * limitations under the License.
  */
 
+import { Button } from '@blueprintjs/core';
 import type { NonNullDateRange } from '@blueprintjs/datetime';
 import IntervalTree from '@flatten-js/interval-tree';
 import classNames from 'classnames';
@@ -46,7 +47,7 @@ import type { Margin, Stage } from '../../utils/stage';
 
 import { ChartAxis } from './chart-axis';
 import type { IntervalBar, IntervalRow, IntervalStat, TrimmedIntervalRow } 
from './common';
-import { aggregateSegmentStats, formatIntervalStat } from './common';
+import { aggregateSegmentStats, formatIntervalStat, formatIsoDateOnly } from 
'./common';
 import { PortalBubble } from './portal-bubble';
 
 import './segment-bar-chart-render.scss';
@@ -192,6 +193,12 @@ interface BubbleInfo {
   intervalBars: IntervalBar[];
 }
 
+interface SelectionRange {
+  start: Date;
+  end: Date;
+  done?: boolean;
+}
+
 export interface DatasourceRules {
   loadRules: Rule[];
   defaultLoadRules: Rule[];
@@ -224,7 +231,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
   const [mouseDownAt, setMouseDownAt] = useState<
     { time: Date; action: 'select' | 'shift' } | undefined
   >();
-  const [dragging, setDragging] = useState<NonNullDateRange | undefined>();
+  const [selection, setSelection] = useState<SelectionRange | undefined>();
   const [shiftOffset, setShiftOffset] = useState<number | undefined>();
   const [bubbleInfo, setBubbleInfo] = useState<BubbleInfo | undefined>();
   const now = useClock(minute.canonicalLength);
@@ -239,10 +246,12 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
   }, [dateRange, stage.width]);
 
   const { intervalBars, intervalTree } = useMemo(() => {
-    const shownIntervalRows = shownDatasource
-      ? intervalRows.filter(({ datasource }) => datasource === shownDatasource)
-      : intervalRows;
-
+    const shownIntervalRows = intervalRows.filter(
+      ({ start, end, datasource }) =>
+        start <= dateRange[1] &&
+        dateRange[0] < end &&
+        (!shownDatasource || datasource === shownDatasource),
+    );
     const averageRowSizeByDatasource = groupByAsMap(
       shownIntervalRows.filter(intervalRow => intervalRow.size > 0 && 
intervalRow.rows > 0),
       intervalRow => intervalRow.datasource,
@@ -298,7 +307,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     );
 
     return stackIntervalRows(fullyGroupedSegmentRows);
-  }, [intervalRows, trimGranularity, shownDatasource]);
+  }, [intervalRows, trimGranularity, dateRange, shownDatasource]);
 
   const innerStage = stage.applyMargin(CHART_MARGIN);
 
@@ -334,16 +343,21 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     const svg = svgRef.current;
     if (!svg) return;
     e.preventDefault();
-    const rect = svg.getBoundingClientRect();
-    const x = e.clientX - rect.x - CHART_MARGIN.left;
-    const y = e.clientY - rect.y - CHART_MARGIN.top;
-    const time = baseTimeScale.invert(x);
-    const action = y > innerStage.height || e.shiftKey ? 'shift' : 'select';
-    setBubbleInfo(undefined);
-    setMouseDownAt({
-      time,
-      action,
-    });
+
+    if (selection) {
+      setSelection(undefined);
+    } else {
+      const rect = svg.getBoundingClientRect();
+      const x = e.clientX - rect.x - CHART_MARGIN.left;
+      const y = e.clientY - rect.y - CHART_MARGIN.top;
+      const time = baseTimeScale.invert(x);
+      const action = y > innerStage.height || e.shiftKey ? 'shift' : 'select';
+      setBubbleInfo(undefined);
+      setMouseDownAt({
+        time,
+        action,
+      });
+    }
   }
 
   useGlobalEventListener('mousemove', (e: MouseEvent) => {
@@ -361,12 +375,12 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
         setShiftOffset(mouseDownAt.time.valueOf() - b.valueOf());
       } else {
         if (mouseDownAt.time < b) {
-          setDragging([day.floor(mouseDownAt.time, TZ_UTC), day.ceil(b, 
TZ_UTC)]);
+          setSelection({ start: day.floor(mouseDownAt.time, TZ_UTC), end: 
day.ceil(b, TZ_UTC) });
         } else {
-          setDragging([day.floor(b, TZ_UTC), day.ceil(mouseDownAt.time, 
TZ_UTC)]);
+          setSelection({ start: day.floor(b, TZ_UTC), end: 
day.ceil(mouseDownAt.time, TZ_UTC) });
         }
       }
-    } else {
+    } else if (!selection) {
       if (
         0 <= x &&
         x <= innerStage.width &&
@@ -421,16 +435,15 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     const x = e.clientX - rect.x - CHART_MARGIN.left;
     const y = e.clientY - rect.y - CHART_MARGIN.top;
 
-    if (shiftOffset || dragging) {
-      setDragging(undefined);
+    if (shiftOffset || selection) {
       setShiftOffset(undefined);
       if (mouseDownAt.action === 'shift' || e.shiftKey) {
         if (shiftOffset) {
           changeDateRange(offsetDateRange(dateRange, shiftOffset));
         }
       } else {
-        if (dragging) {
-          changeDateRange(dragging);
+        if (selection) {
+          setSelection({ ...selection, done: true });
         }
       }
     } else if (0 <= x && x <= innerStage.width && 0 <= y && y <= 
innerStage.height) {
@@ -455,7 +468,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
   useGlobalEventListener('keydown', (e: KeyboardEvent) => {
     if (e.key === 'Escape' && mouseDownAt) {
       setMouseDownAt(undefined);
-      setDragging(undefined);
+      setSelection(undefined);
     }
   });
 
@@ -485,53 +498,84 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
   console.log('Bar chart render');
 
   let hoveredOpenOn: { x: number; y: number; text: ReactNode } | undefined;
-  if (bubbleInfo && svgRef.current) {
-    const hoveredIntervalBars = bubbleInfo.intervalBars;
-
+  if (svgRef.current) {
     const rect = svgRef.current.getBoundingClientRect();
-    let text: ReactNode;
-    if (hoveredIntervalBars.length === 0) {
-      text = bubbleInfo.timeLabel;
-    } else if (hoveredIntervalBars.length === 1) {
-      const hoveredIntervalBar = hoveredIntervalBars[0];
-      text = (
-        <>
-          <div>{`${formatStartDuration(
-            hoveredIntervalBar.start,
-            hoveredIntervalBar.originalTimeSpan,
-          )}${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`}</div>
-          <div>{`Datasource: ${hoveredIntervalBar.datasource}`}</div>
-          <div>{`Size: ${
-            hoveredIntervalBar.realtime
-              ? 'estimated for realtime'
-              : formatIntervalStat('size', hoveredIntervalBar.size)
-          }`}</div>
-          <div>{`Rows: ${formatIntervalStat('rows', 
hoveredIntervalBar.rows)}`}</div>
-          <div>{`Segments: ${formatIntervalStat('segments', 
hoveredIntervalBar.segments)}`}</div>
-        </>
-      );
-    } else {
-      const datasources = uniq(hoveredIntervalBars.map(b => b.datasource));
-      const agg = aggregateSegmentStats(hoveredIntervalBars);
-      text = (
-        <>
-          <div>{bubbleInfo.timeLabel}</div>
-          <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>
-        </>
-      );
-    }
 
-    hoveredOpenOn = {
-      x:
-        rect.x +
-        CHART_MARGIN.left +
-        timeScale(new Date((bubbleInfo.start.valueOf() + 
bubbleInfo.end.valueOf()) / 2)),
-      y: rect.y + CHART_MARGIN.top - 10,
-      text,
-    };
+    if (bubbleInfo) {
+      const hoveredIntervalBars = bubbleInfo.intervalBars;
+
+      let text: ReactNode;
+      if (hoveredIntervalBars.length === 0) {
+        text = bubbleInfo.timeLabel;
+      } else if (hoveredIntervalBars.length === 1) {
+        const hoveredIntervalBar = hoveredIntervalBars[0];
+        text = (
+          <>
+            <div>{`${formatStartDuration(
+              hoveredIntervalBar.start,
+              hoveredIntervalBar.originalTimeSpan,
+            )}${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`}</div>
+            <div>{`Datasource: ${hoveredIntervalBar.datasource}`}</div>
+            <div>{`Size: ${
+              hoveredIntervalBar.realtime
+                ? 'estimated for realtime'
+                : formatIntervalStat('size', hoveredIntervalBar.size)
+            }`}</div>
+            <div>{`Rows: ${formatIntervalStat('rows', 
hoveredIntervalBar.rows)}`}</div>
+            <div>{`Segments: ${formatIntervalStat('segments', 
hoveredIntervalBar.segments)}`}</div>
+          </>
+        );
+      } else {
+        const datasources = uniq(hoveredIntervalBars.map(b => b.datasource));
+        const agg = aggregateSegmentStats(hoveredIntervalBars);
+        text = (
+          <>
+            <div>{bubbleInfo.timeLabel}</div>
+            <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>
+          </>
+        );
+      }
+
+      hoveredOpenOn = {
+        x:
+          rect.x +
+          CHART_MARGIN.left +
+          timeScale(new Date((bubbleInfo.start.valueOf() + 
bubbleInfo.end.valueOf()) / 2)),
+        y: rect.y + CHART_MARGIN.top - 10,
+        text,
+      };
+    } else if (selection) {
+      hoveredOpenOn = {
+        x:
+          rect.x +
+          CHART_MARGIN.left +
+          timeScale(new Date((selection.start.valueOf() + 
selection.end.valueOf()) / 2)),
+        y: rect.y + CHART_MARGIN.top - 10,
+        text: (
+          <>
+            <div>{`${formatIsoDateOnly(selection.start)} → ${formatIsoDateOnly(
+              selection.end,
+            )}`}</div>
+            {selection.done && (
+              <div>
+                <Button
+                  text="Zoom in"
+                  onClick={() => {
+                    if (!selection) return;
+                    setSelection(undefined);
+                    changeDateRange([selection.start, selection.end]);
+                  }}
+                />
+                <Button text="Cancel" onClick={() => setSelection(undefined)} 
/>
+              </div>
+            )}
+          </>
+        ),
+      };
+    }
   }
 
   function renderLoadRule(loadRule: Rule, i: number, isDefault: boolean) {
@@ -620,13 +664,12 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
               bubbleInfo.intervalBars.map((intervalBar, i) => (
                 <rect key={i} className="hovered-bar" 
{...segmentBarToRect(intervalBar)} />
               ))}
-            {dragging && (
+            {selection && (
               <rect
-                className="selection"
-                x={timeScale(dragging[0])}
+                className={classNames('selection', { done: selection.done })}
+                {...startEndToXWidth(selection)}
                 y={0}
                 height={innerStage.height}
-                width={timeScale(dragging[1]) - timeScale(dragging[0])}
               />
             )}
             {!!shiftOffset && (
@@ -656,7 +699,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           <div className="no-data-text">There are no segments in the selected 
range</div>
         </div>
       )}
-      <PortalBubble openOn={hoveredOpenOn} mute direction="up" />
+      <PortalBubble openOn={hoveredOpenOn} mute={!selection?.done} 
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 5029a1a2d76..5a8c162f0c2 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
@@ -69,7 +69,6 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
               sql`"start" <= '${dateRange[1].toISOString()}' AND 
'${dateRange[0].toISOString()}' < "end"`,
               C('start').unequal(START_OF_TIME_DATE),
               C('end').unequal(END_OF_TIME_DATE),
-              // C('is_published').equal(1),
               C('is_overshadowed').equal(0),
               shownDatasource ? C('datasource').equal(L(shownDatasource)) : 
undefined,
             ),
@@ -146,10 +145,6 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
     };
   }, [allLoadRulesState.data, shownDatasource]);
 
-  if (intervalRowsState.loading) {
-    return <Loader />;
-  }
-
   if (intervalRowsState.error) {
     return (
       <div className="empty-placeholder">
@@ -158,19 +153,22 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
     );
   }
 
-  const intervalRows = intervalRowsState.data;
-  if (!intervalRows) return null;
-
+  const intervalRows = intervalRowsState.getSomeData();
   return (
-    <SegmentBarChartRender
-      stage={stage}
-      dateRange={dateRange}
-      changeDateRange={changeDateRange}
-      shownIntervalStat={shownIntervalStat}
-      intervalRows={intervalRows}
-      datasourceRules={datasourceRules}
-      shownDatasource={shownDatasource}
-      changeShownDatasource={changeShownDatasource}
-    />
+    <>
+      {intervalRows && (
+        <SegmentBarChartRender
+          stage={stage}
+          dateRange={dateRange}
+          changeDateRange={changeDateRange}
+          shownIntervalStat={shownIntervalStat}
+          intervalRows={intervalRows}
+          datasourceRules={datasourceRules}
+          shownDatasource={shownDatasource}
+          changeShownDatasource={changeShownDatasource}
+        />
+      )}
+      {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 ae8cde2a9a7..0d8ee73caca 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -51,7 +51,7 @@ import { Stage } from '../../utils/stage';
 import { Loader } from '../loader/loader';
 
 import type { IntervalStat } from './common';
-import { getIntervalStatTitle, INTERVAL_STATS } from './common';
+import { formatIsoDateOnly, getIntervalStatTitle, INTERVAL_STATS } from 
'./common';
 import { SegmentBarChart } from './segment-bar-chart';
 
 import './segment-timeline.scss';
@@ -78,7 +78,7 @@ function getDateRange(shownDuration: Duration): 
NonNullDateRange {
 }
 
 function formatDateRange(dateRange: NonNullDateRange): string {
-  return `${dateRange[0].toISOString().slice(0, 10)} → 
${dateRange[1].toISOString().slice(0, 10)}`;
+  return `${formatIsoDateOnly(dateRange[0])} → 
${formatIsoDateOnly(dateRange[1])}`;
 }
 
 function dateRangesEqual(dr1: NonNullDateRange, dr2: NonNullDateRange): 
boolean {


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

Reply via email to