Merge branch 'master' into msyavuz/chore/react-18

This commit is contained in:
Mehmet Salih Yavuz
2025-07-06 15:22:18 +03:00
1491 changed files with 55368 additions and 40480 deletions

View File

@@ -27,7 +27,7 @@ import {
DragEvent,
useEffect,
} from 'react';
import { styled, typedMemo, usePrevious } from '@superset-ui/core';
import {
useTable,
usePagination,
@@ -41,9 +41,8 @@ import {
Row,
} from 'react-table';
import { matchSorter, rankings } from 'match-sorter';
import { styled, typedMemo, usePrevious } from '@superset-ui/core';
import { isEqual } from 'lodash';
import { Space } from 'antd';
import { Space } from '@superset-ui/core/components';
import GlobalFilter, { GlobalFilterProps } from './components/GlobalFilter';
import SelectPageSize, {
SelectPageSizeProps,

View File

@@ -22,8 +22,10 @@ import {
ChangeEventHandler,
useRef,
useEffect,
Ref,
} from 'react';
import { Row, FilterValue } from 'react-table';
import { Input, type InputRef, Space } from '@superset-ui/core/components';
import useAsyncState from '../utils/useAsyncState';
export interface SearchInputProps {
@@ -31,7 +33,7 @@ export interface SearchInputProps {
value: string;
onChange: ChangeEventHandler<HTMLInputElement>;
onBlur?: () => void;
inputRef?: React.RefObject<HTMLInputElement>;
inputRef?: Ref<InputRef>;
}
const isSearchFocused = new Map();
@@ -56,17 +58,18 @@ function DefaultSearchInput({
inputRef,
}: SearchInputProps) {
return (
<span className="dt-global-filter">
Search{' '}
<input
<Space direction="horizontal" size={4} className="dt-global-filter">
Search
<Input
size="small"
ref={inputRef}
className="form-control input-sm"
placeholder={`${count} records...`}
value={value}
onChange={onChange}
onBlur={onBlur}
className="form-control input-sm"
/>
</span>
</Space>
);
}
@@ -82,7 +85,7 @@ export default (memo as <T>(fn: T) => T)(function GlobalFilter<
rowCount,
}: GlobalFilterProps<D>) {
const count = serverPagination ? rowCount : preGlobalFilteredRows.length;
const inputRef = useRef<HTMLInputElement>(null);
const inputRef = useRef<InputRef>(null);
const [value, setValue] = useAsyncState(
filterValue,

View File

@@ -18,10 +18,10 @@
*/
/* eslint-disable import/no-extraneous-dependencies */
import { styled } from '@superset-ui/core';
import { Select } from 'antd';
import { RawAntdSelect } from '@superset-ui/core/components';
import { SearchOption } from '../../types';
const StyledSelect = styled(Select)`
const StyledSelect = styled(RawAntdSelect)`
width: 120px;
margin-right: 8px;
`;
@@ -29,7 +29,7 @@ const StyledSelect = styled(Select)`
interface SearchSelectDropdownProps {
/** The currently selected search column value */
value?: string;
/** Callback triggered when a new search column is selected */
/** Function triggered when a new search column is selected */
onChange: (searchCol: string) => void;
/** Available search column options to populate the dropdown */
searchOptions: SearchOption[];

View File

@@ -17,8 +17,9 @@
* under the License.
*/
import { memo } from 'react';
import { t } from '@superset-ui/core';
import { css, t } from '@superset-ui/core';
import { formatSelectOptions } from '@superset-ui/chart-controls';
import { RawAntdSelect } from '@superset-ui/core/components';
export type SizeOption = [number, string];
@@ -33,16 +34,18 @@ function DefaultSelectRenderer({
options,
onChange,
}: SelectPageSizeRendererProps) {
const { Option } = RawAntdSelect;
return (
<span className="dt-select-page-size form-inline">
{t('Show')}{' '}
<select
className="form-control input-sm"
<RawAntdSelect<number>
value={current}
onBlur={() => {}}
onChange={e => {
onChange(Number((e.target as HTMLSelectElement).value));
}}
onChange={value => onChange(value)}
size="small"
css={theme => css`
width: ${theme.sizeUnit * 18}px;
`}
>
{options.map(option => {
const [size, text] = Array.isArray(option)
@@ -50,16 +53,16 @@ function DefaultSelectRenderer({
: [option, option];
const sizeLabel = size === 0 ? t('all') : size;
return (
<option
aria-label={t('Show %s entries', sizeLabel)}
<Option
key={size}
value={size}
value={Number(size)}
aria-label={t('Show %s entries', sizeLabel)}
>
{text}
</option>
</Option>
);
})}
</select>{' '}
</RawAntdSelect>{' '}
{t('entries')}
</span>
);

View File

@@ -16,36 +16,50 @@
* specific language governing permissions and limitations
* under the License.
*/
import { css, styled } from '@superset-ui/core';
export default styled.div`
${({ theme }) => css`
/* Base table styles */
table {
width: 100%;
min-width: auto;
max-width: none;
margin: 0;
border-collapse: collapse;
}
/* Cell styling */
th,
td {
min-width: 4.3em;
padding: 0.75rem;
vertical-align: top;
}
/* Header styling */
thead > tr > th {
padding-right: 0;
position: relative;
background: ${theme.colors.grayscale.light5};
background-color: ${theme.colorBgBase};
text-align: left;
border-bottom: 2px solid ${theme.colorSplit};
color: ${theme.colorText};
vertical-align: bottom;
}
/* Icons in header */
th svg {
color: ${theme.colors.grayscale.light2};
margin: ${theme.gridUnit / 2}px;
margin: 1px ${theme.sizeUnit / 2}px;
fill-opacity: 0.2;
}
th.is-sorted svg {
color: ${theme.colors.grayscale.base};
color: ${theme.colorText};
fill-opacity: 1;
}
/* Table body styling */
.table > tbody > tr:first-of-type > td,
.table > tbody > tr:first-of-type > th {
border-top: 0;
@@ -53,58 +67,113 @@ export default styled.div`
.table > tbody tr td {
font-feature-settings: 'tnum' 1;
border-top: 1px solid ${theme.colorSplit};
}
/* Bootstrap-like condensed table styles */
table.table-condensed,
table.table-sm {
font-size: ${theme.fontSizeSM}px;
}
table.table-condensed th,
table.table-condensed td,
table.table-sm th,
table.table-sm td {
padding: 0.3rem;
}
/* Bootstrap-like bordered table styles */
table.table-bordered {
border: 1px solid ${theme.colorSplit};
}
table.table-bordered th,
table.table-bordered td {
border: 1px solid ${theme.colorSplit};
}
/* Bootstrap-like striped table styles */
table.table-striped tbody tr:nth-of-type(odd) {
background-color: ${theme.colorBgLayout};
}
/* Controls and metrics */
.dt-controls {
padding-bottom: 0.65em;
}
.dt-metric {
text-align: right;
}
.dt-totals {
font-weight: ${theme.typography.weights.bold};
font-weight: ${theme.fontWeightStrong};
}
.dt-is-null {
color: ${theme.colors.grayscale.light1};
color: ${theme.colorTextTertiary};
}
td.dt-is-filter {
cursor: pointer;
}
td.dt-is-filter:hover {
background-color: ${theme.colors.secondary.light4};
background-color: ${theme.colorPrimaryBgHover};
}
td.dt-is-active-filter,
td.dt-is-active-filter:hover {
background-color: ${theme.colors.secondary.light3};
background-color: ${theme.colorPrimaryBgHover};
}
.dt-global-filter {
float: right;
}
/* Cell truncation */
.dt-truncate-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dt-truncate-cell:hover {
overflow: visible;
white-space: normal;
height: auto;
}
/* Pagination styling */
.dt-pagination {
text-align: right;
/* use padding instead of margin so clientHeight can capture it */
padding-top: 0.5em;
}
.dt-pagination .pagination {
margin: 0;
.dt-pagination .pagination > li > a,
.dt-pagination .pagination > li > span {
background-color: ${theme.colorBgBase};
color: ${theme.colorText};
border-color: ${theme.colorBorderSecondary};
}
.dt-pagination .pagination > li.active > a,
.dt-pagination .pagination > li.active > span,
.dt-pagination .pagination > li.active > a:focus,
.dt-pagination .pagination > li.active > a:hover,
.dt-pagination .pagination > li.active > span:focus,
.dt-pagination .pagination > li.active > span:hover {
background-color: ${theme.colorPrimary};
color: ${theme.colorBgContainer};
border-color: ${theme.colorBorderSecondary};
}
.pagination > li > span.dt-pagination-ellipsis:focus,
.pagination > li > span.dt-pagination-ellipsis:hover {
background: ${theme.colors.grayscale.light5};
background: ${theme.colorBgLayout};
border-color: ${theme.colorBorderSecondary};
}
.dt-no-results {
@@ -113,8 +182,9 @@ export default styled.div`
}
.right-border-only {
border-right: 2px solid ${theme.colors.grayscale.light2};
border-right: 2px solid ${theme.colorSplit};
}
table .right-border-only:last-child {
border-right: none;
}

View File

@@ -52,8 +52,16 @@ import {
t,
tn,
useTheme,
SupersetTheme,
} from '@superset-ui/core';
import { Dropdown, Menu, Tooltip } from '@superset-ui/chart-controls';
import {
Input,
Space,
RawAntdSelect as Select,
Dropdown,
Menu,
Tooltip,
} from '@superset-ui/core/components';
import {
CheckOutlined,
InfoCircleOutlined,
@@ -62,7 +70,7 @@ import {
PlusCircleOutlined,
TableOutlined,
} from '@ant-design/icons';
import { debounce, isEmpty, isEqual } from 'lodash';
import { isEmpty, debounce, isEqual } from 'lodash';
import {
ColorSchemeEnum,
DataColumnMeta,
@@ -179,49 +187,50 @@ function SortIcon<D extends object>({ column }: { column: ColumnInstance<D> }) {
return sortIcon;
}
const SearchInput = ({
function SearchInput({
count,
value,
onChange,
onBlur,
inputRef,
}: SearchInputProps) => (
<span className="dt-global-filter">
{t('Search')}{' '}
<input
ref={inputRef}
aria-label={t('Search %s records', count)}
className="form-control input-sm"
placeholder={tn('search.num_records', count)}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
</span>
);
}: SearchInputProps) {
return (
<Space direction="horizontal" size={4} className="dt-global-filter">
{t('Search')}
<Input
size="small"
aria-label={t('Search %s records', count)}
placeholder={tn('search.num_records', count)}
value={value}
onChange={onChange}
onBlur={onBlur}
ref={inputRef}
/>
</Space>
);
}
function SelectPageSize({
options,
current,
onChange,
}: SelectPageSizeRendererProps) {
const { Option } = Select;
return (
<span
className="dt-select-page-size form-inline"
role="group"
aria-label={t('Select page size')}
>
<>
<label htmlFor="pageSizeSelect" className="sr-only">
{t('Select page size')}
</label>
{t('Show')}{' '}
<select
<Select<number>
id="pageSizeSelect"
className="form-control input-sm"
value={current}
onChange={e => {
onChange(Number((e.target as HTMLSelectElement).value));
}}
onChange={value => onChange(value)}
size="small"
css={(theme: SupersetTheme) => css`
width: ${theme.sizeUnit * 18}px;
`}
aria-label={t('Show entries per page')}
>
{options.map(option => {
@@ -229,14 +238,14 @@ function SelectPageSize({
? option
: [option, option];
return (
<option key={size} value={size}>
<Option key={size} value={Number(size)}>
{text}
</option>
</Option>
);
})}
</select>{' '}
</Select>{' '}
{t('entries per page')}
</span>
</>
);
}
@@ -569,9 +578,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<div
css={css`
max-width: 242px;
padding: 0 ${theme.gridUnit * 2}px;
color: ${theme.colors.grayscale.base};
font-size: ${theme.typography.sizes.s}px;
padding: 0 ${theme.sizeUnit * 2}px;
color: ${theme.colorText};
font-size: ${theme.fontSizeSM}px;
`}
>
{t(
@@ -582,7 +591,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<Menu.Item key={column.key}>
<span
css={css`
color: ${theme.colors.grayscale.dark2};
color: ${theme.colorText};
`}
>
{column.label}
@@ -590,7 +599,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<span
css={css`
float: right;
font-size: ${theme.typography.sizes.s}px;
font-size: ${theme.fontSizeSM}px;
`}
>
{selectedComparisonColumns.includes(column.key) && (
@@ -641,7 +650,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
css={css`
float: right;
& svg {
color: ${theme.colors.grayscale.base} !important;
color: ${theme.colorIcon} !important;
}
`}
>
@@ -672,7 +681,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
<tr
css={css`
th {
border-right: 2px solid ${theme.colors.grayscale.light2};
border-right: 1px solid ${theme.colorSplit};
}
th:first-child {
border-left: none;
@@ -755,7 +764,6 @@ export default function TableChart<D extends DataRecord = DataRecord>(
isUsingTimeComparison &&
Array.isArray(basicColorFormatters) &&
basicColorFormatters.length > 0;
const valueRange =
!hasBasicColorFormatters &&
!hasColumnColorFormatters &&
@@ -829,15 +837,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
? basicColorColumnFormatters[row.index][column.key]?.mainArrow
: '';
}
const StyledCell = styled.td`
color: ${theme.colorText};
text-align: ${sharedStyle.textAlign};
white-space: ${value instanceof Date ? 'nowrap' : undefined};
position: relative;
background: ${backgroundColor || undefined};
padding-left: ${column.isChildColumn
? `${theme.gridUnit * 5}px`
: `${theme.gridUnit}px`};
? `${theme.sizeUnit * 5}px`
: `${theme.sizeUnit}px`};
`;
const cellBarStyles = css`
@@ -868,9 +876,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
color: ${basicColorFormatters &&
basicColorFormatters[row.index][originKey]?.arrowColor ===
ColorSchemeEnum.Green
? theme.colors.success.base
: theme.colors.error.base};
margin-right: ${theme.gridUnit}px;
? theme.colorSuccess
: theme.colorError};
margin-right: ${theme.sizeUnit}px;
`;
if (
@@ -880,9 +888,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
arrowStyles = css`
color: ${basicColorColumnFormatters[row.index][column.key]
?.arrowColor === ColorSchemeEnum.Green
? theme.colors.success.base
: theme.colors.error.base};
margin-right: ${theme.gridUnit}px;
? theme.colorSuccess
: theme.colorError};
margin-right: ${theme.sizeUnit}px;
`;
}
@@ -971,7 +979,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
},
Header: ({ column: col, onClick, style, onDragStart, onDrop }) => (
<th
id={`header-${column.key}`}
id={`header-${column.originalLabel}`}
title={t('Shift + Click to sort by multiple columns')}
className={[className, col.isSorted ? 'is-sorted' : ''].join(' ')}
style={{
@@ -1027,8 +1035,8 @@ export default function TableChart<D extends DataRecord = DataRecord>(
display: flex;
align-items: center;
& svg {
margin-left: ${theme.gridUnit}px;
color: ${theme.colors.grayscale.dark1} !important;
margin-left: ${theme.sizeUnit}px;
color: ${theme.colorBorder} !important;
}
`}
>

View File

@@ -18,17 +18,18 @@
*/
import {
AdhocColumn,
BuildQuery,
PostProcessingRule,
QueryFormOrderBy,
QueryMode,
QueryObject,
buildQueryContext,
ensureIsArray,
getMetricLabel,
isPhysicalColumn,
QueryFormOrderBy,
QueryMode,
QueryObject,
removeDuplicates,
} from '@superset-ui/core';
import { PostProcessingRule } from '@superset-ui/core/src/query/types/PostProcessing';
import { BuildQuery } from '@superset-ui/core/src/chart/registries/ChartBuildQueryRegistrySingleton';
import {
isTimeComparison,
timeCompareOperator,
@@ -84,7 +85,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
return buildQueryContext(formDataCopy, baseQueryObject => {
let { metrics, orderby = [], columns = [] } = baseQueryObject;
const { extras = {} } = baseQueryObject;
let postProcessing: PostProcessingRule[] = [];
const postProcessing: PostProcessingRule[] = [];
const nonCustomNorInheritShifts = ensureIsArray(
formData.time_compare,
).filter((shift: string) => shift !== 'custom' && shift !== 'inherit');
@@ -115,6 +116,13 @@ const buildQuery: BuildQuery<TableChartFormData> = (
}
}
if (
extra_form_data?.time_compare &&
!timeOffsets.includes(extra_form_data.time_compare)
) {
timeOffsets = [extra_form_data.time_compare];
}
let temporalColumnAdded = false;
let temporalColumn = null;
@@ -129,6 +137,12 @@ const buildQuery: BuildQuery<TableChartFormData> = (
orderby = [[metrics[0], false]];
}
// add postprocessing for percent metrics only when in aggregation mode
type PercentMetricCalculationMode = 'row_limit' | 'all_records';
const calculationMode: PercentMetricCalculationMode =
(formData.percent_metric_calculation as PercentMetricCalculationMode) ||
'row_limit';
if (percentMetrics && percentMetrics.length > 0) {
const percentMetricsLabelsWithTimeComparison = isTimeComparison(
formData,
@@ -139,6 +153,7 @@ const buildQuery: BuildQuery<TableChartFormData> = (
timeOffsets,
)
: percentMetrics.map(getMetricLabel);
const percentMetricLabels = removeDuplicates(
percentMetricsLabelsWithTimeComparison,
);
@@ -146,16 +161,26 @@ const buildQuery: BuildQuery<TableChartFormData> = (
metrics.concat(percentMetrics),
getMetricLabel,
);
postProcessing = [
{
if (calculationMode === 'all_records') {
postProcessing.push({
operation: 'contribution',
options: {
columns: percentMetricLabels,
rename_columns: percentMetricLabels.map(x => `%${x}`),
rename_columns: percentMetricLabels.map(m => `%${m}`),
},
},
];
});
} else {
postProcessing.push({
operation: 'contribution',
options: {
columns: percentMetricLabels,
rename_columns: percentMetricLabels.map(m => `%${m}`),
},
});
}
}
// Add the operator for the time comparison if some is selected
if (!isEmpty(timeOffsets)) {
postProcessing.push(timeCompareOperator(formData, baseQueryObject));
@@ -252,6 +277,26 @@ const buildQuery: BuildQuery<TableChartFormData> = (
});
const extraQueries: QueryObject[] = [];
const calculationMode = formData.percent_metric_calculation || 'row_limit';
if (
calculationMode === 'all_records' &&
percentMetrics &&
percentMetrics.length > 0
) {
extraQueries.push({
...queryObject,
columns: [],
metrics: percentMetrics,
post_processing: [],
row_limit: 0,
row_offset: 0,
orderby: [],
is_timeseries: false,
});
}
if (
metrics?.length &&
formData.show_totals &&
@@ -263,8 +308,8 @@ const buildQuery: BuildQuery<TableChartFormData> = (
row_limit: 0,
row_offset: 0,
post_processing: [],
order_desc: undefined, // we don't need orderby stuff here,
orderby: undefined, // because this query will be used for get total aggregation.
order_desc: undefined,
orderby: undefined,
});
}

View File

@@ -161,12 +161,30 @@ const generateComparisonColumns = (colname: string) => [
`${colname}`,
`% ${colname}`,
];
/**
* Generate column types for the comparison columns.
*/
const generateComparisonColumnTypes = (count: number) =>
Array(count).fill(GenericDataType.Numeric);
const percentMetricCalculationControl: ControlConfig<'SelectControl'> = {
type: 'SelectControl',
label: t('Percentage metric calculation'),
description: t(
'Row Limit: percentages are calculated based on the subset of data retrieved, respecting the row limit. ' +
'All Records: Percentages are calculated based on the total dataset, ignoring the row limit.',
),
default: 'row_limit',
clearable: false,
choices: [
['row_limit', t('Row limit')],
['all_records', t('All records')],
],
visibility: isAggMode,
renderTrigger: false,
};
const processComparisonColumns = (columns: any[], suffix: string) =>
columns
.map(col => {
@@ -433,6 +451,13 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'percent_metric_calculation',
config: percentMetricCalculationControl,
},
],
[
{
name: 'show_totals',

View File

@@ -17,8 +17,7 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import { render, screen } from '@superset-ui/core/spec';
import TableChart from '../src/TableChart';
import transformProps from '../src/transformProps';
import DateWithFormatter from '../src/utils/DateWithFormatter';
@@ -268,9 +267,7 @@ describe('plugin-chart-table', () => {
describe('TableChart', () => {
it('render basic data', () => {
render(
<ThemeProvider theme={supersetTheme}>
<TableChart {...transformProps(testData.basic)} sticky={false} />,
</ThemeProvider>,
<TableChart {...transformProps(testData.basic)} sticky={false} />,
);
const firstDataRow = screen.getAllByRole('rowgroup')[1];
@@ -289,10 +286,10 @@ describe('plugin-chart-table', () => {
it('render advanced data', () => {
render(
<ThemeProvider theme={supersetTheme}>
<>
<TableChart {...transformProps(testData.advanced)} sticky={false} />
,
</ThemeProvider>,
</>,
);
const secondColumnHeader = screen.getByText('Sum of Num');
expect(secondColumnHeader).toBeInTheDocument();
@@ -413,9 +410,7 @@ describe('plugin-chart-table', () => {
it('render empty data', () => {
render(
<ThemeProvider theme={supersetTheme}>
<TableChart {...transformProps(testData.empty)} sticky={false} />,
</ThemeProvider>,
<TableChart {...transformProps(testData.empty)} sticky={false} />,
);
expect(screen.getByText('No records found')).toBeInTheDocument();
});
@@ -491,14 +486,10 @@ describe('plugin-chart-table', () => {
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
});
it('should display originalLabel in grouped headers', () => {
it('should display original label in grouped headers', () => {
const props = transformProps(testData.comparison);
render(
<ThemeProvider theme={supersetTheme}>
<TableChart {...props} sticky={false} />
</ThemeProvider>,
);
render(<TableChart {...props} sticky={false} />);
const groupHeaders = screen.getAllByRole('columnheader');
expect(groupHeaders.length).toBeGreaterThan(0);
const hasMetricHeaders = groupHeaders.some(

View File

@@ -148,5 +148,92 @@ describe('plugin-chart-table', () => {
expect(queries[1].extras?.time_grain_sqla).toEqual(TimeGranularity.MONTH);
expect(queries[1].extras?.where).toEqual("(status IN ('In Process'))");
});
describe('Percent Metric Calculation Modes', () => {
const baseFormDataWithPercents: TableChartFormData = {
...basicFormData,
query_mode: QueryMode.Aggregate,
metrics: ['count'],
percent_metrics: ['sum_sales'],
groupby: ['category'],
};
it('should default to row_limit mode with single query', () => {
const { queries } = buildQuery(baseFormDataWithPercents);
expect(queries).toHaveLength(1);
expect(queries[0].metrics).toEqual(['count', 'sum_sales']);
expect(queries[0].post_processing).toEqual([
{
operation: 'contribution',
options: {
columns: ['sum_sales'],
rename_columns: ['%sum_sales'],
},
},
]);
});
it('should create extra query in all_records mode', () => {
const formData = {
...baseFormDataWithPercents,
percent_metric_calculation: 'all_records',
};
const { queries } = buildQuery(formData);
expect(queries).toHaveLength(2);
expect(queries[0].post_processing).toEqual([
{
operation: 'contribution',
options: {
columns: ['sum_sales'],
rename_columns: ['%sum_sales'],
},
},
]);
expect(queries[1]).toMatchObject({
columns: [],
metrics: ['sum_sales'],
post_processing: [],
row_limit: 0,
row_offset: 0,
orderby: [],
is_timeseries: false,
});
});
it('should work with show_totals in all_records mode', () => {
const formData = {
...baseFormDataWithPercents,
percent_metric_calculation: 'all_records',
show_totals: true,
};
const { queries } = buildQuery(formData);
expect(queries).toHaveLength(3);
expect(queries[1].metrics).toEqual(['sum_sales']);
expect(queries[2].metrics).toEqual(['count', 'sum_sales']);
});
it('should handle empty percent_metrics in all_records mode', () => {
const formData = {
...basicFormData,
query_mode: QueryMode.Aggregate,
metrics: ['count'],
percent_metrics: [],
percent_metric_calculation: 'all_records',
groupby: ['category'],
};
const { queries } = buildQuery(formData);
expect(queries).toHaveLength(1);
expect(queries[0].post_processing).toEqual([]);
});
});
});
});

View File

@@ -1,18 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"declarationDir": "lib",
"composite": true,
"rootDir": "src",
"outDir": "lib",
"rootDir": "src"
},
"exclude": ["lib", "test"],
"extends": "../../tsconfig.json",
"include": ["src/**/*", "types/**/*", "../../types/**/*"],
"references": [
{
"path": "../../packages/superset-ui-chart-controls"
},
{
"path": "../../packages/superset-ui-core"
"baseUrl": ".",
"paths": {
"@superset-ui/core/components": ["../../packages/superset-ui-core/src/components"]
}
},
"include": ["src/**/*", "types/**/*"],
"exclude": ["lib", "test"],
"references": [
{ "path": "../../packages/superset-ui-core" },
{ "path": "../../packages/superset-ui-chart-controls" }
]
}