This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 464ddee4b4 fix: Time Column on Generic X-axis (#23021)
464ddee4b4 is described below
commit 464ddee4b4164460193027645d87cb25e7b2320e
Author: Michael S. Molina <[email protected]>
AuthorDate: Fri Feb 10 13:33:07 2023 -0500
fix: Time Column on Generic X-axis (#23021)
---
.../src/query/buildQueryContext.ts | 17 +---
.../test/query/buildQueryContext.test.ts | 37 ---------
.../src/explore/components/SaveModal.tsx | 3 +-
superset/common/query_context_factory.py | 96 +++++++++++++++++++++-
4 files changed, 97 insertions(+), 56 deletions(-)
diff --git
a/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts
b/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts
index e66c1e201e..c1dc5bd89d 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts
@@ -20,16 +20,11 @@
import buildQueryObject from './buildQueryObject';
import DatasourceKey from './DatasourceKey';
import { QueryFieldAliases, QueryFormData } from './types/QueryFormData';
-import {
- BinaryQueryObjectFilterClause,
- QueryContext,
- QueryObject,
-} from './types/Query';
+import { QueryContext, QueryObject } from './types/Query';
import { SetDataMaskHook } from '../chart';
import { JsonObject } from '../connection';
import { normalizeTimeColumn } from './normalizeTimeColumn';
-import { hasGenericChartAxes, isXAxisSet } from './getXAxis';
-import { ensureIsArray } from '../utils';
+import { isXAxisSet } from './getXAxis';
const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject];
@@ -60,14 +55,6 @@ export default function buildQueryContext(
// eslint-disable-next-line no-param-reassign
query.post_processing = query.post_processing.filter(Boolean);
}
- if (hasGenericChartAxes && query.time_range) {
- // eslint-disable-next-line no-param-reassign
- query.filters = ensureIsArray(query.filters).map(flt =>
- flt?.op === 'TEMPORAL_RANGE'
- ? ({ ...flt, val: query.time_range } as
BinaryQueryObjectFilterClause)
- : flt,
- );
- }
});
if (isXAxisSet(formData)) {
queries = queries.map(query => normalizeTimeColumn(formData, query));
diff --git
a/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts
b/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts
index e14648a984..9d47361e8f 100644
---
a/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts
+++
b/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts
@@ -164,41 +164,4 @@ describe('buildQueryContext', () => {
expect(spyNormalizeTimeColumn).not.toBeCalled();
spyNormalizeTimeColumn.mockRestore();
});
- it('should orverride time filter if GENERIC_CHART_AXES is enabled', () => {
- Object.defineProperty(getXAxisModule, 'hasGenericChartAxes', {
- value: true,
- });
-
- const queryContext = buildQueryContext(
- {
- datasource: '5__table',
- viz_type: 'table',
- },
- () => [
- {
- filters: [
- {
- col: 'col1',
- op: 'TEMPORAL_RANGE',
- val: '2001 : 2002',
- },
- {
- col: 'col2',
- op: 'IN',
- val: ['a', 'b'],
- },
- ],
- time_range: '1990 : 1991',
- },
- ],
- );
- expect(queryContext.queries[0].filters).toEqual([
- { col: 'col1', op: 'TEMPORAL_RANGE', val: '1990 : 1991' },
- {
- col: 'col2',
- op: 'IN',
- val: ['a', 'b'],
- },
- ]);
- });
});
diff --git a/superset-frontend/src/explore/components/SaveModal.tsx
b/superset-frontend/src/explore/components/SaveModal.tsx
index 8552eff7fa..d62cb4d898 100644
--- a/superset-frontend/src/explore/components/SaveModal.tsx
+++ b/superset-frontend/src/explore/components/SaveModal.tsx
@@ -198,7 +198,8 @@ class SaveModal extends React.Component<SaveModalProps,
SaveModalState> {
);
}
- const { url_params, ...formData } = this.props.form_data || {};
+ const formData = this.props.form_data || {};
+ delete formData.url_params;
let dashboard: DashboardGetResponse | null = null;
if (this.state.newDashboardName || this.state.saveToDashboardId) {
diff --git a/superset/common/query_context_factory.py
b/superset/common/query_context_factory.py
index 360ee449a4..3a8380bdb0 100644
--- a/superset/common/query_context_factory.py
+++ b/superset/common/query_context_factory.py
@@ -22,6 +22,7 @@ from superset import app, db
from superset.charts.dao import ChartDAO
from superset.common.chart_data import ChartDataResultFormat,
ChartDataResultType
from superset.common.query_context import QueryContext
+from superset.common.query_object import QueryObject
from superset.common.query_object_factory import QueryObjectFactory
from superset.datasource.dao import DatasourceDAO
from superset.models.slice import Slice
@@ -65,8 +66,12 @@ class QueryContextFactory: # pylint:
disable=too-few-public-methods
result_type = result_type or ChartDataResultType.FULL
result_format = result_format or ChartDataResultFormat.JSON
queries_ = [
- self._query_object_factory.create(
- result_type, datasource=datasource, **query_obj
+ self._process_query_object(
+ datasource_model_instance,
+ form_data,
+ self._query_object_factory.create(
+ result_type, datasource=datasource, **query_obj
+ ),
)
for query_obj in queries
]
@@ -90,7 +95,6 @@ class QueryContextFactory: # pylint:
disable=too-few-public-methods
# pylint: disable=no-self-use
def _convert_to_model(self, datasource: DatasourceDict) -> BaseDatasource:
-
return DatasourceDAO.get_datasource(
session=db.session,
datasource_type=DatasourceType(datasource["type"]),
@@ -99,3 +103,89 @@ class QueryContextFactory: # pylint:
disable=too-few-public-methods
def _get_slice(self, slice_id: Any) -> Optional[Slice]:
return ChartDAO.find_by_id(slice_id)
+
+ def _process_query_object(
+ self,
+ datasource: BaseDatasource,
+ form_data: Optional[Dict[str, Any]],
+ query_object: QueryObject,
+ ) -> QueryObject:
+ self._apply_granularity(query_object, form_data, datasource)
+ self._apply_filters(query_object)
+ return query_object
+
+ def _apply_granularity(
+ self,
+ query_object: QueryObject,
+ form_data: Optional[Dict[str, Any]],
+ datasource: BaseDatasource,
+ ) -> None:
+ temporal_columns = {
+ column.column_name
+ for column in datasource.columns
+ if (column["is_dttm"] if isinstance(column, dict) else
column.is_dttm)
+ }
+ granularity = query_object.granularity
+ x_axis = form_data and form_data.get("x_axis")
+
+ if granularity:
+ filter_to_remove = None
+ if x_axis and x_axis in temporal_columns:
+ filter_to_remove = x_axis
+ x_axis_column = next(
+ (
+ column
+ for column in query_object.columns
+ if column == x_axis
+ or (
+ isinstance(column, dict)
+ and column["sqlExpression"] == x_axis
+ )
+ ),
+ None,
+ )
+ # Replaces x-axis column values with granularity
+ if x_axis_column:
+ if isinstance(x_axis_column, dict):
+ x_axis_column["sqlExpression"] = granularity
+ x_axis_column["label"] = granularity
+ else:
+ query_object.columns = [
+ granularity if column == x_axis_column else column
+ for column in query_object.columns
+ ]
+ for post_processing in query_object.post_processing:
+ if post_processing.get("operation") == "pivot":
+ post_processing["options"]["index"] = [granularity]
+
+ # If no temporal x-axis, then get the default temporal filter
+ if not filter_to_remove:
+ temporal_filters = [
+ filter["col"]
+ for filter in query_object.filter
+ if filter["op"] == "TEMPORAL_RANGE"
+ ]
+ if len(temporal_filters) > 0:
+ # Use granularity if it's already in the filters
+ if granularity in temporal_filters:
+ filter_to_remove = granularity
+ else:
+ # Use the first temporal filter
+ filter_to_remove = temporal_filters[0]
+
+ # Removes the temporal filter which may be an x-axis or
+ # another temporal filter. A new filter based on the value of
+ # the granularity will be added later in the code.
+ # In practice, this is replacing the previous default temporal
filter.
+ if filter_to_remove:
+ query_object.filter = [
+ filter
+ for filter in query_object.filter
+ if filter["col"] != filter_to_remove
+ ]
+
+ def _apply_filters(self, query_object: QueryObject) -> None:
+ if query_object.time_range:
+ for filter_object in query_object.filter:
+ if filter_object["op"] == "TEMPORAL_RANGE":
+ filter_object["val"] = query_object.time_range