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 7e8003112ccdccef2997844449c9e42be7de8fc2 Author: Vadim Ogievetsky <[email protected]> AuthorDate: Fri Nov 1 11:40:41 2024 -0700 fixes --- .../segment-timeline/segment-bar-chart-render.scss | 7 +- .../segment-timeline/segment-bar-chart-render.tsx | 77 ++++++++++++++-------- .../segment-timeline/segment-timeline.tsx | 56 ++++++++++++++-- .../components/source-pane/source-pane.tsx | 4 +- 4 files changed, 105 insertions(+), 39 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 90a6bcbe8d6..c036c313cf3 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 @@ -29,8 +29,13 @@ user-select: none; } + .hover-highlight { + fill: white; + fill-opacity: 0.1; + } + .hovered-bar { - fill: transparent; + fill: none; stroke: #ffffff; stroke-width: 1.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 026b6e7d0d8..edb3a90e655 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 @@ -37,6 +37,7 @@ import { formatInteger, formatNumber, groupBy, + month, prettyFormatIsoDate, TZ_UTC, } from '../../utils'; @@ -49,7 +50,7 @@ import { aggregateSegmentStats } from './common'; import './segment-bar-chart-render.scss'; -const CHART_MARGIN: Margin = { top: 40, right: 10, bottom: 25, left: 80 }; +const CHART_MARGIN: Margin = { top: 40, right: 0, bottom: 25, left: 70 }; const MIN_BAR_WIDTH = 2; const POSSIBLE_GRANULARITIES = [ new Duration('PT15M'), @@ -60,7 +61,6 @@ const POSSIBLE_GRANULARITIES = [ new Duration('P1Y'), ]; -const EXTEND_DATE_RANGE_BY = 0; const EXTEND_X_SCALE_DOMAIN_BY = 10; function offsetDateRange(dateRange: NonNullDateRange, offset: number): NonNullDateRange { @@ -124,22 +124,17 @@ export const SegmentBarChartRender = function SegmentBarChartRender( const [dragging, setDragging] = useState<NonNullDateRange | undefined>(); const [shiftOffset, setShiftOffset] = useState<number | undefined>(); const [bubbleOpenOn, setBubbleOpenOn] = useState< - { x: number; y: number; text: string } | undefined + { start: Date; end: Date; x: number; y: number; text: string } | undefined >(); const svgRef = useRef<SVGSVGElement | null>(null); - const expandedDateRange: NonNullDateRange = useMemo(() => { - const extend = (dateRange[1].valueOf() - dateRange[0].valueOf()) * EXTEND_DATE_RANGE_BY; - return [new Date(dateRange[0].valueOf() - extend), new Date(dateRange[1].valueOf() + extend)]; - }, [dateRange]); - const trimGranularity = useMemo(() => { return Duration.pickSmallestGranularityThatFits( POSSIBLE_GRANULARITIES, - expandedDateRange[1].valueOf() - expandedDateRange[0].valueOf(), + dateRange[1].valueOf() - dateRange[0].valueOf(), Math.floor(stage.width / MIN_BAR_WIDTH), ).toString(); - }, [expandedDateRange, stage.width]); + }, [dateRange, stage.width]); const intervalBars = useMemo(() => { const trimDuration = new Duration(trimGranularity); @@ -186,11 +181,11 @@ export const SegmentBarChartRender = function SegmentBarChartRender( const innerStage = stage.applyMargin(CHART_MARGIN); const baseTimeScale = scaleUtc() - .domain(expandedDateRange) + .domain(dateRange) .range([EXTEND_X_SCALE_DOMAIN_BY, innerStage.width - EXTEND_X_SCALE_DOMAIN_BY]); const timeScale = shiftOffset - ? baseTimeScale.copy().domain(offsetDateRange(expandedDateRange, shiftOffset)) + ? baseTimeScale.copy().domain(offsetDateRange(dateRange, shiftOffset)) : baseTimeScale; const maxNormalizedStat = max( @@ -233,10 +228,16 @@ export const SegmentBarChartRender = function SegmentBarChartRender( 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'; + setBubbleOpenOn(undefined); setMouseDownAt({ - time: baseTimeScale.invert(x), - action: y > innerStage.height ? 'shift' : 'select', + time, + action, }); + if (action === 'select') { + setDragging([day.floor(time, TZ_UTC), day.ceil(time, TZ_UTC)]); + } } useGlobalEventListener('mousemove', (e: MouseEvent) => { @@ -248,16 +249,19 @@ export const SegmentBarChartRender = function SegmentBarChartRender( if (!mouseDownAt) { if (0 <= x && x <= innerStage.width && 0 <= y && y <= innerStage.height) { + const shifter = + new Duration(trimGranularity).getCanonicalLength() > day.canonicalLength * 25 + ? month + : day; const time = baseTimeScale.invert(x); - const granStart = day.floor(time, TZ_UTC); - const granEnd = day.ceil(time, TZ_UTC); + const start = shifter.floor(time, TZ_UTC); + const end = shifter.ceil(time, TZ_UTC); setBubbleOpenOn({ - x: - rect.x + - CHART_MARGIN.left + - baseTimeScale((granStart.valueOf() + granEnd.valueOf()) / 2), + start, + end, + x: rect.x + CHART_MARGIN.left + baseTimeScale((start.valueOf() + end.valueOf()) / 2), y: rect.y + CHART_MARGIN.top + innerStage.height + 10, - text: granStart.toISOString().slice(0, 10), + text: start.toISOString().slice(0, shifter === day ? 10 : 7), }); } else { setBubbleOpenOn(undefined); @@ -298,23 +302,30 @@ export const SegmentBarChartRender = function SegmentBarChartRender( } }); + function startEndToXWidth({ start, end }: { start: Date; end: Date }) { + const xStart = clamp(timeScale(start), 0, innerStage.width); + const xEnd = clamp(timeScale(end), 0, innerStage.width); + + return { + x: xStart, + width: Math.max(xEnd - xStart - 1, 1), + }; + } + function segmentBarToRect(intervalBar: IntervalBar) { - const xStart = clamp(timeScale(intervalBar.start), 0, innerStage.width); - const xEnd = clamp(timeScale(intervalBar.end), 0, innerStage.width); const y0 = statScale(intervalBar.offset[shownIntervalStat]); const y = statScale( intervalBar.normalized[shownIntervalStat] + intervalBar.offset[shownIntervalStat], ); return { - x: xStart, + ...startEndToXWidth(intervalBar), y: y, - width: Math.max(xEnd - xStart - 1, 1), height: Math.abs(y0 - y), }; } - console.log('here'); + console.log('Bar chart render'); return ( <div className="segment-bar-chart-render"> @@ -360,6 +371,14 @@ export const SegmentBarChartRender = function SegmentBarChartRender( .tickFormat(e => formatTickRate(e.valueOf()))} /> <g className="bar-group"> + {bubbleOpenOn && ( + <rect + className="hover-highlight" + {...startEndToXWidth(bubbleOpenOn)} + y={0} + height={innerStage.height} + /> + )} {intervalBars.map((intervalBar, i) => { return ( <rect @@ -397,13 +416,13 @@ export const SegmentBarChartRender = function SegmentBarChartRender( {!!shiftOffset && ( <rect className="shifter" - x={shiftOffset > 0 ? timeScale(expandedDateRange[1]) : 0} + x={shiftOffset > 0 ? timeScale(dateRange[1]) : 0} y={0} height={innerStage.height} width={ shiftOffset > 0 - ? innerStage.width - timeScale(expandedDateRange[1]) - : timeScale(expandedDateRange[0]) + ? innerStage.width - timeScale(dateRange[1]) + : timeScale(dateRange[0]) } /> )} diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx b/web-console/src/components/segment-timeline/segment-timeline.tsx index e113fdea571..5fbc2587ea2 100644 --- a/web-console/src/components/segment-timeline/segment-timeline.tsx +++ b/web-console/src/components/segment-timeline/segment-timeline.tsx @@ -33,12 +33,15 @@ import type React from 'react'; import { useState } from 'react'; import type { Capabilities } from '../../helpers'; +import { useQueryManager } from '../../hooks'; +import { Api } from '../../singletons'; import { day, Duration, isNonNullRange, localToUtcDateRange, prettyFormatIsoDate, + queryDruidSql, TZ_UTC, utcToLocalDateRange, } from '../../utils'; @@ -79,11 +82,27 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr ); const [showCustomDatePicker, setShowCustomDatePicker] = useState(false); - const datasources: string[] = ['wiki', 'kttm']; // ToDo + const [datasourcesState] = useQueryManager<Capabilities, string[]>({ + initQuery: capabilities, + processQuery: async (capabilities, cancelToken) => { + if (capabilities.hasSql()) { + const tables = await queryDruidSql<{ TABLE_NAME: string }>( + { + query: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'TABLE'`, + }, + cancelToken, + ); + + return tables.map(d => d.TABLE_NAME); + } else { + return (await Api.instance.get(`/druid/coordinator/v1/datasources`, { cancelToken })).data; + } + }, + }); const DatasourceSelect: React.FC = () => { const showAll = 'Show all'; - const datasourcesWzAll = [showAll].concat(datasources); + const datasourcesWzAll = [showAll].concat(datasourcesState.data || []); return ( <Select<string> items={datasourcesWzAll} @@ -116,11 +135,7 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr } }} > - <Button - text={focusDatasource === null ? showAll : focusDatasource} - fill - rightIcon={IconNames.CARET_DOWN} - /> + <Button text={focusDatasource ?? showAll} rightIcon={IconNames.CARET_DOWN} /> </Select> ); }; @@ -128,6 +143,32 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr return ( <div className="segment-timeline"> <div className="control-bar"> + <ButtonGroup> + <Button + icon={IconNames.CARET_LEFT} + small + onClick={() => { + const d = Duration.fromRange(dateRange[0], dateRange[1], TZ_UTC); + setDateRange([d.shift(dateRange[0], TZ_UTC, -1), dateRange[0]]); + }} + /> + <Button + icon={IconNames.ZOOM_OUT} + small + onClick={() => { + const d = Duration.fromRange(dateRange[0], dateRange[1], TZ_UTC); + setDateRange([d.shift(dateRange[0], TZ_UTC, -1), dateRange[1]]); + }} + /> + <Button + icon={IconNames.CARET_RIGHT} + small + onClick={() => { + const d = Duration.fromRange(dateRange[0], dateRange[1], TZ_UTC); + setDateRange([dateRange[1], d.shift(dateRange[1], TZ_UTC, 1)]); + }} + /> + </ButtonGroup> <ButtonGroup> {SHOWN_DURATION_OPTIONS.map((d, i) => ( <Button @@ -159,6 +200,7 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr > <Button icon={IconNames.CALENDAR} + small data-tooltip={`Select a custom date range\nCurrent range: ${prettyFormatIsoDate( dateRange[0], )} - ${prettyFormatIsoDate(dateRange[1])}`} diff --git a/web-console/src/views/explore-view/components/source-pane/source-pane.tsx b/web-console/src/views/explore-view/components/source-pane/source-pane.tsx index c97f736d9bc..5922152360b 100644 --- a/web-console/src/views/explore-view/components/source-pane/source-pane.tsx +++ b/web-console/src/views/explore-view/components/source-pane/source-pane.tsx @@ -53,8 +53,8 @@ export interface SourcePaneProps { export const SourcePane = React.memo(function SourcePane(props: SourcePaneProps) { const { selectedSource, onSelectTable, onShowSourceQuery, fill, minimal, disabled } = props; - const [tables] = useQueryManager<string, string[]>({ - initQuery: '', + const [tables] = useQueryManager<null, string[]>({ + initQuery: null, processQuery: async () => { const tables = await queryDruidSql<{ TABLE_NAME: string }>({ query: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'TABLE'`, --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
