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 6a1821a571fd37f11a20333be26ace6f2e5ab704
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Wed Oct 30 20:03:28 2024 -0700

    no highlight
---
 .../segment-timeline/segment-bar-chart-render.scss |  34 +++--
 .../segment-timeline/segment-bar-chart-render.tsx  |  50 ++++---
 .../segment-timeline/segment-bar-chart.tsx         |   2 +-
 .../segment-timeline/segment-timeline.scss         |  36 ++----
 .../segment-timeline/segment-timeline.tsx          | 144 +++++++++++++--------
 5 files changed, 158 insertions(+), 108 deletions(-)

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 a30ee800f0a..90a6bcbe8d6 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
@@ -16,21 +16,12 @@
  * limitations under the License.
  */
 
+@import '../../variables';
+
 .segment-bar-chart-render {
   position: relative;
   overflow: hidden;
 
-  .bar-chart-tooltip {
-    position: absolute;
-    left: 20px;
-    right: 0;
-
-    div {
-      display: inline-block;
-      width: 230px;
-    }
-  }
-
   svg {
     position: absolute;
 
@@ -78,4 +69,25 @@
       }
     }
   }
+
+  .bar-chart-tooltip {
+    position: absolute;
+    left: 20px;
+    right: 0;
+
+    div {
+      display: inline-block;
+      width: 230px;
+    }
+  }
+
+  .empty-placeholder {
+    @include pin-full;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 20px;
+    user-select: none;
+    pointer-events: none;
+  }
 }
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 8753f8bca64..dd813261125 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
@@ -31,7 +31,6 @@ import {
   clamp,
   day,
   Duration,
-  formatByteRate,
   formatBytes,
   formatInteger,
   formatNumber,
@@ -48,7 +47,7 @@ import { aggregateSegmentStats, normalizeIntervalRow } from 
'./common';
 
 import './segment-bar-chart-render.scss';
 
-const CHART_MARGIN: Margin = { top: 40, right: 5, bottom: 20, left: 80 };
+const CHART_MARGIN: Margin = { top: 40, right: 10, bottom: 25, left: 80 };
 const MIN_BAR_WIDTH = 2;
 const POSSIBLE_GRANULARITIES = [
   new Duration('PT15M'),
@@ -210,6 +209,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
   };
 
   const formatTickRate = (n: number) => {
+    n = (n * new Duration(trimGranularity).getCanonicalLength()) / 1000;
     switch (shownIntervalStat) {
       case 'segments':
         return formatNumber(n) + ' seg/s';
@@ -218,13 +218,14 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
         return formatNumber(n) + ' row/s';
 
       case 'size':
-        return formatByteRate(n);
+        return formatBytes(n);
     }
   };
 
   function handleMouseDown(e: React.MouseEvent) {
     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;
@@ -236,6 +237,8 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
 
   useGlobalEventListener('mousemove', (e: MouseEvent) => {
     if (!mouseDownAt) return;
+    e.preventDefault();
+
     const svg = svgRef.current;
     if (!svg) return;
     const rect = svg.getBoundingClientRect();
@@ -254,6 +257,8 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
 
   useGlobalEventListener('mouseup', (e: MouseEvent) => {
     if (!mouseDownAt) return;
+    e.preventDefault();
+
     setMouseDownAt(undefined);
 
     if (!shiftOffset && !dragging) return;
@@ -286,22 +291,6 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
 
   return (
     <div className="segment-bar-chart-render">
-      {dragging ? (
-        <div className="bar-chart-tooltip">
-          <div>Start: {dragging[0].toISOString()}</div>
-          <div>End: {dragging[1].toISOString()}</div>
-        </div>
-      ) : hoverOn ? (
-        <div className="bar-chart-tooltip">
-          <div>Datasource: {hoverOn.datasource}</div>
-          <div>{`Time: 
${prettyFormatIsoDate(hoverOn.start)}/${hoverOn.originalTimeSpan}`}</div>
-          <div>
-            {`${capitalizeFirst(shownIntervalStat)}: ${formatTick(
-              hoverOn[shownIntervalStat] * hoverOn.shownSeconds,
-            )}`}
-          </div>
-        </div>
-      ) : undefined}
       <svg
         ref={svgRef}
         width={stage.width}
@@ -318,7 +307,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
             className="gridline-x"
             transform="translate(0,0)"
             axis={axisLeft(statScale)
-              .ticks(5)
+              .ticks(3)
               .tickSize(-innerStage.width)
               .tickFormat(() => '')
               .tickSizeOuter(0)}
@@ -396,6 +385,27 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
           </g>
         </g>
       </svg>
+      {dragging ? (
+        <div className="bar-chart-tooltip">
+          <div>Start: {dragging[0].toISOString()}</div>
+          <div>End: {dragging[1].toISOString()}</div>
+        </div>
+      ) : hoverOn ? (
+        <div className="bar-chart-tooltip">
+          <div>Datasource: {hoverOn.datasource}</div>
+          <div>{`Time: 
${prettyFormatIsoDate(hoverOn.start)}/${hoverOn.originalTimeSpan}`}</div>
+          <div>
+            {`${capitalizeFirst(shownIntervalStat)}: ${formatTick(
+              hoverOn[shownIntervalStat] * hoverOn.shownSeconds,
+            )}`}
+          </div>
+        </div>
+      ) : undefined}
+      {!intervalRows.length && (
+        <div className="empty-placeholder">
+          <div className="no-data-text">There are no segments in the selected 
date range</div>
+        </div>
+      )}
     </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 d88a023f883..73c69bb8973 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
@@ -125,7 +125,7 @@ export const SegmentBarChart = function 
SegmentBarChart(props: SegmentBarChartPr
   if (intervalRowsState.error) {
     return (
       <div className="empty-placeholder">
-        <span className="no-data-text">{`Error when loading data: 
${intervalRowsState.getErrorMessage()}`}</span>
+        <span className="error-text">{`Error when loading data: 
${intervalRowsState.getErrorMessage()}`}</span>
       </div>
     );
   }
diff --git a/web-console/src/components/segment-timeline/segment-timeline.scss 
b/web-console/src/components/segment-timeline/segment-timeline.scss
index 583ce290ac9..8295fa3cdfd 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.scss
+++ b/web-console/src/components/segment-timeline/segment-timeline.scss
@@ -19,6 +19,14 @@
 @import '../../variables';
 
 .segment-timeline {
+  .control-bar {
+    @include card-like;
+    height: 40px;
+    display: flex;
+    align-items: start;
+    gap: 10px;
+  }
+
   .loading-error {
     position: fixed;
     top: 50%;
@@ -26,34 +34,16 @@
     transform: translate(-50%, -50%);
   }
 
-  .side-control {
-    @include card-like;
-    height: 100%;
-    padding: 10px;
-  }
-
   .chart-container {
     position: absolute;
+    top: 40px;
     width: 100%;
-    height: 100%;
+    bottom: 0;
     overflow: hidden;
-  }
-
-  .segment-bar-chart,
-  .segment-bar-chart-render {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-  }
-
-  .empty-placeholder {
-    height: 100%;
 
-    .no-data-text {
-      position: absolute;
-      left: 30vw;
-      top: 15vh;
-      font-size: 20px;
+    .segment-bar-chart,
+    .segment-bar-chart-render {
+      @include pin-full;
     }
   }
 }
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx 
b/web-console/src/components/segment-timeline/segment-timeline.tsx
index 5344916c95e..83208304d69 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -16,26 +16,33 @@
  * limitations under the License.
  */
 
-import { Button, FormGroup, MenuItem, ResizeSensor, SegmentedControl } from 
'@blueprintjs/core';
+import {
+  Button,
+  ButtonGroup,
+  FormGroup,
+  MenuItem,
+  Popover,
+  ResizeSensor,
+  SegmentedControl,
+} from '@blueprintjs/core';
 import type { NonNullDateRange } from '@blueprintjs/datetime';
-import { DateRangeInput3 } from '@blueprintjs/datetime2';
+import { DateRangePicker3 } from '@blueprintjs/datetime2';
 import { IconNames } from '@blueprintjs/icons';
 import { Select } from '@blueprintjs/select';
-import enUS from 'date-fns/locale/en-US';
 import type React from 'react';
 import { useState } from 'react';
 
 import type { Capabilities } from '../../helpers';
 import {
   day,
+  Duration,
   isNonNullRange,
   localToUtcDateRange,
-  month,
+  prettyFormatIsoDate,
   TZ_UTC,
   utcToLocalDateRange,
 } from '../../utils';
 import { Stage } from '../../utils/stage';
-import { SplitterLayout } from '../splitter-layout/splitter-layout';
 
 import type { IntervalStat } from './common';
 import { SegmentBarChart } from './segment-bar-chart';
@@ -46,11 +53,20 @@ interface SegmentTimelineProps {
   capabilities: Capabilities;
 }
 
-const DEFAULT_TIME_SPAN_MONTHS = 3;
+const DEFAULT_SHOWN_DURATION = new Duration('P3M');
+const SHOWN_DURATION_OPTIONS: Duration[] = [
+  new Duration('P1D'),
+  new Duration('P1W'),
+  new Duration('P1M'),
+  new Duration('P3M'),
+  new Duration('P1Y'),
+  new Duration('P5Y'),
+  new Duration('P10Y'),
+];
 
-function getDefaultDateRange(): NonNullDateRange {
+function getDateRange(shownDuration: Duration): NonNullDateRange {
   const end = day.ceil(new Date(), TZ_UTC);
-  return [month.shift(end, TZ_UTC, -DEFAULT_TIME_SPAN_MONTHS), end];
+  return [shownDuration.shift(end, TZ_UTC, -1), end];
 }
 
 export const SegmentTimeline = function SegmentTimeline(props: 
SegmentTimelineProps) {
@@ -58,7 +74,10 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
   const [stage, setStage] = useState<Stage | undefined>();
   const [activeSegmentStat, setActiveSegmentStat] = 
useState<IntervalStat>('size');
   const [activeDatasource, setActiveDatasource] = useState<string | 
undefined>();
-  const [dateRange, setDateRange] = 
useState<NonNullDateRange>(getDefaultDateRange);
+  const [dateRange, setDateRange] = useState<NonNullDateRange>(
+    getDateRange(DEFAULT_SHOWN_DURATION),
+  );
+  const [showCustomDatePicker, setShowCustomDatePicker] = useState(false);
 
   const datasources: string[] = ['wiki', 'kttm']; // ToDo
 
@@ -96,7 +115,6 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
             return normalizedTitle.includes(normalizedQuery);
           }
         }}
-        fill
       >
         <Button
           text={activeDatasource === null ? showAll : activeDatasource}
@@ -108,39 +126,50 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
   };
 
   return (
-    <SplitterLayout
-      className="segment-timeline"
-      primaryMinSize={400}
-      secondaryInitialSize={220}
-      secondaryMaxSize={400}
-    >
-      <ResizeSensor
-        onResize={(entries: ResizeObserverEntry[]) => {
-          const rect = entries[0].contentRect;
-          setStage(new Stage(rect.width, rect.height));
-        }}
-      >
-        <div className="chart-container">
-          {stage && (
-            <SegmentBarChart
-              capabilities={capabilities}
-              stage={stage}
-              dateRange={dateRange}
-              changeDateRange={setDateRange}
-              shownIntervalStat={activeSegmentStat}
-              changeActiveDatasource={(datasource: string | undefined) =>
-                setActiveDatasource(activeDatasource ? undefined : datasource)
-              }
+    <div className="segment-timeline">
+      <div className="control-bar">
+        <ButtonGroup>
+          {SHOWN_DURATION_OPTIONS.map((d, i) => (
+            <Button
+              key={i}
+              text={d.toString().replace('P', '')}
+              data-tooltip={`Show last ${d.getDescription()}`}
+              small
+              onClick={() => setDateRange(getDateRange(d))}
             />
-          )}
-        </div>
-      </ResizeSensor>
-      <div className="side-control">
-        <FormGroup label="Show">
+          ))}
+          <Popover
+            isOpen={showCustomDatePicker}
+            onInteraction={setShowCustomDatePicker}
+            content={
+              <DateRangePicker3
+                defaultValue={utcToLocalDateRange(dateRange)}
+                onChange={newDateRange => {
+                  const newUtcDateRange = localToUtcDateRange(newDateRange);
+                  if (!isNonNullRange(newUtcDateRange)) return;
+                  setDateRange(newUtcDateRange);
+                  setShowCustomDatePicker(false);
+                }}
+                contiguousCalendarMonths={false}
+                reverseMonthAndYearMenus
+                timePickerProps={undefined}
+                shortcuts={false}
+              />
+            }
+          >
+            <Button
+              icon={IconNames.CALENDAR}
+              data-tooltip={`Select a custom date range\nCurrent range: 
${prettyFormatIsoDate(
+                dateRange[0],
+              )} - ${prettyFormatIsoDate(dateRange[1])}`}
+            />
+          </Popover>
+        </ButtonGroup>
+        <FormGroup label="Show" inline>
           <SegmentedControl
             value={activeSegmentStat}
             onValueChange={s => setActiveSegmentStat(s as IntervalStat)}
-            fill
+            small
             options={[
               {
                 label: 'Size',
@@ -157,22 +186,31 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
             ]}
           />
         </FormGroup>
-        <FormGroup label="Interval">
-          <DateRangeInput3
-            value={utcToLocalDateRange(dateRange)}
-            onChange={newDateRange => {
-              const newUtcDateRange = localToUtcDateRange(newDateRange);
-              if (!isNonNullRange(newUtcDateRange)) return;
-              setDateRange(newUtcDateRange);
-            }}
-            fill
-            locale={enUS}
-          />
-        </FormGroup>
-        <FormGroup label="Datasource">
+        <FormGroup label="Datasource" inline>
           <DatasourceSelect />
         </FormGroup>
       </div>
-    </SplitterLayout>
+      <ResizeSensor
+        onResize={(entries: ResizeObserverEntry[]) => {
+          const rect = entries[0].contentRect;
+          setStage(new Stage(rect.width, rect.height));
+        }}
+      >
+        <div className="chart-container">
+          {stage && (
+            <SegmentBarChart
+              capabilities={capabilities}
+              stage={stage}
+              dateRange={dateRange}
+              changeDateRange={setDateRange}
+              shownIntervalStat={activeSegmentStat}
+              changeActiveDatasource={(datasource: string | undefined) =>
+                setActiveDatasource(activeDatasource ? undefined : datasource)
+              }
+            />
+          )}
+        </div>
+      </ResizeSensor>
+    </div>
   );
 };


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

Reply via email to