"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildRollingPeriodCustomDimension = exports.getRollingPeriodDates = exports.getAvailableCompareMetrics = exports.getSegmentDimensionsForMetric = exports.getTypeValidSegmentDimensions = exports.getFilterDimensionsForMetric = exports.getTypeValidFilterDimensions = exports.getAvailableTimeDimensionsFromTables = exports.getDefaultTimeDimension = exports.getFirstAvailableTimeDimension = exports.DEFAULT_METRICS_EXPLORER_TIME_INTERVAL = exports.getDefaultMetricTreeNodeDateRange = exports.getDefaultDateRangeFromInterval = exports.getMetricExplorerDataPointsWithCompare = exports.getMetricExplorerDataPoints = exports.MAX_SEGMENT_DIMENSION_UNIQUE_VALUES = exports.parseMetricValue = exports.getMetricExplorerDateRangeFilters = exports.getMetricsExplorerSegmentFilters = exports.getGrainForDateRange = exports.getDateRangeFromString = exports.METRICS_EXPLORER_DATE_FORMAT = exports.getDateCalcUtils = exports.getFieldIdForDateDimension = exports.assertUnimplementedTimeframe = void 0;
const tslib_1 = require("tslib");
const dayjs_1 = tslib_1.__importDefault(require("dayjs"));
const isoWeek_1 = tslib_1.__importDefault(require("dayjs/plugin/isoWeek"));
const weekOfYear_1 = tslib_1.__importDefault(require("dayjs/plugin/weekOfYear"));
const groupBy_1 = tslib_1.__importDefault(require("lodash/groupBy"));
const mapKeys_1 = tslib_1.__importDefault(require("lodash/mapKeys"));
const uuid_1 = require("uuid");
const filtersCompiler_1 = require("../compiler/filtersCompiler");
const field_1 = require("../types/field");
const filter_1 = require("../types/filter");
const metricsExplorer_1 = require("../types/metricsExplorer");
const timeFrames_1 = require("../types/timeFrames");
const assertUnreachable_1 = tslib_1.__importDefault(require("./assertUnreachable"));
const item_1 = require("./item");
dayjs_1.default.extend(isoWeek_1.default);
dayjs_1.default.extend(weekOfYear_1.default);
const assertUnimplementedTimeframe = (timeframe) => {
    switch (timeframe) {
        case timeFrames_1.TimeFrames.RAW:
        case timeFrames_1.TimeFrames.QUARTER:
        case timeFrames_1.TimeFrames.HOUR:
        case timeFrames_1.TimeFrames.MINUTE:
        case timeFrames_1.TimeFrames.SECOND:
        case timeFrames_1.TimeFrames.MILLISECOND:
        case timeFrames_1.TimeFrames.DAY_OF_WEEK_INDEX:
        case timeFrames_1.TimeFrames.DAY_OF_MONTH_NUM:
        case timeFrames_1.TimeFrames.DAY_OF_YEAR_NUM:
        case timeFrames_1.TimeFrames.WEEK_NUM:
        case timeFrames_1.TimeFrames.MONTH_NUM:
        case timeFrames_1.TimeFrames.QUARTER_NUM:
        case timeFrames_1.TimeFrames.YEAR_NUM:
        case timeFrames_1.TimeFrames.DAY_OF_WEEK_NAME:
        case timeFrames_1.TimeFrames.MONTH_NAME:
        case timeFrames_1.TimeFrames.QUARTER_NAME:
        case timeFrames_1.TimeFrames.HOUR_OF_DAY_NUM:
        case timeFrames_1.TimeFrames.MINUTE_OF_HOUR_NUM:
            throw new Error(`Timeframe "${timeframe}" is not supported for default time interval`);
        default:
            return (0, assertUnreachable_1.default)(timeframe, `Unknown time interval: "${timeframe}"`);
    }
};
exports.assertUnimplementedTimeframe = assertUnimplementedTimeframe;
const getFieldIdForDateDimension = (fieldId, timeframe) => {
    switch (timeframe) {
        case timeFrames_1.TimeFrames.DAY:
            return `${fieldId}_day`;
        case timeFrames_1.TimeFrames.WEEK:
            return `${fieldId}_week`;
        case timeFrames_1.TimeFrames.MONTH:
            return `${fieldId}_month`;
        case timeFrames_1.TimeFrames.YEAR:
            return `${fieldId}_year`;
        default:
            return (0, exports.assertUnimplementedTimeframe)(timeframe);
    }
};
exports.getFieldIdForDateDimension = getFieldIdForDateDimension;
const getDateCalcUtils = (timeFrame, grain) => {
    switch (timeFrame) {
        case timeFrames_1.TimeFrames.MONTH:
            if (grain &&
                grain !== timeFrames_1.TimeFrames.DAY &&
                grain !== timeFrames_1.TimeFrames.MONTH) {
                throw new Error(`Granularity "${grain}" is not supported yet for this timeframe "${timeFrame}"`);
            }
            return {
                forward: (date) => (0, dayjs_1.default)(date).add(1, 'month').toDate(),
                back: (date) => (0, dayjs_1.default)(date).subtract(1, 'month').toDate(),
            };
        case timeFrames_1.TimeFrames.DAY:
            if (grain && grain !== timeFrames_1.TimeFrames.DAY) {
                throw new Error(`Granularity "${grain}" is not supported yet for this timeframe "${timeFrame}"`);
            }
            return {
                forward: (date) => (0, dayjs_1.default)(date).add(1, 'day').toDate(),
                back: (date) => (0, dayjs_1.default)(date).subtract(1, 'day').toDate(),
            };
        case timeFrames_1.TimeFrames.WEEK:
            if (grain && grain !== timeFrames_1.TimeFrames.WEEK) {
                throw new Error(`Granularity "${grain}" is not supported yet for this timeframe "${timeFrame}"`);
            }
            return {
                forward: (date) => (0, dayjs_1.default)(date).add(1, 'week').toDate(),
                back: (date) => (0, dayjs_1.default)(date).subtract(1, 'week').toDate(),
            };
        case timeFrames_1.TimeFrames.YEAR:
            // Handle week shift for previous year comparison and subtract what the amount of weeks is in a year
            if (grain === timeFrames_1.TimeFrames.WEEK) {
                return {
                    forward: (date) => (0, dayjs_1.default)(date)
                        // 52 weeks in a year
                        .add(52, 'weeks')
                        .startOf('isoWeek')
                        .toDate(),
                    back: (date) => (0, dayjs_1.default)(date)
                        // 52 weeks in a year
                        .subtract(52, 'weeks')
                        .startOf('isoWeek')
                        .toDate(),
                };
            }
            return {
                forward: (date) => (0, dayjs_1.default)(date).add(1, 'year').toDate(),
                back: (date) => (0, dayjs_1.default)(date).subtract(1, 'year').toDate(),
            };
        default:
            return (0, exports.assertUnimplementedTimeframe)(timeFrame);
    }
};
exports.getDateCalcUtils = getDateCalcUtils;
exports.METRICS_EXPLORER_DATE_FORMAT = 'YYYY-MM-DD';
const getDateRangeFromString = (dateRange) => [
    (0, dayjs_1.default)(dateRange[0], exports.METRICS_EXPLORER_DATE_FORMAT).toDate(),
    (0, dayjs_1.default)(dateRange[1], exports.METRICS_EXPLORER_DATE_FORMAT).toDate(),
];
exports.getDateRangeFromString = getDateRangeFromString;
// TODO: refine the time grain for each time frame
//   Time grain Year: -> past 5 years (i.e. 5 completed years + this uncompleted year)
//   Time grain Month -> past 12 months (i.e. 12 completed months + this uncompleted month)
//   Time grain Week -> past 12 weeks (i.e. 12 completed weeks + this uncompleted week)
//   Time grain Day -> past 30 days (i.e. 30 completed days + this uncompleted day)
// above sample is taken from the spec but the implementation is different for practical reasons for visualization
const getGrainForDateRange = (dateRange) => {
    const diff = dateRange[1].getTime() - dateRange[0].getTime();
    const days = diff / (1000 * 60 * 60 * 24);
    if (days <= 31 * 3) {
        return timeFrames_1.TimeFrames.DAY;
    }
    if (days <= 7 * 12) {
        return timeFrames_1.TimeFrames.WEEK;
    }
    if (days <= 31 * 12) {
        return timeFrames_1.TimeFrames.MONTH;
    }
    return timeFrames_1.TimeFrames.YEAR;
};
exports.getGrainForDateRange = getGrainForDateRange;
const getMetricsExplorerSegmentFilters = (segmentDimension, segments) => {
    if (!segmentDimension || segments.length === 0)
        return [];
    return [
        {
            id: (0, uuid_1.v4)(),
            target: { fieldId: segmentDimension },
            operator: filter_1.FilterOperator.EQUALS,
            values: segments,
        },
    ];
};
exports.getMetricsExplorerSegmentFilters = getMetricsExplorerSegmentFilters;
const getMetricExplorerDateRangeFilters = (timeDimensionConfig, dateRange) => {
    const targetFieldId = (0, item_1.getItemId)({
        table: timeDimensionConfig.table,
        name: (0, exports.getFieldIdForDateDimension)(timeDimensionConfig.field, timeDimensionConfig.interval),
    });
    return [
        {
            id: (0, uuid_1.v4)(),
            target: { fieldId: targetFieldId },
            operator: filter_1.FilterOperator.IN_BETWEEN,
            values: dateRange.map((date) => (0, dayjs_1.default)(date).format(exports.METRICS_EXPLORER_DATE_FORMAT)),
        },
    ];
};
exports.getMetricExplorerDateRangeFilters = getMetricExplorerDateRangeFilters;
// Parse the metric value to a number, returning null if it's not a number
const parseMetricValue = (value) => {
    if (value === null || value === undefined)
        return null;
    const parsed = Number(value);
    return Number.isNaN(parsed) ? null : parsed;
};
exports.parseMetricValue = parseMetricValue;
// we are assuming that the dimension value is a string and if it's not defined we just return null
// so actually `null` value will be converted to `"null"` string
const parseDimensionValue = (value) => {
    if (value === undefined)
        return null;
    return String(value);
};
exports.MAX_SEGMENT_DIMENSION_UNIQUE_VALUES = 10;
const getMetricExplorerDataPoints = (dimension, metric, metricRows, segmentDimensionId) => {
    const dimensionId = (0, item_1.getItemId)(dimension);
    const metricId = (0, item_1.getItemId)(metric);
    let filteredMetricRows = metricRows;
    let isSegmentDimensionFiltered = false;
    if (segmentDimensionId) {
        const countUniqueValues = new Set(metricRows.map((row) => row[segmentDimensionId])).size;
        if (countUniqueValues > exports.MAX_SEGMENT_DIMENSION_UNIQUE_VALUES) {
            isSegmentDimensionFiltered = true;
            const first10Values = Array.from(new Set(metricRows.map((row) => row[segmentDimensionId]))).slice(0, exports.MAX_SEGMENT_DIMENSION_UNIQUE_VALUES);
            filteredMetricRows = metricRows.filter((row) => first10Values.includes(row[segmentDimensionId]));
        }
    }
    const groupByMetricRows = (0, groupBy_1.default)(filteredMetricRows, (row) => new Date(String(row[dimensionId])).toISOString());
    const dataPoints = Object.entries(groupByMetricRows).flatMap(([date, rows]) => rows.map((row) => {
        const segmentValue = segmentDimensionId
            ? parseDimensionValue(row[segmentDimensionId])
            : null;
        return {
            date: new Date(date),
            segment: segmentValue,
            metric: {
                value: (0, exports.parseMetricValue)(row[metricId]),
                label: segmentValue ?? metric.label ?? metric.name,
            },
            compareMetric: {
                value: null,
                label: null,
            },
        };
    }));
    return {
        dataPoints,
        isSegmentDimensionFiltered,
    };
};
exports.getMetricExplorerDataPoints = getMetricExplorerDataPoints;
function offsetWeekCompareDates(groupByCompareMetricRows, timeFrame) {
    return Object.fromEntries(Object.keys(groupByCompareMetricRows).map((compareDate) => {
        const compareDateWeek = (0, dayjs_1.default)(compareDate).week();
        const compareDateNextYear = (0, exports.getDateCalcUtils)(timeFrames_1.TimeFrames.YEAR, timeFrame).forward(new Date(compareDate));
        const adjustedCompareDate = (0, dayjs_1.default)(compareDateNextYear)
            .week(compareDateWeek)
            .startOf('isoWeek')
            .toISOString();
        return [compareDate, adjustedCompareDate];
    }));
}
const getMetricExplorerDataPointsWithCompare = (dimension, compareDimension, metric, metricRows, compareMetricRows, query, timeFrame) => {
    if (query.comparison === metricsExplorer_1.MetricExplorerComparison.NONE) {
        throw new Error('Comparison type is required');
    }
    const metricId = (0, item_1.getItemId)(metric);
    const dimensionId = (0, item_1.getItemId)(dimension);
    const compareDimensionId = (0, item_1.getItemId)(compareDimension);
    const groupByMetricRows = (0, groupBy_1.default)(metricRows, (row) => new Date(String(row[dimensionId])).toISOString());
    const groupByCompareMetricRows = (0, groupBy_1.default)(compareMetricRows, (row) => new Date(String(row[compareDimensionId])).toISOString());
    const offsetGroupByCompareMetricRows = (0, mapKeys_1.default)(groupByCompareMetricRows, (_, date) => {
        if (query.comparison === metricsExplorer_1.MetricExplorerComparison.PREVIOUS_PERIOD) {
            if (timeFrame === timeFrames_1.TimeFrames.WEEK) {
                return offsetWeekCompareDates(groupByCompareMetricRows, timeFrame)[date];
            }
            return (0, exports.getDateCalcUtils)(timeFrames_1.TimeFrames.YEAR, timeFrame)
                .forward(new Date(date))
                .toISOString();
        }
        return date;
    });
    const dates = new Set([
        ...Object.keys(groupByMetricRows),
        ...Object.keys(offsetGroupByCompareMetricRows),
    ]);
    const compareMetricId = query.comparison === metricsExplorer_1.MetricExplorerComparison.PREVIOUS_PERIOD
        ? metricId
        : (0, item_1.getItemId)({
            table: query.metric.table,
            name: query.metric.name,
        });
    let comparisonMetricLabel = null;
    if (query.comparison === metricsExplorer_1.MetricExplorerComparison.DIFFERENT_METRIC) {
        comparisonMetricLabel = query.metric.label ?? query.metric.name;
    }
    else if (query.comparison === metricsExplorer_1.MetricExplorerComparison.PREVIOUS_PERIOD) {
        comparisonMetricLabel = 'Previous Period';
    }
    const dataPoints = Array.from(dates).map((date) => ({
        date: new Date(date),
        segment: null,
        metric: {
            value: (0, exports.parseMetricValue)(groupByMetricRows[date]?.[0]?.[metricId]),
            label: metric.label ?? metric.name,
        },
        compareMetric: {
            value: (0, exports.parseMetricValue)(offsetGroupByCompareMetricRows[date]?.[0]?.[compareMetricId]),
            label: comparisonMetricLabel,
        },
    }));
    return { dataPoints };
};
exports.getMetricExplorerDataPointsWithCompare = getMetricExplorerDataPointsWithCompare;
/**
 * Get the date range for a given time interval, based on the current date and the time interval
 * Time grain Year: -> past 3 years (i.e. 3 completed years + this uncompleted year)
 * Time grain Month -> past 12 months (i.e. 12 completed months + this uncompleted month)
 * Time grain Week -> past 12 weeks (i.e. 12 completed weeks + this uncompleted week)
 * Time grain Day -> past 30 days (i.e. 30 completed days + this uncompleted day)
 * @param timeInterval - The time interval
 * @returns The date range
 */
