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 dc1a507081bfb98c7fd78bbb0c0697ccf96d4902
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Tue Nov 5 13:05:24 2024 -0800

    fixes
---
 .../src/components/segment-timeline/bubble.scss    |  5 ++
 .../src/components/segment-timeline/common.ts      |  1 +
 .../segment-timeline/segment-bar-chart-render.scss | 15 ++++
 .../segment-timeline/segment-bar-chart-render.tsx  | 84 ++++++++++++-------
 .../segment-timeline/segment-bar-chart.tsx         | 42 ++++++----
 .../segment-timeline/segment-timeline.tsx          | 96 ++++++++++++----------
 6 files changed, 151 insertions(+), 92 deletions(-)

diff --git a/web-console/src/components/segment-timeline/bubble.scss 
b/web-console/src/components/segment-timeline/bubble.scss
index 5dcf9986551..5961ae4fbe4 100644
--- a/web-console/src/components/segment-timeline/bubble.scss
+++ b/web-console/src/components/segment-timeline/bubble.scss
@@ -22,6 +22,11 @@
   position: absolute;
   @include card-like;
   padding: 5px;
+  white-space: nowrap;
+
+  .#{$bp-ns}-dark & {
+    background: $dark-gray1;
+  }
 
   &.up {
     transform: translate(-50%, -100%);
diff --git a/web-console/src/components/segment-timeline/common.ts 
b/web-console/src/components/segment-timeline/common.ts
index c808161ce17..b4cea01062b 100644
--- a/web-console/src/components/segment-timeline/common.ts
+++ b/web-console/src/components/segment-timeline/common.ts
@@ -69,6 +69,7 @@ export interface IntervalRow extends Record<IntervalStat, 
number> {
   start: Date;
   end: Date;
   datasource: string;
+  realtime: boolean;
   originalTimeSpan: Duration;
 }
 
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 e6badd45a7c..2e4626d5f02 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
@@ -22,6 +22,15 @@
   position: relative;
   overflow: hidden;
 
+  @keyframes pulseOpacity {
+    0% {
+      opacity: 0.8;
+    }
+    100% {
+      opacity: 0.95;
+    }
+  }
+
   svg {
     position: absolute;
 
@@ -79,6 +88,12 @@
       stroke-dasharray: 2, 2;
       opacity: 0.7;
     }
+
+    .bar-unit {
+      &.realtime {
+        animation: pulseOpacity 3s alternate infinite;
+      }
+    }
   }
 
   .empty-placeholder {
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 8b29ed428dd..fc5cfd9ccd6 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
@@ -28,11 +28,9 @@ import { useMemo, useRef, useState } from 'react';
 import { getDatasourceColor } from '../../druid-models';
 import { useClock, useGlobalEventListener } from '../../hooks';
 import {
-  capitalizeFirst,
   clamp,
   day,
   Duration,
-  filterMap,
   formatBytes,
   formatNumber,
   groupBy,
@@ -47,7 +45,7 @@ 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, INTERVAL_STATS } from 
'./common';
+import { aggregateSegmentStats, formatIntervalStat } from './common';
 
 import './segment-bar-chart-render.scss';
 
@@ -62,7 +60,7 @@ const POSSIBLE_GRANULARITIES = [
   new Duration('P1Y'),
 ];
 
-const EXTEND_X_SCALE_DOMAIN_BY = 4;
+const EXTEND_X_SCALE_DOMAIN_BY = 1;
 
 function offsetDateRange(dateRange: NonNullDateRange, offset: number): 
NonNullDateRange {
   return [new Date(dateRange[0].valueOf() + offset), new 
Date(dateRange[1].valueOf() + offset)];
@@ -84,7 +82,10 @@ function stackIntervalRows(trimmedIntervalRows: 
TrimmedIntervalRow[]): IntervalB
       b.originalTimeSpan.getCanonicalLength() - 
a.originalTimeSpan.getCanonicalLength();
     if (timeSpanDiff) return timeSpanDiff;
 
-    return datasourceToTotalSize[b.datasource] - 
datasourceToTotalSize[a.datasource];
+    const totalSizeDiff = datasourceToTotalSize[b.datasource] - 
datasourceToTotalSize[a.datasource];
+    if (totalSizeDiff) return totalSizeDiff;
+
+    return Number(a.realtime) - Number(b.realtime);
   });
 
   const intervalTree = new IntervalTree();
