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

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

commit 2f7feff1c10691c2636d8176756b734167017271
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Mon Feb 26 16:44:33 2024 +0100

    feat: Add comparison time label to Big Number with Period over Period plugin
---
 .../src/time-comparison/fetchTimeRange.ts          | 18 +++++-
 .../src/plugin/controlPanel.ts                     |  6 ++
 .../components/controls/ComparisonRangeLabel.tsx   | 71 ++++++++++++++++++++++
 .../DateFilterControl/utils/dateFilterUtils.ts     |  1 +
 .../src/explore/components/controls/index.js       |  2 +
 superset/views/api.py                              | 28 +++++++++
 tests/integration_tests/charts/api_tests.py        | 15 +++++
 7 files changed, 138 insertions(+), 3 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/time-comparison/fetchTimeRange.ts
 
b/superset-frontend/packages/superset-ui-core/src/time-comparison/fetchTimeRange.ts
index 50509af52b..8e50576741 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/time-comparison/fetchTimeRange.ts
+++ 
b/superset-frontend/packages/superset-ui-core/src/time-comparison/fetchTimeRange.ts
@@ -17,7 +17,11 @@
  * under the License.
  */
 import rison from 'rison';
-import { SupersetClient, getClientErrorObject } from '@superset-ui/core';
+import {
+  SupersetClient,
+  getClientErrorObject,
+  ComparisonTimeRangeType,
+} from '@superset-ui/core';
 
 export const SEPARATOR = ' : ';
 
