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

villebro pushed a commit to branch 0.37
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git

commit 1bc26797add82e60469afccff81e2d9eaa38552e
Author: Jesse Yang <jesse.y...@airbnb.com>
AuthorDate: Mon Jul 13 23:44:57 2020 -0700

    fix(table-viz): table chart time column should use default (#10293)
---
 .../visualizations/{line.test.js => line.test.ts}  | 23 ++----
 .../explore/visualizations/table.test.ts           |  9 +++
 .../spec/javascripts/explore/controlUtils_spec.jsx | 15 +++-
 .../explore/components/ControlPanelsContainer.jsx  |  5 +-
 superset-frontend/src/explore/controlUtils.js      | 82 ++++++++++------------
 .../src/explore/reducers/getInitialState.js        |  2 -
 6 files changed, 70 insertions(+), 66 deletions(-)

diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js
 
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
similarity index 96%
rename from 
superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js
rename to 
superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
index 229c7f4..76a9086 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.js
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/line.test.ts
@@ -45,6 +45,13 @@ describe('Visualization > Line', () => {
     cy.get('.alert-warning').should('not.exist');
   });
 
+  it('should allow negative values in Y bounds', () => {
+    cy.get('#controlSections-tab-display').click();
+    cy.get('span').contains('Y Axis Bounds').scrollIntoView();
+    cy.get('input[placeholder="Min"]').type('-0.1', { delay: 100 });
+    cy.get('.alert-warning').should('not.exist');
+  });
+
   it('should work with adhoc metric', () => {
     const formData = { ...LINE_CHART_DEFAULTS, metrics: [NUM_METRIC] };
     cy.visitChartByParams(JSON.stringify(formData));
@@ -54,9 +61,7 @@ describe('Visualization > Line', () => {
   it('should work with groupby', () => {
     const metrics = ['count'];
     const groupby = ['gender'];
-
     const formData = { ...LINE_CHART_DEFAULTS, metrics, groupby };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
@@ -64,13 +69,11 @@ describe('Visualization > Line', () => {
   it('should work with simple filter', () => {
     const metrics = ['count'];
     const filters = [SIMPLE_FILTER];
-
     const formData = {
       ...LINE_CHART_DEFAULTS,
       metrics,
       adhoc_filters: filters,
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
@@ -83,7 +86,6 @@ describe('Visualization > Line', () => {
       groupby: ['name'],
       timeseries_limit_metric: NUM_METRIC,
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
@@ -97,28 +99,24 @@ describe('Visualization > Line', () => {
       timeseries_limit_metric: NUM_METRIC,
       order_desc: true,
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
 
   it('should work with rolling avg', () => {
     const metrics = [NUM_METRIC];
-
     const formData = {
       ...LINE_CHART_DEFAULTS,
       metrics,
       rolling_type: 'mean',
       rolling_periods: 10,
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
 
   it('should work with time shift 1 year', () => {
     const metrics = [NUM_METRIC];
-
     const formData = {
       ...LINE_CHART_DEFAULTS,
       metrics,
@@ -162,28 +160,24 @@ describe('Visualization > Line', () => {
 
   it('should work with time shift yoy', () => {
     const metrics = [NUM_METRIC];
-
     const formData = {
       ...LINE_CHART_DEFAULTS,
       metrics,
       time_compare: ['1+year'],
       comparison_type: 'ratio',
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
 
   it('should work with time shift percentage change', () => {
     const metrics = [NUM_METRIC];
-
     const formData = {
       ...LINE_CHART_DEFAULTS,
       metrics,
       time_compare: ['1+year'],
       comparison_type: 'percentage',
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
   });
@@ -193,7 +187,6 @@ describe('Visualization > Line', () => {
       ...LINE_CHART_DEFAULTS,
       metrics: ['count'],
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
     cy.get('text.nv-legend-text').contains('COUNT(*)');
@@ -224,7 +217,6 @@ describe('Visualization > Line', () => {
         },
       ],
     };
-
     cy.visitChartByParams(JSON.stringify(formData));
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
     cy.get('.slice_container').within(() => {
@@ -263,7 +255,6 @@ describe('Visualization > Line', () => {
         cy.visitChartByParams(JSON.stringify(formData));
       },
     );
-
     cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
     cy.get('.slice_container').within(() => {
       cy.get('.nv-event-annotation-layer-0')
diff --git 
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
 
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
index 20ba9bb..f01e1c7 100644
--- 
a/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
+++ 
b/superset-frontend/cypress-base/cypress/integration/explore/visualizations/table.test.ts
@@ -35,6 +35,15 @@ describe('Visualization > Table', () => {
     cy.route('POST', '/superset/explore_json/**').as('getJson');
   });
 
+  it('Use default time column', () => {
+    cy.visitChartByParams({
+      ...VIZ_DEFAULTS,
+      granularity_sqla: undefined,
+      metrics: ['count'],
+    });
+    cy.get('input[name="select-granularity_sqla"]').should('have.value', 'ds');
+  });
+
   it('Format non-numeric metrics correctly', () => {
     cy.visitChartByParams({
       ...VIZ_DEFAULTS,
diff --git a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx 
b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx
index e7355cb..ad87021 100644
--- a/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/controlUtils_spec.jsx
@@ -35,6 +35,7 @@ describe('controlUtils', () => {
       columns: ['a', 'b', 'c'],
       metrics: [{ metric_name: 'first' }, { metric_name: 'second' }],
     },
+    controls: {},
   };
 
   beforeAll(() => {
@@ -250,9 +251,10 @@ describe('controlUtils', () => {
     it('should not apply mapStateToProps when initializing', () => {
       const control = getControlState('metrics', 'table', {
         ...state,
-        isInitializing: true,
+        controls: undefined,
       });
-      expect(control.default).toEqual(null);
+      expect(typeof control.default).toBe('function');
+      expect(control.value).toBe(undefined);
     });
   });
 
@@ -261,6 +263,15 @@ describe('controlUtils', () => {
       const control = getControlState('metric', 'table', state, null);
       expect(control.validationErrors).toEqual(['cannot be empty']);
     });
+    it('should not validate if control panel is initializing', () => {
+      const control = getControlState(
+        'metric',
+        'table',
+        { ...state, controls: undefined },
+        undefined,
+      );
+      expect(control.validationErrors).toBeUndefined();
+    });
   });
 
   describe('queryFields', () => {
diff --git 
a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx 
b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
index 77aa5a7..861d301 100644
--- a/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
+++ b/superset-frontend/src/explore/components/ControlPanelsContainer.jsx
@@ -66,15 +66,14 @@ class ControlPanelsContainer extends React.Component {
   renderControl({ name, config }) {
     const { actions, controls, exploreState, form_data: formData } = 
this.props;
     const { visibility } = config;
+
     // If the control item is not an object, we have to look up the control 
data from
     // the centralized controls file.
     // When it is an object we read control data straight from `config` instead
     const controlData = {
-      ...controls[name],
       ...config,
+      ...controls[name],
       name,
-      // apply current value in formData
-      value: formData[name],
     };
     const {
       validationErrors,
diff --git a/superset-frontend/src/explore/controlUtils.js 
b/superset-frontend/src/explore/controlUtils.js
index 1219ff7..ce3022e 100644
--- a/superset-frontend/src/explore/controlUtils.js
+++ b/superset-frontend/src/explore/controlUtils.js
@@ -36,18 +36,17 @@ export function getFormDataFromControls(controlsState) {
 
 export function validateControl(control, processedState) {
   const validators = control.validators;
+  const validationErrors = [];
   if (validators && validators.length > 0) {
-    const validatedControl = { ...control };
-    const validationErrors = [];
     validators.forEach(f => {
       const v = f.call(control, control.value, processedState);
       if (v) {
         validationErrors.push(v);
       }
     });
-    return { ...validatedControl, validationErrors };
   }
-  return control;
+  // always reset validation errors even when there is no validator
+  return { ...control, validationErrors };
 }
 
 /**
@@ -88,17 +87,6 @@ export const getControlConfig = memoizeOne(function 
getControlConfig(
   return control?.config || control;
 });
 
-export function applyMapStateToPropsToControl(controlState, controlPanelState) 
{
-  const { mapStateToProps } = controlState;
-  if (mapStateToProps && controlPanelState) {
-    return {
-      ...controlState,
-      ...mapStateToProps(controlPanelState, controlState),
-    };
-  }
-  return controlState;
-}
-
 function handleMissingChoice(control) {
   // If the value is not valid anymore based on choices, clear it
   const value = control.value;
@@ -121,6 +109,36 @@ function handleMissingChoice(control) {
   return control;
 }
 
+export function applyMapStateToPropsToControl(controlState, controlPanelState) 
{
+  const { mapStateToProps } = controlState;
+  let { value } = controlState;
+  let state = { ...controlState };
+  if (mapStateToProps && controlPanelState) {
+    state = {
+      ...controlState,
+      ...mapStateToProps(controlPanelState, controlState),
+    };
+  }
+  // If default is a function, evaluate it
+  if (typeof state.default === 'function') {
+    state.default = state.default(state, controlPanelState);
+    // if default is still a function, discard
+    if (typeof state.default === 'function') {
+      delete state.default;
+    }
+  }
+  // If no current value, set it as default
+  if (state.default && value === undefined) {
+    value = state.default;
+  }
+  // If a choice control went from multi=false to true, wrap value in array
+  if (value && state.multi && !Array.isArray(value)) {
+    value = [value];
+  }
+  state.value = value;
+  return validateControl(handleMissingChoice(state), state);
+}
+
 export function getControlStateFromControlConfig(
   controlConfig,
   controlPanelState,
@@ -130,38 +148,16 @@ export function getControlStateFromControlConfig(
   if (!controlConfig) {
     return null;
   }
-  let controlState = { ...controlConfig };
+  const controlState = { ...controlConfig, value };
   // only apply mapStateToProps when control states have been initialized
+  // or when explicitly didn't provide control panel state (mostly for testing)
   if (
-    controlPanelState &&
-    (controlPanelState.controls || !controlPanelState.isInitializing)
+    (controlPanelState && controlPanelState.controls) ||
+    controlPanelState === null
   ) {
-    controlState = applyMapStateToPropsToControl(
-      controlState,
-      controlPanelState,
-    );
-  }
-
-  // If default is a function, evaluate it
-  if (typeof controlState.default === 'function') {
-    controlState.default = controlState.default(
-      controlState,
-      controlPanelState,
-    );
-    // if default is still a function, discard
-    if (typeof controlState.default === 'function') {
-      delete controlState.default;
-    }
+    return applyMapStateToPropsToControl(controlState, controlPanelState);
   }
-
-  // If a choice control went from multi=false to true, wrap value in array
-  const controlValue =
-    controlConfig.multi && value && !Array.isArray(value) ? [value] : value;
-
-  controlState.value =
-    typeof controlValue === 'undefined' ? controlState.default : controlValue;
-
-  return validateControl(handleMissingChoice(controlState), controlState);
+  return controlState;
 }
 
 export function getControlState(controlKey, vizType, state, value) {
diff --git a/superset-frontend/src/explore/reducers/getInitialState.js 
b/superset-frontend/src/explore/reducers/getInitialState.js
index 4efa18d..9f1a672 100644
--- a/superset-frontend/src/explore/reducers/getInitialState.js
+++ b/superset-frontend/src/explore/reducers/getInitialState.js
@@ -41,7 +41,6 @@ export default function getInitialState(bootstrapData) {
     filterColumnOpts: [],
     isDatasourceMetaLoading: false,
     isStarred: false,
-    isInitializing: true,
   };
   const controls = getControlsState(bootstrappedState, rawFormData);
   bootstrappedState.controls = controls;
@@ -55,7 +54,6 @@ export default function getInitialState(bootstrapData) {
       bootstrappedState,
     );
   });
-  bootstrappedState.isInitializing = false;
 
   const sliceFormData = slice
     ? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data))

Reply via email to