const getDefaultDateRangeFromInterval = (timeInterval) => {
    const now = (0, dayjs_1.default)();
    switch (timeInterval) {
        case timeFrames_1.TimeFrames.DAY:
            return [
                now.subtract(30, 'day').startOf('day').toDate(),
                now.toDate(),
            ];
        case timeFrames_1.TimeFrames.WEEK:
            return [
                now.subtract(12, 'week').startOf('isoWeek').toDate(),
                now.toDate(),
            ];
        case timeFrames_1.TimeFrames.MONTH:
            return [
                now.subtract(12, 'month').startOf('month').toDate(),
                now.toDate(),
            ];
        case timeFrames_1.TimeFrames.YEAR:
            return [
                now.subtract(3, 'year').startOf('year').toDate(),
                now.toDate(),
            ];
        default:
            return (0, exports.assertUnimplementedTimeframe)(timeInterval);
    }
};
exports.getDefaultDateRangeFromInterval = getDefaultDateRangeFromInterval;
const getDefaultMetricTreeNodeDateRange = (timeFrame) => {
    const now = (0, dayjs_1.default)();
    switch (timeFrame) {
        case timeFrames_1.TimeFrames.DAY:
            return [
                now.startOf('day').subtract(1, 'day').toDate(),
                now.endOf('day').subtract(1, 'day').toDate(),
            ];
        case timeFrames_1.TimeFrames.WEEK:
            return [now.startOf('isoWeek').toDate(), now.toDate()];
        case timeFrames_1.TimeFrames.MONTH:
            return [now.startOf('month').toDate(), now.toDate()];
        case timeFrames_1.TimeFrames.YEAR:
            return [now.startOf('year').toDate(), now.toDate()];
        default:
            return (0, exports.assertUnimplementedTimeframe)(timeFrame);
    }
};
exports.getDefaultMetricTreeNodeDateRange = getDefaultMetricTreeNodeDateRange;
/**
 * Default time interval to use when no time interval is provided.
 * For example, when there is no default time dimension defined for a metric or table.
 */
