@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
357 lines (313 loc) • 13.2 kB
text/typescript
// (C) 2007-2020 GoodData Corporation
import get = require("lodash/get");
import flatMap = require("lodash/flatMap");
import { VisualizationObject, Localization } from "@gooddata/typings";
import { string as stringUtils } from "@gooddata/js-utils";
import DerivedMeasureTitleSuffixFactory from "../factory/DerivedMeasureTitleSuffixFactory";
import ArithmeticMeasureTitleFactory from "../factory/ArithmeticMeasureTitleFactory";
import { OverTimeComparisonType, OverTimeComparisonTypes } from "../interfaces/OverTimeComparison";
import { IMeasureTitleProps } from "../interfaces/MeasureTitle";
import IMeasureDefinitionType = VisualizationObject.IMeasureDefinitionType;
import IMeasure = VisualizationObject.IMeasure;
import IBucket = VisualizationObject.IBucket;
import BucketItem = VisualizationObject.BucketItem;
import IVisualizationObjectContent = VisualizationObject.IVisualizationObjectContent;
import isMeasure = VisualizationObject.isMeasure;
const DEFAULT_MAX_ARITHMETIC_MEASURE_TITLE_LENGTH = 50;
function getAllMeasures(mdObject: IVisualizationObjectContent): IMeasure[] {
const buckets: IBucket[] = get<IVisualizationObjectContent, "buckets", IBucket[]>(
mdObject,
"buckets",
[],
);
const allBucketItems = flatMap<IBucket, BucketItem>(buckets, (bucket: IBucket) => bucket.items);
return allBucketItems.reduce((measureItems, bucketItem) => {
if (VisualizationObject.isMeasure(bucketItem)) {
measureItems.push(bucketItem);
}
return measureItems;
}, []);
}
function getMasterMeasureIdentifier(definition: IMeasureDefinitionType): string | null {
if (VisualizationObject.isPopMeasureDefinition(definition)) {
return definition.popMeasureDefinition.measureIdentifier;
} else if (VisualizationObject.isPreviousPeriodMeasureDefinition(definition)) {
return definition.previousPeriodMeasure.measureIdentifier;
}
return null;
}
function findOverTimeComparisonType(measureDefinitionType: IMeasureDefinitionType): OverTimeComparisonType {
if (VisualizationObject.isPopMeasureDefinition(measureDefinitionType)) {
return OverTimeComparisonTypes.SAME_PERIOD_PREVIOUS_YEAR;
} else if (VisualizationObject.isPreviousPeriodMeasureDefinition(measureDefinitionType)) {
return OverTimeComparisonTypes.PREVIOUS_PERIOD;
}
return OverTimeComparisonTypes.NOTHING;
}
function containsMeasureTitleItem(measureTitleProps: IMeasureTitleProps[], localIdentifier: string): boolean {
return measureTitleProps.some(prop => prop.localIdentifier === localIdentifier);
}
function containsMeasureTitleItems(
measureTitleProps: IMeasureTitleProps[],
localIdentifiers: string[],
): boolean {
return localIdentifiers.every(identifier => containsMeasureTitleItem(measureTitleProps, identifier));
}
function findMeasureTitleItem(
measureTitles: IMeasureTitleProps[],
localIdentifier: string,
): IMeasureTitleProps | null {
return measureTitles.find(prop => prop.localIdentifier === localIdentifier) || null;
}
function findTitleForDerivedMeasure(
measureDefinitionType: IMeasureDefinitionType,
measureTitleProps: IMeasureTitleProps[],
suffixFactory: DerivedMeasureTitleSuffixFactory,
): string {
const masterMeasureIdentifier = getMasterMeasureIdentifier(measureDefinitionType);
if (masterMeasureIdentifier === null) {
return undefined;
}
const measureProps = findMeasureTitleItem(measureTitleProps, masterMeasureIdentifier);
if (measureProps === null) {
return undefined;
}
const derivedMeasureTitleBase = measureProps.alias || measureProps.title || "";
const overTimeComparisonType = findOverTimeComparisonType(measureDefinitionType);
return derivedMeasureTitleBase + suffixFactory.getSuffix(overTimeComparisonType);
}
function buildMeasureTitle(bucketItem: IMeasure): IMeasureTitleProps | null {
if (VisualizationObject.isMeasureDefinition(bucketItem.measure.definition)) {
const { localIdentifier, title, alias } = bucketItem.measure;
return {
localIdentifier,
title,
alias,
};
}
return null;
}
function buildArithmeticMeasureTitle(
bucketItem: IMeasure,
measureTitleProps: IMeasureTitleProps[],
titleFactory: ArithmeticMeasureTitleFactory,
maxArithmeticMeasureTitleLength: number,
): IMeasureTitleProps | null {
if (VisualizationObject.isArithmeticMeasureDefinition(bucketItem.measure.definition)) {
const { alias, localIdentifier } = bucketItem.measure;
const arithmeticMeasure = bucketItem.measure.definition.arithmeticMeasure;
if (containsMeasureTitleItems(measureTitleProps, arithmeticMeasure.measureIdentifiers)) {
const fullLengthTitle = titleFactory.getTitle(
{
operator: arithmeticMeasure.operator,
masterMeasureLocalIdentifiers: arithmeticMeasure.measureIdentifiers,
},
measureTitleProps,
);
const title = stringUtils.shortenText(fullLengthTitle, {
maxLength: maxArithmeticMeasureTitleLength,
});
return {
localIdentifier,
title,
alias,
};
}
}
return null;
}
function buildDerivedMeasureTitle(
bucketItem: IMeasure,
measureTitleProps: IMeasureTitleProps[],
suffixFactory: DerivedMeasureTitleSuffixFactory,
): IMeasureTitleProps | null {
if (
VisualizationObject.isPopMeasureDefinition(bucketItem.measure.definition) ||
VisualizationObject.isPreviousPeriodMeasureDefinition(bucketItem.measure.definition)
) {
const { alias, localIdentifier } = bucketItem.measure;
const definition = bucketItem.measure.definition;
const masterMeasureIdentifier = getMasterMeasureIdentifier(definition);
if (containsMeasureTitleItem(measureTitleProps, masterMeasureIdentifier)) {
return {
localIdentifier,
title: findTitleForDerivedMeasure(definition, measureTitleProps, suffixFactory),
alias,
};
}
}
return null;
}
function buildMeasureTitles(
measureBucketItems: VisualizationObject.IMeasure[],
locale: Localization.ILocale,
maxArithmeticMeasureTitleLength: number,
): IMeasureTitleProps[] {
const titleFactory = new ArithmeticMeasureTitleFactory(locale);
const suffixFactory = new DerivedMeasureTitleSuffixFactory(locale);
const measureTitleProps: IMeasureTitleProps[] = [];
let isMeasureTitlePropsChanged = true;
while (isMeasureTitlePropsChanged) {
isMeasureTitlePropsChanged = false;
measureBucketItems.forEach(bucketItem => {
if (!containsMeasureTitleItem(measureTitleProps, bucketItem.measure.localIdentifier)) {
const newMeasureTitleProp =
buildMeasureTitle(bucketItem) ||
buildArithmeticMeasureTitle(
bucketItem,
measureTitleProps,
titleFactory,
maxArithmeticMeasureTitleLength,
) ||
buildDerivedMeasureTitle(bucketItem, measureTitleProps, suffixFactory);
if (newMeasureTitleProp !== null) {
measureTitleProps.push(newMeasureTitleProp);
isMeasureTitlePropsChanged = true;
}
}
});
}
return measureTitleProps;
}
function updateBucketItemTitle(
bucketItem: VisualizationObject.BucketItem,
measureTitleProps: IMeasureTitleProps[],
): BucketItem {
if (isMeasure(bucketItem)) {
const measureTitleProp = findMeasureTitleItem(measureTitleProps, bucketItem.measure.localIdentifier);
if (measureTitleProp !== null) {
const { title, alias } = measureTitleProp;
return {
...bucketItem,
measure: {
...bucketItem.measure,
title,
alias,
},
};
}
}
return bucketItem;
}
function updateBucketTitles(
bucket: VisualizationObject.IBucket,
measureTitleProps: IMeasureTitleProps[],
): VisualizationObject.IBucket {
return {
...bucket,
items: bucket.items.map(bucketItem => updateBucketItemTitle(bucketItem, measureTitleProps)),
};
}
function updateVisualizationObjectTitles(
mdObject: IVisualizationObjectContent,
measureTitleProps: IMeasureTitleProps[],
): IVisualizationObjectContent {
return {
...mdObject,
buckets: mdObject.buckets.map(bucket => updateBucketTitles(bucket, measureTitleProps)),
};
}
/**
* The function fills the titles of the measures that does not have it set.
*
* The derived measures
* have the title built from the current name of the master measure and suffix based on the derived measure type.
*
* The arithmetic measures
* have the title built from the current names of the referenced master measures and type of the arithmetic
* operation.
*
* @param {VisualizationObject.IVisualizationObjectContent} mdObject - metadata object that must be processed.
* @param {Localization.ILocale} locale - locale used for localization of the measure titles.
* @param {number} maxArithmeticMeasureTitleLength - maximum length of generated arithmetic measures titles.
* Longer names will be shortened. Default value is 50 characters.
*
* @returns {VisualizationObject.IVisualizationObjectContent}
*
* @internal
*/
export function fillMissingTitles(
mdObject: IVisualizationObjectContent,
locale: Localization.ILocale,
maxArithmeticMeasureTitleLength: number = DEFAULT_MAX_ARITHMETIC_MEASURE_TITLE_LENGTH,
): IVisualizationObjectContent {
const measureBucketItems = getAllMeasures(mdObject);
const measureTitleProps = buildMeasureTitles(measureBucketItems, locale, maxArithmeticMeasureTitleLength);
return updateVisualizationObjectTitles(mdObject, measureTitleProps);
}
function isAdhocMeasure(definition: IMeasureDefinitionType): boolean {
return (
VisualizationObject.isMeasureDefinition(definition) &&
(!!definition.measureDefinition.aggregation ||
!!definition.measureDefinition.computeRatio ||
!!definition.measureDefinition.filters)
);
}
function isNonSimpleMeasure(definition: IMeasureDefinitionType): boolean {
return (
VisualizationObject.isArithmeticMeasureDefinition(definition) ||
VisualizationObject.isPopMeasureDefinition(definition) ||
VisualizationObject.isPreviousPeriodMeasureDefinition(definition)
);
}
function removeTitleForSimpleMeasure(bucket: IBucket): IBucket {
return {
...bucket,
items: bucket.items.map(
(bucketItem: VisualizationObject.BucketItem): VisualizationObject.BucketItem => {
if (
VisualizationObject.isMeasure(bucketItem) &&
!isAdhocMeasure(bucketItem.measure.definition) &&
!isNonSimpleMeasure(bucketItem.measure.definition)
) {
delete bucketItem.measure.title;
}
return bucketItem;
},
),
};
}
function isBucketContainingMeasureDateFilter(bucket: IBucket): boolean {
return !!bucket.items.find(
(bucketItem: VisualizationObject.BucketItem): boolean => {
if (!VisualizationObject.isMeasure(bucketItem)) {
return false;
}
const definition: IMeasureDefinitionType = bucketItem.measure.definition;
if (!VisualizationObject.isMeasureDefinition(definition)) {
return false;
}
const filters: VisualizationObject.VisualizationObjectFilter[] =
definition.measureDefinition.filters;
return filters && !!filters.find(VisualizationObject.isDateFilter);
},
);
}
function isBucketsContainingMeasureDateFilter(buckets: IBucket[]): boolean {
return !!buckets.find(isBucketContainingMeasureDateFilter);
}
/**
* This function ignores the titles of simple measures.
*
* For simple measures, their titles are removed.
* For adhoc or non-simple measures, their titles are left intact.
*
* @param {VisualizationObject.IVisualizationObjectContent} mdObject - metadata object that must be processed.
*
* @returns {VisualizationObject.IVisualizationObjectContent}
*
* @internal
*/
export function ignoreTitlesForSimpleMeasures(
mdObject: IVisualizationObjectContent,
): IVisualizationObjectContent {
if (isBucketsContainingMeasureDateFilter(mdObject.buckets)) {
// If there is one bucket containing a measure with date filter, all other measures are considered adhoc measures
// and all the titles should be left intact
return mdObject;
}
return {
...mdObject,
buckets: mdObject.buckets.map(removeTitleForSimpleMeasure),
};
}