1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-18 00:19:49 +01:00

chore: remove dataUsageMultiMonthView flag (#9429)

Remove data usage multi month view flag and deprecated components and
functions.
This commit is contained in:
Thomas Heartman 2025-03-05 12:08:33 +01:00 committed by GitHub
parent 2e086161eb
commit 8629cda4d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 35 additions and 582 deletions

View File

@ -1,26 +1,13 @@
import { endOfMonth, format, startOfMonth } from 'date-fns';
import {
useInstanceTrafficMetrics,
useTrafficSearch,
} from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import { useTrafficSearch } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import { useMemo } from 'react';
import {
calculateOverageCost,
calculateTotalUsage,
} from 'utils/traffic-calculations';
import { BILLING_TRAFFIC_BUNDLE_PRICE } from './BillingPlan';
import { useUiFlag } from 'hooks/useUiFlag';
import { useTrafficDataEstimation } from 'hooks/useTrafficData';
export const useOverageCost = (includedTraffic: number) => {
if (useUiFlag('dataUsageMultiMonthView')) {
return useNewOverageCostCalculation(includedTraffic);
} else {
return useOldOverageCostCalculation(includedTraffic);
}
};
const useNewOverageCostCalculation = (includedTraffic: number) => {
if (!includedTraffic) {
return 0;
}
@ -46,34 +33,3 @@ const useNewOverageCostCalculation = (includedTraffic: number) => {
return overageCost;
};
const useOldOverageCostCalculation = (includedTraffic: number) => {
const {
currentPeriod,
toChartData,
toTrafficUsageSum,
endpointsInfo,
getDayLabels,
} = useTrafficDataEstimation();
const traffic = useInstanceTrafficMetrics(currentPeriod.key);
const overageCost = useMemo(() => {
if (!includedTraffic) {
return 0;
}
const trafficData = toChartData(
getDayLabels(currentPeriod.dayCount),
traffic,
endpointsInfo,
);
const totalTraffic = toTrafficUsageSum(trafficData);
return calculateOverageCost(
totalTraffic,
includedTraffic,
BILLING_TRAFFIC_BUNDLE_PRICE,
);
}, [includedTraffic, traffic, currentPeriod, endpointsInfo]);
return overageCost;
};

View File

@ -1,10 +1,8 @@
import { type FC, useEffect, useMemo, useState } from 'react';
import useTheme from '@mui/material/styles/useTheme';
import type { FC } from 'react';
import styled from '@mui/material/styles/styled';
import { usePageTitle } from 'hooks/usePageTitle';
import Select from 'component/common/select';
import { Link as RouterLink } from 'react-router-dom';
import { Alert, Link } from '@mui/material';
import { Alert } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import {
@ -18,25 +16,14 @@ import {
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import Grid from '@mui/material/Grid';
import { NetworkTrafficUsagePlanSummary } from './NetworkTrafficUsagePlanSummary';
import annotationPlugin from 'chartjs-plugin-annotation';
import {
calculateEstimatedMonthlyCost as deprecatedCalculateEstimatedMonthlyCost,
useTrafficDataEstimation,
} from 'hooks/useTrafficData';
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
import { useTrafficLimit } from './hooks/useTrafficLimit';
import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { PeriodSelector } from './PeriodSelector';
import { useUiFlag } from 'hooks/useUiFlag';
import { OverageInfo, RequestSummary } from './RequestSummary';
import { calculateOverageCost } from 'utils/traffic-calculations';
import { currentMonth } from './dates';
import { type ChartDatasetType, getChartLabel } from './chart-functions';
import { createBarChartOptions } from './bar-chart-options';
import { getChartLabel } from './chart-functions';
import { useTrafficStats } from './hooks/useStats';
import { BoldText, StyledBox, TopRow } from './SharedComponents';
import { useChartDataSelection } from './hooks/useChartDataSelection';
@ -48,7 +35,7 @@ const TrafficInfoBoxes = styled('div')(({ theme }) => ({
gap: theme.spacing(2, 4),
}));
const NewNetworkTrafficUsage: FC = () => {
const NetworkTrafficUsage: FC = () => {
usePageTitle('Network - Data Usage');
const estimateTrafficDataCost = useUiFlag('estimateTrafficDataCost');
@ -142,183 +129,6 @@ const NewNetworkTrafficUsage: FC = () => {
);
};
export const NetworkTrafficUsage: FC = () => {
const useNewNetworkTraffic = useUiFlag('dataUsageMultiMonthView');
return useNewNetworkTraffic ? (
<NewNetworkTrafficUsage />
) : (
<OldNetworkTrafficUsage />
);
};
const OldNetworkTrafficUsage: FC = () => {
usePageTitle('Network - Data Usage');
const theme = useTheme();
const { isOss } = useUiConfig();
const { locationSettings } = useLocationSettings();
const {
record,
period,
setPeriod,
selectablePeriods,
getDayLabels,
toChartData,
toTrafficUsageSum,
endpointsInfo,
} = useTrafficDataEstimation();
const includedTraffic = useTrafficLimit();
const options = useMemo(() => {
return createBarChartOptions(
theme,
(tooltipItems: any) => {
const periodItem = record[period];
const tooltipDate = new Date(
periodItem.year,
periodItem.month,
Number.parseInt(tooltipItems[0].label),
);
return tooltipDate.toLocaleDateString(
locationSettings?.locale ?? 'en-US',
{
month: 'long',
day: 'numeric',
year: 'numeric',
},
);
},
includedTraffic,
);
}, [theme, period]);
const traffic = useInstanceTrafficMetrics(period);
const [labels, setLabels] = useState<number[]>([]);
const [datasets, setDatasets] = useState<ChartDatasetType[]>([]);
const [usageTotal, setUsageTotal] = useState<number>(0);
const [overageCost, setOverageCost] = useState<number>(0);
const [estimatedMonthlyCost, setEstimatedMonthlyCost] = useState<number>(0);
const data = {
labels,
datasets,
};
useEffect(() => {
setDatasets(toChartData(labels, traffic, endpointsInfo));
}, [labels, traffic]);
useEffect(() => {
if (record && period) {
const periodData = record[period];
setLabels(getDayLabels(periodData.dayCount));
}
}, [period]);
useEffect(() => {
if (data) {
const usage = toTrafficUsageSum(data.datasets);
setUsageTotal(usage);
if (includedTraffic > 0) {
const calculatedOverageCost = calculateOverageCost(
usage,
includedTraffic,
BILLING_TRAFFIC_BUNDLE_PRICE,
);
setOverageCost(calculatedOverageCost);
setEstimatedMonthlyCost(
deprecatedCalculateEstimatedMonthlyCost(
period,
data.datasets,
includedTraffic,
new Date(),
BILLING_TRAFFIC_BUNDLE_PRICE,
),
);
}
}
}, [data]);
return (
<ConditionallyRender
condition={isOss()}
show={<Alert severity='warning'>Not enabled.</Alert>}
elseShow={
<>
<ConditionallyRender
condition={includedTraffic > 0 && overageCost > 0}
show={
<Alert severity='warning' sx={{ mb: 4 }}>
<b>Heads up!</b> You are currently consuming
more requests than your plan includes and will
be billed according to our terms. Please see{' '}
<Link
component={RouterLink}
to='https://www.getunleash.io/pricing'
>
this page
</Link>{' '}
for more information. In order to reduce your
traffic consumption, you may configure an{' '}
<Link
component={RouterLink}
to='https://docs.getunleash.io/reference/unleash-edge'
>
Unleash Edge instance
</Link>{' '}
in your own datacenter.
</Alert>
}
/>
<StyledBox>
<Grid container component='header' spacing={2}>
<Grid item xs={12} md={10}>
<NetworkTrafficUsagePlanSummary
usageTotal={usageTotal}
includedTraffic={includedTraffic}
overageCost={overageCost}
estimatedMonthlyCost={estimatedMonthlyCost}
/>
</Grid>
<Grid item xs={12} md={2}>
<Select
id='dataperiod-select'
name='dataperiod'
options={selectablePeriods}
value={period}
onChange={(e) => setPeriod(e.target.value)}
style={{
minWidth: '100%',
marginBottom: theme.spacing(2),
}}
formControlStyles={{ width: '100%' }}
/>
</Grid>
</Grid>
<Grid item xs={12} md={2}>
<Bar
data={data}
plugins={[customHighlightPlugin()]}
options={options}
aria-label='An instance metrics line chart with two lines: requests per second for admin API and requests per second for client API'
/>
</Grid>
</StyledBox>
</>
}
/>
);
};
// Register dependencies that we need to draw the chart.
ChartJS.register(
annotationPlugin,

View File

@ -2,42 +2,10 @@ import useSWR from 'swr';
import { useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import type {
TrafficUsageDataSegmentedCombinedSchema,
TrafficUsageDataSegmentedSchema,
} from 'openapi';
import type { TrafficUsageDataSegmentedCombinedSchema } from 'openapi';
import { cleanTrafficData } from 'utils/traffic-calculations';
export interface IInstanceTrafficMetricsResponse {
usage: TrafficUsageDataSegmentedSchema;
refetch: () => void;
loading: boolean;
error?: Error;
}
export const useInstanceTrafficMetrics = (
period: string,
): IInstanceTrafficMetricsResponse => {
const { data, error, mutate } = useSWR(
formatApiPath(`api/admin/metrics/traffic/${period}`),
fetcher,
);
return useMemo(
() => ({
usage: data,
loading: !error && !data,
refetch: () => mutate(),
error,
}),
[data, error, mutate],
);
};
export type InstanceTrafficMetricsResponse2 = {
export type InstanceTrafficMetricsResponse = {
refetch: () => void;
result:
| { state: 'success'; data: TrafficUsageDataSegmentedCombinedSchema }
@ -54,7 +22,7 @@ export const useTrafficSearch = (
from: string;
to: string;
},
): InstanceTrafficMetricsResponse2 => {
): InstanceTrafficMetricsResponse => {
const apiPath = `api/admin/metrics/traffic-search?grouping=${grouping}&from=${from}&to=${to}`;
const { data, error, mutate } = useSWR(formatApiPath(apiPath), fetcher);

View File

@ -1,228 +0,0 @@
import type { ChartDatasetType } from 'component/admin/network/NetworkTrafficUsage/chart-functions';
import type { IInstanceTrafficMetricsResponse } from './api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import { useState } from 'react';
import {
DEFAULT_TRAFFIC_DATA_UNIT_COST,
DEFAULT_TRAFFIC_DATA_UNIT_SIZE,
calculateOverageCost,
} from 'utils/traffic-calculations';
import {
currentMonth,
daysInCurrentMonth,
} from 'component/admin/network/NetworkTrafficUsage/dates';
export type SelectablePeriod = {
key: string;
dayCount: number;
label: string;
year: number;
month: number;
};
export type EndpointInfo = {
label: string;
color: string;
order: number;
};
const endpointsInfo: Record<string, EndpointInfo> = {
'/api/admin': {
label: 'Admin',
color: '#6D66D9',
order: 1,
},
'/api/frontend': {
label: 'Frontend',
color: '#A39EFF',
order: 2,
},
'/api/client': {
label: 'Server',
color: '#D8D6FF',
order: 3,
},
};
const padMonth = (month: number): string => month.toString().padStart(2, '0');
export const toSelectablePeriod = (
date: Date,
label?: string,
): SelectablePeriod => {
const year = date.getFullYear();
const month = date.getMonth();
const period = `${year}-${padMonth(month + 1)}`;
const dayCount = new Date(year, month + 1, 0).getDate();
return {
key: period,
year,
month,
dayCount,
label:
label ||
date.toLocaleString('en-US', { month: 'long', year: 'numeric' }),
};
};
const currentDate = new Date(Date.now());
const currentPeriod = toSelectablePeriod(currentDate, 'Current month');
const getSelectablePeriods = (): SelectablePeriod[] => {
const selectablePeriods = [currentPeriod];
for (
let subtractMonthCount = 1;
subtractMonthCount < 13;
subtractMonthCount++
) {
// JavaScript wraps around the year, so we don't need to handle that.
const date = new Date(
currentDate.getFullYear(),
currentDate.getMonth() - subtractMonthCount,
1,
);
if (date > new Date('2024-03-31')) {
selectablePeriods.push(toSelectablePeriod(date));
}
}
return selectablePeriods;
};
const toPeriodsRecord = (
periods: SelectablePeriod[],
): Record<string, SelectablePeriod> => {
return periods.reduce(
(acc, period) => {
acc[period.key] = period;
return acc;
},
{} as Record<string, SelectablePeriod>,
);
};
const toChartData = (
days: number[],
traffic: IInstanceTrafficMetricsResponse,
endpointsInfo: Record<string, EndpointInfo>,
): ChartDatasetType[] => {
if (!traffic || !traffic.usage || !traffic.usage.apiData) {
return [];
}
const data = traffic.usage.apiData
.filter((item) => !!endpointsInfo[item.apiPath])
.sort(
(item1: any, item2: any) =>
endpointsInfo[item1.apiPath].order -
endpointsInfo[item2.apiPath].order,
)
.map((item: any) => {
const daysRec = days.reduce(
(acc, day: number) => {
acc[`d${day}`] = 0;
return acc;
},
{} as Record<string, number>,
);
for (const dayKey in item.days) {
const day = item.days[dayKey];
const dayNum = new Date(Date.parse(day.day)).getUTCDate();
daysRec[`d${dayNum}`] = day.trafficTypes[0].count;
}
const epInfo = endpointsInfo[item.apiPath];
return {
label: epInfo.label,
data: Object.values(daysRec),
backgroundColor: epInfo.color,
hoverBackgroundColor: epInfo.color,
};
});
return data;
};
const toTrafficUsageSum = (trafficData: ChartDatasetType[]): number => {
const data = trafficData.reduce(
(acc: number, current: ChartDatasetType) => {
return (
acc +
current.data.reduce(
(acc_inner, current_inner) => acc_inner + current_inner,
0,
)
);
},
0,
);
return data;
};
export const calculateProjectedUsage = (
today: number,
trafficData: ChartDatasetType[],
daysInPeriod: number,
) => {
if (today < 5) {
return 0;
}
const spliceToYesterday = today - 1;
const trafficDataUpToYesterday = trafficData.map((item) => {
return {
...item,
data: item.data.slice(0, spliceToYesterday),
};
});
const dataUsage = toTrafficUsageSum(trafficDataUpToYesterday);
return (dataUsage / spliceToYesterday) * daysInPeriod;
};
export const calculateEstimatedMonthlyCost = (
period: string,
trafficData: ChartDatasetType[],
includedTraffic: number,
currentDate: Date,
trafficUnitCost = DEFAULT_TRAFFIC_DATA_UNIT_COST,
trafficUnitSize = DEFAULT_TRAFFIC_DATA_UNIT_SIZE,
) => {
if (period !== currentMonth) {
return 0;
}
const today = currentDate.getDate();
const projectedUsage = calculateProjectedUsage(
today,
trafficData,
daysInCurrentMonth,
);
return calculateOverageCost(
projectedUsage,
includedTraffic,
trafficUnitCost,
trafficUnitSize,
);
};
const getDayLabels = (dayCount: number): number[] => {
return [...Array(dayCount).keys()].map((i) => i + 1);
};
export const useTrafficDataEstimation = () => {
const selectablePeriods = getSelectablePeriods();
const record = toPeriodsRecord(selectablePeriods);
const [period, setPeriod] = useState<string>(selectablePeriods[0].key);
return {
record,
period,
setPeriod,
selectablePeriods,
getDayLabels,
currentPeriod,
toChartData,
toTrafficUsageSum,
endpointsInfo,
};
};

View File

@ -90,7 +90,6 @@ export type UiFlags = {
showUserDeviceCount?: boolean;
flagOverviewRedesign?: boolean;
granularAdminPermissions?: boolean;
dataUsageMultiMonthView?: boolean;
consumptionModel?: boolean;
edgeObservability?: boolean;
};

View File

@ -11,10 +11,6 @@ import type {
TrafficUsageDataSegmentedCombinedSchema,
TrafficUsageDataSegmentedCombinedSchemaApiDataItem,
} from 'openapi';
import {
calculateEstimatedMonthlyCost as deprecatedCalculateEstimatedMonthlyCost,
calculateProjectedUsage as deprecatedCalculateProjectedUsage,
} from 'hooks/useTrafficData';
const testData4Days = [
{
@ -97,21 +93,13 @@ describe('traffic overage calculation', () => {
const period = toSelectablePeriod(now);
const testNow = new Date(now.getFullYear(), now.getMonth(), 4);
const includedTraffic = 53_000_000;
const result = deprecatedCalculateEstimatedMonthlyCost(
period.key,
testData4Days,
includedTraffic,
testNow,
);
expect(result).toBe(0);
const rawData = trafficData4Days(now);
const result2 = calculateEstimatedMonthlyCost(
const result = calculateEstimatedMonthlyCost(
rawData,
includedTraffic,
testNow,
);
expect(result2).toBe(result);
expect(result).toBe(0);
});
it('needs 5 days or more to estimate for the month', () => {
@ -120,27 +108,18 @@ describe('traffic overage calculation', () => {
testData[1].data.push(23_000_000);
testData[2].data.push(23_000_000);
const now = new Date();
const period = toSelectablePeriod(now);
const testNow = new Date(now.getFullYear(), now.getMonth(), 5);
const includedTraffic = 53_000_000;
const result = deprecatedCalculateEstimatedMonthlyCost(
period.key,
testData,
includedTraffic,
testNow,
);
expect(result).toBeGreaterThan(1430);
const rawData = trafficData4Days(now);
rawData[0].dataPoints.push(dataPoint(now)(5, 23_000_000));
rawData[1].dataPoints.push(dataPoint(now)(5, 23_000_000));
rawData[2].dataPoints.push(dataPoint(now)(5, 23_000_000));
const result2 = calculateEstimatedMonthlyCost(
const result = calculateEstimatedMonthlyCost(
rawData,
includedTraffic,
testNow,
);
expect(result2).toBe(result);
expect(result).toBeGreaterThan(1430);
});
it('estimates projected data usage', () => {
@ -151,24 +130,17 @@ describe('traffic overage calculation', () => {
// Testing April 5th of 2024 (30 days)
const now = new Date(2024, 3, 5);
const period = toSelectablePeriod(now);
const result = deprecatedCalculateProjectedUsage(
now.getDate(),
testData,
period.dayCount,
);
// 22_500_000 * 3 * 30 = 2_025_000_000
expect(result).toBe(2_025_000_000);
const rawData = trafficData4Days(now);
rawData[0].dataPoints.push(dataPoint(now)(5, 22_500_000));
rawData[1].dataPoints.push(dataPoint(now)(5, 22_500_000));
rawData[2].dataPoints.push(dataPoint(now)(5, 22_500_000));
const result2 = calculateProjectedUsage({
const result = calculateProjectedUsage({
dayOfMonth: now.getDate(),
daysInMonth: period.dayCount,
trafficData: rawData,
});
expect(result2).toBe(result);
// 22_500_000 * 3 * 30 = 2_025_000_000
expect(result).toBe(2_025_000_000);
});
it('supports custom price and unit size', () => {
@ -194,9 +166,13 @@ describe('traffic overage calculation', () => {
const includedTraffic = 53_000_000;
const trafficUnitSize = 500_000;
const trafficUnitCost = 10;
const result = deprecatedCalculateEstimatedMonthlyCost(
period.key,
testData,
const rawData = trafficData4Days(now);
rawData[0].dataPoints.push(dataPoint(now)(5, 22_500_000));
rawData[1].dataPoints.push(dataPoint(now)(5, 22_500_000));
rawData[2].dataPoints.push(dataPoint(now)(5, 22_500_000));
const result = calculateEstimatedMonthlyCost(
rawData,
includedTraffic,
testNow,
trafficUnitCost,
@ -208,20 +184,6 @@ describe('traffic overage calculation', () => {
const overageUnits = Math.floor(overage / trafficUnitSize);
const total = overageUnits * trafficUnitCost;
expect(result).toBe(total);
const rawData = trafficData4Days(now);
rawData[0].dataPoints.push(dataPoint(now)(5, 22_500_000));
rawData[1].dataPoints.push(dataPoint(now)(5, 22_500_000));
rawData[2].dataPoints.push(dataPoint(now)(5, 22_500_000));
const result2 = calculateEstimatedMonthlyCost(
rawData,
includedTraffic,
testNow,
trafficUnitCost,
trafficUnitSize,
);
expect(result2).toBe(result);
});
});

View File

@ -24,9 +24,6 @@ export interface ITrafficDataUsageStore
extends Store<IStatTrafficUsage, IStatTrafficUsageKey> {
upsert(trafficDataUsage: IStatTrafficUsage): Promise<void>;
getTrafficDataUsageForPeriod(period: string): Promise<IStatTrafficUsage[]>;
getTrafficDataForMonthRange(
monthsBack: number,
): Promise<IStatMonthlyTrafficUsage[]>;
getDailyTrafficDataUsageForPeriod(
from: Date,
to: Date,

View File

@ -1,4 +1,9 @@
import { differenceInCalendarMonths, subMonths } from 'date-fns';
import {
differenceInCalendarMonths,
endOfMonth,
startOfMonth,
subMonths,
} from 'date-fns';
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import getLogger from '../../../test/fixtures/no-logger';
import type { ITrafficDataUsageStore, IUnleashStores } from '../../types';
@ -234,8 +239,13 @@ test('can query for monthly aggregation of data for a specified range', async ()
}
for (const monthsBack of [3, 6, 12]) {
const to = endOfMonth(now);
const from = subMonths(startOfMonth(now), monthsBack);
const result =
await trafficDataUsageStore.getTrafficDataForMonthRange(monthsBack);
await trafficDataUsageStore.getMonthlyTrafficDataUsageForPeriod(
from,
to,
);
// should have the current month and the preceding n months (one entry per group)
expect(result.length).toBe((monthsBack + 1) * 2);

View File

@ -1,10 +1,4 @@
import {
endOfDay,
endOfMonth,
startOfDay,
startOfMonth,
subMonths,
} from 'date-fns';
import { endOfDay, endOfMonth, startOfDay, startOfMonth } from 'date-fns';
import type { Db } from '../../db/db';
import type { Logger, LogProvider } from '../../logger';
import type {
@ -148,13 +142,4 @@ export class TrafficDataUsageStore implements ITrafficDataUsageStore {
endOfMonth(month),
);
}
// @deprecated: remove with flag `dataUsageMultiMonthView`
async getTrafficDataForMonthRange(
monthsBack: number,
): Promise<IStatMonthlyTrafficUsage[]> {
const to = endOfMonth(new Date());
const from = startOfMonth(subMonths(to, monthsBack));
return this.getMonthlyTrafficDataUsageForPeriod(from, to);
}
}

View File

@ -62,7 +62,6 @@ export type IFlagKey =
| 'etagVariant'
| 'deltaApi'
| 'uniqueSdkTracking'
| 'dataUsageMultiMonthView'
| 'consumptionModel'
| 'teamsIntegrationChangeRequests'
| 'edgeObservability'
@ -299,10 +298,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_UNIQUE_SDK_TRACKING,
false,
),
dataUsageMultiMonthView: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_DATA_USAGE_MULTI_MONTH_VIEW,
false,
),
consumptionModel: parseEnvVarBoolean(
process.env.EXPERIMENTAL_CONSUMPTION_MODEL,
false,

View File

@ -56,7 +56,6 @@ process.nextTick(async () => {
granularAdminPermissions: true,
deltaApi: true,
uniqueSdkTracking: true,
dataUsageMultiMonthView: true,
filterExistingFlagNames: true,
teamsIntegrationChangeRequests: true,
simplifyDisableFeature: true,