@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
147 lines (129 loc) • 5.47 kB
text/typescript
// (C) 2019 GoodData Corporation
import { Execution, AFM } from "@gooddata/typings";
import cloneDeep = require("lodash/cloneDeep");
import get = require("lodash/get");
import isEmpty = require("lodash/isEmpty");
import zip = require("lodash/zip");
import * as invariant from "invariant";
import { calculateXirr, IXirrInput } from "./calculateXirr";
import { IHeadlineData } from "../../../../interfaces/Headlines";
import { isSomeHeaderPredicateMatched } from "../../../../helpers/headerPredicate";
import { IHeaderPredicate } from "../../../../interfaces/HeaderPredicate";
import { VisualizationTypes } from "../../../../constants/visualizationTypes";
import {
IDrillEventIntersectionElementExtended,
IDrillEventExtended,
IDrillEventContextXirrExtended,
} from "../../../../interfaces/DrillEvents";
import { IHeadlineDrillItemContext } from "../types";
const getExecutionResponseMeasureHeader = (
executionResponse: Execution.IExecutionResponse,
): Execution.IMeasureHeaderItem =>
get(executionResponse, ["dimensions", 0, "headers", 0, "measureGroupHeader", "items", 0], {
measureHeaderItem: null,
});
const computeXirr = (executionResult: Execution.IExecutionResult): number => {
// prevent errors on invalid inputs
if (!executionResult.headerItems[0][1] || !executionResult.data) {
return NaN;
}
const values = executionResult.data as string[];
const parsedValues = values.map(value => Number.parseFloat(value));
const dates = executionResult.headerItems[0][1].map(
(h: Execution.IResultAttributeHeaderItem) => h.attributeHeaderItem.name,
);
const transactions: IXirrInput[] = zip(parsedValues, dates)
.filter(([value]) => value !== 0) // zero values are irrelevant to XIRR computation, filter them out here to avoid useless Date parsing later
.map(([amount, date]) => ({
amount,
when: new Date(date),
}));
return calculateXirr(transactions);
};
/**
* Get {HeadlineData} used by the {Headline} component.
*
* @param executionResponse - The execution response with dimensions definition.
* @param executionResult - The execution result with an actual data values.
*/
export function getHeadlineData(
executionResponse: Execution.IExecutionResponse,
executionResult: Execution.IExecutionResult,
): IHeadlineData {
const { measureHeaderItem } = getExecutionResponseMeasureHeader(executionResponse);
const value = computeXirr(executionResult);
invariant(value !== undefined, "Undefined execution value data for XIRR transformation");
invariant(measureHeaderItem, "Missing expected measureHeaderItem");
return {
primaryItem: {
localIdentifier: measureHeaderItem.localIdentifier,
title: measureHeaderItem.name,
value: value ? String(value) : null,
format: measureHeaderItem.format,
isDrillable: false,
},
};
}
/**
* Take headline data and apply list of drillable items.
* The method will return copied collection of the headline data with altered drillable status.
*
* @param headlineData - The headline data that we want to change the drillable status.
* @param drillableItems - list of drillable items {uri, identifier}
* @param executionRequest - Request with required measure id (uri or identifier) for activation of drill eventing
* @param executionResponse - Response headers for drilling predicate matching
* @returns altered headlineData
*/
export function applyDrillableItems(
headlineData: IHeadlineData,
drillableItems: IHeaderPredicate[],
executionRequest: AFM.IExecution["execution"],
executionResponse: Execution.IExecutionResponse,
): IHeadlineData {
const data = cloneDeep(headlineData);
const { primaryItem } = data;
const itemHeader = getExecutionResponseMeasureHeader(executionResponse);
if (!isEmpty(primaryItem) && !isEmpty(itemHeader)) {
primaryItem.isDrillable = isSomeHeaderPredicateMatched(
drillableItems,
itemHeader,
executionRequest.afm,
executionResponse,
);
}
return data;
}
/**
* Build drill event data (object with execution and drill context) from the data obtained by clicking on the {Xirr}
* component an from the execution objects.
*
* @param itemContext - data received from the click on the {Xirr} component.
* @param executionRequest - The execution request with AFM and ResultSpec.
* @param executionResponse - The execution response with dimensions definition.
* @returns {*}
*/
export function buildDrillEventData(
itemContext: IHeadlineDrillItemContext,
executionRequest: AFM.IExecution["execution"],
executionResponse: Execution.IExecutionResponse,
): IDrillEventExtended {
const { measureHeaderItem } = getExecutionResponseMeasureHeader(executionResponse);
if (!measureHeaderItem) {
throw new Error("The measure uri has not been found in execution response!");
}
const intersectionElement: IDrillEventIntersectionElementExtended = {
header: {
measureHeaderItem,
},
};
const drillContext: IDrillEventContextXirrExtended = {
type: VisualizationTypes.XIRR,
element: "primaryValue",
value: itemContext.value,
intersection: [intersectionElement],
};
return {
executionContext: executionRequest.afm,
drillContext,
};
}