gooddata-js v13.5.0

File: src/DataLayer/utils/AfmUtils.ts

                        // (C) 2007-2018 GoodData Corporation
                        import flatMap from "lodash/flatMap";
                        import compact from "lodash/compact";
                        import { AFM } from "@gooddata/typings";
                        
                        export const ALL_TIME_GRANULARITY = "ALL_TIME_GRANULARITY";
                        
                        export interface INormalizedAFM {
                            attributes: AFM.IAttribute[];
                            measures: AFM.IMeasure[];
                            filters: AFM.CompatibilityFilter[];
                            nativeTotals: AFM.INativeTotalItem[];
                        }
                        
                        /**
                         * Unwraps measure object
                         *
                         * @method unwrapSimpleMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {AFM.ISimpleMeasure}
                         */
                        export function unwrapSimpleMeasure(item: AFM.IMeasure): AFM.ISimpleMeasure {
                            return (item.definition as AFM.ISimpleMeasureDefinition).measure;
                        }
                        
                        /**
                         * Unwraps popMeasure object
                         *
                         * @method unwrapPoPMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {AFM.IPopMeasure}
                         */
                        export function unwrapPoPMeasure(item: AFM.IMeasure): AFM.IPopMeasure {
                            return (item.definition as AFM.IPopMeasureDefinition).popMeasure;
                        }
                        
                        /**
                         * Unwraps previousPeriodMeasure object
                         *
                         * @method unwrapPreviousPeriodMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {AFM.IPreviousPeriodMeasure}
                         */
                        export function unwrapPreviousPeriodMeasure(item: AFM.IMeasure): AFM.IPreviousPeriodMeasure {
                            return (item.definition as AFM.IPreviousPeriodMeasureDefinition).previousPeriodMeasure;
                        }
                        
                        /**
                         * Unwraps arithmeticMeasure object
                         *
                         * @method unwrapArithmeticMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {AFM.IArithmeticMeasure}
                         */
                        export function unwrapArithmeticMeasure(item: AFM.IMeasure): AFM.IArithmeticMeasure {
                            return (item.definition as AFM.IArithmeticMeasureDefinition).arithmeticMeasure;
                        }
                        
                        /**
                         * Normalize AFM
                         *
                         * @method normalizeAfm
                         * @param {AFM.IAfm} afm
                         * @returns {INormalizedAFM}
                         */
                        export function normalizeAfm(afm: AFM.IAfm): INormalizedAFM {
                            return {
                                attributes: afm.attributes || [],
                                measures: afm.measures || [],
                                filters: afm.filters || [],
                                nativeTotals: afm.nativeTotals || [],
                            };
                        }
                        
                        /**
                         * Returns true if measure is a simple measure
                         *
                         * @method isSimpleMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {boolean}
                         */
                        export function isSimpleMeasure(item: AFM.IMeasure): boolean {
                            return !!unwrapSimpleMeasure(item);
                        }
                        
                        /**
                         * Returns true if measure is PeriodOverPeriod
                         *
                         * @method isPoP
                         * @param {AFM.IMeasure} item
                         * @returns {boolean}
                         */
                        export function isPoP(item: AFM.IMeasure): boolean {
                            return !!unwrapPoPMeasure(item);
                        }
                        
                        /**
                         * Returns true if measure is previous period measure
                         *
                         * @method isPreviousPeriodMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {boolean}
                         */
                        export function isPreviousPeriodMeasure(item: AFM.IMeasure): boolean {
                            return !!unwrapPreviousPeriodMeasure(item);
                        }
                        
                        /**
                         * Returns true if measure is arithmetic measure
                         *
                         * @method isArithmeticMeasure
                         * @param {AFM.IMeasure} item
                         * @returns {boolean}
                         */
                        export function isArithmeticMeasure(item: AFM.IMeasure): boolean {
                            return !!unwrapArithmeticMeasure(item);
                        }
                        
                        /**
                         * Returns true if filter is attributeFilter
                         *
                         * @method isAttributeFilter
                         * @param {AFM.FilterItem} filter
                         * @returns {boolean}
                         * @deprecated use AFM.isAttributeFilter instead
                         */
                        export function isAttributeFilter(filter: AFM.FilterItem): filter is AFM.AttributeFilterItem {
                            return AFM.isAttributeFilter(filter);
                        }
                        
                        /**
                         * Returns true if filter is dateFilter
                         *
                         * @method isDateFilter
                         * @param {AFM.CompatibilityFilter} filter
                         * @returns {boolean}
                         * @deprecated use AFM.isDateFilter instead
                         */
                        export function isDateFilter(filter: AFM.CompatibilityFilter): filter is AFM.DateFilterItem {
                            return AFM.isDateFilter(filter);
                        }
                        
                        /**
                         * Returns true if filter is negative attribute filter and has no selected elements,
                         * meaning that this is "Select all"
                         *
                         * @method isAttributeFilterSelectAll
                         * @param {AFM.FilterItem} filter
                         * @returns {boolean}
                         */
                        export function isAttributeFilterSelectAll(filter: AFM.FilterItem): boolean {
                            if (AFM.isNegativeAttributeFilter(filter)) {
                                return filter.negativeAttributeFilter.notIn.length === 0;
                            }
                        
                            return false;
                        }
                        
                        /**
                         * Returns true if measure has dateFilters
                         *
                         * @method hasMetricDateFilters
                         * @param {INormalizedAFM} normalizedAfm
                         * @returns {boolean}
                         */
                        export function hasMetricDateFilters(normalizedAfm: INormalizedAFM): boolean {
                            return normalizedAfm.measures.some(measure => {
                                if (isSimpleMeasure(measure)) {
                                    const filters = unwrapSimpleMeasure(measure).filters;
                                    return !!(filters && filters.some(AFM.isDateFilter));
                                }
                                return false;
                            });
                        }
                        
                        /**
                         * Returns global date filters
                         *
                         * @method getGlobalDateFilters
                         * @param {INormalizedAFM} normalizedAfm
                         * @returns {AFM.DateFilterItem[]}
                         */
                        export function getGlobalDateFilters(normalizedAfm: INormalizedAFM): AFM.DateFilterItem[] {
                            return normalizedAfm.filters.filter(AFM.isDateFilter);
                        }
                        
                        /**
                         * Returns true if measure has filters
                         *
                         * @method hasFilters
                         * @param {AFM.ISimpleMeasure} measure
                         * @returns {boolean}
                         */
                        export const hasFilters = (measure: AFM.ISimpleMeasure): boolean => {
                            return !!(measure.filters && measure.filters.length > 0);
                        };
                        
                        /**
                         * Return date filters from AFM
                         *
                         * @method getMeasureDateFilters
                         * @param {AFM.IAfm} normalizedAfm
                         * @returns {AFM.DateFilterItem[]}
                         */
                        export function getMeasureDateFilters(normalizedAfm: AFM.IAfm): AFM.DateFilterItem[] {
                            return flatMap(normalizedAfm.measures, (item: AFM.IMeasure) => {
                                const measure = unwrapSimpleMeasure(item);
                                if (!measure || !hasFilters(measure)) {
                                    return [];
                                }
                                return (measure.filters || []).filter(AFM.isDateFilter);
                            });
                        }
                        
                        /**
                         * Return true if AFM has global date filter
                         *
                         * @method hasGlobalDateFilter
                         * @param {INormalizedAFM} afm
                         * @returns {boolean}
                         */
                        export function hasGlobalDateFilter(afm: INormalizedAFM): boolean {
                            return afm.filters.some(AFM.isDateFilter);
                        }
                        
                        /**
                         * Return uri or identifier from ObjQualifier
                         *
                         * @method getId
                         * @param {AFM.ObjQualifier} obj
                         * @returns {string|null}
                         */
                        export function getId(obj: AFM.ObjQualifier): string | null {
                            if ((obj as AFM.IObjUriQualifier).uri) {
                                return (obj as AFM.IObjUriQualifier).uri;
                            }
                            if ((obj as AFM.IObjIdentifierQualifier).identifier) {
                                return (obj as AFM.IObjIdentifierQualifier).identifier;
                            }
                            return null;
                        }
                        
                        /**
                         * Returns date filter date dataset
                         *
                         * @method getDateFilterDateDataSet
                         * @param {AFM.DateFilterItem} filter
                         * @returns {AFM.ObjQualifier | null }
                         */
                        export function getDateFilterDateDataSet(filter: AFM.DateFilterItem): AFM.ObjQualifier {
                            if (AFM.isRelativeDateFilter(filter)) {
                                return filter.relativeDateFilter.dataSet;
                            }
                            if (AFM.isAbsoluteDateFilter(filter)) {
                                return filter.absoluteDateFilter.dataSet;
                            }
                            throw new Error("Unsupported type of date filter");
                        }
                        
                        /**
                         * Returns true if dateFilters dataSets match
                         *
                         * @method dateFiltersDataSetsMatch
                         * @param {AFM.DateFilterItem} f1
                         * @param {AFM.DateFilterItem} f2
                         * @returns {AFM.ObjQualifier | null | boolean}
                         */
                        export function dateFiltersDataSetsMatch(f1: AFM.DateFilterItem, f2: AFM.DateFilterItem) {
                            const d1 = getDateFilterDateDataSet(f1);
                            const d2 = getDateFilterDateDataSet(f2);
                            return d1 && d2 && getId(d1) === getId(d2);
                        }
                        
                        function isDateFilterAllTime(dateFilter: AFM.DateFilterItem): boolean {
                            if (AFM.isRelativeDateFilter(dateFilter)) {
                                return dateFilter.relativeDateFilter.granularity === ALL_TIME_GRANULARITY;
                            }
                            return false;
                        }
                        
                        /**
                         * Append attribute filters and date filter to afm
                         *
                         * Date filter handling:
                         *      - Override if date filter has the same id
                         *      - Add if date filter if date filter id is different
                         *
                         * Attribute filter handling:
                         *      - Add all
                         *
                         * @method appendFilters
                         * @param {AFM.IAfm} afm
                         * @param {AFM.AttributeFilterItem[]} attributeFilters
                         * @param {AFM.DateFilterItem} dateFilter
                         * @param {AFM.IMeasureValueFilter[]} measureValueFilters
                         * @return {AFM.IAfm}
                         */
                        export function appendFilters(
                            afm: AFM.IAfm,
                            attributeFilters: AFM.AttributeFilterItem[],
                            dateFilter?: AFM.DateFilterItem,
                            measureValueFilters?: AFM.IMeasureValueFilter[],
                        ): AFM.IAfm {
                            const dateFilters: AFM.DateFilterItem[] =
                                dateFilter && !isDateFilterAllTime(dateFilter) ? [dateFilter] : [];
                            const afmDateFilter = afm.filters ? afm.filters.filter(AFM.isDateFilter)[0] : null;
                        
                            // all-time selected, need to delete date filter from filters
                            let afmFilters = afm.filters || [];
                            if (dateFilter && isDateFilterAllTime(dateFilter)) {
                                afmFilters = afmFilters.filter(filter => {
                                    if (AFM.isDateFilter(filter)) {
                                        return !dateFiltersDataSetsMatch(filter, dateFilter);
                                    }
                                    return true;
                                });
                            }
                        
                            if (
                                (afmDateFilter && dateFilter && !dateFiltersDataSetsMatch(afmDateFilter, dateFilter)) ||
                                (afmDateFilter && !dateFilter)
                            ) {
                                dateFilters.push(afmDateFilter);
                            }
                        
                            const afmNonDateFilters = afmFilters.filter(filter => !AFM.isDateFilter(filter));
                        
                            const filters = compact([
                                ...afmNonDateFilters,
                                ...attributeFilters,
                                ...dateFilters,
                                ...(measureValueFilters || []),
                            ]);
                        
                            if (filters.length || (afm.filters && afm.filters.length)) {
                                return {
                                    ...afm,
                                    filters,
                                };
                            }
                        
                            return afm;
                        }
                        
                        /**
                         * Returns true if AFM is executable
                         *
                         * @method isAfmExecutable
                         * @param {AFM.IAfm} afm
                         * @returns {boolean}
                         */
                        export function isAfmExecutable(afm: AFM.IAfm) {
                            const normalizedAfm = normalizeAfm(afm);
                            return normalizedAfm.measures.length > 0 || normalizedAfm.attributes.length > 0;
                        }