@@ -109,8 +110,8 @@ interface SegmentBarChartRenderProps {
   changeDateRange(dateRange: NonNullDateRange): void;
   shownIntervalStat: IntervalStat;
   intervalRows: IntervalRow[];
-  focusDatasource: string | undefined;
-  changeFocusDatasource(datasource: string | undefined): void;
+  shownDatasource: string | undefined;
+  changeShownDatasource(datasource: string | undefined): void;
 }
 
 export const SegmentBarChartRender = function SegmentBarChartRender(
@@ -122,8 +123,8 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     dateRange,
     changeDateRange,
     intervalRows,
-    focusDatasource,
-    changeFocusDatasource,
+    shownDatasource,
+    changeShownDatasource,
   } = props;
   const [hoveredIntervalBar, setHoveredIntervalBar] = useState<IntervalBar>();
   const [mouseDownAt, setMouseDownAt] = useState<
@@ -146,21 +147,40 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
   }, [dateRange, stage.width]);
 
   const intervalBars = useMemo(() => {
+    const shownIntervalRows = shownDatasource
+      ? intervalRows.filter(({ datasource }) => datasource === shownDatasource)
+      : intervalRows;
+
+    const averageRowSizeByDatasource = groupByAsMap(
+      shownIntervalRows.filter(intervalRow => intervalRow.size > 0 && 
intervalRow.rows > 0),
+      intervalRow => intervalRow.datasource,
+      intervalRows => sum(intervalRows, d => d.size) / sum(intervalRows, d => 
d.rows),
+    );
+
     const trimDuration = new Duration(trimGranularity);
-    const trimmedIntervalRows = filterMap(intervalRows, intervalRow => {
-      if (focusDatasource && intervalRow.datasource !== focusDatasource) 
return;
-      const start = trimDuration.floor(intervalRow.start, TZ_UTC);
-      const end = trimDuration.ceil(intervalRow.end, TZ_UTC);
-      const shownDays = (end.valueOf() - start.valueOf()) / 
day.canonicalLength;
+    const trimmedIntervalRows = shownIntervalRows.map(intervalRow => {
+      const { start, end, segments, size, rows } = intervalRow;
+      const startTrimmed = trimDuration.floor(start, TZ_UTC);
+      let endTrimmed = trimDuration.ceil(end, TZ_UTC);
+
+      // Special handling to catch WEEK intervals when trimming to month.
+      if (trimGranularity === 'P1M' && intervalRow.originalTimeSpan.toString() 
=== 'P7D') {
+        endTrimmed = trimDuration.shift(startTrimmed, TZ_UTC);
+      }
+
+      const shownDays = (endTrimmed.valueOf() - startTrimmed.valueOf()) / 
day.canonicalLength;
+      const shownSize =
+        size === 0 ? rows * averageRowSizeByDatasource[intervalRow.datasource] 
: size;
       return {
         ...intervalRow,
-        start,
-        end,
+        start: startTrimmed,
+        end: endTrimmed,
         shownDays,
+        size: shownSize,
         normalized: {
-          segments: intervalRow.segments / shownDays,
-          size: intervalRow.size / shownDays,
-          rows: intervalRow.rows / shownDays,
+          size: shownSize / shownDays,
+          rows: rows / shownDays,
+          segments: segments / shownDays,
         },
       };
     });
@@ -173,6 +193,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           trimmedIntervalRow.end.toISOString(),
           trimmedIntervalRow.originalTimeSpan,
           trimmedIntervalRow.datasource,
+          trimmedIntervalRow.realtime,
         ].join('/'),
       (trimmedIntervalRows): TrimmedIntervalRow => {
         const firstIntervalRow = trimmedIntervalRows[0];
@@ -185,7 +206,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     );
 
     return stackIntervalRows(fullyGroupedSegmentRows);
-  }, [intervalRows, trimGranularity, focusDatasource]);
+  }, [intervalRows, trimGranularity, shownDatasource]);
 
   const innerStage = stage.applyMargin(CHART_MARGIN);
 
