mirror of
https://github.com/apache/superset.git
synced 2026-05-10 10:25:51 +00:00
feat(viz-type): Ag grid table plugin Integration (#33517)
Signed-off-by: hainenber <dotronghai96@gmail.com> Co-authored-by: Amaan Nawab <nelsondrew07@gmail.com> Co-authored-by: Levis Mbote <111055098+LevisNgigi@users.noreply.github.com> Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com> Co-authored-by: Paul Rhodes <withnale@users.noreply.github.com> Co-authored-by: Vitor Avila <96086495+Vitor-Avila@users.noreply.github.com> Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com> Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com> Co-authored-by: Sam Firke <sfirke@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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 {
|
||||
DataRecordValue,
|
||||
normalizeTimestamp,
|
||||
TimeFormatFunction,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
/**
|
||||
* Extended Date object with a custom formatter, and retains the original input
|
||||
* when the formatter is simple `String(..)`.
|
||||
*/
|
||||
export default class DateWithFormatter extends Date {
|
||||
formatter: TimeFormatFunction;
|
||||
|
||||
input: DataRecordValue;
|
||||
|
||||
constructor(
|
||||
input: DataRecordValue,
|
||||
{ formatter = String }: { formatter?: TimeFormatFunction } = {},
|
||||
) {
|
||||
let value = input;
|
||||
// assuming timestamps without a timezone is in UTC time
|
||||
if (typeof value === 'string') {
|
||||
value = normalizeTimestamp(value);
|
||||
}
|
||||
|
||||
super(value as string);
|
||||
|
||||
this.input = input;
|
||||
this.formatter = formatter;
|
||||
this.toString = (): string => {
|
||||
if (this.formatter === String) {
|
||||
return String(this.input);
|
||||
}
|
||||
return this.formatter ? this.formatter(this) : Date.toString.call(this);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const dateFilterComparator = (filterDate: Date, cellValue: Date) => {
|
||||
const cellDate = new Date(cellValue);
|
||||
cellDate.setHours(0, 0, 0, 0);
|
||||
if (Number.isNaN(cellDate?.getTime())) return -1;
|
||||
|
||||
const cellDay = cellDate.getDate();
|
||||
const cellMonth = cellDate.getMonth();
|
||||
const cellYear = cellDate.getFullYear();
|
||||
|
||||
const filterDay = filterDate.getDate();
|
||||
const filterMonth = filterDate.getMonth();
|
||||
const filterYear = filterDate.getFullYear();
|
||||
|
||||
if (cellYear < filterYear) return -1;
|
||||
if (cellYear > filterYear) return 1;
|
||||
if (cellMonth < filterMonth) return -1;
|
||||
if (cellMonth > filterMonth) return 1;
|
||||
if (cellDay < filterDay) return -1;
|
||||
if (cellDay > filterDay) return 1;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export default dateFilterComparator;
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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 { isNil } from 'lodash';
|
||||
|
||||
export default function extent<T = number | string | Date | undefined | null>(
|
||||
values: T[],
|
||||
) {
|
||||
let min: T | undefined;
|
||||
let max: T | undefined;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const value of values) {
|
||||
if (value !== null) {
|
||||
if (isNil(min)) {
|
||||
if (value !== undefined) {
|
||||
min = value;
|
||||
max = value;
|
||||
}
|
||||
} else if (value !== undefined) {
|
||||
if (min > value) {
|
||||
min = value;
|
||||
}
|
||||
if (!isNil(max)) {
|
||||
if (max < value) {
|
||||
max = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [min, max];
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* 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 { SetDataMaskHook } from '@superset-ui/core';
|
||||
import { SortByItem } from '../types';
|
||||
|
||||
interface TableOwnState {
|
||||
currentPage?: number;
|
||||
pageSize?: number;
|
||||
sortColumn?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
searchText?: string;
|
||||
sortBy?: SortByItem[];
|
||||
}
|
||||
|
||||
export const updateTableOwnState = (
|
||||
setDataMask: SetDataMaskHook = () => {},
|
||||
modifiedOwnState: TableOwnState,
|
||||
) =>
|
||||
setDataMask({
|
||||
ownState: modifiedOwnState,
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* 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 { ValueGetterParams } from 'ag-grid-community';
|
||||
|
||||
const filterValueGetter = (params: ValueGetterParams) => {
|
||||
const raw = params.data[params.colDef.field as string];
|
||||
const formatter = params.colDef.valueFormatter as Function;
|
||||
if (!raw || !formatter) return null;
|
||||
const formatted = formatter({
|
||||
value: raw,
|
||||
});
|
||||
|
||||
const numeric = parseFloat(String(formatted).replace('%', '').trim());
|
||||
return Number.isNaN(numeric) ? null : numeric;
|
||||
};
|
||||
|
||||
export default filterValueGetter;
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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 {
|
||||
CurrencyFormatter,
|
||||
DataRecordValue,
|
||||
GenericDataType,
|
||||
getNumberFormatter,
|
||||
isDefined,
|
||||
isProbablyHTML,
|
||||
sanitizeHtml,
|
||||
} from '@superset-ui/core';
|
||||
import { ValueFormatterParams, ValueGetterParams } from 'ag-grid-community';
|
||||
import { DataColumnMeta, InputColumn } from '../types';
|
||||
import DateWithFormatter from './DateWithFormatter';
|
||||
|
||||
/**
|
||||
* Format text for cell value.
|
||||
*/
|
||||
function formatValue(
|
||||
formatter: DataColumnMeta['formatter'],
|
||||
value: DataRecordValue,
|
||||
): [boolean, string] {
|
||||
// render undefined as empty string
|
||||
if (value === undefined) {
|
||||
return [false, ''];
|
||||
}
|
||||
// render null as `N/A`
|
||||
if (
|
||||
value === null ||
|
||||
// null values in temporal columns are wrapped in a Date object, so make sure we
|
||||
// handle them here too
|
||||
(value instanceof DateWithFormatter && value.input === null)
|
||||
) {
|
||||
return [false, 'N/A'];
|
||||
}
|
||||
if (formatter) {
|
||||
return [false, formatter(value as number)];
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return isProbablyHTML(value) ? [true, sanitizeHtml(value)] : [false, value];
|
||||
}
|
||||
return [false, value.toString()];
|
||||
}
|
||||
|
||||
export function formatColumnValue(
|
||||
column: DataColumnMeta,
|
||||
value: DataRecordValue,
|
||||
) {
|
||||
const { dataType, formatter, config = {} } = column;
|
||||
const isNumber = dataType === GenericDataType.Numeric;
|
||||
const smallNumberFormatter =
|
||||
config.d3SmallNumberFormat === undefined
|
||||
? formatter
|
||||
: config.currencyFormat
|
||||
? new CurrencyFormatter({
|
||||
d3Format: config.d3SmallNumberFormat,
|
||||
currency: config.currencyFormat,
|
||||
})
|
||||
: getNumberFormatter(config.d3SmallNumberFormat);
|
||||
return formatValue(
|
||||
isNumber && typeof value === 'number' && Math.abs(value) < 1
|
||||
? smallNumberFormatter
|
||||
: formatter,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
export const valueFormatter = (
|
||||
params: ValueFormatterParams,
|
||||
col: InputColumn,
|
||||
): string => {
|
||||
const { value, node } = params;
|
||||
if (
|
||||
isDefined(value) &&
|
||||
value !== '' &&
|
||||
!(value instanceof DateWithFormatter && value.input === null)
|
||||
) {
|
||||
return col.formatter?.(value) || value;
|
||||
}
|
||||
if (node?.level === -1) {
|
||||
return '';
|
||||
}
|
||||
return 'N/A';
|
||||
};
|
||||
|
||||
export const valueGetter = (params: ValueGetterParams, col: InputColumn) => {
|
||||
// @ts-ignore
|
||||
if (params?.colDef?.isMain) {
|
||||
const modifiedColId = `Main ${params.column.getColId()}`;
|
||||
return params.data[modifiedColId];
|
||||
}
|
||||
if (isDefined(params.data?.[params.column.getColId()])) {
|
||||
return params.data[params.column.getColId()];
|
||||
}
|
||||
if (col.isNumeric) {
|
||||
return undefined;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* 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 { CUSTOM_AGG_FUNCS } from '../consts';
|
||||
import { InputColumn } from '../types';
|
||||
|
||||
export const getAggFunc = (col: InputColumn) =>
|
||||
col.isMetric || col.isPercentMetric
|
||||
? CUSTOM_AGG_FUNCS.queryTotal
|
||||
: col.isNumeric
|
||||
? 'sum'
|
||||
: undefined;
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 { CellClassParams } from 'ag-grid-community';
|
||||
import { InputColumn } from '../types';
|
||||
|
||||
type GetCellClassParams = CellClassParams & {
|
||||
col: InputColumn;
|
||||
emitCrossFilters: boolean | undefined;
|
||||
};
|
||||
|
||||
const getCellClass = (params: GetCellClassParams) => {
|
||||
const { col, emitCrossFilters } = params;
|
||||
const isActiveFilterValue = params?.context?.isActiveFilterValue;
|
||||
let className = '';
|
||||
if (emitCrossFilters) {
|
||||
if (!col?.isMetric) {
|
||||
className += ' dt-is-filter';
|
||||
}
|
||||
if (isActiveFilterValue?.(col?.key, params?.value)) {
|
||||
className += ' dt-is-active-filter';
|
||||
}
|
||||
if (col?.config?.truncateLongCells) {
|
||||
className += ' dt-truncate-cell';
|
||||
}
|
||||
}
|
||||
return className;
|
||||
};
|
||||
|
||||
export default getCellClass;
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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 { ColorFormatters } from '@superset-ui/chart-controls';
|
||||
import { CellClassParams } from 'ag-grid-community';
|
||||
import { BasicColorFormatterType, InputColumn } from '../types';
|
||||
|
||||
type CellStyleParams = CellClassParams & {
|
||||
hasColumnColorFormatters: boolean | undefined;
|
||||
columnColorFormatters: ColorFormatters;
|
||||
hasBasicColorFormatters: boolean | undefined;
|
||||
basicColorFormatters?: {
|
||||
[Key: string]: BasicColorFormatterType;
|
||||
}[];
|
||||
col: InputColumn;
|
||||
};
|
||||
|
||||
const getCellStyle = (params: CellStyleParams) => {
|
||||
const {
|
||||
value,
|
||||
colDef,
|
||||
rowIndex,
|
||||
hasBasicColorFormatters,
|
||||
basicColorFormatters,
|
||||
hasColumnColorFormatters,
|
||||
columnColorFormatters,
|
||||
col,
|
||||
node,
|
||||
} = params;
|
||||
let backgroundColor;
|
||||
if (hasColumnColorFormatters) {
|
||||
columnColorFormatters!
|
||||
.filter(formatter => {
|
||||
const colTitle = formatter?.column?.includes('Main')
|
||||
? formatter?.column?.replace('Main', '').trim()
|
||||
: formatter?.column;
|
||||
return colTitle === colDef.field;
|
||||
})
|
||||
.forEach(formatter => {
|
||||
const formatterResult =
|
||||
value || value === 0 ? formatter.getColorFromValue(value) : false;
|
||||
if (formatterResult) {
|
||||
backgroundColor = formatterResult;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
hasBasicColorFormatters &&
|
||||
col?.metricName &&
|
||||
node?.rowPinned !== 'bottom'
|
||||
) {
|
||||
backgroundColor =
|
||||
basicColorFormatters?.[rowIndex]?.[col.metricName]?.backgroundColor;
|
||||
}
|
||||
|
||||
const textAlign =
|
||||
col?.config?.horizontalAlign || (col?.isNumeric ? 'right' : 'left');
|
||||
|
||||
return {
|
||||
backgroundColor: backgroundColor || '',
|
||||
textAlign,
|
||||
};
|
||||
};
|
||||
|
||||
export default getCellStyle;
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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 {
|
||||
DataRecordFilters,
|
||||
DataRecordValue,
|
||||
DTTM_ALIAS,
|
||||
ensureIsArray,
|
||||
TimeGranularity,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
type GetCrossFilterDataMaskProps = {
|
||||
key: string;
|
||||
value: DataRecordValue;
|
||||
filters?: DataRecordFilters;
|
||||
timeGrain?: TimeGranularity;
|
||||
isActiveFilterValue: (key: string, val: DataRecordValue) => boolean;
|
||||
timestampFormatter: (value: DataRecordValue) => string;
|
||||
};
|
||||
|
||||
export const getCrossFilterDataMask = ({
|
||||
key,
|
||||
value,
|
||||
filters,
|
||||
timeGrain,
|
||||
isActiveFilterValue,
|
||||
timestampFormatter,
|
||||
}: GetCrossFilterDataMaskProps) => {
|
||||
let updatedFilters = { ...(filters || {}) };
|
||||
if (filters && isActiveFilterValue(key, value)) {
|
||||
updatedFilters = {};
|
||||
} else {
|
||||
updatedFilters = {
|
||||
[key]: [value],
|
||||
};
|
||||
}
|
||||
if (Array.isArray(updatedFilters[key]) && updatedFilters[key].length === 0) {
|
||||
delete updatedFilters[key];
|
||||
}
|
||||
|
||||
const groupBy = Object.keys(updatedFilters);
|
||||
const groupByValues = Object.values(updatedFilters);
|
||||
const labelElements: string[] = [];
|
||||
groupBy.forEach(col => {
|
||||
const isTimestamp = col === DTTM_ALIAS;
|
||||
const filterValues = ensureIsArray(updatedFilters?.[col]);
|
||||
if (filterValues.length) {
|
||||
const valueLabels = filterValues.map(value =>
|
||||
isTimestamp ? timestampFormatter(value) : value,
|
||||
);
|
||||
labelElements.push(`${valueLabels.join(', ')}`);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
dataMask: {
|
||||
extraFormData: {
|
||||
filters:
|
||||
groupBy.length === 0
|
||||
? []
|
||||
: groupBy.map(col => {
|
||||
const val = ensureIsArray(updatedFilters?.[col]);
|
||||
if (!val.length)
|
||||
return {
|
||||
col,
|
||||
op: 'IS NULL' as const,
|
||||
};
|
||||
return {
|
||||
col,
|
||||
op: 'IN' as const,
|
||||
val: val.map(el => (el instanceof Date ? el.getTime() : el!)),
|
||||
grain: col === DTTM_ALIAS ? timeGrain : undefined,
|
||||
};
|
||||
}),
|
||||
},
|
||||
filterState: {
|
||||
label: labelElements.join(', '),
|
||||
value: groupByValues.length ? groupByValues : null,
|
||||
filters:
|
||||
updatedFilters && Object.keys(updatedFilters).length
|
||||
? updatedFilters
|
||||
: null,
|
||||
},
|
||||
},
|
||||
isCurrentValueSelected: isActiveFilterValue(key, value),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// All ag grid sort related stuff
|
||||
import { GridState, SortModelItem } from 'ag-grid-community';
|
||||
import { SortByItem } from '../types';
|
||||
|
||||
const getInitialSortState = (sortBy?: SortByItem[]): SortModelItem[] => {
|
||||
if (Array.isArray(sortBy) && sortBy.length > 0) {
|
||||
return [
|
||||
{
|
||||
colId: sortBy[0]?.id,
|
||||
sort: sortBy[0]?.desc ? 'desc' : 'asc',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const shouldSort = ({
|
||||
colId,
|
||||
sortDir,
|
||||
percentMetrics,
|
||||
serverPagination,
|
||||
gridInitialState,
|
||||
}: {
|
||||
colId: string;
|
||||
sortDir: string;
|
||||
percentMetrics: string[];
|
||||
serverPagination: boolean;
|
||||
gridInitialState: GridState;
|
||||
}) => {
|
||||
// percent metrics are not sortable
|
||||
if (percentMetrics.includes(colId)) return false;
|
||||
// if server pagination is not enabled, return false
|
||||
// since this is server pagination sort
|
||||
if (!serverPagination) return false;
|
||||
|
||||
const {
|
||||
colId: initialColId = '',
|
||||
sort: initialSortDir,
|
||||
}: Partial<SortModelItem> = gridInitialState?.sort?.sortModel?.[0] || {};
|
||||
|
||||
// if the initial sort is the same as the current sort, return false
|
||||
if (initialColId === colId && initialSortDir === sortDir) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default getInitialSortState;
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 { isEqualArray } from '@superset-ui/core';
|
||||
import { TableChartProps } from '../types';
|
||||
|
||||
export default function isEqualColumns(
|
||||
propsA: TableChartProps[],
|
||||
propsB: TableChartProps[],
|
||||
) {
|
||||
const a = propsA[0];
|
||||
const b = propsB[0];
|
||||
return (
|
||||
a.datasource.columnFormats === b.datasource.columnFormats &&
|
||||
a.datasource.currencyFormats === b.datasource.currencyFormats &&
|
||||
a.datasource.verboseMap === b.datasource.verboseMap &&
|
||||
a.formData.tableTimestampFormat === b.formData.tableTimestampFormat &&
|
||||
a.formData.timeGrainSqla === b.formData.timeGrainSqla &&
|
||||
JSON.stringify(a.formData.columnConfig || null) ===
|
||||
JSON.stringify(b.formData.columnConfig || null) &&
|
||||
isEqualArray(a.formData.metrics, b.formData.metrics) &&
|
||||
isEqualArray(a.queriesData?.[0]?.colnames, b.queriesData?.[0]?.colnames) &&
|
||||
isEqualArray(a.queriesData?.[0]?.coltypes, b.queriesData?.[0]?.coltypes) &&
|
||||
JSON.stringify(a.formData.extraFilters || null) ===
|
||||
JSON.stringify(b.formData.extraFilters || null) &&
|
||||
JSON.stringify(a.formData.extraFormData || null) ===
|
||||
JSON.stringify(b.formData.extraFormData || null) &&
|
||||
JSON.stringify(a.rawFormData.column_config || null) ===
|
||||
JSON.stringify(b.rawFormData.column_config || null)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/* eslint-disable camelcase */
|
||||
/**
|
||||
* 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 { ColDef } from 'ag-grid-community';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { DataRecord, GenericDataType } from '@superset-ui/core';
|
||||
import { ColorFormatters } from '@superset-ui/chart-controls';
|
||||
import { extent as d3Extent, max as d3Max } from 'd3-array';
|
||||
import {
|
||||
BasicColorFormatterType,
|
||||
CellRendererProps,
|
||||
InputColumn,
|
||||
} from '../types';
|
||||
import getCellClass from './getCellClass';
|
||||
import filterValueGetter from './filterValueGetter';
|
||||
import dateFilterComparator from './dateFilterComparator';
|
||||
import { getAggFunc } from './getAggFunc';
|
||||
import { TextCellRenderer } from '../renderers/TextCellRenderer';
|
||||
import { NumericCellRenderer } from '../renderers/NumericCellRenderer';
|
||||
import CustomHeader from '../AgGridTable/components/CustomHeader';
|
||||
import { valueFormatter, valueGetter } from './formatValue';
|
||||
import getCellStyle from './getCellStyle';
|
||||
|
||||
interface InputData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type UseColDefsProps = {
|
||||
columns: InputColumn[];
|
||||
data: InputData[];
|
||||
serverPagination: boolean;
|
||||
isRawRecords: boolean;
|
||||
defaultAlignPN: boolean;
|
||||
showCellBars: boolean;
|
||||
colorPositiveNegative: boolean;
|
||||
totals: DataRecord | undefined;
|
||||
columnColorFormatters: ColorFormatters;
|
||||
allowRearrangeColumns?: boolean;
|
||||
basicColorFormatters?: { [Key: string]: BasicColorFormatterType }[];
|
||||
isUsingTimeComparison?: boolean;
|
||||
emitCrossFilters?: boolean;
|
||||
alignPositiveNegative: boolean;
|
||||
slice_id: number;
|
||||
};
|
||||
|
||||
type ValueRange = [number, number];
|
||||
|
||||
function getValueRange(
|
||||
key: string,
|
||||
alignPositiveNegative: boolean,
|
||||
data: InputData[],
|
||||
) {
|
||||
if (typeof data?.[0]?.[key] === 'number') {
|
||||
const nums = data.map(row => row[key]) as number[];
|
||||
return (
|
||||
alignPositiveNegative ? [0, d3Max(nums.map(Math.abs))] : d3Extent(nums)
|
||||
) as ValueRange;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const getCellDataType = (col: InputColumn) => {
|
||||
switch (col.dataType) {
|
||||
case GenericDataType.Numeric:
|
||||
return 'number';
|
||||
case GenericDataType.Temporal:
|
||||
return 'date';
|
||||
case GenericDataType.Boolean:
|
||||
return 'boolean';
|
||||
default:
|
||||
return 'text';
|
||||
}
|
||||
};
|
||||
|
||||
const getFilterType = (col: InputColumn) => {
|
||||
switch (col.dataType) {
|
||||
case GenericDataType.Numeric:
|
||||
return 'agNumberColumnFilter';
|
||||
case GenericDataType.String:
|
||||
return 'agMultiColumnFilter';
|
||||
case GenericDataType.Temporal:
|
||||
return 'agDateColumnFilter';
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
function getHeaderLabel(col: InputColumn) {
|
||||
let headerLabel: string | undefined;
|
||||
|
||||
const hasOriginalLabel = !!col?.originalLabel;
|
||||
const isMain = col?.key?.includes('Main');
|
||||
const hasDisplayTypeIcon = col?.config?.displayTypeIcon !== false;
|
||||
const hasCustomColumnName = !!col?.config?.customColumnName;
|
||||
|
||||
if (hasOriginalLabel && hasCustomColumnName) {
|
||||
if ('displayTypeIcon' in col.config) {
|
||||
headerLabel =
|
||||
hasDisplayTypeIcon && !isMain
|
||||
? `${col.label} ${col.config.customColumnName}`
|
||||
: col.config.customColumnName;
|
||||
} else {
|
||||
headerLabel = col.config.customColumnName;
|
||||
}
|
||||
} else if (hasOriginalLabel && isMain) {
|
||||
headerLabel = col.originalLabel;
|
||||
} else if (hasOriginalLabel && !hasDisplayTypeIcon) {
|
||||
headerLabel = '';
|
||||
} else {
|
||||
headerLabel = col?.label;
|
||||
}
|
||||
return headerLabel || '';
|
||||
}
|
||||
|
||||
export const useColDefs = ({
|
||||
columns,
|
||||
data,
|
||||
serverPagination,
|
||||
isRawRecords,
|
||||
defaultAlignPN,
|
||||
showCellBars,
|
||||
colorPositiveNegative,
|
||||
totals,
|
||||
columnColorFormatters,
|
||||
allowRearrangeColumns,
|
||||
basicColorFormatters,
|
||||
isUsingTimeComparison,
|
||||
emitCrossFilters,
|
||||
alignPositiveNegative,
|
||||
slice_id,
|
||||
}: UseColDefsProps) => {
|
||||
const getCommonColProps = useCallback(
|
||||
(
|
||||
col: InputColumn,
|
||||
): ColDef & {
|
||||
isMain: boolean;
|
||||
} => {
|
||||
const {
|
||||
config,
|
||||
isMetric,
|
||||
isPercentMetric,
|
||||
isNumeric,
|
||||
key: originalKey,
|
||||
dataType,
|
||||
originalLabel,
|
||||
} = col;
|
||||
|
||||
const alignPN =
|
||||
config.alignPositiveNegative === undefined
|
||||
? defaultAlignPN
|
||||
: config.alignPositiveNegative;
|
||||
|
||||
const hasColumnColorFormatters =
|
||||
isNumeric &&
|
||||
Array.isArray(columnColorFormatters) &&
|
||||
columnColorFormatters.length > 0;
|
||||
|
||||
const hasBasicColorFormatters =
|
||||
isUsingTimeComparison &&
|
||||
Array.isArray(basicColorFormatters) &&
|
||||
basicColorFormatters.length > 0;
|
||||
|
||||
const isMain = originalKey?.includes('Main');
|
||||
const colId = isMain
|
||||
? originalKey.replace('Main', '').trim()
|
||||
: originalKey;
|
||||
const isTextColumn =
|
||||
dataType === GenericDataType.String ||
|
||||
dataType === GenericDataType.Temporal;
|
||||
|
||||
const valueRange =
|
||||
!hasBasicColorFormatters &&
|
||||
!hasColumnColorFormatters &&
|
||||
showCellBars &&
|
||||
(config.showCellBars ?? true) &&
|
||||
(isMetric || isRawRecords || isPercentMetric) &&
|
||||
getValueRange(originalKey, alignPN || alignPositiveNegative, data);
|
||||
|
||||
const filter = getFilterType(col);
|
||||
|
||||
return {
|
||||
field: colId,
|
||||
headerName: getHeaderLabel(col),
|
||||
valueFormatter: p => valueFormatter(p, col),
|
||||
valueGetter: p => valueGetter(p, col),
|
||||
cellStyle: p =>
|
||||
getCellStyle({
|
||||
...p,
|
||||
hasColumnColorFormatters,
|
||||
columnColorFormatters,
|
||||
hasBasicColorFormatters,
|
||||
basicColorFormatters,
|
||||
col,
|
||||
}),
|
||||
cellClass: p =>
|
||||
getCellClass({
|
||||
...p,
|
||||
col,
|
||||
emitCrossFilters,
|
||||
}),
|
||||
minWidth: config?.columnWidth ?? 100,
|
||||
filter,
|
||||
...(isPercentMetric && {
|
||||
filterValueGetter,
|
||||
}),
|
||||
...(dataType === GenericDataType.Temporal && {
|
||||
filterParams: {
|
||||
comparator: dateFilterComparator,
|
||||
},
|
||||
}),
|
||||
cellDataType: getCellDataType(col),
|
||||
defaultAggFunc: getAggFunc(col),
|
||||
initialAggFunc: getAggFunc(col),
|
||||
...(!(isMetric || isPercentMetric) && {
|
||||
allowedAggFuncs: [
|
||||
'sum',
|
||||
'min',
|
||||
'max',
|
||||
'count',
|
||||
'avg',
|
||||
'first',
|
||||
'last',
|
||||
],
|
||||
}),
|
||||
cellRenderer: (p: CellRendererProps) =>
|
||||
isTextColumn ? TextCellRenderer(p) : NumericCellRenderer(p),
|
||||
cellRendererParams: {
|
||||
allowRenderHtml: true,
|
||||
columns,
|
||||
hasBasicColorFormatters,
|
||||
col,
|
||||
basicColorFormatters,
|
||||
valueRange,
|
||||
alignPositiveNegative: alignPN || alignPositiveNegative,
|
||||
colorPositiveNegative,
|
||||
},
|
||||
context: {
|
||||
isMetric,
|
||||
isPercentMetric,
|
||||
isNumeric,
|
||||
},
|
||||
lockPinned: !allowRearrangeColumns,
|
||||
sortable: !serverPagination || !isPercentMetric,
|
||||
...(serverPagination && {
|
||||
headerComponent: CustomHeader,
|
||||
comparator: () => 0,
|
||||
headerComponentParams: {
|
||||
slice_id,
|
||||
},
|
||||
}),
|
||||
isMain,
|
||||
...(!isMain &&
|
||||
originalLabel && {
|
||||
columnGroupShow: 'open',
|
||||
}),
|
||||
...(originalLabel && {
|
||||
timeComparisonKey: originalLabel,
|
||||
}),
|
||||
wrapText: !config?.truncateLongCells,
|
||||
autoHeight: !config?.truncateLongCells,
|
||||
};
|
||||
},
|
||||
[
|
||||
columns,
|
||||
data,
|
||||
defaultAlignPN,
|
||||
columnColorFormatters,
|
||||
basicColorFormatters,
|
||||
showCellBars,
|
||||
colorPositiveNegative,
|
||||
isUsingTimeComparison,
|
||||
isRawRecords,
|
||||
emitCrossFilters,
|
||||
allowRearrangeColumns,
|
||||
serverPagination,
|
||||
alignPositiveNegative,
|
||||
],
|
||||
);
|
||||
|
||||
const stringifiedCols = JSON.stringify(columns);
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const groupIndexMap = new Map<string, number>();
|
||||
|
||||
return columns.reduce<ColDef[]>((acc, col) => {
|
||||
const colDef = getCommonColProps(col);
|
||||
|
||||
if (col?.originalLabel) {
|
||||
if (groupIndexMap.has(col.originalLabel)) {
|
||||
const groupIdx = groupIndexMap.get(col.originalLabel)!;
|
||||
(acc[groupIdx] as { children: ColDef[] }).children.push(colDef);
|
||||
} else {
|
||||
const group = {
|
||||
headerName: col.originalLabel,
|
||||
marryChildren: true,
|
||||
openByDefault: true,
|
||||
children: [colDef],
|
||||
};
|
||||
groupIndexMap.set(col.originalLabel, acc.length);
|
||||
acc.push(group);
|
||||
}
|
||||
} else {
|
||||
acc.push(colDef);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}, [stringifiedCols, getCommonColProps]);
|
||||
|
||||
return colDefs;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 { useTheme } from '@superset-ui/core';
|
||||
import {
|
||||
colorSchemeDark,
|
||||
colorSchemeLight,
|
||||
themeQuartz,
|
||||
} from 'ag-grid-community';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
export const useIsDark = () => {
|
||||
const theme = useTheme();
|
||||
return tinycolor(theme.colorBgContainer).isDark();
|
||||
};
|
||||
|
||||
const useTableTheme = () => {
|
||||
const baseTheme = themeQuartz;
|
||||
const isDarkTheme = useIsDark();
|
||||
const tableTheme = isDarkTheme
|
||||
? baseTheme.withPart(colorSchemeDark)
|
||||
: baseTheme.withPart(colorSchemeLight);
|
||||
return tableTheme;
|
||||
};
|
||||
|
||||
export default useTableTheme;
|
||||
Reference in New Issue
Block a user