This is an automated email from the ASF dual-hosted git repository.

arivero pushed a commit to branch table-time-comparison-offset
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3deaf2f228bb3df6d7ea4ca29cc8ace49be28652
Author: lilykuang <[email protected]>
AuthorDate: Tue Apr 23 18:20:29 2024 -0700

    implement start date offset controls
---
 .../src/sections/timeComparison.tsx                |   9 ++
 .../plugin-chart-table/src/transformProps.ts       |   2 +
 .../plugins/plugin-chart-table/src/types.ts        |   1 +
 .../components/controls/ComparisonRangeLabel.tsx   |  29 ++++--
 .../components/controls/TimeOffsetControl.tsx      | 112 +++++++++++++++++++++
 .../src/explore/components/controls/index.js       |   2 +
 6 files changed, 147 insertions(+), 8 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-chart-controls/src/sections/timeComparison.tsx
 
b/superset-frontend/packages/superset-ui-chart-controls/src/sections/timeComparison.tsx
index f1a4aad6ac..2b77c9c311 100644
--- 
a/superset-frontend/packages/superset-ui-chart-controls/src/sections/timeComparison.tsx
+++ 
b/superset-frontend/packages/superset-ui-chart-controls/src/sections/timeComparison.tsx
@@ -59,6 +59,15 @@ export const timeComparisonControls: 
ControlPanelSectionConfig = {
         },
       },
     ],
