@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
180 lines (157 loc) • 5.73 kB
text/typescript
// (C) 2007-2019 GoodData Corporation
import { AFM, Execution } from "@gooddata/typings";
import { IMappingHeader } from "../../../interfaces/MappingHeader";
import { getIdsFromUri } from "./agGridUtils";
import {
FIELD_SEPARATOR,
FIELD_TYPE_ATTRIBUTE,
FIELD_TYPE_MEASURE,
ID_SEPARATOR,
ROW_TOTAL,
ROW_SUBTOTAL,
} from "./agGridConst";
import { IGridHeader, IGridRow, IGridTotalsRow } from "./agGridTypes";
import invariant = require("invariant");
import { IntlShape } from "react-intl";
import zipObject = require("lodash/zipObject");
/*
* All code related to transforming data from our backend to ag-grid structures
*/
const getSubtotalLabelCellIndex = (
resultHeaderItems: Execution.IResultHeaderItem[][],
rowIndex: number,
): number => {
return resultHeaderItems.findIndex(headerItem => Execution.isTotalHeaderItem(headerItem[rowIndex]));
};
const getCell = (
rowHeaderData: Execution.IResultHeaderItem[][],
rowIndex: number,
rowHeader: IGridHeader,
rowHeaderIndex: number,
intl: IntlShape,
): {
field: string;
value: string;
rowHeaderDataItem: Execution.IResultHeaderItem;
isSubtotal: boolean;
} => {
const rowHeaderDataItem = rowHeaderData[rowHeaderIndex][rowIndex];
const cell = {
field: rowHeader.field,
rowHeaderDataItem,
isSubtotal: false,
};
if (Execution.isAttributeHeaderItem(rowHeaderDataItem)) {
return {
...cell,
value: rowHeaderDataItem.attributeHeaderItem.name,
};
}
if (Execution.isTotalHeaderItem(rowHeaderDataItem)) {
const totalName = rowHeaderDataItem.totalHeaderItem.name;
return {
...cell,
isSubtotal: true,
value:
getSubtotalLabelCellIndex(rowHeaderData, rowIndex) === rowHeaderIndex
? intl.formatMessage({ id: `visualizations.totals.dropdown.title.${totalName}` })
: null,
};
}
invariant(
rowHeaderDataItem,
"row header is not of type IResultAttributeHeaderItem or IResultTotalHeaderItem",
);
};
export const getRow = (
cellData: Execution.DataValue[],
rowIndex: number,
columnFields: string[],
rowHeaders: IGridHeader[],
rowHeaderData: Execution.IResultHeaderItem[][],
subtotalStyles: string[],
intl: IntlShape,
): IGridRow => {
const row: IGridRow = {
headerItemMap: {},
};
rowHeaders.forEach((rowHeader, rowHeaderIndex) => {
const { isSubtotal, field, value, rowHeaderDataItem } = getCell(
rowHeaderData,
rowIndex,
rowHeader,
rowHeaderIndex,
intl,
);
if (isSubtotal) {
row.type = ROW_SUBTOTAL;
if (!row.subtotalStyle) {
row.subtotalStyle = subtotalStyles[rowHeaderIndex];
}
}
row[field] = value;
row.headerItemMap[field] = rowHeaderDataItem as IMappingHeader;
});
cellData.forEach((cell: Execution.DataValue, cellIndex: number) => {
const field = columnFields[cellIndex];
if (field) {
row[field] = cell;
}
});
return row;
};
export const getRowTotals = (
totals: Execution.DataValue[][][],
columnKeys: string[],
headers: Execution.IHeader[],
resultSpec: AFM.IResultSpec,
measureIds: string[],
intl: IntlShape,
): IGridTotalsRow[] => {
if (!totals) {
return null;
}
return totals[0].map((totalRow: string[], totalIndex: number) => {
const attributeKeys: string[] = [];
const measureKeys: string[] = [];
// assort keys by type
columnKeys.forEach((key: any) => {
const currentKey = key.split(FIELD_SEPARATOR).pop();
const fieldType = currentKey.split(ID_SEPARATOR)[0];
if (fieldType === FIELD_TYPE_ATTRIBUTE) {
attributeKeys.push(currentKey);
}
if (fieldType === FIELD_TYPE_MEASURE) {
measureKeys.push(key);
}
});
const [totalAttributeKey] = attributeKeys;
const totalAttributeId: string = totalAttributeKey.split(ID_SEPARATOR).pop();
const totalHeader: Execution.IAttributeHeader = headers.find(
(header: Execution.IHeader) =>
Execution.isAttributeHeader(header) &&
getIdsFromUri(header.attributeHeader.uri)[0] === totalAttributeId,
) as Execution.IAttributeHeader;
invariant(totalHeader, `Could not find header for ${totalAttributeKey}`);
const measureCells = zipObject(measureKeys, totalRow);
const grandTotalName = totalHeader.attributeHeader.totalItems[totalIndex].totalHeaderItem.name;
const grandTotalAttributeIdentifier = totalHeader.attributeHeader.localIdentifier;
// create measure ids in the form of "m_index" for measures having the current type of grand total
// this makes it easier to match against in the cell renderer
const rowTotalActiveMeasures = resultSpec.dimensions[0].totals
.filter(t => t.type === grandTotalName && t.attributeIdentifier === grandTotalAttributeIdentifier)
.map(t => `m_${measureIds.indexOf(t.measureIdentifier)}`);
return {
colSpan: {
count: attributeKeys.length,
headerKey: totalAttributeKey,
},
...measureCells,
[totalAttributeKey]: intl.formatMessage({
id: `visualizations.totals.dropdown.title.${grandTotalName}`,
}),
rowTotalActiveMeasures,
type: ROW_TOTAL,
};
});
};