// (C) 2007-2019 GoodData Corporation import { uniq, get, isEmpty, includes, flattenDepth } from "lodash"; import { ExecuteAFM as AFM, Execution } from "@gooddata/typings"; import * as HttpStatusCodes from "http-status-codes"; import { IErrorResponse } from "../executeAfm"; import { IRandomDataResult } from "./dataResult"; export interface ITotalsResultRandomGeneratorParams { totalType: AFM.TotalType; localIdentifier: AFM.Identifier; } export type TotalsResultRandomGenerator = (params: ITotalsResultRandomGeneratorParams) => string; export interface ITotalsResponse { types: AFM.TotalType[]; data?: Execution.IExecutionResult["totals"]; } export function getTotalsFromExecution(normalizedExecution: AFM.IExecution): AFM.ITotalItem[] { return get(normalizedExecution, "execution.resultSpec.dimensions[0].totals", []); } export function getTotalsTypesFromExecution(normalizedExecution: AFM.IExecution): AFM.TotalType[] { const totals = getTotalsFromExecution(normalizedExecution); const totalsTypes = totals.map(total => total.type); return uniq(totalsTypes); } export function getTotals( normalizedExecution: AFM.IExecution, dataResult: IRandomDataResult, randomGenerator: TotalsResultRandomGenerator, ): IErrorResponse | ITotalsResponse { const types = getTotalsTypesFromExecution(normalizedExecution); const data = getTotalsData(normalizedExecution, dataResult, randomGenerator); // just basic check for afm.nativeTotals field, not checking if afm.nativeTotals match resultSpec.totals definition if (includes(types, "nat") && isEmpty(normalizedExecution.execution.afm.nativeTotals)) { return { error: { statusCode: HttpStatusCodes.BAD_REQUEST, message: "Missing AFM.nativeTotal field", }, }; } return { types, data }; } export function getTotalsData( execution: AFM.IExecution, dataResult: IRandomDataResult, randomGenerator: TotalsResultRandomGenerator, ): Execution.IExecutionResult["totals"] | undefined { const { resultSpec, afm } = execution.execution; const dimensionsFlattened = flattenDepth(dataResult.headerItems, 2); const measureHeaderItems = dimensionsFlattened.filter(headerItem => Execution.isMeasureHeaderItem(headerItem), ) as Execution.IResultMeasureHeaderItem[]; const totalTypes: AFM.TotalType[] = ["sum", "max", "min", "avg", "med", "nat"]; return dataResult.headerItems.map((_, headerDimensionIndex) => { if (resultSpec.dimensions.length === 0) { return []; } const totals = resultSpec.dimensions[headerDimensionIndex] ? resultSpec.dimensions[headerDimensionIndex].totals : []; if (totals && totals.length > 0) { return totalTypes .filter(totalType => totals.some(total => total.type === totalType)) .map(totalType => measureHeaderItems.map(measureHeaderItem => { const localIdentifier = afm.measures && afm.measures[measureHeaderItem.measureHeaderItem.order] ? afm.measures[measureHeaderItem.measureHeaderItem.order].localIdentifier : null; if (!localIdentifier) { return null; } const isTotalDefined = totals.some( total => total.measureIdentifier === localIdentifier && total.type === totalType, ); return isTotalDefined ? randomGenerator({ totalType, localIdentifier }) : null; }), ); } return []; }); }