exports.DEFAULT_METRICS_EXPLORER_TIME_INTERVAL = timeFrames_1.TimeFrames.MONTH;
const getFirstAvailableTimeDimension = (metric) => {
    if (metric.availableTimeDimensions &&
        metric.availableTimeDimensions.length > 0) {
        return {
            table: metric.availableTimeDimensions[0].table,
            field: metric.availableTimeDimensions[0].name,
            interval: exports.DEFAULT_METRICS_EXPLORER_TIME_INTERVAL,
        };
    }
    return undefined;
};
exports.getFirstAvailableTimeDimension = getFirstAvailableTimeDimension;
const getDefaultTimeDimension = (metric, table) => {
    // Priority 1: Use metric-level default time dimension if defined in yml
    if (metric.defaultTimeDimension) {
        return {
            field: metric.defaultTimeDimension.field,
            interval: metric.defaultTimeDimension.interval ??
                exports.DEFAULT_METRICS_EXPLORER_TIME_INTERVAL,
        };
    }
    // Priority 2: Use model-level default time dimension if defined in yml
    if (table?.defaultTimeDimension) {
        return {
            field: table.defaultTimeDimension.field,
            interval: table.defaultTimeDimension.interval ??
                exports.DEFAULT_METRICS_EXPLORER_TIME_INTERVAL,
        };
    }
    // Priority 3: Use the only time dimension if there's exactly one
    if (table?.dimensions) {
        const timeDimensions = Object.values(table.dimensions).filter((dim) => (dim.type === field_1.DimensionType.DATE ||
            dim.type === field_1.DimensionType.TIMESTAMP) &&
            !!dim.isIntervalBase &&
            !dim.hidden);
        if (timeDimensions.length === 1) {
            return {
                field: timeDimensions[0].name,
                interval: exports.DEFAULT_METRICS_EXPLORER_TIME_INTERVAL,
            };
        }
    }
    return undefined;
};
exports.getDefaultTimeDimension = getDefaultTimeDimension;
const getAvailableTimeDimensionsFromTables = (tables) => Object.values(tables).flatMap((table) => Object.values(table.dimensions).filter((dim) => (dim.type === field_1.DimensionType.DATE ||
    dim.type === field_1.DimensionType.TIMESTAMP) &&
    !!dim.isIntervalBase &&
    !dim.hidden));
