diff --git a/cccs-build/superset/Dockerfile b/cccs-build/superset/Dockerfile index 3120dd5268f80..8627e5a57413d 100644 --- a/cccs-build/superset/Dockerfile +++ b/cccs-build/superset/Dockerfile @@ -1,7 +1,7 @@ # Vault CA container import ARG VAULT_CA_CONTAINER=uchimera.azurecr.io/cccs/hogwarts/vault-ca:master_11376_a25c34e1 FROM $VAULT_CA_CONTAINER AS vault_ca -FROM uchimera.azurecr.io/cccs/superset-base:cccs-2.1_20230704153241_b7209 +FROM uchimera.azurecr.io/cccs/superset-base:feature_dataset-explorer-chart-update-chart_20230725150622_b7385 USER root diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx index eee9e5dcaa6c6..bf29bb559ae71 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/advancedDataTypeValueControl/index.tsx @@ -37,19 +37,33 @@ const AdvancedDataTypeValueControlValueControl: React.FC = ({ value = [], description, }) => { - const [rawValues, setRawValues] = useState([]); + const [rawValues, setRawValues] = useState( + value && ensureIsArray(value).length === 1 ? value[0].rawData : [], + ); const [validationErrors, setValidationErrors] = useState([]); const [currentAdvancedDataType, setCurrentAdvancedDataType] = - useState(); + useState(advancedDataType); + + const default_advanced_data_type_state = { + parsedAdvancedDataType: '', + advancedDataTypeOperatorList: [], + errorMessage: '', + useDefaultOperators: false, + values: value && ensureIsArray(value).length === 1 ? value[0].data : [], + }; const { advancedDataTypesState, subjectAdvancedDataType, fetchAdvancedDataTypeValueCallback, - } = useAdvancedDataTypes(() => {}); + } = useAdvancedDataTypes(() => {}, default_advanced_data_type_state); const onChangeWrapper = (selection: any) => { - setValidationErrors([...validationErrors, 'Validation in progress']); + setValidationErrors( + selection.length > 0 + ? [...validationErrors, 'Validation in progress'] + : [...validationErrors], + ); setRawValues(selection); }; @@ -68,8 +82,7 @@ const AdvancedDataTypeValueControlValueControl: React.FC = ({ useEffect(() => { const data = - advancedDataTypesState.parsedAdvancedDataType.length > 0 && - advancedDataTypesState.parsedAdvancedDataType.split(',').length > 0 + advancedDataTypesState.values.length > 0 ? { data: advancedDataTypesState.values, columns: datasource.columns @@ -82,11 +95,13 @@ const AdvancedDataTypeValueControlValueControl: React.FC = ({ }, [advancedDataTypesState, validationErrors]); useEffect(() => { - fetchAdvancedDataTypeValueCallback( - rawValues, - advancedDataTypesState, - ensureIsArray(advancedDataType)[0], - ); + if (rawValues.length > 0) { + fetchAdvancedDataTypeValueCallback( + rawValues, + advancedDataTypesState, + ensureIsArray(advancedDataType)[0], + ); + } }, [advancedDataType, rawValues, subjectAdvancedDataType]); useEffect(() => { diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx index 755d56fa218aa..c212a99f86925 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/changeDatasourceButton/index.tsx @@ -3,7 +3,7 @@ import { ChangeDatasourceModal } from 'src/components/Datasource'; import { withTheme } from '@superset-ui/core'; import { connect, useDispatch } from 'react-redux'; import Button from 'src/components/Button'; -import { updateFormDataByDatasource } from 'src/explore/actions/exploreActions'; +import { updateCCCSFormDataByDatasource } from 'src/explore/actions/exploreActions'; export interface Props { colorScheme: string; @@ -32,7 +32,7 @@ const ChangeDatasourceButtonControll: React.FC = ({ setShowChangeDatasourceModal(!showChangeDatasourceModal); }; const onDatasourceSave = (new_datasource: any) => { - dispatch(updateFormDataByDatasource(datasource, new_datasource)); + dispatch(updateCCCSFormDataByDatasource(datasource, new_datasource)); }; const onChangeWrapper = (a: any) => { onChange(a); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx index 3d106234261ef..77c1b3381a2f1 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-cccs-grid/src/components/controls/datetimeControl/index.tsx @@ -1,5 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { SLOW_DEBOUNCE, SupersetClient, t, withTheme } from '@superset-ui/core'; +import { + NO_TIME_RANGE, + SLOW_DEBOUNCE, + SupersetClient, + t, + withTheme, +} from '@superset-ui/core'; import { buildTimeRangeString, formatTimeRange, @@ -21,7 +27,7 @@ export interface Props { name: string; actions: object; label: string; - value?: object[]; + value?: string; onChange: (value: any, errors: any[]) => void; default: string; disabled: boolean; @@ -52,9 +58,17 @@ const fetchTimeRange = async (timeRange: string) => { }; const DatetimeControl: React.FC = props => { - const [timeRange, setTimeRange] = useState(props.default); + // if the value passed in is "no filter", leave the control empty + // if the value does not exist, set it to the default + const [timeRange, setTimeRange] = useState( + props.value + ? props.value === NO_TIME_RANGE + ? '' + : props.value + : props.default, + ); const [validationErrors, setValidationErrors] = useState([]); - const [actualTimeRange, setactualTimeRange] = useState(); + const [actualTimeRange, setActualTimeRange] = useState(); const [since, until] = timeRange.split(SEPARATOR); @@ -64,7 +78,9 @@ const DatetimeControl: React.FC = props => { function onChange(control: 'since' | 'until', value: string) { if (control === 'since') { - setTimeRange(`${value}${SEPARATOR}${until}`); + setTimeRange( + until ? `${value}${SEPARATOR}${until}` : `${value}${SEPARATOR}`, + ); } else { setTimeRange(`${since}${SEPARATOR}${value}`); } @@ -74,7 +90,7 @@ const DatetimeControl: React.FC = props => { () => { fetchTimeRange(timeRange) .then(value => { - setactualTimeRange( + setActualTimeRange( value?.value ? `Actual Time Range ${value?.value}` : '', ); setValidationErrors(value?.error ? [value?.error] : []); diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts index 80a02fab53ad7..02e17ff880de1 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/buildQuery.ts @@ -21,6 +21,7 @@ import { ensureIsArray, getMetricLabel, PostProcessingRule, + QueryFormColumn, QueryMode, QueryObject, removeDuplicates, @@ -125,7 +126,7 @@ const buildQuery: BuildQuery = ( const { advanced_data_type_value } = formData; const { advanced_data_type_selection } = formData; let filter = []; - if (advanced_data_type_selection.length > 0) { + if (ensureIsArray(advanced_data_type_selection).length > 0) { // in the case of ipv4s sometimes they can be ranges and not simple values // this will be handled in the advanced data type definition in the future // to avoid this complex logic @@ -152,8 +153,21 @@ const buildQuery: BuildQuery = ( [], ); } + + // load the time range as a filter if it exists + if (baseQueryObject.granularity) { + baseQueryObject.filters?.push({ + col: baseQueryObject.granularity as QueryFormColumn, + op: 'TEMPORAL_RANGE', + val: baseQueryObject.time_range || '', + }); + } + + const baseQueryObjectCopy = baseQueryObject; + baseQueryObjectCopy.granularity = undefined; + const queryObject = { - ...baseQueryObject, + ...baseQueryObjectCopy, formData: formDataCopy, orderby, metrics, diff --git a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx index 26269084b8ece..fdd7d1aff2f68 100644 --- a/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx +++ b/superset-frontend/src/cccs-viz/plugins/plugin-chart-dataset-explorer/src/plugin/controlPanel.tsx @@ -130,9 +130,6 @@ const config: ControlPanelConfig = { 'expression', ), clearable: false, - optionRenderer: (c: any) => ( - - ), valueKey: 'column_name', rerender: ['time_range'], mapStateToProps: state => { @@ -141,12 +138,23 @@ const config: ControlPanelConfig = { state.datasource && 'granularity_sqla' in state.datasource ) { - props.choices = state.datasource.granularity_sqla; - props.default = null; + props.options = state.datasource.columns + .filter(c => + ensureIsArray( + (state.datasource as Dataset)?.granularity_sqla, + ) + .map(g => g[0]) + .includes(c.column_name), + ) + .map(c => ({ + label: c.verbose_name ? c.verbose_name : c.column_name, + column_name: c.column_name, + })); + props.default = undefined; if (state.datasource.main_dttm_col) { props.default = state.datasource.main_dttm_col; - } else if (props.choices && props.choices.length > 0) { - props.default = props.choices[0].column_name; + } else if (props.options && props.options.length > 0) { + props.default = props.options.column_name; } } return props; diff --git a/superset-frontend/src/explore/actions/exploreActions.ts b/superset-frontend/src/explore/actions/exploreActions.ts index 36300b4a123a4..fcfc4b3e78382 100644 --- a/superset-frontend/src/explore/actions/exploreActions.ts +++ b/superset-frontend/src/explore/actions/exploreActions.ts @@ -40,6 +40,19 @@ export function updateFormDataByDatasource( }; } +export const UPDATE_CCCS_FORM_DATA_BY_DATASOURCE = + 'UPDATE_CCCS_FORM_DATA_BY_DATASOURCE'; +export function updateCCCSFormDataByDatasource( + prevDatasource: Dataset, + newDatasource: Dataset, +) { + return { + type: UPDATE_CCCS_FORM_DATA_BY_DATASOURCE, + prevDatasource, + newDatasource, + }; +} + export const POST_DATASOURCE_STARTED = 'POST_DATASOURCE_STARTED'; export const FETCH_DATASOURCE_SUCCEEDED = 'FETCH_DATASOURCE_SUCCEEDED'; export function fetchDatasourceSucceeded() { diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts index 8440b1416dda6..3ed3638f0bf4a 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/useAdvancedDataTypes.ts @@ -28,9 +28,12 @@ const INITIAL_ADVANCED_DATA_TYPES_STATE: AdvancedDataTypesState = { errorMessage: '', }; -const useAdvancedDataTypes = (validHandler: (isValid: boolean) => void) => { +const useAdvancedDataTypes = ( + validHandler: (isValid: boolean) => void, + default_state: AdvancedDataTypesState = INITIAL_ADVANCED_DATA_TYPES_STATE, +) => { const [advancedDataTypesState, setAdvancedDataTypesState] = - useState(INITIAL_ADVANCED_DATA_TYPES_STATE); + useState(default_state); const [subjectAdvancedDataType, setSubjectAdvancedDataType] = useState< string | undefined >(); diff --git a/superset-frontend/src/explore/reducers/exploreReducer.js b/superset-frontend/src/explore/reducers/exploreReducer.js index 1797c57637d2d..2a290b49defd8 100644 --- a/superset-frontend/src/explore/reducers/exploreReducer.js +++ b/superset-frontend/src/explore/reducers/exploreReducer.js @@ -97,6 +97,101 @@ export default function exploreReducer(state = {}, action) { controlsTransferred, }; }, + [actions.UPDATE_CCCS_FORM_DATA_BY_DATASOURCE]() { + const newFormData = { ...state.form_data }; + const { prevDatasource, newDatasource } = action; + const controls = { ...state.controls }; + const controlsTransferred = []; + + if ( + prevDatasource.id !== newDatasource.id || + prevDatasource.type !== newDatasource.type + ) { + newFormData.datasource = newDatasource.uid; + } + // reset control values for column/metric related controls + Object.entries(controls).forEach(([controlName, controlState]) => { + if ( + // for direct column select controls + controlState.valueKey === 'column_name' || + // for all other controls + 'savedMetrics' in controlState || + 'columns' in controlState || + ('options' in controlState && !Array.isArray(controlState.options)) + ) { + newFormData[controlName] = getControlValuesCompatibleWithDatasource( + newDatasource, + controlState, + controlState.value, + ); + if ( + ensureIsArray(newFormData[controlName]).length > 0 && + newFormData[controlName] !== controls[controlName].default + ) { + controlsTransferred.push(controlName); + } + } + }); + + const newState = { + ...state, + controls, + datasource: action.newDatasource, + }; + const newControls = getControlsState(newState, newFormData); + + if (newControls.advanced_data_type_selection) { + // filter out incompatible Advanced Data Types in the new Datasource + const advancedDataTypeSelectionControl = + newControls.advanced_data_type_selection; + newFormData.advanced_data_type_selection = newDatasource.columns.some( + c => c.advanced_data_type === advancedDataTypeSelectionControl.value, + ) + ? advancedDataTypeSelectionControl.value + : []; + newControls.advanced_data_type_selection.value = + newFormData.advanced_data_type_selection; + // check if the new datasource has any columns with the same Advanced Data Type + if ( + ensureIsArray(newFormData.advanced_data_type_selection).length > 0 + ) { + // transfer the control to use the new columns + newControls.advanced_data_type_value.value[0].columns = + newDatasource.columns + .filter( + c => + c.advanced_data_type === + advancedDataTypeSelectionControl.value, + ) + .map(c => c.column_name); + controlsTransferred.push('advanced_data_type_selection'); + controlsTransferred.push('advanced_data_type_value'); + } else { + // if there are no compatible columns, clear the controls and disable the value control + newControls.advanced_data_type_value.value = [ + { columns: [], data: [], rawData: [] }, + ]; + newControls.advanced_data_type_value.disabled = true; + newControls.advanced_data_type_value.advancedDataType = []; + newFormData.advanced_data_type_value = + newControls.advanced_data_type_value.value; + } + } + // check if date column is compatible with new datasource + if (!newDatasource.columns.includes(newFormData.granularity_sqla)) { + // set the time column to the new default time column + newControls.granularity_sqla.value = newDatasource.main_dttm_col; + newFormData.granularity_sqla = newDatasource.main_dttm_col; + // disable the time range if there is no default + newControls.time_range.disabled = !newDatasource.main_dttm_col; + } + return { + ...newState, + form_data: newFormData, + controls: newControls, + controlsTransferred, + }; + }, [actions.FETCH_DATASOURCES_STARTED]() { return { ...state,