airbnb/caravel

View on GitHub
superset-frontend/src/explore/controlUtils/standardizedFormData.test.ts

Summary

Maintainability
D
1 day
Test Coverage
/**
 * 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 {
  AdhocColumn,
  AdhocMetricSimple,
  AdhocMetricSQL,
  getChartControlPanelRegistry,
  QueryFormData,
  TimeGranularity,
} from '@superset-ui/core';
import TableChartPlugin from '@superset-ui/plugin-chart-table';
import { BigNumberTotalChartPlugin } from '@superset-ui/plugin-chart-echarts';
import { sections } from '@superset-ui/chart-controls';
import {
  StandardizedFormData,
  sharedMetricsKey,
  sharedColumnsKey,
  publicControls,
} from './standardizedFormData';

const adhocColumn: AdhocColumn = {
  expressionType: 'SQL',
  label: 'country',
  optionName: 'country',
  sqlExpression: 'country',
};
const adhocMetricSQL: AdhocMetricSQL = {
  expressionType: 'SQL',
  label: 'count',
  optionName: 'count',
  sqlExpression: 'count(*)',
};
const adhocMetricSimple: AdhocMetricSimple = {
  expressionType: 'SIMPLE',
  column: {
    id: 1,
    column_name: 'sales',
    columnName: 'sales',
    verbose_name: 'sales',
  },
  aggregate: 'SUM',
  label: 'count',
  optionName: 'count',
};

const tableVizFormData = {
  datasource: '30__table',
  viz_type: 'table',
  granularity_sqla: 'ds',
  time_grain_sqla: TimeGranularity.DAY,
  time_range: 'No filter',
  query_mode: 'aggregate',
  groupby: ['name', 'gender', adhocColumn],
  metrics: ['count', 'avg(sales)', adhocMetricSimple, adhocMetricSQL],
  all_columns: [],
  percent_metrics: [],
  adhoc_filters: [],
  order_by_cols: [],
  row_limit: 10000,
  server_page_length: 10,
  order_desc: true,
  table_timestamp_format: 'smart_date',
  show_cell_bars: true,
  color_pn: true,
  url_params: {
    form_data_key:
      'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
    dataset_id: '30',
  },
};
const tableVizStore = {
  form_data: tableVizFormData,
  controls: {
    datasource: {
      value: '30__table',
    },
    viz_type: {
      value: 'table',
    },
    slice_id: {},
    cache_timeout: {},
    url_params: {
      value: {
        form_data_key:
          'p3No_sqDW7k-kMTzlBPAPd9vwp1IXTf6stbyzjlrPPa0ninvdYUUiMC6F1iKit3Y',
        dataset_id: '30',
      },
    },
    granularity_sqla: {
      value: 'ds',
    },
    time_grain_sqla: {
      value: 'P1D',
    },
    time_range: {
      value: 'No filter',
    },
    query_mode: {
      value: 'aggregate',
    },
    groupby: {
      value: ['name', 'gender', adhocColumn],
    },
    metrics: {
      value: ['count', 'avg(sales)', adhocMetricSimple, adhocMetricSQL],
    },
    all_columns: {
      value: [],
    },
    percent_metrics: {
      value: [],
    },
    adhoc_filters: {
      value: [],
    },
    timeseries_limit_metric: {},
    order_by_cols: {
      value: [],
    },
    server_pagination: {},
    row_limit: {
      value: 10000,
    },
    server_page_length: {
      value: 10,
    },
    include_time: {},
    order_desc: {
      value: true,
    },
    show_totals: {},
    table_timestamp_format: {
      value: 'smart_date',
    },
    page_length: {},
    include_search: {},
    show_cell_bars: {
      value: true,
    },
    align_pn: {},
    color_pn: {
      value: true,
    },
    column_config: {},
    conditional_formatting: {},
  },
  datasource: {
    type: 'table',
    columns: [],
  },
};

describe('should collect control values and create SFD', () => {
  const sharedKey = [...sharedMetricsKey, ...sharedColumnsKey];
  const sharedControlsFormData = {
    // metrics
    metric: 'm1',
    metrics: ['m2'],
    metric_2: 'm3',
    size: 'm4',
    x: 'm5',
    y: 'm6',
    secondary_metric: 'm7',
    // columns
    groupby: ['c1'],
    columns: ['c2'],
    groupbyColumns: ['c3'],
    groupbyRows: ['c4'],
    series: 'c5',
    entity: 'c6',
    series_columns: ['c7'],
  };
  const publicControlsFormData = {
    // time section
    granularity_sqla: 'time_column',
    time_grain_sqla: TimeGranularity.DAY,
    time_range: '2000 : today',
    // filters
    adhoc_filters: [],
    // subquery limit(series limit)
    limit: 5,
    // order by clause
    timeseries_limit_metric: 'orderby_metric',
    series_limit_metric: 'orderby_metric',
    // desc or asc in order by clause
    order_desc: true,
    // outer query limit
    row_limit: 100,
    // x asxs column
    x_axis: 'x_axis_column',
    // advanced analytics - rolling window
    rolling_type: 'sum',
    rolling_periods: 1,
    min_periods: 0,
    // advanced analytics - time comparison
    time_compare: '1 year ago',
    comparison_type: 'values',
    // advanced analytics - resample
    resample_rule: '1D',
    resample_method: 'zerofill',
  };
  const sourceMockFormData: QueryFormData = {
    ...sharedControlsFormData,
    ...publicControlsFormData,
    datasource: '100__table',
    viz_type: 'source_viz',
  };

  const sourceMockStore = {
    form_data: sourceMockFormData,
    controls: Object.fromEntries(
      Object.entries(sourceMockFormData).map(([key, value]) => [
        key,
        { value },
      ]),
    ),
    datasource: {
      type: 'table',
      columns: [],
    },
  };

  beforeAll(() => {
    getChartControlPanelRegistry().registerValue('source_viz', {
      controlPanelSections: [
        sections.advancedAnalyticsControls,
        {
          label: 'transform controls',
          controlSetRows: publicControls.map(control => [control]),
        },
        {
          label: 'axis column',
          controlSetRows: [['x_axis']],
        },
      ],
    });
    getChartControlPanelRegistry().registerValue('target_viz', {
      controlPanelSections: [
        sections.advancedAnalyticsControls,
        {
          label: 'transform controls',
          controlSetRows: publicControls.map(control => [control]),
        },
        {
          label: 'axis column',
          controlSetRows: [['x_axis']],
        },
      ],
      formDataOverrides: (formData: QueryFormData) => ({
        ...formData,
        columns: formData.standardizedFormData.controls.columns,
        metrics: formData.standardizedFormData.controls.metrics,
      }),
    });
  });

  test('should avoid to overlap', () => {
    const sharedControlsSet = new Set(Object.keys(sharedKey));
    const publicControlsSet = new Set(publicControls);
    expect(
      [...sharedControlsSet].filter((x: string) => publicControlsSet.has(x)),
    ).toEqual([]);
  });

  test('should collect all sharedControls', () => {
    expect(Object.entries(sharedControlsFormData).length).toBe(
      Object.entries(sharedKey).length,
    );
    const sfd = new StandardizedFormData(sourceMockFormData);
    expect(sfd.serialize().controls.metrics).toEqual([
      'm1',
      'm2',
      'm3',
      'm4',
      'm5',
      'm6',
      'm7',
    ]);
    expect(sfd.serialize().controls.columns).toEqual([
      'c1',
      'c2',
      'c3',
      'c4',
      'c5',
      'c6',
      'c7',
    ]);
  });

  test('should transform all publicControls and sharedControls', () => {
    expect(Object.entries(publicControlsFormData).length).toBe(
      publicControls.length,
    );

    const sfd = new StandardizedFormData(sourceMockFormData);
    const { formData } = sfd.transform('target_viz', sourceMockStore);
    Object.entries(publicControlsFormData).forEach(([key, value]) => {
      expect(formData).toHaveProperty(key);
      expect(value).toEqual(publicControlsFormData[key]);
    });
    expect(formData.columns).toEqual([
      'c1',
      'c2',
      'c3',
      'c4',
      'c5',
      'c6',
      'c7',
    ]);
    expect(formData.metrics).toEqual([
      'm1',
      'm2',
      'm3',
      'm4',
      'm5',
      'm6',
      'm7',
    ]);
  });

  test('should inherit standardizedFormData and memorizedFormData is LIFO', () => {
    // from source_viz to target_viz
    const sfd = new StandardizedFormData(sourceMockFormData);
    const { formData, controlsState } = sfd.transform(
      'target_viz',
      sourceMockStore,
    );
    expect(
      formData.standardizedFormData.memorizedFormData.map(
        (fd: [string, QueryFormData]) => fd[0],
      ),
    ).toEqual(['source_viz']);

    // from target_viz to source_viz
    const sfd2 = new StandardizedFormData(formData);
    const { formData: fd2, controlsState: cs2 } = sfd2.transform('source_viz', {
      ...sourceMockStore,
      form_data: formData,
      controls: controlsState,
    });
    expect(
      fd2.standardizedFormData.memorizedFormData.map(
        (fd: [string, QueryFormData]) => fd[0],
      ),
    ).toEqual(['source_viz', 'target_viz']);

    // from source_viz to target_viz
    const sfd3 = new StandardizedFormData(fd2);
    const { formData: fd3 } = sfd3.transform('target_viz', {
      ...sourceMockStore,
      form_data: fd2,
      controls: cs2,
    });
    expect(
      fd3.standardizedFormData.memorizedFormData.map(
        (fd: [string, QueryFormData]) => fd[0],
      ),
    ).toEqual(['target_viz', 'source_viz']);
  });
});

describe('should transform form_data between table and bigNumberTotal', () => {
  beforeAll(() => {
    getChartControlPanelRegistry().registerValue(
      'big_number_total',
      new BigNumberTotalChartPlugin().controlPanel,
    );
    getChartControlPanelRegistry().registerValue(
      'table',
      new TableChartPlugin().controlPanel,
    );
  });

  test('get and has', () => {
    // table -> bigNumberTotal
    const sfd = new StandardizedFormData(tableVizFormData);
    const { formData: bntFormData } = sfd.transform(
      'big_number_total',
      tableVizStore,
    );

    // bigNumberTotal -> table
    const sfd2 = new StandardizedFormData(bntFormData);
    expect(sfd2.has('big_number_total')).toBeTruthy();
    expect(sfd2.has('table')).toBeTruthy();
    expect(sfd2.get('big_number_total').viz_type).toBe('big_number_total');
    expect(sfd2.get('table').viz_type).toBe('table');
  });

  test('transform', () => {
    // table -> bigNumberTotal
    const sfd = new StandardizedFormData(tableVizFormData);
    const { formData: bntFormData, controlsState: bntControlsState } =
      sfd.transform('big_number_total', tableVizStore);
    expect(Object.keys(bntFormData).sort()).toEqual(
      [...Object.keys(bntControlsState), 'standardizedFormData'].sort(),
    );
    expect(bntFormData.viz_type).toBe('big_number_total');
    expect(bntFormData.metric).toBe('count');

    // change control values on bigNumber
    bntFormData.metric = 'sum(sales)';

    bntControlsState.metric.value = 'sum(sales)';

    // bigNumberTotal -> table
    const sfd2 = new StandardizedFormData(bntFormData);
    const { formData: tblFormData, controlsState: tblControlsState } =
      sfd2.transform('table', {
        ...tableVizStore,
        form_data: bntFormData,
        controls: bntControlsState,
      });
    expect(Object.keys(tblFormData).sort()).toEqual(
      [...Object.keys(tblControlsState), 'standardizedFormData'].sort(),
    );
    expect(tblFormData.viz_type).toBe('table');
    expect(tblFormData.metrics).toEqual([
      'sum(sales)',
      'avg(sales)',
      adhocMetricSimple,
      adhocMetricSQL,
    ]);
    expect(tblFormData.groupby).toEqual(['name', 'gender', adhocColumn]);
  });
});

describe('initial SFD between different datasource', () => {
  beforeAll(() => {
    getChartControlPanelRegistry().registerValue(
      'big_number_total',
      new BigNumberTotalChartPlugin().controlPanel,
    );
    getChartControlPanelRegistry().registerValue(
      'table',
      new TableChartPlugin().controlPanel,
    );
  });

  test('initial SFD between different datasource', () => {
    const sfd = new StandardizedFormData(tableVizFormData);
    // table -> big number
    const { formData: bntFormData, controlsState: bntControlsState } =
      sfd.transform('big_number_total', tableVizStore);
    const sfd2 = new StandardizedFormData(bntFormData);
    // big number -> table
    const { formData: tblFormData } = sfd2.transform('table', {
      ...tableVizStore,
      form_data: bntFormData,
      controls: bntControlsState,
    });

    expect(
      tblFormData.standardizedFormData.memorizedFormData.map(
        (mfd: [string, QueryFormData][]) => mfd[0],
      ),
    ).toEqual(['table', 'big_number_total']);
    const newDatasourceFormData = { ...tblFormData, datasource: '20__table' };
    const newDatasourceSFD = new StandardizedFormData(newDatasourceFormData);
    expect(
      newDatasourceSFD
        .serialize()
        .memorizedFormData.map(([vizType]) => vizType),
    ).toEqual(['table']);
    expect(newDatasourceSFD.get('table')).not.toHaveProperty(
      'standardizedFormData',
    );
  });
});