You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by vo...@apache.org on 2024/11/18 23:29:16 UTC
(druid) 41/42: fixes
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 <va...@ogievetsky.com>
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: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org