@@ -42,9 +46,17 @@ export const formatTimeRange = (
 export const fetchTimeRange = async (
   timeRange: string,
   columnPlaceholder = 'col',
+  shift?: ComparisonTimeRangeType,
 ) => {
-  const query = rison.encode_uri(timeRange);
-  const endpoint = `/api/v1/time_range/?q=${query}`;
+  let query;
+  let endpoint;
+  if (shift) {
+    query = rison.encode_uri({ base_time_range: timeRange, shift });
+    endpoint = `/api/v1/relative_time_range/?q=${query}`;
+  } else {
+    query = rison.encode_uri(timeRange);
+    endpoint = `/api/v1/time_range/?q=${query}`;
+  }
   try {
     const response = await SupersetClient.get({ endpoint });
     const timeRangeString = buildTimeRangeString(
diff --git 
a/superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/plugin/controlPanel.ts
 
b/superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/plugin/controlPanel.ts
index 7feb3445df..bb70859d9a 100644
--- 
a/superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/plugin/controlPanel.ts
+++ 
b/superset-frontend/plugins/plugin-chart-period-over-period-kpi/src/plugin/controlPanel.ts
@@ -98,6 +98,12 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'comparison_range_label',
+            config: { type: 'ComparisonRangeLabel' },
+          },
+        ],
         [
           {
             name: 'row_limit',
diff --git 
a/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx 
b/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
new file mode 100644
index 0000000000..3d2c6a6e68
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/ComparisonRangeLabel.tsx
@@ -0,0 +1,71 @@
+/**
+ * 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, { useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
+import {
+  ComparisonTimeRangeType,
+  css,
+  SimpleAdhocFilter,
+  t,
+  fetchTimeRange,
+} from '@superset-ui/core';
+import { RootState } from 'src/views/store';
+import { Tooltip } from 'src/components/Tooltip';
+
+export const ComparisonRangeLabel = () => {
+  const [label, setLabel] = useState('');
+  const currentTimeRange = useSelector<RootState, string>(
+    state =>
+      state.explore.form_data.adhoc_filters.filter(
+        (adhoc_filter: SimpleAdhocFilter) =>
+          adhoc_filter.operator === 'TEMPORAL_RANGE',
+      )[0]?.comparator,
+  );
+  const shift = useSelector<RootState, ComparisonTimeRangeType>(
+    state => state.explore.form_data.time_comparison,
+  );
+
+  useEffect(() => {
+    if (shift === ComparisonTimeRangeType.Custom) {
+      setLabel('');
+    }
+  }, [shift]);
+
+  useEffect(() => {
+    if (shift !== ComparisonTimeRangeType.Custom) {
+      fetchTimeRange(currentTimeRange, 'col', shift).then(res => {
+        setLabel(res.value ?? '');
+      });
+    }
+  }, [currentTimeRange, shift]);
+
+  return label ? (
+    <Tooltip title={t('Actual time range for comparison')}>
+      <span
+        css={theme => css`
+          font-size: ${theme.typography.sizes.m}px;
+          color: ${theme.colors.grayscale.base};
+        `}
+      >
+        {label}
+      </span>
+    </Tooltip>
+  ) : null;
+};
diff --git 
a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateFilterUtils.ts
 
b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateFilterUtils.ts
index 4be932e34a..39f3728eaa 100644
--- 
a/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateFilterUtils.ts
+++ 
b/superset-frontend/src/explore/components/controls/DateFilterControl/utils/dateFilterUtils.ts
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 import { NO_TIME_RANGE, JsonObject } from '@superset-ui/core';
 import { useSelector } from 'react-redux';
 import {
diff --git a/superset-frontend/src/explore/components/controls/index.js 
b/superset-frontend/src/explore/components/controls/index.js
index a5d65f7768..2bf2662d0c 100644
--- a/superset-frontend/src/explore/components/controls/index.js
+++ b/superset-frontend/src/explore/components/controls/index.js
@@ -48,6 +48,7 @@ import DndColumnSelectControl, {
 import XAxisSortControl from './XAxisSortControl';
 import CurrencyControl from './CurrencyControl';
 import ColumnConfigControl from './ColumnConfigControl';
+import { ComparisonRangeLabel } from './ComparisonRangeLabel';
 
 const controlMap = {
   AnnotationLayerControl,
@@ -80,6 +81,7 @@ const controlMap = {
   ConditionalFormattingControl,
   XAxisSortControl,
   ContourControl,
+  ComparisonRangeLabel,
   ...sharedControlComponents,
 };
 export default controlMap;
diff --git a/superset/views/api.py b/superset/views/api.py
index d7b1b8434e..a44bf81234 100644
--- a/superset/views/api.py
+++ b/superset/views/api.py
@@ -42,6 +42,11 @@ if TYPE_CHECKING:
 
 get_time_range_schema = {"type": "string"}
 
+get_relative_time_range_schema = {
+    "type": "object",
+    "properties": {"base_time_range": {"type": "string"}, "shift": {"type": 
"string"}},
+}
+
 
 class Api(BaseSupersetView):
     query_context_factory = None
@@ -108,6 +113,29 @@ class Api(BaseSupersetView):
             error_msg = {"message": _("Unexpected time range: %(error)s", 
error=error)}
             return self.json_response(error_msg, 400)
 
+    @api
+    @handle_api_exception
+    @has_access_api
+    @rison(ger_relative_time_range_schema)
+    @expose("/v1/relative_time_range/", methods=("GET",))
+    def relative_time_range(self, **kwargs: Any) -> FlaskResponse:
+        """Get actually time range from human-readable string or datetime 
expression."""
+        base_time_range, shift = kwargs["rison"].values()
+        try:
+            since, until = get_since_until(
+                time_range=base_time_range, instant_time_comparison_range=shift
+            )
+            result = {
+                "since": since.isoformat() if since else "",
+                "until": until.isoformat() if until else "",
+                "baseTimeRange": base_time_range,
+                "shift": shift,
+            }
+            return self.json_response({"result": result})
+        except (ValueError, TimeRangeParseFailError, TimeRangeAmbiguousError) 
as error:
+            error_msg = {"message": _("Unexpected time range: %(error)s", 
error=error)}
+            return self.json_response(error_msg, 400)
+
     def get_query_context_factory(self) -> QueryContextFactory:
         if self.query_context_factory is None:
             # pylint: disable=import-outside-toplevel
diff --git a/tests/integration_tests/charts/api_tests.py 
b/tests/integration_tests/charts/api_tests.py
index 829c7ba518..fe71df6ba5 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -31,6 +31,7 @@ from sqlalchemy.sql import func
 from superset.commands.chart.data.get_data_command import ChartDataCommand
 from superset.commands.chart.exceptions import ChartDataQueryFailedError
 from superset.connectors.sqla.models import SqlaTable
+from superset.constants import InstantTimeComparison
 from superset.extensions import cache_manager, db, security_manager
 from superset.models.core import Database, FavStar, FavStarClassName
 from superset.models.dashboard import Dashboard
@@ -1472,6 +1473,20 @@ class TestChartApi(ApiOwnersTestCaseMixin, 
InsertChartMixin, SupersetTestCase):
         self.assertEqual(rv.status_code, 200)
         self.assertEqual(len(data["result"]), 3)
 
+    def test_get_relative_time_range(self):
+        """
+        Chart API: Test get shifted time range from human readable string
+        """
+        self.login(username="admin")
+        humanize_time_range = "100 years ago : now"
+        shift = InstantTimeComparison.YEAR
+
+        uri = f'api/v1/relative_time_range/?q={prison.dumps({ 
"base_time_range": humanize_time_range, "shift": shift })}'
+        rv = self.client.get(uri)
+        data = json.loads(rv.data.decode("utf-8"))
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(len(data["result"]), 4)
+
     def test_query_form_data(self):
         """
         Chart API: Test query form data

Reply via email to