/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { ReactNode, useCallback, useEffect, useState } from 'react'; import { isEmpty, isEqual } from 'lodash'; import moment, { Moment } from 'moment'; import { parseDttmToDate, BinaryAdhocFilter, SimpleAdhocFilter, css, customTimeRangeDecode, computeCustomDateTime, fetchTimeRange, } from '@superset-ui/core'; import { DatePicker } from 'antd'; import { RangePickerProps } from 'antd/lib/date-picker'; import { useSelector } from 'react-redux'; import ControlHeader from 'src/explore/components/ControlHeader'; import { RootState } from 'src/views/store'; import { DEFAULT_DATE_PATTERN } from '@superset-ui/chart-controls'; export interface TimeOffsetControlsProps { label?: ReactNode; startDate?: string; description?: string; hovered?: boolean; value?: Moment; onChange: (datetime: string) => void; } const MOMENT_FORMAT = 'YYYY-MM-DD'; const isTimeRangeEqual = ( left: BinaryAdhocFilter[], right: BinaryAdhocFilter[], ) => isEqual(left, right); const isStartDateEqual = (left: string, right: string) => isEqual(left, right); export default function TimeOffsetControls({ onChange, ...props }: TimeOffsetControlsProps) { const [startDate, setStartDate] = useState(''); const [formatedDate, setFormatedDate] = useState( undefined, ); const [customStartDateInFilter, setCustomStartDateInFilter] = useState< moment.Moment | undefined >(undefined); const [formatedFilterDate, setFormatedFilterDate] = useState< moment.Moment | undefined >(undefined); const [savedStartDate, setSavedStartDate] = useState(null); const currentTimeRangeFilters = useSelector( state => state.explore.form_data.adhoc_filters.filter( (adhoc_filter: SimpleAdhocFilter) => adhoc_filter.operator === 'TEMPORAL_RANGE', ), isTimeRangeEqual, ); const currentStartDate = useSelector( state => state.explore.form_data.start_date_offset, isStartDateEqual, ); useEffect(() => { if (savedStartDate !== currentStartDate) { setSavedStartDate(currentStartDate); onChange(moment(currentStartDate).format(MOMENT_FORMAT)); } }, [currentStartDate]); const previousCustomFilter = useSelector( state => state.explore.form_data.adhoc_custom?.filter( (adhoc_filter: SimpleAdhocFilter) => adhoc_filter.operator === 'TEMPORAL_RANGE', ), isTimeRangeEqual, ); // let's use useCallback to compute the custom start date const customTimeRange = useCallback( (date: string) => { const customRange = customTimeRangeDecode(date); if (customRange.matchedFlag) { const { sinceDatetime, sinceMode, sinceGrain, sinceGrainValue } = { ...customRange.customRange, }; let customStartDate: Date | null = null; if (sinceMode !== 'relative') { if (sinceMode === 'specific') { customStartDate = new Date(sinceDatetime); } else { customStartDate = parseDttmToDate(sinceDatetime, false, true); } } else { customStartDate = computeCustomDateTime( sinceDatetime, sinceGrain, sinceGrainValue, ); } customStartDate?.setHours(0, 0, 0, 0); setCustomStartDateInFilter(moment(customStartDate)); } else { setCustomStartDateInFilter(undefined); } }, [setCustomStartDateInFilter], ); useEffect(() => { if (!isEmpty(currentTimeRangeFilters)) { fetchTimeRange( currentTimeRangeFilters[0]?.comparator, currentTimeRangeFilters[0]?.subject, ).then(res => { const dates = res?.value?.match(DEFAULT_DATE_PATTERN); const [startDate, endDate] = dates ?? []; customTimeRange(`${startDate} : ${endDate}` ?? ''); setFormatedFilterDate(moment(parseDttmToDate(startDate))); }); } else { setCustomStartDateInFilter(undefined); setFormatedFilterDate(moment(parseDttmToDate(''))); } }, [currentTimeRangeFilters, customTimeRange]); useEffect(() => { if (!savedStartDate && (previousCustomFilter || customStartDateInFilter)) { let date = ''; if (isEmpty(previousCustomFilter)) { date = currentTimeRangeFilters[0]?.comparator.split(' : ')[0]; } else if ( previousCustomFilter[0]?.comparator.split(' : ')[0] !== 'No filter' ) { date = previousCustomFilter[0]?.comparator.split(' : ')[0]; } if (customStartDateInFilter) { setStartDate(customStartDateInFilter.toString()); setFormatedDate(moment(customStartDateInFilter)); } else if (date) { setStartDate(date); setFormatedDate(moment(parseDttmToDate(date))); } } else if (savedStartDate) { setStartDate(savedStartDate); setFormatedDate(moment(parseDttmToDate(savedStartDate))); } }, [previousCustomFilter, savedStartDate, customStartDateInFilter]); useEffect(() => { // When switching offsets from inherit and the previous custom is no longer valid if (customStartDateInFilter) { if (formatedDate && formatedDate > customStartDateInFilter) { const resetDate = moment .utc(customStartDateInFilter) .subtract(1, 'day'); setStartDate(resetDate.toString()); setFormatedDate(resetDate); onChange(moment.utc(resetDate).format(MOMENT_FORMAT)); } } if ( formatedDate && formatedFilterDate && formatedDate > formatedFilterDate ) { const resetDate = moment.utc(formatedFilterDate).subtract(1, 'day'); setStartDate(resetDate.toString()); setFormatedDate(resetDate); onChange(moment.utc(resetDate).format(MOMENT_FORMAT)); } }, [formatedFilterDate, formatedDate, customStartDateInFilter]); const disabledDate: RangePickerProps['disabledDate'] = current => { if (!customStartDateInFilter) { return formatedFilterDate ? current && current > formatedFilterDate : false; } return current && current > moment(customStartDateInFilter); }; return startDate || formatedDate ? (
onChange(datetime ? datetime.format(MOMENT_FORMAT) : '') } defaultPickerValue={ startDate ? moment(formatedDate).subtract(1, 'day') : undefined } disabledDate={disabledDate} defaultValue={moment(formatedDate)} value={moment(formatedDate)} />
) : null; }