@@ -325,7 +346,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     return {
       ...startEndToXWidth(intervalBar),
       y: y,
-      height: Math.abs(y0 - y),
+      height: y0 - y,
     };
   }
 
@@ -345,21 +366,22 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
       text: (
         <>
           <div>Datasource: {hoveredIntervalBar.datasource}</div>
-          <div>{`Time: ${prettyFormatIsoDate(hoveredIntervalBar.start)}/${
+          <div>{`${prettyFormatIsoDate(hoveredIntervalBar.start)}/${
             hoveredIntervalBar.originalTimeSpan
+          }${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`}</div>
+          <div>{`Size: ${
+            hoveredIntervalBar.realtime
+              ? 'estimated for realtime'
+              : formatIntervalStat('size', hoveredIntervalBar.size)
           }`}</div>
-          {INTERVAL_STATS.map(stat => (
-            <div key={stat}>
-              {`${capitalizeFirst(stat)}: ${formatIntervalStat(stat, 
hoveredIntervalBar[stat])}`}
-            </div>
-          ))}
+          <div>{`Rows: ${formatIntervalStat('rows', 
hoveredIntervalBar.rows)}`}</div>
+          <div>{`Segments: ${formatIntervalStat('segments', 
hoveredIntervalBar.segments)}`}</div>
         </>
       ),
     };
   }
 
   const nowX = timeScale(now);
-
   return (
     <div className="segment-bar-chart-render">
       <svg
@@ -419,10 +441,10 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
               return (
                 <rect
                   key={i}
-                  className="bar-unit"
+                  className={classNames('bar-unit', { realtime: 
intervalBar.realtime })}
                   {...segmentBarToRect(intervalBar)}
                   style={{ fill: getDatasourceColor(intervalBar.datasource) }}
-                  onClick={() => changeFocusDatasource(intervalBar.datasource)}
+                  onClick={() => changeShownDatasource(intervalBar.datasource)}
                   onMouseOver={() => {
                     if (mouseDownAt) return;
                     setHoveredIntervalBar(intervalBar);
@@ -436,7 +458,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
                 {...segmentBarToRect(hoveredIntervalBar)}
                 onClick={() => {
                   setHoveredIntervalBar(undefined);
-                  changeFocusDatasource(hoveredIntervalBar.datasource);
+                  changeShownDatasource(hoveredIntervalBar.datasource);
                 }}
               />
             )}
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 ba73c8422c6..b745014d0a2 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
@@ -28,7 +28,7 @@ import { Duration, filterMap, getApiArray, queryDruidSql, 
TZ_UTC } from '../../u
 import type { Stage } from '../../utils/stage';
 import { Loader } from '../loader/loader';
 
-import type { IntervalStat } from './common';
+import type { IntervalRow, IntervalStat } from './common';
 import { SegmentBarChartRender } from './segment-bar-chart-render';
 
 import './segment-bar-chart.scss';
@@ -39,8 +39,8 @@ interface SegmentBarChartProps {
   dateRange: NonNullDateRange;
   changeDateRange(newDateRange: NonNullDateRange): void;
   shownIntervalStat: IntervalStat;
-  focusDatasource: string | undefined;
-  changeFocusDatasource: (datasource: string | undefined) => void;
+  shownDatasource: string | undefined;
+  changeShownDatasource(datasource: string | undefined): void;
 }
 
 export const SegmentBarChart = function SegmentBarChart(props: 
SegmentBarChartProps) {
@@ -50,18 +50,18 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
     changeDateRange,
     stage,
     shownIntervalStat,
-    focusDatasource,
-    changeFocusDatasource,
+    shownDatasource,
+    changeShownDatasource,
   } = props;
 
   const intervalsQuery = useMemo(
-    () => ({ capabilities, dateRange, focusDatasource }),
-    [capabilities, dateRange, focusDatasource],
+    () => ({ capabilities, dateRange, shownDatasource: shownDatasource }),
+    [capabilities, dateRange, shownDatasource],
   );
 
   const [intervalRowsState] = useQueryManager({
     query: intervalsQuery,
-    processQuery: async ({ capabilities, dateRange, focusDatasource }, 
cancelToken) => {
+    processQuery: async ({ capabilities, dateRange, shownDatasource }, 
cancelToken) => {
       if (capabilities.hasSql()) {
         const query = SqlQuery.from(N('sys').table('segments'))
           .changeWhereExpression(
@@ -69,19 +69,23 @@ 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_published').equal(1),
               C('is_overshadowed').equal(0),
-              focusDatasource ? C('datasource').equal(L(focusDatasource)) : 
undefined,
+              shownDatasource ? C('datasource').equal(L(shownDatasource)) : 
undefined,
             ),
           )
           .addSelect(C('start'), { addToGroupBy: 'end' })
           .addSelect(C('end'), { addToGroupBy: 'end' })
           .addSelect(C('datasource'), { addToGroupBy: 'end' })
+          .addSelect(C('is_realtime').as('realtime'), { addToGroupBy: 'end' })
           .addSelect(F.count().as('segments'))
           .addSelect(F.sum(C('size')).as('size'))
-          .addSelect(F.sum(C('num_rows')).as('rows'));
+          .addSelect(F.sum(C('num_rows')).as('rows'))
+          .toString();
 
-        return (await queryDruidSql({ query: query.toString() }, 
cancelToken)).map(sr => {
+        console.log(query);
+
+        return (await queryDruidSql({ query }, cancelToken)).map(sr => {
           const start = new Date(sr.start);
           const end = new Date(sr.end);
 
@@ -89,14 +93,15 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
             ...sr,
             start,
             end,
+            realtime: Boolean(sr.realtime),
             originalTimeSpan: Duration.fromRange(start, end, TZ_UTC),
-          };
+          } as IntervalRow;
         }); // This trimming should ideally be pushed into the SQL query but 
at the time of this writing queries on the sys.* tables do not allow substring
       } else {
         return filterMap(
           await getApiArray(
             `/druid/coordinator/v1/metadata/segments?${
-              focusDatasource ? 
`datasources=${Api.encodePath(focusDatasource)}` : ''
+              shownDatasource ? 
`datasources=${Api.encodePath(shownDatasource)}` : ''
             }`,
             cancelToken,
           ),
@@ -111,11 +116,12 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
               start,
               end,
               datasource: segment.dataSource,
+              realtime: false, // ToDo: fill me
               originalTimeSpan: Duration.fromRange(start, end, TZ_UTC),
               segments: 1,
               size: segment.size,
-              num_rows: segment.num_rows || 0, // segment.num_rows is really 
null on this API :-(
-            };
+              rows: segment.num_rows || 0, // segment.num_rows is really null 
on this API :-(
+            } as IntervalRow;
           },
         );
       }
@@ -144,8 +150,8 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
       changeDateRange={changeDateRange}
       shownIntervalStat={shownIntervalStat}
       intervalRows={intervalRows}
-      focusDatasource={focusDatasource}
-      changeFocusDatasource={changeFocusDatasource}
+      shownDatasource={shownDatasource}
+      changeShownDatasource={changeShownDatasource}
     />
   );
 };
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx 
b/web-console/src/components/segment-timeline/segment-timeline.tsx
index 81b26c04d8b..dd79f07f0dd 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -16,7 +16,15 @@
  * limitations under the License.
  */
 
-import { Button, ButtonGroup, Menu, MenuItem, Popover, ResizeSensor } from 
'@blueprintjs/core';
+import {
+  Button,
+  ButtonGroup,
+  Menu,
+  MenuItem,
+  Popover,
+  Position,
+  ResizeSensor,
+} from '@blueprintjs/core';
 import type { NonNullDateRange } from '@blueprintjs/datetime';
 import { DateRangePicker3 } from '@blueprintjs/datetime2';
 import { IconNames } from '@blueprintjs/icons';
@@ -60,8 +68,6 @@ const SHOWN_DURATION_OPTIONS: Duration[] = [
   new Duration('P10Y'),
 ];
 
-const SHOW_ALL = 'Show all';
-
 function getDateRange(shownDuration: Duration): NonNullDateRange {
   const end = day.ceil(new Date(), TZ_UTC);
   return [shownDuration.shift(end, TZ_UTC, -1), end];
@@ -71,7 +77,7 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
   const { capabilities } = props;
   const [stage, setStage] = useState<Stage | undefined>();
   const [activeSegmentStat, setActiveSegmentStat] = 
useState<IntervalStat>('size');
-  const [focusDatasource, setFocusDatasource] = useState<string | undefined>();
+  const [shownDatasource, setShownDatasource] = useState<string | undefined>();
   const [dateRange, setDateRange] = useState<NonNullDateRange>(
     getDateRange(DEFAULT_SHOWN_DURATION),
   );
@@ -163,6 +169,7 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
           </Popover>
         </ButtonGroup>
         <Popover
+          position={Position.BOTTOM_LEFT}
           content={
             <Menu>
               {INTERVAL_STATS.map(stat => (
@@ -182,43 +189,46 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
             rightIcon={IconNames.CARET_DOWN}
           />
         </Popover>
-        <Select<string>
-          items={[SHOW_ALL].concat(datasourcesState.data || [])}
-          onItemSelect={(selectedItem: string) => {
-            setFocusDatasource(selectedItem === SHOW_ALL ? undefined : 
selectedItem);
-          }}
-          itemRenderer={(val, { handleClick, handleFocus, modifiers }) => {
-            if (!modifiers.matchesPredicate) return null;
-            return (
-              <MenuItem
-                key={val}
-                disabled={modifiers.disabled}
-                active={modifiers.active}
-                onClick={handleClick}
-                onFocus={handleFocus}
-                roleStructure="listoption"
-                text={val}
-              />
-            );
-          }}
-          noResults={<MenuItem disabled text="No results" 
roleStructure="listoption" />}
-          itemPredicate={(query, val, _index, exactMatch) => {
-            const normalizedTitle = val.toLowerCase();
-            const normalizedQuery = query.toLowerCase();
+        <ButtonGroup>
+          <Select<string>
+            items={datasourcesState.data || []}
+            onItemSelect={setShownDatasource}
+            itemRenderer={(val, { handleClick, handleFocus, modifiers }) => {
+              if (!modifiers.matchesPredicate) return null;
+              return (
+                <MenuItem
+                  key={val}
+                  disabled={modifiers.disabled}
+                  active={modifiers.active}
+                  onClick={handleClick}
+                  onFocus={handleFocus}
+                  roleStructure="listoption"
+                  text={val}
+                />
+              );
+            }}
+            noResults={<MenuItem disabled text="No results" 
roleStructure="listoption" />}
+            itemPredicate={(query, val, _index, exactMatch) => {
+              const normalizedTitle = val.toLowerCase();
+              const normalizedQuery = query.toLowerCase();
 
-            if (exactMatch) {
-              return normalizedTitle === normalizedQuery;
-            } else {
-              return normalizedTitle.includes(normalizedQuery);
-            }
-          }}
-        >
-          <Button
-            text={`Datasource: ${focusDatasource ?? 'all'}`}
-            small
-            rightIcon={IconNames.CARET_DOWN}
-          />
-        </Select>
+              if (exactMatch) {
+                return normalizedTitle === normalizedQuery;
+              } else {
+                return normalizedTitle.includes(normalizedQuery);
+              }
+            }}
+          >
+            <Button
+              text={`Datasource: ${shownDatasource ?? 'all'}`}
+              small
+              rightIcon={IconNames.CARET_DOWN}
+            />
+          </Select>
+          {shownDatasource && (
+            <Button icon={IconNames.CROSS} small onClick={() => 
setShownDatasource(undefined)} />
+          )}
+        </ButtonGroup>
       </div>
       <ResizeSensor
         onResize={(entries: ResizeObserverEntry[]) => {
@@ -234,9 +244,9 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
               dateRange={dateRange}
               changeDateRange={setDateRange}
               shownIntervalStat={activeSegmentStat}
-              focusDatasource={focusDatasource}
-              changeFocusDatasource={(datasource: string | undefined) =>
-                setFocusDatasource(focusDatasource ? undefined : datasource)
+              shownDatasource={shownDatasource}
+              changeShownDatasource={(datasource: string | undefined) =>
+                setShownDatasource(shownDatasource ? undefined : datasource)
               }
             />
           )}


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

Reply via email to