mirror of
https://github.com/apache/superset.git
synced 2024-09-16 10:39:55 -04:00
fix: Time Column on Generic X-axis (#23021)
This commit is contained in:
parent
85f07798bf
commit
464ddee4b4
@ -20,16 +20,11 @@
|
|||||||
import buildQueryObject from './buildQueryObject';
|
import buildQueryObject from './buildQueryObject';
|
||||||
import DatasourceKey from './DatasourceKey';
|
import DatasourceKey from './DatasourceKey';
|
||||||
import { QueryFieldAliases, QueryFormData } from './types/QueryFormData';
|
import { QueryFieldAliases, QueryFormData } from './types/QueryFormData';
|
||||||
import {
|
import { QueryContext, QueryObject } from './types/Query';
|
||||||
BinaryQueryObjectFilterClause,
|
|
||||||
QueryContext,
|
|
||||||
QueryObject,
|
|
||||||
} from './types/Query';
|
|
||||||
import { SetDataMaskHook } from '../chart';
|
import { SetDataMaskHook } from '../chart';
|
||||||
import { JsonObject } from '../connection';
|
import { JsonObject } from '../connection';
|
||||||
import { normalizeTimeColumn } from './normalizeTimeColumn';
|
import { normalizeTimeColumn } from './normalizeTimeColumn';
|
||||||
import { hasGenericChartAxes, isXAxisSet } from './getXAxis';
|
import { isXAxisSet } from './getXAxis';
|
||||||
import { ensureIsArray } from '../utils';
|
|
||||||
|
|
||||||
const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject];
|
const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject];
|
||||||
|
|
||||||
@ -60,14 +55,6 @@ export default function buildQueryContext(
|
|||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
query.post_processing = query.post_processing.filter(Boolean);
|
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)) {
|
if (isXAxisSet(formData)) {
|
||||||
queries = queries.map(query => normalizeTimeColumn(formData, query));
|
queries = queries.map(query => normalizeTimeColumn(formData, query));
|
||||||
|
@ -164,41 +164,4 @@ describe('buildQueryContext', () => {
|
|||||||
expect(spyNormalizeTimeColumn).not.toBeCalled();
|
expect(spyNormalizeTimeColumn).not.toBeCalled();
|
||||||
spyNormalizeTimeColumn.mockRestore();
|
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'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -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;
|
let dashboard: DashboardGetResponse | null = null;
|
||||||
if (this.state.newDashboardName || this.state.saveToDashboardId) {
|
if (this.state.newDashboardName || this.state.saveToDashboardId) {
|
||||||
|
@ -22,6 +22,7 @@ from superset import app, db
|
|||||||
from superset.charts.dao import ChartDAO
|
from superset.charts.dao import ChartDAO
|
||||||
from superset.common.chart_data import ChartDataResultFormat, ChartDataResultType
|
from superset.common.chart_data import ChartDataResultFormat, ChartDataResultType
|
||||||
from superset.common.query_context import QueryContext
|
from superset.common.query_context import QueryContext
|
||||||
|
from superset.common.query_object import QueryObject
|
||||||
from superset.common.query_object_factory import QueryObjectFactory
|
from superset.common.query_object_factory import QueryObjectFactory
|
||||||
from superset.datasource.dao import DatasourceDAO
|
from superset.datasource.dao import DatasourceDAO
|
||||||
from superset.models.slice import Slice
|
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_type = result_type or ChartDataResultType.FULL
|
||||||
result_format = result_format or ChartDataResultFormat.JSON
|
result_format = result_format or ChartDataResultFormat.JSON
|
||||||
queries_ = [
|
queries_ = [
|
||||||
self._query_object_factory.create(
|
self._process_query_object(
|
||||||
result_type, datasource=datasource, **query_obj
|
datasource_model_instance,
|
||||||
|
form_data,
|
||||||
|
self._query_object_factory.create(
|
||||||
|
result_type, datasource=datasource, **query_obj
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for query_obj in queries
|
for query_obj in queries
|
||||||
]
|
]
|
||||||
@ -90,7 +95,6 @@ class QueryContextFactory: # pylint: disable=too-few-public-methods
|
|||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
def _convert_to_model(self, datasource: DatasourceDict) -> BaseDatasource:
|
def _convert_to_model(self, datasource: DatasourceDict) -> BaseDatasource:
|
||||||
|
|
||||||
return DatasourceDAO.get_datasource(
|
return DatasourceDAO.get_datasource(
|
||||||
session=db.session,
|
session=db.session,
|
||||||
datasource_type=DatasourceType(datasource["type"]),
|
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]:
|
def _get_slice(self, slice_id: Any) -> Optional[Slice]:
|
||||||
return ChartDAO.find_by_id(slice_id)
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user