import * as React from 'react';
import { getTransformsDataPipeline } from 'mmviz';
import {
COMPONENT_MODE_INITIAL,
COMPONENT_MODE_EMPTY,
COMPONENT_MODE_ERROR,
COMPONENT_MODE_LOADING,
COMPONENT_MODE_OK,
ComponentModeWindow,
MmuiProps,
MmuiState,
MmuiTableComponent,
} from '../common';
import { QueryData } from '../../mmui-data';
import { formatValue } from '../../mmui-util';
import { SummaryColumnComponent } from './refiner/SummaryColumnComponent';
import { ColumnHeadComponent } from './refiner/ColumnHeadComponent';
const ROW_MODE_CLOSED = 'closed';
const ROW_MODE_OPEN = 'open';
const ROW_MODE_LOADING = 'loading';
export interface MmuiTableProps extends MmuiProps {
hasColumnAction?: boolean;
hasColumnSelection?: boolean;
hasLevelColumns?: boolean; //whether levels are expanded to dedicated columns
transforms?: any;
colOrderIgnoreCase?: boolean; //whether column sort ignores case
colOrderEmptyLocation?: string; //sort location of empty values (bottom, always_top, always_bottom)
reportIssueUrl?: string; // url link to report issue when something goes wrong
hasPipeline?: boolean;
transformsDelay?: number;
tableClassNameList?: [];
tableContainerClassNameList?: [];
displayDefaultLabel?: boolean;
}
export interface MmuiTableState extends MmuiState {
// contains transforms objects that are supported by mmviz.getTransformsDataPipeline function
transforms?: any;
rows?: any;
hasPipeline?: boolean;
}
/**
* Crafted Table Component for rendering different types of tables (regular, nested, pivoted)
* The data payload is passed in the props. Either this.props.payload or this.props.store.data (store)
* Create a base class that inherits from MmuiCraftedTableComponent
* to override properties like this.name or this.tableClassNameList
* as well as override methods such as this.getColumnSummaryRender and this.getColumnSummaryRender
*/
export class MmuiCraftedTableComponent<
P extends MmuiTableProps,
S extends MmuiTableState
> extends MmuiTableComponent
{
// css classes to apply to the rendered table
tableClassNameList;
tableContainerClassNameList;
numberOfItems;
defaultColumnConfig: any;
constructor(props: MmuiTableProps) {
super(props);
// css classes to apply to the rendered table
this.tableClassNameList = [
'table',
'table-sm',
'table-hover',
'mmui-table',
'mmui-table-sticky',
'mmui-table-sortable',
];
this.tableContainerClassNameList = [
'table-responsive',
'table-responsive-short',
'mmui-table-sticky-wrapper',
'mmui-hmi-30',
];
if (props.tableClassNameList) {
for (const tableClassName of props.tableClassNameList) {
this.tableClassNameList.push(tableClassName);
}
}
if (props.tableContainerClassNameList) {
this.tableContainerClassNameList = props.tableContainerClassNameList;
}
this.defaultColumnConfig = {
orderEmptyLocation: props.colOrderEmptyLocation,
orderIgnoreCase: props.colOrderIgnoreCase === false ? false : true,
};
}
/**
* @return this component's initial state.
*/
getInitialComponentState(): any {
const state: any = {
// contains transforms objects that are supported by mmviz.getTransformsDataPipeline function
transforms: {},
rows: {},
isVisible: true,
hasPipeline: true,
};
if (this.props.payload) {
state.payload = this.props.payload;
}
if (this.props.transforms) {
state.transforms = this.props.transforms;
}
if (this.props.hasPipeline !== undefined) {
state.hasPipeline = this.props.hasPipeline;
}
return state;
}
/**
* Get the dataModel from the payload.
* The dataModel should match the structure returned by the following mmvizutil functions:
* mmvizutil.data.shape.shape_data
* mmvizutil.data.shape.shape_data_model
* mmvizutil.data.shape.shape_data_model_levels
* mmvizutil.data.shape.shape_data_model_pivot
*/
getDataModel() {
let dataModel,
payload = this.getPayloadData();
if (payload) {
dataModel = payload.data;
}
return dataModel;
}
/**
* Extract the dataArray portion of the payload received from the server.
* @param payload
*/
extractDataArray(payload) {
let dataModel,
dataArray = [];
if (payload) {
dataModel = payload.data;
if (dataModel && dataModel.rows) {
dataArray = dataModel.rows;
}
}
return dataArray;
}
/**
* Get the query data object that details how the data was queried
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getQueryDataObj(fromStore = false) {
let state = this.getComponentState(),
payload = this.getPayloadData(),
queryDataObj = {
dim: [],
dim_level: [],
dim_pivot: [],
};
if (state.queryDataObj) {
queryDataObj = state.queryDataObj;
} else if (payload && payload.data && payload.data.query) {
queryDataObj = payload.data.query;
}
return queryDataObj;
}
/**
* Get a copy of the rows states in the Component state.
*/
getStateRowsCopy(): any {
const newRows: any = {},
state = this.getComponentState();
for (const rowId in state.rows) {
newRows[rowId] = Object.assign({}, state.rows[rowId]);
}
return newRows;
}
/**
* Create a default row state
*/
createStateRowDefault(): any {
return {
mode: ROW_MODE_CLOSED,
isSelected: false,
isVisibleAction: false,
};
}
/**
* Get configuration details for the provided column
* such as name, data type, if sortable, if metric, if distinct.
* Override this method in a child class to customize configuration of each column (isSortable, isMetric, hasFilter)
* @param column
* @param config
*/
getColumnConfig(field, config: any = {}) {
let columnConfig,
defaultConfig = {
name: field.toUpperCase(),
columnName: field,
dataType: 'categorical',
isSortable: true,
isDistinct: false,
isNumeric: false,
hasFilter: true,
hasLevels: false,
},
dataModel = this.getDataModel(),
columnData;
// check if column is in data or if column is datum attribute prefix
if (Object.prototype.hasOwnProperty.call(dataModel.columns, field)) {
columnData = dataModel.columns[field];
} else {
const columnName = field + '_name';
if (
Object.prototype.hasOwnProperty.call(
dataModel.columns,
columnName
)
) {
defaultConfig.columnName = columnName;
columnData = dataModel.columns[columnName];
}
}
columnConfig = Object.assign(defaultConfig, config);
if (columnData) {
columnConfig.dataType = columnData.dataType;
columnConfig.isNumeric = columnData.dataType == 'numerical';
columnConfig.isDistinct = columnData.is_distinct;
columnConfig.isSortable = !columnConfig.isDistinct;
if (dataModel.columns.hasSummary) {
columnConfig.hasSummary = true;
}
columnConfig.hasFilter = !(
columnData.extent?.length == 1 && columnData.extent[0] == ''
);
}
switch (field) {
case 'level':
columnConfig.name = 'Level';
columnConfig.columnName = 'level_name';
columnConfig.hasLevels = true;
break;
case 'pivot':
columnConfig.dataType = 'numerical';
columnConfig.isNumeric = true;
columnConfig.columnName = columnConfig.rowKey;
break;
default:
break;
}
return columnConfig;
}
/**
* Handles column row action. To be overriden by inheriting table component class.
* @param e
* @param data
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onColumnAction(e, data) {
e.preventDefault();
}
/**
*
* @param data
* @param newState
*/
updateRowState(row_id, newState) {
let rowState: any = {},
newRows = this.getStateRowsCopy(),
currentRowState = newRows[row_id];
if (currentRowState) {
rowState = Object.assign(currentRowState, newState);
} else {
rowState = Object.assign(this.createStateRowDefault(), newState);
}
newRows[row_id] = rowState;
this.setComponentStateValue('rows', newRows);
}
onColumnActionOver(e, data) {
this.updateRowState(data.row_id, { isVisibleAction: true });
}
onColumnActionOut(e, data) {
this.updateRowState(data.row_id, { isVisibleAction: false });
}
getColumnActionRender(data) {
let onColumnActionClick = (e) => {
this.onColumnAction(e, data);
},
onColumnActionMouseOver = (e) => {
this.onColumnActionOver(e, data);
},
onColumnActionMouseOut = (e) => {
this.onColumnActionOut(e, data);
},
state = this.getComponentState(),
rowState = state.rows[data.row_id],
classArray = [],
actionRender;
if (rowState && rowState.isVisibleAction) {
classArray.push('visible');
} else {
classArray.push('invisible');
}
actionRender = (
Filter
);
return (
{actionRender}
);
}
/**
* Get the header summary render of the provided data of the provided columnName.
* @param columnName
* @return (
{value}
)
*/
getColumnSummaryRender(field, index) {
let valueFormatted,
dataModel = this.getDataModel(),
columnData = dataModel.columns[field],
isMetric = false,
classNameArray = [],
summaryValue,
summaryValueRender = ;
if (columnData) {
isMetric = columnData.dataType == 'numerical';
summaryValue = columnData.summaryValue;
if (summaryValue) {
if (isMetric) {
valueFormatted = summaryValue.toLocaleString('en-US');
summaryValueRender = valueFormatted;
} else {
summaryValueRender = summaryValue;
}
}
}
const key = `th-summary-${index}`;
return (
{summaryValueRender}
);
}
/**
* Request row handler to request the children of provided rowId.
* Called in onToggleLevel, override and implement in inheriting class.
* @param rowId
* @param onRowRequestCallback
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
requestRowData(rowId, onRowRequestCallback) {}
/**
* Called when the a nexted level's toggle button is clicked to toggle the visibility of the level's child rows.
* @param e
* @param data
*/
onToggleLevel = (e, data) => {
e.preventDefault();
e.stopPropagation();
const newRowState: any = {},
state = this.getComponentState(),
currentRowState = state.rows[data.row_id];
if (data.children != null || data.child_data != null) {
if (
currentRowState === undefined ||
currentRowState.mode === ROW_MODE_CLOSED
) {
newRowState.mode = ROW_MODE_OPEN;
} else {
newRowState.mode = ROW_MODE_CLOSED;
}
} else {
newRowState.mode = ROW_MODE_LOADING;
}
this.updateRowState(data.row_id, newRowState);
if (newRowState.mode == ROW_MODE_LOADING) {
this.requestRowData(data.row_id, (rowId) => {
let componentState = this.getComponentState(),
currentRowState = componentState.rows[rowId];
if (currentRowState === undefined) {
currentRowState = this.createStateRowDefault();
}
currentRowState.mode = 'open';
this.updateRowState(rowId, currentRowState);
});
}
};
getLevelColumnRender(
columnData,
data,
levelDisplay,
currentRowState,
classNameArray,
config: any = {}
) {
let columnRender,
levelBtn,
levelIcon,
columnSelectionRender = null,
tLevel = (e) => {
this.onToggleLevel(e, data);
};
const hasChildrenProperty = Object.prototype.hasOwnProperty.call(
data,
'children'
),
hasChildDataProperty = Object.prototype.hasOwnProperty.call(
data,
'child_data'
);
if (hasChildrenProperty || hasChildDataProperty) {
const hasChildren =
hasChildrenProperty &&
(data.children == null || data.children.length > 0),
hasChildData =
hasChildDataProperty &&
(data.child_data == null || data.child_data);
if (hasChildren || hasChildData) {
levelIcon = ;
if (currentRowState) {
if (currentRowState.mode === ROW_MODE_OPEN) {
levelIcon = ;
} else if (currentRowState.mode === ROW_MODE_LOADING) {
levelIcon = ;
}
}
levelBtn = (
);
}
return columnRender;
}
getColumnRenderValue(field, data, isMetric) {
let valueRender = data[field];
if (valueRender === undefined) {
valueRender = data[field + '_name'];
}
if (isMetric) {
valueRender = formatValue(valueRender);
}
return valueRender;
}
/**
* Get the render of the provided data of the provided columnName.
* Override this in a child class to customize the render of each table data element
* @param columnName
* @param data
* @param config
* @return (
);
}
break;
}
return columnRender;
}
/**
* Save the transforms into the state
* @param transforms
*/
setTransforms = (transforms) => {
transforms.updatedAt = new Date();
this.setComponentStateValue('transforms', transforms);
};
/**
* Update the selection state of the Table header based on the number of selected rows.
*/
getRowSelectionCount() {
let row,
selectionCount = 0,
state = this.getComponentState();
for (const rowId in state.rows) {
if (
rowId !== 'head' &&
Object.prototype.hasOwnProperty.call(state.rows, rowId)
) {
row = state.rows[rowId];
if (row.isSelected) {
selectionCount++;
}
}
}
return selectionCount;
}
onRowSelectionClick = (e) => {
e.stopPropagation();
};
/**
* Handles row selection state changes.
* @param e
*/
onRowSelectionChange = (e) => {
const input = e.target as HTMLInputElement,
rowId = input.value;
let newRowState,
state = this.getComponentState(),
rowState = state.rows[rowId];
if (rowState === undefined) {
newRowState = this.createStateRowDefault();
newRowState.isSelected = true;
} else {
newRowState = Object.assign({}, rowState);
newRowState.isSelected = !newRowState.isSelected;
}
this.updateRowState(rowId, newRowState);
const dataModel = this.getDataModel(),
newRowsState = this.getStateRowsCopy();
const selectedRow = this.findSelectedRow(dataModel.rows, rowId);
newRowsState[selectedRow.row_id] = newRowState;
if (selectedRow.children) {
this.updateSelectionEachRecursive(
selectedRow.children,
newRowState.isSelected,
newRowsState
);
}
this.updateParentSelections(dataModel.rows, newRowsState);
this.numberOfItems = this.countRows(dataModel.rows);
this.setComponentStateValue('rows', newRowsState);
};
/**
* Counts the number of rows
* @param rows
*/
countRows(rows) {
let rowCount = 0;
for (const row of rows) {
rowCount++;
if (row.children) {
rowCount = rowCount + this.countRows(row.children);
}
}
return rowCount;
}
/**
* This function loops through the rows and updates the selection states based on selection state of children.
* @param rows
* @param newRowsState
*/
updateParentSelections(rows, newRowsState) {
let rowState, childRowState;
for (const r of rows) {
if (r.children) {
this.updateParentSelections(r.children, newRowsState);
let isAllSelected = true,
isSomeSelected = false;
for (const c of r.children) {
childRowState = newRowsState[c.row_id];
if (childRowState) {
isAllSelected =
isAllSelected && childRowState.isSelected;
isSomeSelected =
isSomeSelected ||
childRowState.isSelected ||
childRowState.isIndeterminate;
} else {
isAllSelected = false;
break;
}
}
rowState = newRowsState[r.row_id];
if (rowState === undefined) {
rowState = this.createStateRowDefault();
}
rowState.isSelected = isAllSelected;
rowState.isIndeterminate =
!isAllSelected && isSomeSelected ? true : false;
newRowsState[r.row_id] = rowState;
}
}
}
/**
* Finds a distinct row given a set of rows and a rowId
* @param rows
* @param rowId
*/
findSelectedRow(rows, rowId) {
let selectedRow;
for (const row of rows) {
if (row.row_id == rowId) {
selectedRow = row;
break;
}
if (row.children) {
selectedRow = this.findSelectedRow(row.children, rowId);
if (selectedRow) {
return selectedRow;
}
}
}
return selectedRow;
}
/**
* Set the selection state of all rows.
* @param isSelected
*/
setRowsSelection(isSelected: boolean) {
const dataModel = this.getDataModel(),
newRowState = this.getStateRowsCopy();
this.numberOfItems = this.countRows(dataModel.rows);
this.updateSelectionEachRecursive(
dataModel.rows,
isSelected,
newRowState
);
this.setComponentStateValue('rows', newRowState);
}
/**
* Updates the row state of every row and counts the number of selected items
* @param rows
* @param isSelected
* @param newRows
*/
updateSelectionEachRecursive(rows, isSelected, newRowsState) {
let rowState;
if (rows == undefined || rows.length < 1) {
return;
}
for (const row of rows) {
rowState = newRowsState[row.row_id];
if (rowState === undefined) {
rowState = this.createStateRowDefault();
}
rowState.isSelected = isSelected;
newRowsState[row.row_id] = rowState;
if (row.children) {
this.updateSelectionEachRecursive(
row.children,
isSelected,
newRowsState
);
}
}
}
/**
*
* @param e
*/
onSelectionClick = (e) => {
this.setRowsSelection(e.target.checked);
};
/**
* Get Component mode based on data payload
*/
getComponentMode(payload) {
let mode = COMPONENT_MODE_INITIAL,
dataArray,
hasData = false;
if (payload) {
dataArray = this.extractDataArray(payload);
hasData = dataArray.length > 0;
if (payload.hasError) {
mode = COMPONENT_MODE_ERROR;
} else if (payload.isInvalidated) {
mode = COMPONENT_MODE_LOADING;
} else if (hasData) {
mode = COMPONENT_MODE_OK;
} else {
mode = COMPONENT_MODE_EMPTY;
}
}
return mode;
}
/**
* On update to Component props or state
* @param prevProps
* @param prevState
*/
componentDidUpdate() {
const state = this.getComponentState();
if (state.operation === 'selection_clear') {
this.setRowsSelection(false);
this.setOperation('');
}
}
/**
* Parse out the table header renders of pivot table into tHeadRows.
* @param tHeadRows - renders of the table header rows
* @param columnsData - data for all the columns
* @param columnName - name of the column
* @param rowStart
* @param transforms - transforms applied on the data
*/
parsePivotTableHeaders(
tHeadRows,
columnsArray,
columnName,
rowStart,
transforms
) {
/**
* Parse pivot table header render into tHeadRows.
* @param columnsData - data for all the columns
* @param dataKey - key in the data that the column corresponds with
* @param rowDepth - current depth, into the header, of the row
*/
const parseTableHeader = (cArray, dataKey, rowDepth = 0) => {
let totalColSpan = 0;
if (cArray && cArray.length > 0) {
let tColumns = [];
if (rowDepth >= tHeadRows.length) {
tHeadRows.push(tColumns);
} else {
tColumns = tHeadRows[rowDepth];
}
for (let i = 0; i < cArray.length; i++) {
let colSpan = 0,
cData = cArray[i],
dKey = `${dataKey}_${cData.key}`;
// Recursively apply function to children.
if (cData.children) {
colSpan = parseTableHeader(
cData.children,
dKey,
rowDepth + 1
);
}
colSpan = colSpan === 0 ? 1 : colSpan;
totalColSpan = totalColSpan + colSpan;
const key = `th-${rowDepth}-${tColumns.length}`,
columnConfig = this.getColumnConfig('pivot', {
rowKey: cData.row_key,
id: cData.id,
name: cData.name,
columnName: dKey,
colSpan: colSpan,
});
if (cData.children) {
tColumns.push(
{cData.name}
);
} else {
const completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
tColumns.push(
);
}
}
tHeadRows[rowDepth] = tColumns;
}
return totalColSpan;
};
return parseTableHeader(columnsArray, columnName, rowStart);
}
/**
* Row Click event handler class method. Called by this.onRowClick. Can be overridden child class.
* @param evt
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
rowClick(evt) {}
/**
* Row Click event handler.
* @param evt
*/
onRowClick = (evt) => {
this.rowClick(evt);
};
/**
* Parse out the row renders of table with dimension levels into tBodyRows.
* @param queryData - details of the data query (dimension, levels, pivots, facts, filters)
* @param tBodyRows - renders of the table body rows
* @param factColumns - column names that are facts
* @param rData - current row data
* @param lDepth - current level depth
*/
parseTableLevelsRows(queryData, tBodyRows, factColumns, rData) {
/**
* Compose dimension level row render with fact row render into tBodyRows.
* @param rowData - renders of the table body rows
* @param levelDepth - current level depth
*/
const dimensionLevel = (rowData, levelDepth = 0) => {
if (levelDepth > queryData.dimension_levels.length) {
return;
}
let tBodyColumns = [],
fact,
dimension,
rowRender,
rowId = `${rowData.row_id}`;
if (this.props.hasLevelColumns) {
// let dimension_level = queryData.dimension_levels[levelDepth];
rowRender = this.getColumnRender('level', rowData, {
key: `td-${tBodyRows.length}-${tBodyColumns.length}`,
style: { paddingLeft: '0px' },
});
for (let i = 0; i < levelDepth; i++) {
tBodyColumns.push(
);
}
} else {
rowRender = this.getColumnRender('level', rowData, {
key: `td-${tBodyRows.length}-${tBodyColumns.length}`,
style: { paddingLeft: `${levelDepth * 30}px` },
});
}
tBodyColumns.push(rowRender);
// add dimension renders
if (queryData.dimensions) {
for (let i = 0; i < queryData.dimensions.length; i++) {
dimension = queryData.dimensions[i];
rowRender = this.getColumnRender(dimension, rowData, {
key: `td-${tBodyRows.length}-${tBodyColumns.length}`,
});
tBodyColumns.push(rowRender);
const dimId = rowData[`${dimension}_id`];
rowId = rowId + `_${dimId}`;
}
}
if (this.props.hasLevelColumns) {
for (
let i = levelDepth;
i < queryData.dimension_levels.length - 1;
i++
) {
tBodyColumns.push(
);
}
}
// add fact renders
for (let i = 0; i < factColumns.length; i++) {
fact = factColumns[i];
rowRender = this.getColumnRender(fact, rowData, {
key: `td-${tBodyRows.length}-${tBodyColumns.length}`,
});
tBodyColumns.push(rowRender);
}
if (this.props.hasColumnAction) {
tBodyColumns.push(this.getColumnActionRender(rowData));
}
tBodyRows.push(
{tBodyColumns}
);
// Recursively apply function to children.
const children = rowData['children'];
if (children) {
const state = this.getComponentState(),
rowState = state.rows[rowData.row_id];
// only render the children if the row is open
if (rowState && rowState.mode == ROW_MODE_OPEN) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
dimensionLevel(child, levelDepth + 1);
}
}
}
};
dimensionLevel(rData);
}
/**
* Parse out the row renders of a pivot table, with dimension levels, into tBodyRows.
* @param queryData - details of the data query (dimension, levels, pivots, facts, filters)
* @param dataModel - details of the data (columns, rows, pivots)
* @param tBodyRows - renders of the table body rows
* @param factColumns - column names that are facts
* @param rData - current row data
* @param lDepth - current level depth
*/
parseTablePivotLevelsRows(
queryData,
dataModel,
tBodyRows,
factColumns,
rData,
lDepth = 0
) {
/**
* Compose dimension level row render with pivot data row render into tBodyRows.
* @param rowData - current row data
* @param levelIndex - current level depth
*/
const dimensionLevel = (rowData, levelDepth = 0) => {
if (levelDepth > queryData.dimension_levels.length) {
return;
}
const tBodyColumns = [],
style = { paddingLeft: `${levelDepth * 30}px` },
indexColumn = this.getColumnRender('level', rowData, {
key: `td-${tBodyRows.length}-${tBodyColumns.length}`,
style,
});
for (let c = 0; c < dataModel.columns.length; c++) {
const factData = dataModel.columns[c],
columnName = factData.key;
for (const columnData of factData.children) {
this.parsePivotDataRow(
columnName,
columnData,
tBodyColumns,
rowData
);
}
}
tBodyRows.push(
{indexColumn}
{tBodyColumns}
);
// Recursively apply function to children.
if (rowData.children) {
const state = this.getComponentState(),
rowState = state.rows[rowData.row_id];
// only render the children if the row is open
if (rowState && rowState.mode == ROW_MODE_OPEN) {
let child;
for (let i = 0; i < rowData.children.length; i++) {
child = rowData.children[i];
dimensionLevel(child, levelDepth + 1);
}
}
}
};
dimensionLevel(rData, lDepth);
}
/**
* Parse out the row renders of a pivot table, for columnName, into dataColumnRenders.
* @param columnName - Name of the column
* @param columnData - Data associated with the column
* @param dataColumnRenders - renders of the column data
* @param data - data for the current row
*/
parsePivotDataRow(columnName, columnData, dataColumnRenders, data) {
/**
* Compose pivot data column render using columnData and dataKey based on columnName.
* @param cData
* @param dataKey
*/
const renderPivotDataColumn = (cData, dataKey) => {
const newDataKey = `${dataKey}_${cData.key}`;
// Recursively apply function to children.
if (cData.children) {
// apply renderPivotDataColumn to each child
for (let i = 0; i < cData.children.length; i++) {
const childData = cData.children[i];
renderPivotDataColumn(childData, newDataKey);
}
} else {
// Get data columnRender based on data key lookup into data.
const columnRender = this.getColumnRender('pivot', data, {
dataKey: newDataKey,
});
dataColumnRenders.push(
{columnRender}
);
}
};
renderPivotDataColumn(columnData, columnName);
}
getChildRender(childData) {
const childPayload = {
data: childData,
hasError: false,
isInvalidated: false,
updatedAt: new Date(),
};
return (
);
}
/**
* Render the Table Component
*/
render() {
let state = this.getComponentState(),
payload = this.getPayloadData(),
mode = this.getComponentMode(payload),
queryData,
isOkMode,
isEmptyMode,
transforms,
preSortColumn,
preSortMethod,
tHead,
tBody,
dataRows,
dataPipeline,
dataModel,
tBodyRows,
columnCount = 0;
if (state === undefined || state === null || !state.isVisible) {
return null;
}
isOkMode = mode === COMPONENT_MODE_OK;
isEmptyMode = mode === COMPONENT_MODE_EMPTY;
// isErrorMode = mode === COMPONENT_MODE_ERROR;
transforms = state.transforms;
if (isOkMode || isEmptyMode) {
dataModel = this.getDataModel();
if (state.hasPipeline) {
dataPipeline = getTransformsDataPipeline(transforms);
dataRows = dataPipeline.transform(dataModel.rows);
} else {
dataRows = dataModel.rows;
}
queryData = new QueryData(this.getQueryDataObj());
if (transforms === undefined || transforms.order === undefined) {
preSortColumn = dataModel.order_by_column;
preSortMethod = dataModel.order_by_method
? dataModel.order_by_method
: 'ASC';
}
const rowSelectionCount = this.getRowSelectionCount();
this.numberOfItems = this.countRows(dataModel.rows);
let isHeadSelected = false,
isHeadIndeterminate = false;
if (this.numberOfItems != 0 && rowSelectionCount != 0) {
isHeadSelected = rowSelectionCount == this.numberOfItems;
isHeadIndeterminate = !isHeadSelected && rowSelectionCount > 0;
}
/**
* Data is the result of rollup of columns with a pivot.
*/
if (queryData.hasPivot && queryData.hasDimensionLevels) {
const tHeadRows = [],
tHeadColumns = [],
factColumns = queryData.factColumns,
key = `th-level`,
columnConfig = this.getColumnConfig('level'),
completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
//determine if the dimension level is pre-sorted
let preSort, levelColumnHead;
if (preSortColumn === 'level') {
preSort = preSortMethod;
}
levelColumnHead = (
);
tHeadRows.push(tHeadColumns);
for (let c = 0; c < dataModel.columns.length; c++) {
const factData = dataModel.columns[c],
columnName = factData.key,
columnConfig = this.getColumnConfig(columnName);
let colSpan = this.parsePivotTableHeaders(
tHeadRows,
factData.children,
columnName,
1,
transforms
);
colSpan = colSpan === 0 ? 1 : colSpan;
tHeadColumns.push(
{columnConfig.name}
);
// add up column count for possible empty row
columnCount = columnCount + colSpan;
}
const tHeadRowRenders = tHeadRows.map(
(columnRenderArray, index) => {
let rowRender = null;
if (index === tHeadRows.length - 1) {
rowRender = levelColumnHead;
} else {
rowRender =
;
}
return (
{rowRender}
{columnRenderArray}
);
}
);
// add up column count for possible empty row, add one for levels column
columnCount = columnCount + 1;
tHead = {tHeadRowRenders};
tBodyRows = [];
for (let i = 0; i < dataModel.rows.length; i++) {
const rowData = dataModel.rows[i];
this.parseTablePivotLevelsRows(
queryData,
dataModel,
tBodyRows,
factColumns,
rowData
);
}
tBody = {tBodyRows};
} else if (queryData.hasDimensionLevels) {
/**
* Data is the result of rollup of columns.
*/
let tHeadColumns = [],
tSummaryColumns = [],
levelColumnHead,
levelColumnSummary,
columnData,
columnConfig,
fact,
dimension,
dimensionLevel,
factColumns = queryData.factColumns;
if (this.props.hasLevelColumns) {
levelColumnHead = [];
let isSelectionColumn = false;
for (
let i = 0;
i < queryData.dimension_levels.length;
i++
) {
dimensionLevel = queryData.dimension_levels[i];
columnConfig = this.getColumnConfig(dimensionLevel);
isSelectionColumn = false;
if (i == 0) {
columnConfig.hasLevels = true;
isSelectionColumn = this.props.hasColumnSelection;
}
//determine if the dimension level is pre-sorted
let preSort;
if (preSortColumn === dimensionLevel) {
preSort = preSortMethod;
}
if (dataModel.columns.hasSummary) {
columnConfig.hasSummary = true;
columnConfig.summaryValueRender =
this.getColumnSummaryRender(dimensionLevel, i);
}
const key = `th-level-${i}`;
const completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
levelColumnHead.push(
);
if (dataModel.columns.hasSummary) {
tSummaryColumns.push(
);
}
}
} else {
const columnName = 'level';
columnConfig = this.getColumnConfig(columnName);
if (dataModel.columns.hasSummary) {
columnConfig.hasSummary = true;
columnConfig.summaryValueRender =
this.getColumnSummaryRender(columnName, 0);
}
const key = `th-level`;
const completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
levelColumnHead = (
);
if (dataModel.columns.hasSummary) {
levelColumnSummary = (
);
}
}
if (queryData.dimensions) {
for (let i = 0; i < queryData.dimensions.length; i++) {
dimension = queryData.dimensions[i];
columnConfig = this.getColumnConfig(dimension);
if (columnConfig.hasSummary) {
columnConfig.summaryValueRender =
this.getColumnSummaryRender(dimension, i);
}
const completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
const key = `th-dim-${i}`;
tHeadColumns.push(
);
if (columnConfig.hasSummary) {
tSummaryColumns.push(
);
}
}
}
for (let i = 0; i < factColumns.length; i++) {
const key = `th-fact-${i}`;
fact = factColumns[i];
columnConfig = this.getColumnConfig(fact);
//determine if the dimension level is pre-sorted
let preSort;
if (preSortColumn === fact) {
preSort = preSortMethod;
}
if (dataModel.columns.hasSummary) {
columnConfig.hasSummary = true;
columnConfig.summaryValueRender =
this.getColumnSummaryRender(fact, i);
}
const completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
columnData = dataModel.columns[fact];
tHeadColumns.push(
);
if (dataModel.columns.hasSummary) {
tSummaryColumns.push(
);
}
}
if (this.props.hasColumnAction) {
tHeadColumns.push(
);
}
// set column count for possible empty row
if (this.props.hasLevelColumns) {
columnCount =
queryData.dimension_levels.length + factColumns.length;
} else {
columnCount = queryData.columns.length;
}
tHead = (
{levelColumnHead}
{tHeadColumns}
{levelColumnSummary}
{tSummaryColumns}
);
tBodyRows = [];
for (let i = 0; i < dataRows.length; i++) {
const rowData = dataRows[i];
this.parseTableLevelsRows(
queryData,
tBodyRows,
factColumns,
rowData
);
}
tBody = {tBodyRows};
} else if (queryData.hasPivot) {
/**
* Data is the result of a pivot.
*/
const tHeadRows = [],
tHeadColumns = [];
tHeadRows.push(tHeadColumns);
const pivotRowCount = queryData.dimensions.length,
pivotRowRender = queryData.dimensions.map(
(columnName, index) => {
const key = `th-${index}`,
columnConfig = this.getColumnConfig(columnName),
completeColumnConfig = Object.assign(
columnConfig,
this.defaultColumnConfig
);
//determine if columnName is pre-sorted
let preSort;
if (preSortColumn === columnName) {
preSort = preSortMethod;
}
return (
);
}
);
// add up column count for possible empty row
columnCount = columnCount + pivotRowCount;
const pivotRowRenderEmpty =
{childRenderRow}
);
});
tBody = {tBodyRows};
}
// create an empty row if data has been refined to no results
if (dataRows.length <= 0) {
tBodyRows = this.createEmptyRow(columnCount);
tBody = {tBodyRows};
}
}
return (