exports.getAvailableTimeDimensionsFromTables = getAvailableTimeDimensionsFromTables;
/**
 * Returns dimensions that can be used for filtering based on type constraints only.
 * Only STRING and BOOLEAN dimensions are allowed, excluding time-derived dimensions.
 * Does NOT apply spotlight settings - frontend handles that with metric allowlist.
 */
const getTypeValidFilterDimensions = (dimensions) => dimensions.filter((d) => {
    // Exclude date-derived dimensions (month name, day name, etc.) even though they're strings
    if (d.timeIntervalBaseDimensionName)
        return false;
    return (d.type === field_1.DimensionType.STRING || d.type === field_1.DimensionType.BOOLEAN);
});
exports.getTypeValidFilterDimensions = getTypeValidFilterDimensions;
/**
 * Filters dimensions for a specific metric, applying metric's spotlight allowlist.
 * Dimensions should already be type-valid from the backend.
 */
const getFilterDimensionsForMetric = (dimensions, metric) => dimensions.filter((d) => {
    // If metric has allowlist, use it (supersedes dimension-level)
    if (metric?.spotlightFilterBy) {
        return metric.spotlightFilterBy.includes(d.name);
    }
    // Otherwise use dimension-level setting
    return d.spotlight?.filterBy !== false;
});
exports.getFilterDimensionsForMetric = getFilterDimensionsForMetric;
/**
 * Returns dimensions that can be used for segmenting based on type constraints only.
 * Only STRING and BOOLEAN dimensions are allowed, excluding time-derived dimensions.
 * Does NOT apply spotlight settings - frontend handles that with metric allowlist.
 */
