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 02b963d818885b60380e2c01021142088a6bef89 Author: Vadim Ogievetsky <[email protected]> AuthorDate: Wed Nov 6 20:43:24 2024 -0800 fixes --- .../segment-timeline/segment-bar-chart-render.tsx | 3 +- .../segment-timeline/segment-bar-chart.tsx | 4 +- .../segment-timeline/segment-timeline.spec.tsx | 2 +- .../segment-timeline/segment-timeline.tsx | 141 ++++++++++++++++----- .../views/datasources-view/datasources-view.tsx | 9 +- .../src/views/segments-view/segments-view.tsx | 2 +- 6 files changed, 117 insertions(+), 44 deletions(-) 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 054e06a1725..4666656e246 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 @@ -213,7 +213,6 @@ export const SegmentBarChartRender = function SegmentBarChartRender( const baseTimeScale = scaleUtc() .domain(dateRange) .range([EXTEND_X_SCALE_DOMAIN_BY, innerStage.width - EXTEND_X_SCALE_DOMAIN_BY]); - const timeScale = shiftOffset ? baseTimeScale.copy().domain(offsetDateRange(dateRange, shiftOffset)) : baseTimeScale; @@ -224,7 +223,7 @@ export const SegmentBarChartRender = function SegmentBarChartRender( ); const statScale = scaleLinear() .rangeRound([innerStage.height, 0]) - .domain([0, maxNormalizedStat ?? 1]); + .domain([0, (maxNormalizedStat ?? 1) * 1.05]); const formatTickRate = (n: number) => { switch (shownIntervalStat) { 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 b745014d0a2..87fb41bc278 100644 --- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx +++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx @@ -83,8 +83,6 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr .addSelect(F.sum(C('num_rows')).as('rows')) .toString(); - console.log(query); - return (await queryDruidSql({ query }, cancelToken)).map(sr => { const start = new Date(sr.start); const end = new Date(sr.end); @@ -96,7 +94,7 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr 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( diff --git a/web-console/src/components/segment-timeline/segment-timeline.spec.tsx b/web-console/src/components/segment-timeline/segment-timeline.spec.tsx index 966e3113153..dbbb7c35fe3 100644 --- a/web-console/src/components/segment-timeline/segment-timeline.spec.tsx +++ b/web-console/src/components/segment-timeline/segment-timeline.spec.tsx @@ -25,7 +25,7 @@ import { SegmentTimeline } from './segment-timeline'; describe('SegmentTimeline', () => { it('matches snapshot', () => { const segmentTimeline = ( - <SegmentTimeline capabilities={Capabilities.FULL} initDatasource={undefined} /> + <SegmentTimeline capabilities={Capabilities.FULL} datasource={undefined} /> ); const { container } = render(segmentTimeline); expect(container.firstChild).toMatchSnapshot(); diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx b/web-console/src/components/segment-timeline/segment-timeline.tsx index 75964a70205..4446d7fff99 100644 --- a/web-console/src/components/segment-timeline/segment-timeline.tsx +++ b/web-console/src/components/segment-timeline/segment-timeline.tsx @@ -29,8 +29,10 @@ import type { NonNullDateRange } from '@blueprintjs/datetime'; import { DateRangePicker3 } from '@blueprintjs/datetime2'; import { IconNames } from '@blueprintjs/icons'; import { Select } from '@blueprintjs/select'; -import { useState } from 'react'; +import { C, L, N, SqlExpression, SqlQuery } from '@druid-toolkit/query'; +import { useEffect, useMemo, useState } from 'react'; +import { END_OF_TIME_DATE, START_OF_TIME_DATE } from '../../druid-models'; import type { Capabilities } from '../../helpers'; import { useQueryManager } from '../../hooks'; import { @@ -40,12 +42,12 @@ import { getApiArray, isNonNullRange, localToUtcDateRange, - prettyFormatIsoDate, queryDruidSql, TZ_UTC, utcToLocalDateRange, } from '../../utils'; import { Stage } from '../../utils/stage'; +import { Loader } from '../loader/loader'; import type { IntervalStat } from './common'; import { getIntervalStatTitle, INTERVAL_STATS } from './common'; @@ -55,7 +57,7 @@ import './segment-timeline.scss'; interface SegmentTimelineProps { capabilities: Capabilities; - initDatasource: string | undefined; + datasource: string | undefined; } const DEFAULT_SHOWN_DURATION = new Duration('P3M'); @@ -74,16 +76,30 @@ function getDateRange(shownDuration: Duration): NonNullDateRange { return [shownDuration.shift(end, TZ_UTC, -1), end]; } +function formatDateRange(dateRange: NonNullDateRange): string { + return `${dateRange[0].toISOString().slice(0, 10)} → ${dateRange[1].toISOString().slice(0, 10)}`; +} + +function dateRangesEqual(dr1: NonNullDateRange, dr2: NonNullDateRange): boolean { + return dr1[0].valueOf() === dr2[0].valueOf() && dr2[1].valueOf() === dr2[1].valueOf(); +} + export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelineProps) { - const { capabilities, initDatasource } = props; + const { capabilities, datasource } = props; const [stage, setStage] = useState<Stage | undefined>(); const [activeSegmentStat, setActiveSegmentStat] = useState<IntervalStat>('size'); - const [shownDatasource, setShownDatasource] = useState<string | undefined>(initDatasource); - const [dateRange, setDateRange] = useState<NonNullDateRange>( - getDateRange(DEFAULT_SHOWN_DURATION), - ); + const [shownDatasource, setShownDatasource] = useState<string | undefined>(datasource); + const [dateRange, setDateRange] = useState<NonNullDateRange | undefined>(); const [showCustomDatePicker, setShowCustomDatePicker] = useState(false); + useEffect(() => { + setShownDatasource(datasource); + }, [datasource]); + + const defaultDateRange = useMemo(() => { + return getDateRange(DEFAULT_SHOWN_DURATION); + }, []); + const [datasourcesState] = useQueryManager<Capabilities, string[]>({ initQuery: capabilities, processQuery: async (capabilities, cancelToken) => { @@ -102,6 +118,55 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr }, }); + const [initDatasourceDateRangeState] = useQueryManager<string, NonNullDateRange>({ + query: dateRange ? undefined : shownDatasource, + processQuery: async (datasource, cancelToken) => { + if (capabilities.hasSql()) { + const baseQuery = SqlQuery.from(N('sys').table('segments')) + .changeWhereExpression( + SqlExpression.and( + C('start').unequal(START_OF_TIME_DATE), + C('end').unequal(END_OF_TIME_DATE), + C('is_overshadowed').equal(0), + C('datasource').equal(L(datasource)), + ), + ) + .changeLimitValue(1); + + const startQuery = baseQuery + .addSelect(C('start'), { addToOrderBy: 'end', direction: 'ASC' }) + .toString(); + + const startRes = await queryDruidSql<{ start: string }>({ query: startQuery }, cancelToken); + if (startRes.length !== 1) { + return getDateRange(DEFAULT_SHOWN_DURATION); + } + + const start = day.floor(new Date(startRes[0].start), TZ_UTC); + + const endQuery = baseQuery + .addSelect(C('end'), { addToOrderBy: 'end', direction: 'DESC' }) + .toString(); + + const endRes = await queryDruidSql<{ end: string }>({ query: endQuery }, cancelToken); + if (endRes.length !== 1) { + return [start, day.ceil(new Date(), TZ_UTC)]; // Should not really get here + } + + const end = day.ceil(new Date(endRes[0].end), TZ_UTC); + return [start, end]; + } else { + // ToDo: fill me + throw new Error('fill me'); + } + }, + }); + + const effectiveDateRange = + dateRange || + initDatasourceDateRangeState.data || + (initDatasourceDateRangeState.isLoading() ? undefined : defaultDateRange); + return ( <div className="segment-timeline"> <div className="control-bar"> @@ -110,20 +175,24 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr icon={IconNames.CARET_LEFT} data-tooltip="Previous time period" small + disabled={!effectiveDateRange} onClick={() => { - const d = Duration.fromRange(dateRange[0], dateRange[1], TZ_UTC); - setDateRange([d.shift(dateRange[0], TZ_UTC, -1), dateRange[0]]); + if (!effectiveDateRange) return; + const d = Duration.fromRange(effectiveDateRange[0], effectiveDateRange[1], TZ_UTC); + setDateRange([d.shift(effectiveDateRange[0], TZ_UTC, -1), effectiveDateRange[0]]); }} /> <Button icon={IconNames.ZOOM_OUT} data-tooltip="Zoom out" small + disabled={!effectiveDateRange} onClick={e => { - const d = Duration.fromRange(dateRange[0], dateRange[1], TZ_UTC); + if (!effectiveDateRange) return; + const d = Duration.fromRange(effectiveDateRange[0], effectiveDateRange[1], TZ_UTC); setDateRange([ - d.shift(dateRange[0], TZ_UTC, -1), - e.altKey ? d.shift(dateRange[1], TZ_UTC, 1) : dateRange[1], + d.shift(effectiveDateRange[0], TZ_UTC, -1), + e.altKey ? d.shift(effectiveDateRange[1], TZ_UTC, 1) : effectiveDateRange[1], ]); }} /> @@ -131,28 +200,36 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr icon={IconNames.CARET_RIGHT} data-tooltip="Next time period" small + disabled={!effectiveDateRange} onClick={() => { - const d = Duration.fromRange(dateRange[0], dateRange[1], TZ_UTC); - setDateRange([dateRange[1], d.shift(dateRange[1], TZ_UTC, 1)]); + if (!effectiveDateRange) return; + const d = Duration.fromRange(effectiveDateRange[0], effectiveDateRange[1], TZ_UTC); + setDateRange([effectiveDateRange[1], d.shift(effectiveDateRange[1], TZ_UTC, 1)]); }} /> </ButtonGroup> <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))} - /> - ))} + {SHOWN_DURATION_OPTIONS.map((d, i) => { + const dr = getDateRange(d); + return ( + <Button + key={i} + text={d.toString().replace('P', '')} + active={effectiveDateRange && dateRangesEqual(effectiveDateRange, dr)} + data-tooltip={`Show last ${d.getDescription()}\n${formatDateRange(dr)}`} + small + onClick={() => setDateRange(dr)} + /> + ); + })} <Popover isOpen={showCustomDatePicker} onInteraction={setShowCustomDatePicker} content={ <DateRangePicker3 - defaultValue={utcToLocalDateRange(dateRange)} + defaultValue={utcToLocalDateRange( + effectiveDateRange || getDateRange(DEFAULT_SHOWN_DURATION), + )} onChange={newDateRange => { const newUtcDateRange = localToUtcDateRange(newDateRange); if (!isNonNullRange(newUtcDateRange)) return; @@ -168,14 +245,9 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr > <Button icon={IconNames.CALENDAR} + text={effectiveDateRange ? formatDateRange(effectiveDateRange) : '? → ?'} small - data-tooltip={ - showCustomDatePicker - ? undefined - : `Select a custom date range\nCurrent range: ${prettyFormatIsoDate( - dateRange[0], - )} - ${prettyFormatIsoDate(dateRange[1])}` - } + data-tooltip={showCustomDatePicker ? undefined : `Select a custom date range`} /> </Popover> </ButtonGroup> @@ -248,11 +320,11 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr }} > <div className="chart-container"> - {stage && ( + {stage && effectiveDateRange && ( <SegmentBarChart capabilities={capabilities} stage={stage} - dateRange={dateRange} + dateRange={effectiveDateRange} changeDateRange={setDateRange} shownIntervalStat={activeSegmentStat} shownDatasource={shownDatasource} @@ -261,6 +333,7 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr } /> )} + {initDatasourceDateRangeState.isLoading() && <Loader />} </div> </ResizeSensor> </div> diff --git a/web-console/src/views/datasources-view/datasources-view.tsx b/web-console/src/views/datasources-view/datasources-view.tsx index de30832ce39..e79592d3aa5 100644 --- a/web-console/src/views/datasources-view/datasources-view.tsx +++ b/web-console/src/views/datasources-view/datasources-view.tsx @@ -1147,7 +1147,7 @@ GROUP BY 1, 2`; } private renderDatasourcesTable() { - const { goToSegments, goToTasks, capabilities, filters, onFiltersChange } = this.props; + const { goToTasks, capabilities, filters, onFiltersChange } = this.props; const { datasourcesAndDefaultRulesState, showUnused, visibleColumns, showSegmentTimeline } = this.state; @@ -1240,7 +1240,10 @@ GROUP BY 1, 2`; const hasZeroReplicationRule = RuleUtil.hasZeroReplicaRule(rules, defaultRules); const descriptor = hasZeroReplicationRule ? 'pre-cached' : 'available'; const segmentsEl = ( - <a onClick={() => goToSegments(datasource)}> + <a + onClick={() => this.setState({ showSegmentTimeline: { datasource } })} + data-tooltip="Show in segment timeline" + > {pluralIfNeeded(num_segments, 'segment')} </a> ); @@ -1742,7 +1745,7 @@ GROUP BY 1, 2`; {showSegmentTimeline && ( <SegmentTimeline capabilities={capabilities} - initDatasource={showSegmentTimeline.datasource} + datasource={showSegmentTimeline.datasource} /> )} {this.renderDatasourcesTable()} diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 98e8fa44bb0..6f3cd468360 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -1054,7 +1054,7 @@ export class SegmentsView extends React.PureComponent<SegmentsViewProps, Segment secondaryMinSize={10} > {showSegmentTimeline && ( - <SegmentTimeline capabilities={capabilities} initDatasource={undefined} /> + <SegmentTimeline capabilities={capabilities} datasource={undefined} /> )} {this.renderSegmentsTable()} </SplitterLayout> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
