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 ded6fc2a0d66783213c8d9c9d84b36c562e78b03 Author: Vadim Ogievetsky <[email protected]> AuthorDate: Wed Oct 30 09:24:13 2024 -0700 checkpoint --- .../src/components/segment-timeline/common.ts | 8 +- .../segment-timeline/segment-bar-chart-render.tsx | 10 +- .../segment-timeline/segment-bar-chart.tsx | 117 ++++++--------------- .../segment-timeline/segment-timeline.tsx | 13 ++- web-console/src/utils/date.spec.ts | 9 -- web-console/src/utils/date.ts | 89 ---------------- web-console/src/utils/duration/duration.ts | 18 +++- .../explore-view/utils/get-auto-granularity.ts | 80 ++++++-------- .../time-menu-items/time-menu-items.tsx | 28 ++--- 9 files changed, 98 insertions(+), 274 deletions(-) diff --git a/web-console/src/components/segment-timeline/common.ts b/web-console/src/components/segment-timeline/common.ts index a1907846f29..bfb6d84e5f3 100644 --- a/web-console/src/components/segment-timeline/common.ts +++ b/web-console/src/components/segment-timeline/common.ts @@ -30,18 +30,18 @@ export function aggregateSegmentStats( }; } -export interface SegmentRow extends Record<SegmentStat, number> { +export interface IntervalRow extends Record<SegmentStat, number> { start: Date; end: Date; durationSeconds: number; - datasource?: string; + datasource: string; } -export interface SegmentBar extends SegmentRow { +export interface SegmentBar extends IntervalRow { offset: Record<SegmentStat, number>; } -export function normalizedSegmentRow(sr: SegmentRow): SegmentRow { +export function normalizedSegmentRow(sr: IntervalRow): IntervalRow { return { ...sr, count: sr.count / sr.durationSeconds, 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 f1e67c9665a..7864021622d 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 @@ -27,13 +27,13 @@ import { useMemo, useRef, useState } from 'react'; import { useGlobalEventListener } from '../../hooks'; import { capitalizeFirst, - ceilDay, clamp, - floorDay, + day, formatByteRate, formatBytes, formatInteger, formatNumber, + TZ_UTC, } from '../../utils'; import type { Margin, Stage } from '../../utils/stage'; @@ -105,7 +105,7 @@ export const SegmentBarChartRender = function SegmentBarChartRender( const colorizer = useMemo(() => { const s = scaleOrdinal().range(COLORS); - return (d: SegmentBar) => (d.datasource ? s(d.datasource) : COLORS[0]) as string; + return (d: SegmentBar) => s(d.datasource) as string; }, []); const maxStat = max(segmentBars, d => d[shownSegmentStat] + d.offset[shownSegmentStat]); @@ -160,9 +160,9 @@ export const SegmentBarChartRender = function SegmentBarChartRender( setShiftOffset(mouseDownAt.time.valueOf() - b.valueOf()); } else { if (mouseDownAt.time < b) { - setDragging([floorDay(mouseDownAt.time), ceilDay(b)]); + setDragging([day.floor(mouseDownAt.time, TZ_UTC), day.ceil(b, TZ_UTC)]); } else { - setDragging([floorDay(b), ceilDay(mouseDownAt.time)]); + setDragging([day.floor(b, TZ_UTC), day.ceil(mouseDownAt.time, TZ_UTC)]); } } }); 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 4b24849b723..3ba9b16f082 100644 --- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx +++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx @@ -24,69 +24,17 @@ import { useMemo } from 'react'; import type { Capabilities } from '../../helpers'; import { useQueryManager } from '../../hooks'; import { Api } from '../../singletons'; -import { - ceilDay, - ceilHour, - ceilMonth, - ceilYear, - filterMap, - floorDay, - floorHour, - floorMonth, - floorYear, - groupBy, - queryDruidSql, -} from '../../utils'; +import { Duration, filterMap, groupBy, queryDruidSql, TZ_UTC } from '../../utils'; import type { Stage } from '../../utils/stage'; import { Loader } from '../loader/loader'; -import type { SegmentBar, SegmentRow, SegmentStat } from './common'; +import type { IntervalRow, SegmentBar, SegmentStat } from './common'; import { aggregateSegmentStats, normalizedSegmentRow } from './common'; import { SegmentBarChartRender } from './segment-bar-chart-render'; import './segment-bar-chart.scss'; -type TrimDuration = 'PT1H' | 'P1D' | 'P1M' | 'P1Y'; - -function floorToDuration(date: Date, duration: TrimDuration): Date { - switch (duration) { - case 'PT1H': - return floorHour(date); - - case 'P1D': - return floorDay(date); - - case 'P1M': - return floorMonth(date); - - case 'P1Y': - return floorYear(date); - - default: - throw new Error(`Unexpected duration: ${duration}`); - } -} - -function ceilToDuration(date: Date, duration: TrimDuration): Date { - switch (duration) { - case 'PT1H': - return ceilHour(date); - - case 'P1D': - return ceilDay(date); - - case 'P1M': - return ceilMonth(date); - - case 'P1Y': - return ceilYear(date); - - default: - throw new Error(`Unexpected duration: ${duration}`); - } -} - -function stackSegmentRows(segmentRows: SegmentRow[]): SegmentBar[] { +function stackIntervalRows(segmentRows: IntervalRow[]): SegmentBar[] { const sorted = segmentRows.sort((a, b) => { const diff = b.durationSeconds - a.durationSeconds; if (diff) return diff; @@ -99,7 +47,7 @@ function stackSegmentRows(segmentRows: SegmentRow[]): SegmentBar[] { segmentRow = normalizedSegmentRow(segmentRow); const startMs = segmentRow.start.valueOf(); const endMs = segmentRow.end.valueOf(); - const segmentRowsBelow = intervalTree.search([startMs + 1, startMs + 2]) as SegmentRow[]; + const segmentRowsBelow = intervalTree.search([startMs + 1, startMs + 2]) as IntervalRow[]; intervalTree.insert([startMs, endMs], segmentRow); return { ...segmentRow, @@ -113,7 +61,6 @@ interface SegmentBarChartProps { stage: Stage; dateRange: NonNullDateRange; changeDateRange(newDateRange: NonNullDateRange): void; - breakByDataSource: boolean; shownSegmentStat: SegmentStat; changeActiveDatasource: (datasource: string | undefined) => void; } @@ -123,22 +70,18 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr capabilities, dateRange, changeDateRange, - breakByDataSource, stage, shownSegmentStat, changeActiveDatasource, } = props; - const intervalsQuery = useMemo( - () => ({ capabilities, dateRange, breakByDataSource }), - [capabilities, dateRange, breakByDataSource], - ); + const intervalsQuery = useMemo(() => ({ capabilities, dateRange }), [capabilities, dateRange]); - const [segmentBarsState] = useQueryManager({ + const [intervalBarsState] = useQueryManager({ query: intervalsQuery, - processQuery: async ({ capabilities, dateRange, breakByDataSource }, cancelToken) => { - const trimDuration: TrimDuration = 'PT1H'; - let segmentRows: SegmentRow[]; + processQuery: async ({ capabilities, dateRange }, cancelToken) => { + const trimDuration = new Duration('PT1H'); + let intervalRows: IntervalRow[]; if (capabilities.hasSql()) { const query = SqlQuery.from(N('sys').table('segments')) .changeWhereExpression( @@ -146,14 +89,14 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr ) .addSelect(C('start'), { addToGroupBy: 'end' }) .addSelect(C('end'), { addToGroupBy: 'end' }) - .applyIf(breakByDataSource, q => q.addSelect(C('datasource'), { addToGroupBy: 'end' })) + .addSelect(C('datasource'), { addToGroupBy: 'end' }) .addSelect(F.count().as('count')) .addSelect(F.sum(C('size')).as('size')) .addSelect(F.sum(C('num_rows')).as('rows')); - segmentRows = (await queryDruidSql({ query: query.toString() })).map(sr => { - const start = floorToDuration(new Date(sr.start), trimDuration); - const end = ceilToDuration(new Date(sr.end), trimDuration); + intervalRows = (await queryDruidSql({ query: query.toString() })).map(sr => { + const start = trimDuration.floor(new Date(sr.start), TZ_UTC); + const end = trimDuration.ceil(new Date(sr.end), TZ_UTC); return { ...sr, start, @@ -165,7 +108,7 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr const datasources: string[] = ( await Api.instance.get(`/druid/coordinator/v1/datasources`, { cancelToken }) ).data; - segmentRows = ( + intervalRows = ( await Promise.all( datasources.map(async datasource => { const intervalMap = ( @@ -180,14 +123,14 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr return filterMap(Object.entries(intervalMap), ([interval, v]) => { // ToDo: Filter on start end const [startStr, endStr] = interval.split('/'); - const start = floorToDuration(new Date(startStr), trimDuration); - const end = ceilToDuration(new Date(endStr), trimDuration); + const start = trimDuration.floor(new Date(startStr), TZ_UTC); + const end = trimDuration.ceil(new Date(endStr), TZ_UTC); const { count, size, rows } = v as any; return { start, end, durationSeconds: (end.valueOf() - start.valueOf()) / 1000, - datasource: breakByDataSource ? datasource : undefined, + datasource, count, size, rows, @@ -199,38 +142,38 @@ export const SegmentBarChart = function SegmentBarChart(props: SegmentBarChartPr } const fullyGroupedSegmentRows = groupBy( - segmentRows, - segmentRow => + intervalRows, + intervalRow => [ - segmentRow.start.toISOString(), - segmentRow.end.toISOString(), - segmentRow.datasource || '', + intervalRow.start.toISOString(), + intervalRow.end.toISOString(), + intervalRow.datasource || '', ].join('/'), - (segmentRows): SegmentRow => { + (intervalRows): IntervalRow => { return { - ...segmentRows[0], - ...aggregateSegmentStats(segmentRows), + ...intervalRows[0], + ...aggregateSegmentStats(intervalRows), }; }, ); - return stackSegmentRows(fullyGroupedSegmentRows); + return stackIntervalRows(fullyGroupedSegmentRows); }, }); - if (segmentBarsState.loading) { + if (intervalBarsState.loading) { return <Loader />; } - if (segmentBarsState.error) { + if (intervalBarsState.error) { return ( <div className="empty-placeholder"> - <span className="no-data-text">{`Error when loading data: ${segmentBarsState.getErrorMessage()}`}</span> + <span className="no-data-text">{`Error when loading data: ${intervalBarsState.getErrorMessage()}`}</span> </div> ); } - const segmentBars = segmentBarsState.data; + const segmentBars = intervalBarsState.data; if (!segmentBars) return null; return ( diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx b/web-console/src/components/segment-timeline/segment-timeline.tsx index 10118c30f9d..db6653c912b 100644 --- a/web-console/src/components/segment-timeline/segment-timeline.tsx +++ b/web-console/src/components/segment-timeline/segment-timeline.tsx @@ -27,9 +27,11 @@ import { useState } from 'react'; import type { Capabilities } from '../../helpers'; import { - ceilToUtcDay, + day, isNonNullRange, localToUtcDateRange, + month, + TZ_UTC, utcToLocalDateRange, } from '../../utils'; import { Stage } from '../../utils/stage'; @@ -47,10 +49,8 @@ interface SegmentTimelineProps { const DEFAULT_TIME_SPAN_MONTHS = 3; function getDefaultDateRange(): NonNullDateRange { - const start = ceilToUtcDay(new Date()); - const end = new Date(start.valueOf()); - start.setUTCMonth(start.getUTCMonth() - DEFAULT_TIME_SPAN_MONTHS); - return [start, end]; + const end = day.ceil(new Date(), TZ_UTC); + return [month.shift(end, TZ_UTC, -DEFAULT_TIME_SPAN_MONTHS), end]; } export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelineProps) { @@ -60,7 +60,7 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr const [activeDatasource, setActiveDatasource] = useState<string | undefined>(); const [dateRange, setDateRange] = useState<NonNullDateRange>(getDefaultDateRange); - const datasources: string[] = ['wiki', 'kttm']; + const datasources: string[] = ['wiki', 'kttm']; // ToDo const DatasourceSelect: React.FC = () => { const showAll = 'Show all'; @@ -128,7 +128,6 @@ export const SegmentTimeline = function SegmentTimeline(props: SegmentTimelinePr dateRange={dateRange} changeDateRange={setDateRange} shownSegmentStat={activeSegmentStat} - breakByDataSource changeActiveDatasource={(datasource: string | undefined) => setActiveDatasource(activeDatasource ? undefined : datasource) } diff --git a/web-console/src/utils/date.spec.ts b/web-console/src/utils/date.spec.ts index 843c144244e..b219ee17af0 100644 --- a/web-console/src/utils/date.spec.ts +++ b/web-console/src/utils/date.spec.ts @@ -17,7 +17,6 @@ */ import { - ceilToUtcDay, dateToIsoDateString, intervalToLocalDateRange, localDateRangeToInterval, @@ -60,12 +59,4 @@ describe('date', () => { expect(localDateRangeToInterval(intervalToLocalDateRange(interval))).toEqual(interval); }); }); - - describe('ceilToUtcDay', () => { - it('works', () => { - expect(ceilToUtcDay(new Date('2021-02-03T12:03:02.001Z'))).toEqual( - new Date('2021-02-04T00:00:00Z'), - ); - }); - }); }); diff --git a/web-console/src/utils/date.ts b/web-console/src/utils/date.ts index 897618c030b..e59c01a74df 100644 --- a/web-console/src/utils/date.ts +++ b/web-console/src/utils/date.ts @@ -98,92 +98,3 @@ export function localDateRangeToInterval(localRange: DateRange): string { localEndDate ? localToUtcDate(localEndDate).toISOString().slice(0, 19) : '' }`; } - -export function ceilToUtcDay(date: Date): Date { - date = new Date(date.valueOf()); - date.setUTCHours(0, 0, 0, 0); - date.setUTCDate(date.getUTCDate() + 1); - return date; -} - -// ------------------------------------ - -export function floorHour(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCMinutes(0, 0, 0); - return dt; -} - -export function nextHour(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCHours(dt.getUTCHours() + 1); - return dt; -} - -export function ceilHour(dt: Date): Date { - const floor = floorHour(dt); - if (floor.valueOf() === dt.valueOf()) return dt; - return nextHour(floor); -} - -// ------------------------------------ - -export function floorDay(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCHours(0, 0, 0, 0); - return dt; -} - -export function nextDay(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCDate(dt.getUTCDate() + 1); - return dt; -} - -export function ceilDay(dt: Date): Date { - const floor = floorDay(dt); - if (floor.valueOf() === dt.valueOf()) return dt; - return nextDay(floor); -} - -// ------------------------------------ - -export function floorMonth(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCHours(0, 0, 0, 0); - dt.setUTCDate(1); - return dt; -} - -export function nextMonth(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCMonth(dt.getUTCMonth() + 1); - return dt; -} - -export function ceilMonth(dt: Date): Date { - const floor = floorMonth(dt); - if (floor.valueOf() === dt.valueOf()) return dt; - return nextMonth(floor); -} - -// ------------------------------------ - -export function floorYear(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCHours(0, 0, 0, 0); - dt.setUTCMonth(0, 1); - return dt; -} - -export function nextYear(dt: Date): Date { - dt = new Date(dt.valueOf()); - dt.setUTCFullYear(dt.getUTCFullYear() + 1); - return dt; -} - -export function ceilYear(dt: Date): Date { - const floor = floorYear(dt); - if (floor.valueOf() === dt.valueOf()) return dt; - return nextYear(floor); -} diff --git a/web-console/src/utils/duration/duration.ts b/web-console/src/utils/duration/duration.ts index b93dab175ad..e582556b3f1 100755 --- a/web-console/src/utils/duration/duration.ts +++ b/web-console/src/utils/duration/duration.ts @@ -19,6 +19,8 @@ import { second, shifters } from '../date-floor-shift-ceil/date-floor-shift-ceil'; 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']; @@ -139,11 +141,6 @@ export class Duration { public readonly singleSpan?: string; public readonly spans: Readonly<DurationValue>; - static parse(durationStr: string): Duration { - if (typeof durationStr !== 'string') throw new TypeError('Duration JS must be a string'); - return new Duration(getSpansFromString(durationStr)); - } - static fromCanonicalLength(length: number, skipMonths = false): Duration { if (length <= 0) throw new Error('length must be positive'); let spans = fitIntoSpans(length, skipMonths ? SPANS_WITHOUT_WEEK_OR_MONTH : SPANS_WITHOUT_WEEK); @@ -168,6 +165,17 @@ export class Duration { return new Duration(getSpansFromStartEnd(start, end, timezone)); } + static pickSmallestGranularityThatFits( + granularities: Duration[], + span: number, + maxEntities: number, + ): Duration { + for (const granularity of granularities) { + if (span / granularity.getCanonicalLength() < maxEntities) return granularity; + } + return granularities[granularities.length - 1]; + } + constructor(spans: DurationValue | string) { const effectiveSpans: DurationValue = typeof spans === 'string' ? getSpansFromString(spans) : removeZeros(spans); diff --git a/web-console/src/views/explore-view/utils/get-auto-granularity.ts b/web-console/src/views/explore-view/utils/get-auto-granularity.ts index 21cc2743820..34c3f27b4a6 100644 --- a/web-console/src/views/explore-view/utils/get-auto-granularity.ts +++ b/web-console/src/views/explore-view/utils/get-auto-granularity.ts @@ -17,65 +17,47 @@ */ import type { SqlExpression } from '@druid-toolkit/query'; -import { fitFilterPattern, SqlMulti } from '@druid-toolkit/query'; +import { fitFilterPatterns } from '@druid-toolkit/query'; -import { day, Duration, hour } from '../../../utils'; +import { Duration } from '../../../utils'; -function getCanonicalDuration( +function getTimeSpanInExpression( expression: SqlExpression, timeColumnName: string, ): number | undefined { - const pattern = fitFilterPattern(expression); - if ('column' in pattern && pattern.column !== timeColumnName) return undefined; - - switch (pattern.type) { - case 'timeInterval': + const patterns = fitFilterPatterns(expression); + for (const pattern of patterns) { + if (pattern.type === 'timeInterval' && pattern.column === timeColumnName) { return pattern.end.valueOf() - pattern.start.valueOf(); - - case 'timeRelative': + } else if (pattern.type === 'timeRelative' && pattern.column === timeColumnName) { return new Duration(pattern.rangeDuration).getCanonicalLength(); - - case 'custom': - if (pattern.expression instanceof SqlMulti) { - for (const value of pattern.expression.args.values) { - const canonicalDuration = getCanonicalDuration(value, timeColumnName); - if (canonicalDuration !== undefined) return canonicalDuration; - } - } - - break; - - default: - break; + } } - return undefined; + return; } -const DEFAULT_GRANULARITY = 'PT1H'; - -/** - * Computes the granularity string from a where clause. If the where clause is TRUE, the default - * granularity is returned (PT1H). Otherwise, the granularity is computed from the time column and the - * duration of the where clause. - * - * @param where the where SQLExpression to read from - * @param timeColumnName the name of the time column (any other time column will be ignored) - * @returns the granularity string (default is PT1H) - */ export function getAutoGranularity(where: SqlExpression, timeColumnName: string): string { - if (where.toString() === 'TRUE') return DEFAULT_GRANULARITY; - - const canonicalDuration = getCanonicalDuration(where, timeColumnName); - - if (canonicalDuration) { - if (canonicalDuration > day.canonicalLength * 95) return 'P1W'; - if (canonicalDuration > day.canonicalLength * 8) return 'P1D'; - if (canonicalDuration > hour.canonicalLength * 8) return 'PT1H'; - if (canonicalDuration > hour.canonicalLength * 3) return 'PT5M'; - - return 'PT1M'; - } - - return DEFAULT_GRANULARITY; + const timeSpan = getTimeSpanInExpression(where, timeColumnName); + if (!timeSpan) return 'P1D'; + return Duration.pickSmallestGranularityThatFits( + [ + 'PT1S', + 'PT5S', + 'PT20S', + 'PT1M', + 'PT5M', + 'PT20M', + 'PT1H', + 'PT3H', + 'PT6H', + 'P1D', + 'P1W', + 'P1M', + 'P3M', + 'P1Y', + ].map(s => new Duration(s)), + timeSpan, + 200, + ).toString(); } diff --git a/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx b/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx index 11b3a67a3cb..f4af22ebaf5 100644 --- a/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx +++ b/web-console/src/views/workbench-view/column-tree/column-tree-menu/time-menu-items/time-menu-items.tsx @@ -23,17 +23,7 @@ import { C, F, SqlExpression } from '@druid-toolkit/query'; import type { JSX } from 'react'; import React from 'react'; -import { - floorDay, - floorHour, - floorMonth, - floorYear, - nextDay, - nextHour, - nextMonth, - nextYear, - prettyPrintSql, -} from '../../../../../utils'; +import { day, hour, month, prettyPrintSql, TZ_UTC, year } from '../../../../../utils'; const LATEST_HOUR: SqlExpression = SqlExpression.parse( `? >= CURRENT_TIMESTAMP - INTERVAL '1' HOUR`, @@ -87,10 +77,10 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt } const now = new Date(); - const hourStart = floorHour(now); - const dayStart = floorDay(now); - const monthStart = floorMonth(now); - const yearStart = floorYear(now); + const hourStart = hour.floor(now, TZ_UTC); + const dayStart = day.floor(now, TZ_UTC); + const monthStart = month.floor(now, TZ_UTC); + const yearStart = year.floor(now, TZ_UTC); return ( <MenuItem icon={IconNames.FILTER} text="Filter"> {filterMenuItem(`Latest hour`, fillWithColumn(LATEST_HOUR, columnName))} @@ -101,19 +91,19 @@ export const TimeMenuItems = React.memo(function TimeMenuItems(props: TimeMenuIt <MenuDivider /> {filterMenuItem( `Current hour`, - fillWithColumnStartEnd(columnName, hourStart, nextHour(hourStart)), + fillWithColumnStartEnd(columnName, hourStart, hour.shift(hourStart, TZ_UTC, 1)), )} {filterMenuItem( `Current day`, - fillWithColumnStartEnd(columnName, dayStart, nextDay(dayStart)), + fillWithColumnStartEnd(columnName, dayStart, day.shift(dayStart, TZ_UTC, 1)), )} {filterMenuItem( `Current month`, - fillWithColumnStartEnd(columnName, monthStart, nextMonth(monthStart)), + fillWithColumnStartEnd(columnName, monthStart, month.shift(monthStart, TZ_UTC, 1)), )} {filterMenuItem( `Current year`, - fillWithColumnStartEnd(columnName, yearStart, nextYear(yearStart)), + fillWithColumnStartEnd(columnName, yearStart, year.shift(yearStart, TZ_UTC, 1)), )} </MenuItem> ); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