const getTypeValidSegmentDimensions = (dimensions) => dimensions.filter((d) => {
    // Exclude date-derived dimensions (month name, day name, etc.) even though they're strings
    if (d.timeIntervalBaseDimensionName)
        return false;
    return (d.type === field_1.DimensionType.STRING || d.type === field_1.DimensionType.BOOLEAN);
});
exports.getTypeValidSegmentDimensions = getTypeValidSegmentDimensions;
/**
 * Filters dimensions for a specific metric, applying metric's spotlight allowlist.
 * Dimensions should already be type-valid from the backend.
 */
const getSegmentDimensionsForMetric = (dimensions, metric) => dimensions.filter((d) => {
    // If metric has allowlist, use it (supersedes dimension-level)
    if (metric?.spotlightSegmentBy) {
        return metric.spotlightSegmentBy.includes(d.name);
    }
    // Otherwise use dimension-level setting
    return d.spotlight?.segmentBy !== false;
});
exports.getSegmentDimensionsForMetric = getSegmentDimensionsForMetric;
const getAvailableCompareMetrics = (metrics) => metrics
    .filter((metric) => !!metric.timeDimension)
    .filter((metric) => metric.type !== field_1.MetricType.STRING &&
    metric.type !== field_1.MetricType.BOOLEAN &&
    metric.type !== field_1.MetricType.DATE &&
    metric.type !== field_1.MetricType.TIMESTAMP);
