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 4a1286048a6b647943cdc5aae20a95c2aa2689fb Author: Vadim Ogievetsky <[email protected]> AuthorDate: Wed Nov 13 13:12:17 2024 -0800 portal bubble --- .../{bubble.scss => portal-bubble.scss} | 2 +- .../{bubble.tsx => portal-bubble.tsx} | 20 ++++++--- .../segment-timeline/segment-bar-chart-render.tsx | 52 +++++++++++++++++----- web-console/src/utils/duration/duration.ts | 41 +++++++++-------- 4 files changed, 77 insertions(+), 38 deletions(-) diff --git a/web-console/src/components/segment-timeline/bubble.scss b/web-console/src/components/segment-timeline/portal-bubble.scss similarity index 98% rename from web-console/src/components/segment-timeline/bubble.scss rename to web-console/src/components/segment-timeline/portal-bubble.scss index 5961ae4fbe4..38663be81eb 100644 --- a/web-console/src/components/segment-timeline/bubble.scss +++ b/web-console/src/components/segment-timeline/portal-bubble.scss @@ -18,7 +18,7 @@ @import '../../variables'; -.bubble { +.portal-bubble { position: absolute; @include card-like; padding: 5px; diff --git a/web-console/src/components/segment-timeline/bubble.tsx b/web-console/src/components/segment-timeline/portal-bubble.tsx similarity index 68% rename from web-console/src/components/segment-timeline/bubble.tsx rename to web-console/src/components/segment-timeline/portal-bubble.tsx index efb72431379..e35c0246919 100644 --- a/web-console/src/components/segment-timeline/bubble.tsx +++ b/web-console/src/components/segment-timeline/portal-bubble.tsx @@ -18,25 +18,35 @@ import classNames from 'classnames'; import type { ReactNode } from 'react'; +import { useRef } from 'react'; import { createPortal } from 'react-dom'; -import './bubble.scss'; +import { clamp } from '../../utils'; -interface BubbleProps { +import './portal-bubble.scss'; + +interface PortalBubbleProps { className?: string; openOn: { x: number; y: number; text: ReactNode } | undefined; direction?: 'up' | 'down'; mute?: boolean; } -export const Bubble = function Bubble(props: BubbleProps) { +export const PortalBubble = function PortalBubble(props: PortalBubbleProps) { const { className, openOn, direction = 'up', mute } = props; + const ref = useRef<HTMLDivElement | null>(null); if (!openOn) return null; + const div: HTMLDivElement | null = ref.current; + const myWidth = div ? div.offsetWidth : 200; + + const x = clamp(openOn.x, myWidth / 2, window.innerWidth - myWidth / 2); + return createPortal( <div - className={classNames('bubble', className, direction, { mute })} - style={{ left: openOn.x, top: openOn.y }} + className={classNames('portal-bubble', className, direction, { mute })} + ref={ref} + style={{ left: x, top: openOn.y }} > {openOn.text} </div>, 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 6dcadc9b629..dfbb50fc628 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 @@ -38,15 +38,14 @@ import { groupByAsMap, minute, month, - prettyFormatIsoDate, TZ_UTC, } from '../../utils'; 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 } from './common'; +import { PortalBubble } from './portal-bubble'; import './segment-bar-chart-render.scss'; @@ -63,6 +62,38 @@ const POSSIBLE_GRANULARITIES = [ const EXTEND_X_SCALE_DOMAIN_BY = 1; +function formatStartDuration(start: Date, duration: Duration): string { + let sliceLength; + const { singleSpan } = duration; + switch (singleSpan) { + case 'year': + sliceLength = 4; + break; + + case 'month': + sliceLength = 7; + break; + + case 'day': + sliceLength = 10; + break; + + case 'hour': + sliceLength = 13; + break; + + case 'minute': + sliceLength = 16; + break; + + default: + sliceLength = 19; + break; + } + + return `${start.toISOString().slice(0, sliceLength)}/${duration}`; +} + // --------------------------------------- // Load rule stuff @@ -413,11 +444,10 @@ export const SegmentBarChartRender = function SegmentBarChartRender( y: rect.y + CHART_MARGIN.top - 10, text: ( <> - <div>{`${prettyFormatIsoDate(hoveredIntervalBar.start) - .replace(/:00:00$/, '') - .replace(/ 00$/, '')}/${hoveredIntervalBar.originalTimeSpan}${ - hoveredIntervalBar.realtime ? ' (realtime)' : '' - }`}</div> + <div>{`${formatStartDuration( + hoveredIntervalBar.start, + hoveredIntervalBar.originalTimeSpan, + )}${hoveredIntervalBar.realtime ? ' (realtime)' : ''}`}</div> <div>Datasource: {hoveredIntervalBar.datasource}</div> <div>{`Size: ${ hoveredIntervalBar.realtime @@ -469,7 +499,7 @@ export const SegmentBarChartRender = function SegmentBarChartRender( className="gridline-x" transform="translate(0,0)" axis={axisLeft(statScale) - .ticks(3) + .tickValues(statScale.ticks(3).filter(v => v !== 0)) .tickSize(-innerStage.width) .tickFormat(() => '') .tickSizeOuter(0)} @@ -491,7 +521,7 @@ export const SegmentBarChartRender = function SegmentBarChartRender( <ChartAxis className="axis-y" axis={axisLeft(statScale) - .ticks(5) + .ticks(3) .tickFormat(e => formatTickRate(e.valueOf()))} /> <g className="bar-group"> @@ -567,8 +597,8 @@ export const SegmentBarChartRender = function SegmentBarChartRender( <div className="no-data-text">There are no segments in the selected range</div> </div> )} - <Bubble openOn={hoveredOpenOn} mute direction="up" /> - <Bubble openOn={bubbleOpenOn} mute direction="down" /> + <PortalBubble openOn={hoveredOpenOn} mute direction="up" /> + <PortalBubble openOn={bubbleOpenOn} mute direction="down" /> </div> ); }; diff --git a/web-console/src/utils/duration/duration.ts b/web-console/src/utils/duration/duration.ts index e582556b3f1..bc7236821f1 100755 --- a/web-console/src/utils/duration/duration.ts +++ b/web-console/src/utils/duration/duration.ts @@ -21,23 +21,22 @@ import { capitalizeFirst, pluralIfNeeded } from '../general'; export const TZ_UTC = 'Etc/UTC'; -const SPANS_WITH_WEEK = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second']; -const SPANS_WITHOUT_WEEK = ['year', 'month', 'day', 'hour', 'minute', 'second']; -const SPANS_WITHOUT_WEEK_OR_MONTH = ['year', 'day', 'hour', 'minute', 'second']; -const SPANS_UP_TO_DAY = ['day', 'hour', 'minute', 'second']; - -export interface DurationValue { - year?: number; - month?: number; - week?: number; - day?: number; - hour?: number; - minute?: number; - second?: number; - - // Indexable - [span: string]: number | undefined; -} +export type DurationSpan = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'; + +const SPANS_WITH_WEEK: DurationSpan[] = [ + 'year', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', +]; +const SPANS_WITHOUT_WEEK: DurationSpan[] = ['year', 'month', 'day', 'hour', 'minute', 'second']; +const SPANS_WITHOUT_WEEK_OR_MONTH: DurationSpan[] = ['year', 'day', 'hour', 'minute', 'second']; +const SPANS_UP_TO_DAY: DurationSpan[] = ['day', 'hour', 'minute', 'second']; + +export type DurationValue = Partial<Record<DurationSpan, number>>; const periodWeekRegExp = /^P(\d+)W$/; const periodRegExp = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/; @@ -116,8 +115,8 @@ function removeZeros(spans: DurationValue): DurationValue { return newSpans; } -function fitIntoSpans(length: number, spansToCheck: string[]): Record<string, number> { - const spans: Record<string, number> = {}; +function fitIntoSpans(length: number, spansToCheck: DurationSpan[]): DurationValue { + const spans: DurationValue = {}; let lengthLeft = length; for (let i = 0; i < spansToCheck.length; i++) { @@ -138,7 +137,7 @@ function fitIntoSpans(length: number, spansToCheck: string[]): Record<string, nu * Represents an ISO duration like P1DT3H */ export class Duration { - public readonly singleSpan?: string; + public readonly singleSpan?: DurationSpan; public readonly spans: Readonly<DurationValue>; static fromCanonicalLength(length: number, skipMonths = false): Duration { @@ -180,7 +179,7 @@ export class Duration { const effectiveSpans: DurationValue = typeof spans === 'string' ? getSpansFromString(spans) : removeZeros(spans); - const usedSpans = Object.keys(effectiveSpans); + const usedSpans = Object.keys(effectiveSpans) as DurationSpan[]; if (!usedSpans.length) throw new Error('Duration can not be empty'); if (usedSpans.length === 1) { this.singleSpan = usedSpans[0]; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