+    [
+      {
+        name: 'start_date_offset',
+        config: {
+          type: 'TimeOffsetControl',
+          label: t('shift start date'),
+        },
+      },
+    ],
     [
       {
         name: 'comparison_type',
diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts 
b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
index 47bd113147..0a9da3a590 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
@@ -569,6 +569,7 @@ const transformProps = (
     conditionalFormatting,
   );
 
+  const startDateOffset = chartProps.rawFormData?.start_date_offset;
   return {
     height,
     width,
@@ -602,6 +603,7 @@ const transformProps = (
     onContextMenu,
     isUsingTimeComparison,
     basicColorFormatters,
+    startDateOffset,
     basicColorColumnFormatters,
   };
 };
diff --git a/superset-frontend/plugins/plugin-chart-table/src/types.ts 
b/superset-frontend/plugins/plugin-chart-table/src/types.ts
index 7aa14d06ac..1ec3cbe29d 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/types.ts
@@ -145,6 +145,7 @@ export interface TableChartTransformedProps<D extends 
DataRecord = DataRecord> {
   isUsingTimeComparison?: boolean;
   basicColorFormatters?: { [Key: string]: BasicColorFormatterType }[];
   basicColorColumnFormatters?: { [Key: string]: BasicColorFormatterType }[];
+  startDateOffset?: string;
 }
 
 export enum ColorSchemeEnum {
diff --git 
a/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx 
b/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
index 1d34c6e129..d14ba60b90 100644
--- a/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
+++ b/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
@@ -20,6 +20,7 @@
 import React, { useEffect, useState } from 'react';
 import { useSelector } from 'react-redux';
 import { isEmpty, isEqual } from 'lodash';
+import moment from 'moment';
 import {
   BinaryAdhocFilter,
   css,
@@ -57,24 +58,36 @@ export const ComparisonRangeLabel = ({
   const shifts = useSelector<RootState, string[]>(
     state => state.explore.form_data.time_compare,
   );
+  const startDate = useSelector<RootState, string>(
+    state => state.explore.form_data.start_date_offset,
+  );
 
   useEffect(() => {
-    if (isEmpty(currentTimeRangeFilters) || isEmpty(shifts)) {
+    if (isEmpty(currentTimeRangeFilters) || (isEmpty(shifts) && !startDate)) {
       setLabels([]);
-    } else if (!isEmpty(shifts)) {
-      const promises = currentTimeRangeFilters.map(filter =>
-        fetchTimeRange(
+    } else if (!isEmpty(shifts) || startDate) {
+      const promises = currentTimeRangeFilters.map(filter => {
+        const startDateShift = moment(
+          (filter as any).comparator.split(' : ')[0],
+        ).diff(moment(startDate), 'days');
+        const newshift = startDateShift
+          ? [`${startDateShift} days ago`]
+          : shifts
+            ? shifts.slice(0, 1)
+            : undefined;
+
+        return fetchTimeRange(
           filter.comparator,
           filter.subject,
-          multi ? shifts : ensureIsArray(shifts).slice(0, 1),
-        ),
-      );
+          multi ? shifts : newshift,
+        );
+      });
       Promise.all(promises).then(res => {
         // access the value property inside the res and set the labels with it 
in the state
         setLabels(res.map(r => r.value ?? ''));
       });
     }
-  }, [currentTimeRangeFilters, shifts]);
+  }, [currentTimeRangeFilters, shifts, startDate]);
 
   return labels.length ? (
     <>
diff --git 
a/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx 
b/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx
new file mode 100644
index 0000000000..b3f8892ad1
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/TimeOffsetControl.tsx
@@ -0,0 +1,112 @@
+/**
+ * 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 React, { ReactNode } from 'react';
+import { isEqual } from 'lodash';
+import moment, { Moment } from 'moment';
+import { BinaryAdhocFilter, SimpleAdhocFilter, css } 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';
+
+export interface TimeOffsetControlsProps {
+  label?: ReactNode;
+  startDate?: string;
+  description?: string;
+  hovered?: boolean;
+  value?: Moment;
+  onChange: (datetime: string) => void;
+}
+const MOMENT_FORMAT = 'YYYY-MM-DD';
+const dttmToMoment = (dttm: string): Moment => {
+  if (dttm === 'now') {
+    return moment().utc().startOf('second');
+  }
+  if (dttm === 'today' || dttm === 'No filter') {
+    return moment().utc().startOf('day');
+  }
+  if (dttm === 'Last week') {
+    return moment().utc().startOf('day').subtract(7, 'day');
+  }
+  if (dttm === 'Last month') {
+    return moment().utc().startOf('day').subtract(1, 'month');
+  }
+  if (dttm === 'Last quarter') {
+    return moment().utc().startOf('day').subtract(1, 'quarter');
+  }
+  if (dttm === 'Last year') {
+    return moment().utc().startOf('day').subtract(1, 'year');
+  }
+  if (dttm === 'previous calendar week') {
+    return moment().utc().subtract(1, 'weeks').startOf('isoWeek');
+  }
+  if (dttm === 'previous calendar month') {
+    return moment().utc().subtract(1, 'months').startOf('month');
+  }
+  if (dttm === 'previous calendar year') {
+    return moment().utc().subtract(1, 'years').startOf('year');
+  }
+
+  return moment(dttm);
+};
+const isTimeRangeEqual = (
+  left: BinaryAdhocFilter[],
+  right: BinaryAdhocFilter[],
+) => isEqual(left, right);
+
+export default function TimeOffsetControls({
+  onChange,
+  ...props
+}: TimeOffsetControlsProps) {
+  const currentTimeRangeFilters = useSelector<RootState, BinaryAdhocFilter[]>(
+    state =>
+      state.explore.form_data.adhoc_filters.filter(
+        (adhoc_filter: SimpleAdhocFilter) =>
+          adhoc_filter.operator === 'TEMPORAL_RANGE',
+      ),
+    isTimeRangeEqual,
+  );
+  const startDate = currentTimeRangeFilters[0]?.comparator.split(' : ')[0];
+
+  const formatedDate = startDate
+    ? dttmToMoment(startDate)
+    : dttmToMoment('now');
+  const disabledDate: RangePickerProps['disabledDate'] = current =>
+    current && current >= formatedDate;
+
+  return (
+    <div>
+      <ControlHeader {...props} />
+      <DatePicker
+        css={css`
+          width: 100%;
+        `}
+        onChange={(datetime: Moment) =>
+          onChange(datetime ? datetime.format(MOMENT_FORMAT) : '')
+        }
+        defaultPickerValue={
+          startDate ? moment(formatedDate).subtract(1, 'day') : undefined
+        }
+        disabledDate={disabledDate}
+      />
+    </div>
+  );
+}
diff --git a/superset-frontend/src/explore/components/controls/index.js 
b/superset-frontend/src/explore/components/controls/index.js
index 2bf2662d0c..4c2cf00420 100644
--- a/superset-frontend/src/explore/components/controls/index.js
+++ b/superset-frontend/src/explore/components/controls/index.js
@@ -34,6 +34,7 @@ import SpatialControl from './SpatialControl';
 import TextAreaControl from './TextAreaControl';
 import TextControl from './TextControl';
 import TimeSeriesColumnControl from './TimeSeriesColumnControl';
+import TimeOffsetControl from './TimeOffsetControl';
 import ViewportControl from './ViewportControl';
 import VizTypeControl from './VizTypeControl';
 import MetricsControl from './MetricControl/MetricsControl';
@@ -82,6 +83,7 @@ const controlMap = {
   XAxisSortControl,
   ContourControl,
   ComparisonRangeLabel,
+  TimeOffsetControl,
   ...sharedControlComponents,
 };
 export default controlMap;

Reply via email to