import isEmpty from 'lodash/isEmpty' import isEqual from 'lodash/isEqual' export const DEFAULT_PROPERTY_VALUES = { allowAdditionalGroupByPerMetric: false, allowChangeDateRange: false, scoreIcon: '', applyDateRange: false, autoAggregatePeriods: false, bareBorder: false, cacheExpirationTime: 0, chartVariation: 'stacked', dataLabelPosition: 'middle', dataLabelsType: 'both', dateFormat: 'default', disableTooltip: false, emptyStateAlignment: 'vertical-centered', emptyStateContent: 'There are no scores available for the selected criteria. Contact your admin for more info.', emptyStateHeading: 'No data available.', emptyStateHeadingLevel: 3, emptyStateIllustration: 'no-data', enableAddZerosAtApi: true, enableClickAndStay: false, enableCustomEmptyState: false, enableDrilldown: true, enableRealTimeUpdate: false, extensionType: 'step', filterConfigurations: '@state.parFilters', followFilters: true, freezeFirstColumn: true, freezeFooter: false, freezeHeader: true, gaugeArcAngle: '180', headerTitleAlignment: 'start', headingLevel: 2, headingPosition: 'top', hideDevSettings: false, hideMatrixAggregate: false, hideYAxis0: false, hideYAxis1: false, hideYAxis2: false, hideYAxis3: false, hideYAxis4: false, iconStyle: 'background', legendExpandToFit: false, legendHorizontalAlignment: 'center', legendPosition: 'bottom', lineClamp: 1, lineStrokeWidth: '2', mapSysId: '93b8a3a2d7101200bd4a4ebfae61033a', minValue: 0, newReporting: false, numberOfPeriods: 3, period: 'M', registerOrientation: 'horizontal', registerPosition: 'bottom', scoreAlignment: 'start', scoreLabel: 'none', scoreSize: 'auto', scoreType: 'latest', showAbsolutePeriod: false, showAdditionalGroupBySelector: false, showAlternateColumnColors: false, showAlternateRowColors: true, showBorder: true, showCacheTime: true, showChange: false, showChangeFrom: 'previous', showChangePercentage: false, showClosestSeriesOnHover: true, showColumnAggregate: true, showColumnDividers: true, showComment: false, showConfidenceBand: false, showContinuousLine: true, showDataLabels: false, showDataPassthrough: false, showDataTable: false, showDateRangeByDefault: false, showExportOptions: true, showFilterAsSeparateSeries: false, showFilterIcon: true, showFirstGroupAggregate: false, showForecast: false, showForecastRange: false, showGapPercentage: false, showHeader: true, showHeaderSeparator: false, showLabelFirst: false, showLegend: true, showLegendValue: false, showLoadingIndicator: false, showMarkers: false, showMaximumRange: false, showMetricLabel: false, showOverlappingLabels: false, showPercentageOfTotalInTooltip: true, showPivotExpanded: true, showRangeLabels: false, showRangeLimit: true, showRefresh: true, showRegister: true, showRegisterOnSameLine: false, showRegisterPercentage: false, showRegisterValue: false, showRowAggregate: true, showRowDividers: false, showScoreDate: false, showScoreUpdateTime: false, showSecondGroupAggregate: false, showSparkline: false, showStackedValues: 'none', showSubAggregate: true, showTarget: false, showThreshold: false, showTotalAggregate: false, showTrend: false, showXGrid: false, showYGrid: true, showZero: false, sortByOrder: 'desc', sparklineStyle: 'area', truncateColumnHeader: true, truncateFirstColumn: true, truncationType: 'end', useCurrentDateForEnd: false, useDataCache: false, useRelativeScoreTime: false, valueLabelSize: 'md', wrapColumnHeader: false, wrapElements: false, wrapTitle: false, xAxisHidden: false, xAxisMaxLabelSize: 100, xAxisTruncationType: 'end', xAxisWrapLabels: false, yAxis0Position: 'left', yAxis1Position: 'right', yAxis2Position: 'right', yAxis3Position: 'right', yAxis4Position: 'right', yAxisHidden: false, yAxisPosition: 'bottom', yAxisShowGrid: true, yAxisStyle: 'clean', colorConfig: { type: 'default', }, userInfo: '@context.session.user', } export const RESTRICTED_PROPERTIES = ['componentId', 'dataPassThrough', 'sharedContext', 'configVersion'] const DEFAULT_NUMBER_FORMAT = { customFormat: false, } /** * Properties to omit from nested structures. * Key = array property name, Value = array of property names to omit from items */ type OmitConfig = string[] | Record const OMIT_DEEP_PATHS: Record = { dataSources: ['allowRealTime', 'isDatabaseView', 'preferredVisualizations', 'dataCategories'], metrics: ['numberFormat'], groupBy: { groupBy: ['isRange', 'isChoice', 'isPaBucket'], }, } /** * Omits deep properties from an object */ function omitDeepProperties(obj: unknown): unknown { if (!obj || typeof obj !== 'object') { return obj } if (Array.isArray(obj)) { return obj.map(omitDeepProperties) } const result: Record = {} for (const [key, value] of Object.entries(obj)) { const omitConfig = OMIT_DEEP_PATHS[key] if (Array.isArray(value) && omitConfig) { if (Array.isArray(omitConfig)) { // Omit specified properties from each array item result[key] = value.map((item) => { if (!item || typeof item !== 'object') { return item } const filtered: Record = {} for (const [itemKey, itemValue] of Object.entries(item)) { if (omitConfig.includes(itemKey)) { // Special case: keep numberFormat if it's not default if (itemKey === 'numberFormat' && key === 'metrics') { if (itemValue && !isEqual(itemValue, DEFAULT_NUMBER_FORMAT)) { filtered[itemKey] = omitDeepProperties(itemValue) } } continue } filtered[itemKey] = omitDeepProperties(itemValue) } return filtered }) } else if (typeof omitConfig === 'object') { // Handle nested config (e.g., groupBy.groupBy) result[key] = value.map((item) => { if (!item || typeof item !== 'object') { return item } const filtered: Record = {} for (const [itemKey, itemValue] of Object.entries(item)) { const nestedConfig = omitConfig[itemKey] if (Array.isArray(itemValue) && Array.isArray(nestedConfig)) { // Process nested array filtered[itemKey] = itemValue.map((nestedItem) => { if (!nestedItem || typeof nestedItem !== 'object') { return nestedItem } const nestedFiltered: Record = {} for (const [nestedKey, nestedValue] of Object.entries(nestedItem)) { if (!nestedConfig.includes(nestedKey)) { nestedFiltered[nestedKey] = omitDeepProperties(nestedValue) } } return nestedFiltered }) } else { filtered[itemKey] = omitDeepProperties(itemValue) } } return filtered }) } } else if (value && typeof value === 'object') { const filtered = omitDeepProperties(value) if (!isEmpty(filtered)) { result[key] = filtered } } else { result[key] = value } } return result } /** * Removes properties from an object that match their default values */ export function removeRestrictedValues(props: object): unknown { if (!props) { return props } const filtered: Record = {} for (const [key, value] of Object.entries(props)) { if (RESTRICTED_PROPERTIES.find((prop) => prop === key)) { continue } filtered[key] = value } return omitDeepProperties(filtered) }