exports.getAvailableCompareMetrics = getAvailableCompareMetrics;
/**
 * Calculate date ranges for rolling period comparisons.
 * Current period: yesterday - (days-1) days ago to yesterday
 * Previous period: (days*2) days ago to (days+1) days ago
 *
 * Example for 7 days (if today is Feb 4):
 * Current:  Jan 28 - Feb 3
 * Previous: Jan 21 - Jan 27
 */
const getRollingPeriodDates = (days) => {
    const now = (0, dayjs_1.default)();
    const yesterday = now.subtract(1, 'day').endOf('day');
    return {
        current: {
            start: now.subtract(days, 'day').startOf('day'),
            end: yesterday,
        },
        previous: {
            start: now.subtract(days * 2, 'day').startOf('day'),
            end: now.subtract(days + 1, 'day').endOf('day'),
        },
    };
};
exports.getRollingPeriodDates = getRollingPeriodDates;
/**
 * Build a custom SQL dimension that buckets data into periods:
 * - 0 = current period
 * - 1 = previous period
 * - NULL = outside both periods
 */
const buildRollingPeriodCustomDimension = (timeDimensionFieldRef, // e.g., "${orders.order_date_day}"
table, rollingDays, adapterType) => {
    const { current, previous } = (0, exports.getRollingPeriodDates)(rollingDays);
    const cast = (d) => (0, filtersCompiler_1.castDateLiteral)(d.format(exports.METRICS_EXPLORER_DATE_FORMAT), adapterType);
    const id = 'lightdash_metric_total_period';
    return {
        id,
        name: id,
        table,
        type: field_1.CustomDimensionType.SQL,
        sql: `CASE
  WHEN ${timeDimensionFieldRef} >= ${cast(current.start)} AND ${timeDimensionFieldRef} <= ${cast(current.end)} THEN 0
  WHEN ${timeDimensionFieldRef} >= ${cast(previous.start)} AND ${timeDimensionFieldRef} <= ${cast(previous.end)} THEN 1
  ELSE NULL
END`,
        dimensionType: field_1.DimensionType.NUMBER,
    };
};
exports.buildRollingPeriodCustomDimension = buildRollingPeriodCustomDimension;
//# sourceMappingURL=metricsExplorer.js.map